mirror of
https://github.com/demostf/demo.js
synced 2026-06-03 16:44:12 +02:00
initial handling of hl2dm
This commit is contained in:
parent
5aa5fe95fd
commit
04fc9b1f76
9 changed files with 730 additions and 479 deletions
|
|
@ -1,6 +1,5 @@
|
|||
import {ParseMode} from "../src/Demo";
|
||||
|
||||
const Demo = require('../index');
|
||||
const ParseMode = require('../build/Demo').ParseMode;
|
||||
const fs = require('fs');
|
||||
const argv = require('minimist')(process.argv.slice(2), {boolean: true});
|
||||
|
||||
|
|
@ -17,7 +16,7 @@ const echo = function (data) {
|
|||
fs.readFile(argv._[0], function (err, data) {
|
||||
if (err) throw err;
|
||||
const demo = Demo.fromNodeBuffer(data);
|
||||
const analyser = demo.getAnalyser(argv.slow ? ParseMode.ENTITIES : ParseMode.MINIMAL);
|
||||
const analyser = demo.getAnalyser(argv.slow ? ParseMode.ENTITIES : Demo.ParseMode.MINIMAL);
|
||||
const head = analyser.getHeader();
|
||||
if (argv.head) {
|
||||
echo(head);
|
||||
|
|
|
|||
|
|
@ -102,6 +102,9 @@ export class Match {
|
|||
userId -= 256;
|
||||
}
|
||||
const user = this.users.get(userId);
|
||||
if (user && user.userId !== userId) {
|
||||
throw new Error(`Invalid user info id`);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
const entityInfo = this.parserState.getUserEntityInfo(userId);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
UserMessageType, UserMessageTypeMap, VoiceSubtitlePacket
|
||||
} from './UserMessage';
|
||||
import {Vector} from './Vector';
|
||||
import {Game} from './ParserState';
|
||||
|
||||
export interface StringTablePacket {
|
||||
packetType: 'stringTable';
|
||||
|
|
@ -179,7 +180,7 @@ export interface ServerInfoPacket {
|
|||
maxPlayerCount: number;
|
||||
intervalPerTick: number;
|
||||
platform: string;
|
||||
game: string;
|
||||
game: Game;
|
||||
skybox: string;
|
||||
serverName: string;
|
||||
replay: boolean;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import {ServerClass, ServerClassId} from './ServerClass';
|
|||
import {StringTable} from './StringTable';
|
||||
import {UserEntityInfo, UserId} from './UserInfo';
|
||||
|
||||
export type Game = 'tf' | 'hl2mp';
|
||||
|
||||
export class ParserState {
|
||||
public version: number = 0;
|
||||
public staticBaseLines: Map<ServerClassId, BitStream> = new Map();
|
||||
|
|
@ -30,6 +32,7 @@ export class ParserState {
|
|||
public skippedPackets: PacketTypeId[] = [];
|
||||
public userInfo: Map<UserId, UserEntityInfo> = new Map();
|
||||
public tick: number = 0;
|
||||
public game: Game;
|
||||
|
||||
public handlePacket(packet: Packet) {
|
||||
switch (packet.packetType) {
|
||||
|
|
@ -38,6 +41,7 @@ export class ParserState {
|
|||
break;
|
||||
case 'serverInfo':
|
||||
this.version = packet.version;
|
||||
this.game = packet.game;
|
||||
break;
|
||||
case 'stringTable':
|
||||
handleStringTables(packet, this);
|
||||
|
|
@ -77,7 +81,7 @@ export class ParserState {
|
|||
}
|
||||
|
||||
public getUserEntityInfo(userId: number): UserEntityInfo {
|
||||
const info = this.userInfo.get(userId);
|
||||
const info = this.userInfo.get(JSON.parse(JSON.stringify(userId)));
|
||||
if (info) {
|
||||
return info;
|
||||
}
|
||||
|
|
|
|||
32
src/PacketHandler/BaseEntityHandler.ts
Normal file
32
src/PacketHandler/BaseEntityHandler.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import {Vector} from '../Data/Vector';
|
||||
import {PacketEntity} from '../Data/PacketEntity';
|
||||
import {Match} from '../Data/Match';
|
||||
import {PacketMessage} from '../Data/Message';
|
||||
|
||||
export function handleBaseEntity(entity: PacketEntity, match: Match, message: PacketMessage) {
|
||||
for (const prop of entity.props) {
|
||||
if (prop.definition.ownerTableName === 'DT_AttributeContainer' && prop.definition.name === 'm_hOuter') {
|
||||
if (!match.outerMap.has(prop.value as number)) {
|
||||
match.outerMap.set(prop.value as number, entity.entityIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const prop of entity.props) {
|
||||
if (prop.definition.ownerTableName === 'DT_BaseCombatWeapon' && prop.definition.name === 'm_hOwner') {
|
||||
if (!match.weaponMap.has(entity.entityIndex)) {
|
||||
match.weaponMap.set(entity.entityIndex, {
|
||||
className: entity.serverClass.name,
|
||||
owner: prop.value as number
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (entity.serverClass.name) {
|
||||
case 'CWorld':
|
||||
match.world.boundaryMin = entity.getProperty('DT_WORLD', 'm_WorldMins').value as Vector;
|
||||
match.world.boundaryMax = entity.getProperty('DT_WORLD', 'm_WorldMaxs').value as Vector;
|
||||
break;
|
||||
}
|
||||
}
|
||||
215
src/PacketHandler/HL2DMEntityHandler.ts
Normal file
215
src/PacketHandler/HL2DMEntityHandler.ts
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
import {Vector} from '../Data/Vector';
|
||||
import {PacketEntity} from '../Data/PacketEntity';
|
||||
import {Match} from '../Data/Match';
|
||||
import {TeamNumber} from '../Data/Team';
|
||||
import {PacketMessage} from '../Data/Message';
|
||||
import {Player} from '../Data/Player';
|
||||
|
||||
export function handleHL2DMEntity(entity: PacketEntity, match: Match, message: PacketMessage) {
|
||||
switch (entity.serverClass.name) {
|
||||
case 'CHL2MP_Player':
|
||||
const userInfo = match.getUserInfoForEntity(entity);
|
||||
if (!userInfo) {
|
||||
console.log(match.parserState.userInfo);
|
||||
throw new Error(`No user info for entity ${entity.entityIndex}`);
|
||||
}
|
||||
if (userInfo.entityId !== entity.entityIndex) {
|
||||
console.log(match.parserState.userInfo);
|
||||
console.log(userInfo);
|
||||
throw new Error(`Invalid user info for entity ${entity.entityIndex} vs ${userInfo.entityId}`);
|
||||
}
|
||||
const player: Player = (match.playerEntityMap.has(entity.entityIndex)) ?
|
||||
match.playerEntityMap.get(entity.entityIndex) as Player :
|
||||
new Player(match, userInfo);
|
||||
if (!match.playerEntityMap.has(entity.entityIndex)) {
|
||||
match.playerEntityMap.set(entity.entityIndex, player);
|
||||
}
|
||||
|
||||
for (const prop of entity.props) {
|
||||
if (prop.definition.ownerTableName === 'm_hMyWeapons') {
|
||||
if (prop.value !== 2097151) {
|
||||
player.weaponIds[parseInt(prop.definition.name, 10)] = prop.value as number;
|
||||
}
|
||||
}
|
||||
if (prop.definition.ownerTableName === 'm_iAmmo') {
|
||||
if (prop.value !== null && prop.value > 0) {
|
||||
player.ammo[parseInt(prop.definition.name, 10)] = prop.value as number;
|
||||
}
|
||||
}
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
switch (propName) {
|
||||
case 'DT_BasePlayer.m_iHealth':
|
||||
player.health = prop.value as number;
|
||||
break;
|
||||
case 'DT_BasePlayer.m_iMaxHealth':
|
||||
player.maxHealth = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseEntity.m_vecOrigin':
|
||||
player.position.x = (prop.value as Vector).x;
|
||||
player.position.y = (prop.value as Vector).y;
|
||||
player.position.z = (prop.value as Vector).z;
|
||||
break;
|
||||
case 'DT_HL2MP_Player.m_angEyeAngles[0]':
|
||||
player.viewAngle = prop.value as number;
|
||||
break;
|
||||
case 'DT_HL2MP_Player.m_angEyeAngles[1]':
|
||||
player.viewAngle = prop.value as number;
|
||||
break;
|
||||
case 'DT_BasePlayer.m_lifeState':
|
||||
player.lifeState = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseCombatCharacter.m_hActiveWeapon':
|
||||
for (let i = 0; i < player.weapons.length; i++) {
|
||||
if (player.weaponIds[i] === prop.value) {
|
||||
player.activeWeapon = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case'CTeam':
|
||||
if (entity.hasProperty('DT_Team', 'm_iTeamNum')) {
|
||||
const teamId = entity.getProperty('DT_Team', 'm_iTeamNum').value as TeamNumber;
|
||||
if (!match.teams.has(teamId)) {
|
||||
const team = {
|
||||
name: entity.getProperty('DT_Team', 'm_szTeamname').value as string,
|
||||
score: entity.getProperty('DT_Team', 'm_iScore').value as number,
|
||||
roundsWon: entity.getProperty('DT_Team', 'm_iRoundsWon').value as number,
|
||||
players: entity.getProperty('DT_Team', '"player_array"').value as number[],
|
||||
teamNumber: teamId
|
||||
};
|
||||
match.teams.set(teamId, team);
|
||||
match.teamEntityMap.set(entity.entityIndex, team);
|
||||
}
|
||||
} else {
|
||||
const team = match.teamEntityMap.get(entity.entityIndex);
|
||||
if (!team) {
|
||||
throw new Error(`No team with entity id: ${entity.entityIndex}`);
|
||||
}
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
switch (propName) {
|
||||
case 'DT_Team.m_iScore':
|
||||
team.score = prop.value as number;
|
||||
break;
|
||||
case 'DT_Team.m_szTeamname':
|
||||
team.name = prop.value as string;
|
||||
break;
|
||||
case 'DT_Team.m_iRoundsWon':
|
||||
team.roundsWon = prop.value as number;
|
||||
break;
|
||||
case 'DT_Team."player_array"':
|
||||
team.players = prop.value as number[];
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'CPlayerResource':
|
||||
for (const prop of entity.props) {
|
||||
const playerId = parseInt(prop.definition.name, 10);
|
||||
const value = prop.value as number;
|
||||
if (!match.playerResources[playerId]) {
|
||||
match.playerResources[playerId] = {
|
||||
alive: false,
|
||||
arenaSpectator: false,
|
||||
bonusPoints: 0,
|
||||
chargeLevel: 0,
|
||||
connected: false,
|
||||
damageAssists: 0,
|
||||
damageBlocked: 0,
|
||||
deaths: 0,
|
||||
dominations: 0,
|
||||
healing: 0,
|
||||
healingAssist: 0,
|
||||
health: 0,
|
||||
killStreak: 0,
|
||||
maxBuffedHealth: 0,
|
||||
maxHealth: 0,
|
||||
nextRespawn: 0,
|
||||
ping: 0,
|
||||
playerClass: 0,
|
||||
playerLevel: 0,
|
||||
score: 0,
|
||||
team: 0,
|
||||
totalScore: 0,
|
||||
damage: 0
|
||||
};
|
||||
}
|
||||
const playerResource = match.playerResources[playerId];
|
||||
switch (prop.definition.ownerTableName) {
|
||||
case 'm_iPing':
|
||||
playerResource.ping = value;
|
||||
break;
|
||||
case 'm_iScore':
|
||||
playerResource.score = value;
|
||||
break;
|
||||
case 'm_iDeaths':
|
||||
playerResource.deaths = value;
|
||||
break;
|
||||
case 'm_bConnected':
|
||||
playerResource.connected = value > 0;
|
||||
break;
|
||||
case 'm_iTeam':
|
||||
playerResource.team = value;
|
||||
break;
|
||||
case'm_bAlive':
|
||||
playerResource.alive = value > 0;
|
||||
break;
|
||||
case 'm_iHealth':
|
||||
playerResource.health = value;
|
||||
break;
|
||||
case 'm_iTotalScore':
|
||||
playerResource.totalScore = value;
|
||||
break;
|
||||
case 'm_iMaxHealth':
|
||||
playerResource.maxHealth = value;
|
||||
break;
|
||||
case 'm_iMaxBuffedHealth':
|
||||
playerResource.maxBuffedHealth = value;
|
||||
break;
|
||||
case 'm_iPlayerClass':
|
||||
playerResource.playerClass = value;
|
||||
break;
|
||||
case 'm_bArenaSpectator':
|
||||
playerResource.arenaSpectator = value > 0;
|
||||
break;
|
||||
case 'm_iActiveDominations':
|
||||
playerResource.dominations = value;
|
||||
break;
|
||||
case 'm_flNextRespawnTime':
|
||||
playerResource.nextRespawn = value;
|
||||
break;
|
||||
case 'm_iChargeLevel':
|
||||
playerResource.chargeLevel = value;
|
||||
break;
|
||||
case 'm_iDamage':
|
||||
playerResource.damage = value;
|
||||
break;
|
||||
case 'm_iDamageAssist':
|
||||
playerResource.damageAssists = value;
|
||||
break;
|
||||
case 'm_iHealing':
|
||||
playerResource.healing = value;
|
||||
break;
|
||||
case 'm_iHealingAssist':
|
||||
playerResource.healingAssist = value;
|
||||
break;
|
||||
case 'm_iDamageBlocked':
|
||||
playerResource.damageBlocked = value;
|
||||
break;
|
||||
case 'm_iBonusPoints':
|
||||
playerResource.bonusPoints = value;
|
||||
break;
|
||||
case 'm_iPlayerLevel':
|
||||
playerResource.playerLevel = value;
|
||||
break;
|
||||
case 'm_iKillstreak':
|
||||
playerResource.killStreak = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,10 +9,15 @@ import {SendProp} from '../Data/SendProp';
|
|||
import {TeamNumber} from '../Data/Team';
|
||||
import {Vector} from '../Data/Vector';
|
||||
import {CWeaponMedigun, Weapon} from '../Data/Weapon';
|
||||
import {handleTFEntity} from './TFEntityHandler';
|
||||
import {handleHL2DMEntity} from './HL2DMEntityHandler';
|
||||
import {handleBaseEntity} from './BaseEntityHandler';
|
||||
|
||||
export function handlePacketEntities(packet: PacketEntitiesPacket, match: Match, message: PacketMessage) {
|
||||
for (const entity of packet.entities) {
|
||||
handleEntity(entity, match, message);
|
||||
handleBaseEntity(entity, match, message);
|
||||
handleTFEntity(entity, match, message);
|
||||
handleHL2DMEntity(entity, match, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -33,472 +38,3 @@ function saveEntity(packetEntity: PacketEntity, state: ParserState) {
|
|||
|
||||
state.entityClasses.set(packetEntity.entityIndex, packetEntity.serverClass);
|
||||
}
|
||||
|
||||
function handleEntity(entity: PacketEntity, match: Match, message: PacketMessage) {
|
||||
for (const prop of entity.props) {
|
||||
if (prop.definition.ownerTableName === 'DT_AttributeContainer' && prop.definition.name === 'm_hOuter') {
|
||||
if (!match.outerMap.has(prop.value as number)) {
|
||||
match.outerMap.set(prop.value as number, entity.entityIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const prop of entity.props) {
|
||||
if (prop.definition.ownerTableName === 'DT_BaseCombatWeapon' && prop.definition.name === 'm_hOwner') {
|
||||
if (!match.weaponMap.has(entity.entityIndex)) {
|
||||
match.weaponMap.set(entity.entityIndex, {
|
||||
className: entity.serverClass.name,
|
||||
owner: prop.value as number
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (entity.serverClass.name) {
|
||||
case 'CWorld':
|
||||
match.world.boundaryMin = entity.getProperty('DT_WORLD', 'm_WorldMins').value as Vector;
|
||||
match.world.boundaryMax = entity.getProperty('DT_WORLD', 'm_WorldMaxs').value as Vector;
|
||||
break;
|
||||
case 'CTFPlayer':
|
||||
/**
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iCaptures": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iDefenses": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iKills": 5,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iDeaths": 17,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iSuicides": 7,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iDominations": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iRevenge": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iBuildingsBuilt": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iBuildingsDestroyed": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iHeadshots": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iBackstabs": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iHealPoints": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iInvulns": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iTeleports": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iDamageDone": 847,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iCrits": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iResupplyPoints": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iKillAssists": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iBonusPoints": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iPoints": 6,
|
||||
* "DT_TFPlayerSharedLocal.m_nDesiredDisguiseTeam": 0,
|
||||
* "DT_TFPlayerSharedLocal.m_nDesiredDisguiseClass": 0,
|
||||
* "DT_TFPlayerShared.m_iKillStreak": 0,
|
||||
* "DT_TFPlayerShared.m_flCloakMeter": 100,
|
||||
*/
|
||||
|
||||
const userInfo = match.getUserInfoForEntity(entity);
|
||||
if (!userInfo) {
|
||||
throw new Error(`No user info for entity ${entity.entityIndex}`);
|
||||
}
|
||||
const player: Player = (match.playerEntityMap.has(entity.entityIndex)) ?
|
||||
match.playerEntityMap.get(entity.entityIndex) as Player :
|
||||
new Player(match, userInfo);
|
||||
if (!match.playerEntityMap.has(entity.entityIndex)) {
|
||||
match.playerEntityMap.set(entity.entityIndex, player);
|
||||
}
|
||||
|
||||
for (const prop of entity.props) {
|
||||
if (prop.definition.ownerTableName === 'm_hMyWeapons') {
|
||||
if (prop.value !== 2097151) {
|
||||
player.weaponIds[parseInt(prop.definition.name, 10)] = prop.value as number;
|
||||
}
|
||||
}
|
||||
if (prop.definition.ownerTableName === 'm_iAmmo') {
|
||||
if (prop.value !== null && prop.value > 0) {
|
||||
player.ammo[parseInt(prop.definition.name, 10)] = prop.value as number;
|
||||
}
|
||||
}
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
switch (propName) {
|
||||
case 'DT_BasePlayer.m_iHealth':
|
||||
player.health = prop.value as number;
|
||||
break;
|
||||
case 'DT_BasePlayer.m_iMaxHealth':
|
||||
player.maxHealth = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFLocalPlayerExclusive.m_vecOrigin':
|
||||
player.position.x = (prop.value as Vector).x;
|
||||
player.position.y = (prop.value as Vector).y;
|
||||
|
||||
// set the view angles for the local player since that prop isn't send
|
||||
player.viewAngle = message.localViewAngles[0].y;
|
||||
break;
|
||||
case 'DT_TFNonLocalPlayerExclusive.m_vecOrigin':
|
||||
player.position.x = (prop.value as Vector).x;
|
||||
player.position.y = (prop.value as Vector).y;
|
||||
break;
|
||||
case 'DT_TFLocalPlayerExclusive.m_vecOrigin[2]':
|
||||
player.position.z = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFNonLocalPlayerExclusive.m_vecOrigin[2]':
|
||||
player.position.z = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFNonLocalPlayerExclusive.m_angEyeAngles[1]':
|
||||
player.viewAngle = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFLocalPlayerExclusive.m_angEyeAngles[1]':
|
||||
player.viewAngle = prop.value as number;
|
||||
break;
|
||||
case 'DT_BasePlayer.m_lifeState':
|
||||
player.lifeState = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseCombatCharacter.m_hActiveWeapon':
|
||||
for (let i = 0; i < player.weapons.length; i++) {
|
||||
if (player.weaponIds[i] === prop.value) {
|
||||
player.activeWeapon = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'CWeaponMedigun':
|
||||
const weapon = match.weaponMap.get(entity.entityIndex) as CWeaponMedigun | undefined;
|
||||
if (weapon && weapon.className === 'CWeaponMedigun') {
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
switch (propName) {
|
||||
case 'DT_WeaponMedigun.m_hHealingTarget':
|
||||
weapon.healTarget = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFWeaponMedigunDataNonLocal.m_flChargeLevel':
|
||||
weapon.chargeLevel = prop.value as number;
|
||||
break;
|
||||
case 'DT_LocalTFWeaponMedigunData.m_flChargeLevel':
|
||||
weapon.chargeLevel = prop.value as number;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case'CTFTeam':
|
||||
if (entity.hasProperty('DT_Team', 'm_iTeamNum')) {
|
||||
const teamId = entity.getProperty('DT_Team', 'm_iTeamNum').value as TeamNumber;
|
||||
if (!match.teams.has(teamId)) {
|
||||
const team = {
|
||||
name: entity.getProperty('DT_Team', 'm_szTeamname').value as string,
|
||||
score: entity.getProperty('DT_Team', 'm_iScore').value as number,
|
||||
roundsWon: entity.getProperty('DT_Team', 'm_iRoundsWon').value as number,
|
||||
players: entity.getProperty('DT_Team', '"player_array"').value as number[],
|
||||
teamNumber: teamId
|
||||
};
|
||||
match.teams.set(teamId, team);
|
||||
match.teamEntityMap.set(entity.entityIndex, team);
|
||||
}
|
||||
} else {
|
||||
const team = match.teamEntityMap.get(entity.entityIndex);
|
||||
if (!team) {
|
||||
throw new Error(`No team with entity id: ${entity.entityIndex}`);
|
||||
}
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
switch (propName) {
|
||||
case 'DT_Team.m_iScore':
|
||||
team.score = prop.value as number;
|
||||
break;
|
||||
case 'DT_Team.m_szTeamname':
|
||||
team.name = prop.value as string;
|
||||
break;
|
||||
case 'DT_Team.m_iRoundsWon':
|
||||
team.roundsWon = prop.value as number;
|
||||
break;
|
||||
case 'DT_Team."player_array"':
|
||||
team.players = prop.value as number[];
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'CObjectSentrygun':
|
||||
if (!match.buildings.has(entity.entityIndex)) {
|
||||
match.buildings.set(entity.entityIndex, {
|
||||
type: 'sentry',
|
||||
ammoRockets: 0,
|
||||
ammoShells: 0,
|
||||
autoAimTarget: 0,
|
||||
builder: 0,
|
||||
health: 0,
|
||||
isBuilding: false,
|
||||
isSapped: false,
|
||||
level: 0,
|
||||
maxHealth: 0,
|
||||
playerControlled: false,
|
||||
position: new Vector(0, 0, 0),
|
||||
shieldLevel: 0,
|
||||
isMini: false,
|
||||
team: 0,
|
||||
angle: 0
|
||||
});
|
||||
}
|
||||
const sentry = match.buildings.get(entity.entityIndex) as Sentry;
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
applyBuildingProp(sentry, prop, propName);
|
||||
switch (propName) {
|
||||
case 'DT_ObjectSentrygun.m_bPlayerControlled':
|
||||
sentry.playerControlled = prop.value as number > 0;
|
||||
break;
|
||||
case 'DT_ObjectSentrygun.m_hAutoAimTarget':
|
||||
sentry.autoAimTarget = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectSentrygun.m_nShieldLevel':
|
||||
sentry.shieldLevel = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectSentrygun.m_iAmmoShells':
|
||||
sentry.ammoShells = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectSentrygun.m_iAmmoRockets':
|
||||
sentry.ammoRockets = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_bMiniBuilding':
|
||||
sentry.isMini = prop.value as number > 1;
|
||||
break;
|
||||
case 'DT_TFNonLocalPlayerExclusive.m_angEyeAngles[1]':
|
||||
sentry.angle = prop.value as number;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (entity.pvs & PVS.LEAVE) {
|
||||
match.buildings.delete(entity.entityIndex);
|
||||
}
|
||||
break;
|
||||
case 'CObjectDispenser':
|
||||
if (!match.buildings.has(entity.entityIndex)) {
|
||||
match.buildings.set(entity.entityIndex, {
|
||||
type: 'dispenser',
|
||||
builder: 0,
|
||||
health: 0,
|
||||
isBuilding: false,
|
||||
isSapped: false,
|
||||
level: 0,
|
||||
maxHealth: 0,
|
||||
position: new Vector(0, 0, 0),
|
||||
team: 0,
|
||||
healing: [],
|
||||
metal: 0,
|
||||
angle: 0
|
||||
});
|
||||
}
|
||||
const dispenser = match.buildings.get(entity.entityIndex) as Dispenser;
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
applyBuildingProp(dispenser, prop, propName);
|
||||
switch (propName) {
|
||||
case 'DT_ObjectDispenser.m_iAmmoMetal':
|
||||
dispenser.metal = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectDispenser."healing_array"':
|
||||
dispenser.healing = prop.value as number[];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (entity.pvs & PVS.LEAVE) {
|
||||
match.buildings.delete(entity.entityIndex);
|
||||
}
|
||||
break;
|
||||
case 'CObjectTeleporter':
|
||||
if (!match.buildings.has(entity.entityIndex)) {
|
||||
match.buildings.set(entity.entityIndex, {
|
||||
type: 'teleporter',
|
||||
builder: 0,
|
||||
health: 0,
|
||||
isBuilding: false,
|
||||
isSapped: false,
|
||||
level: 0,
|
||||
maxHealth: 0,
|
||||
position: new Vector(0, 0, 0),
|
||||
team: 0,
|
||||
isEntrance: false,
|
||||
otherEnd: 0,
|
||||
rechargeTime: 0,
|
||||
rechargeDuration: 0,
|
||||
timesUsed: 0,
|
||||
angle: 0,
|
||||
yawToExit: 0
|
||||
});
|
||||
}
|
||||
const teleporter = match.buildings.get(entity.entityIndex) as Teleporter;
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
applyBuildingProp(teleporter, prop, propName);
|
||||
switch (propName) {
|
||||
case 'DT_ObjectTeleporter.m_flRechargeTime':
|
||||
teleporter.rechargeTime = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectTeleporter.m_flCurrentRechargeDuration':
|
||||
teleporter.rechargeDuration = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectTeleporter.m_iTimesUsed':
|
||||
teleporter.timesUsed = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectTeleporter.m_bMatchBuilding':
|
||||
teleporter.otherEnd = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectTeleporter.m_flYawToExit':
|
||||
teleporter.yawToExit = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_iObjectMode':
|
||||
teleporter.isEntrance = prop.value as number === 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (entity.pvs & PVS.LEAVE) {
|
||||
match.buildings.delete(entity.entityIndex);
|
||||
}
|
||||
break;
|
||||
case 'CTFPlayerResource':
|
||||
for (const prop of entity.props) {
|
||||
const playerId = parseInt(prop.definition.name, 10);
|
||||
const value = prop.value as number;
|
||||
if (!match.playerResources[playerId]) {
|
||||
match.playerResources[playerId] = {
|
||||
alive: false,
|
||||
arenaSpectator: false,
|
||||
bonusPoints: 0,
|
||||
chargeLevel: 0,
|
||||
connected: false,
|
||||
damageAssists: 0,
|
||||
damageBlocked: 0,
|
||||
deaths: 0,
|
||||
dominations: 0,
|
||||
healing: 0,
|
||||
healingAssist: 0,
|
||||
health: 0,
|
||||
killStreak: 0,
|
||||
maxBuffedHealth: 0,
|
||||
maxHealth: 0,
|
||||
nextRespawn: 0,
|
||||
ping: 0,
|
||||
playerClass: 0,
|
||||
playerLevel: 0,
|
||||
score: 0,
|
||||
team: 0,
|
||||
totalScore: 0,
|
||||
damage: 0
|
||||
};
|
||||
}
|
||||
const playerResource = match.playerResources[playerId];
|
||||
switch (prop.definition.ownerTableName) {
|
||||
case 'm_iPing':
|
||||
playerResource.ping = value;
|
||||
break;
|
||||
case 'm_iScore':
|
||||
playerResource.score = value;
|
||||
break;
|
||||
case 'm_iDeaths':
|
||||
playerResource.deaths = value;
|
||||
break;
|
||||
case 'm_bConnected':
|
||||
playerResource.connected = value > 0;
|
||||
break;
|
||||
case 'm_iTeam':
|
||||
playerResource.team = value;
|
||||
break;
|
||||
case'm_bAlive':
|
||||
playerResource.alive = value > 0;
|
||||
break;
|
||||
case 'm_iHealth':
|
||||
playerResource.health = value;
|
||||
break;
|
||||
case 'm_iTotalScore':
|
||||
playerResource.totalScore = value;
|
||||
break;
|
||||
case 'm_iMaxHealth':
|
||||
playerResource.maxHealth = value;
|
||||
break;
|
||||
case 'm_iMaxBuffedHealth':
|
||||
playerResource.maxBuffedHealth = value;
|
||||
break;
|
||||
case 'm_iPlayerClass':
|
||||
playerResource.playerClass = value;
|
||||
break;
|
||||
case 'm_bArenaSpectator':
|
||||
playerResource.arenaSpectator = value > 0;
|
||||
break;
|
||||
case 'm_iActiveDominations':
|
||||
playerResource.dominations = value;
|
||||
break;
|
||||
case 'm_flNextRespawnTime':
|
||||
playerResource.nextRespawn = value;
|
||||
break;
|
||||
case 'm_iChargeLevel':
|
||||
playerResource.chargeLevel = value;
|
||||
break;
|
||||
case 'm_iDamage':
|
||||
playerResource.damage = value;
|
||||
break;
|
||||
case 'm_iDamageAssist':
|
||||
playerResource.damageAssists = value;
|
||||
break;
|
||||
case 'm_iHealing':
|
||||
playerResource.healing = value;
|
||||
break;
|
||||
case 'm_iHealingAssist':
|
||||
playerResource.healingAssist = value;
|
||||
break;
|
||||
case 'm_iDamageBlocked':
|
||||
playerResource.damageBlocked = value;
|
||||
break;
|
||||
case 'm_iBonusPoints':
|
||||
playerResource.bonusPoints = value;
|
||||
break;
|
||||
case 'm_iPlayerLevel':
|
||||
playerResource.playerLevel = value;
|
||||
break;
|
||||
case 'm_iKillstreak':
|
||||
playerResource.killStreak = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'CTeamRoundTimer':
|
||||
break;
|
||||
case 'CLaserDot':
|
||||
// for (const prop of entity.props) {
|
||||
// const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
// switch (propName) {
|
||||
// case 'DT_BaseEntity.m_iParentAttachment':
|
||||
// console.log(prop.value);
|
||||
// process.exit();
|
||||
// break;
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// console.log(match.getSendTable(entity.serverClass.dataTable).flattenedProps);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function applyBuildingProp(building: Building, prop: SendProp, propName: string) {
|
||||
switch (propName) {
|
||||
case 'DT_BaseObject.m_iUpgradeLevel':
|
||||
building.level = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_hBuilder':
|
||||
building.builder = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_iMaxHealth':
|
||||
building.maxHealth = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_iHealth':
|
||||
building.health = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_bBuilding':
|
||||
building.isBuilding = prop.value as number > 0;
|
||||
break;
|
||||
case 'DT_BaseObject.m_bHasSapper':
|
||||
building.isSapped = prop.value as number > 0;
|
||||
break;
|
||||
case 'DT_BaseEntity.m_vecOrigin':
|
||||
building.position = prop.value as Vector;
|
||||
break;
|
||||
case 'DT_BaseEntity.m_iTeamNum':
|
||||
building.team = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseEntity.m_angRotation':
|
||||
building.angle = (prop.value as Vector).y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,22 +47,28 @@ function handleStringTableEntries(tableName: string, entries: StringTableEntry[]
|
|||
function calculateUserInfoFromEntry(text: string, extraData: BitStream, state: ParserState) {
|
||||
if (extraData.bitsLeft > (32 * 8)) {
|
||||
const name = extraData.readUTF8String(32);
|
||||
const userId = extraData.readUint32();
|
||||
let userId = extraData.readUint32();
|
||||
while (userId > 256) {
|
||||
userId -= 256;
|
||||
}
|
||||
const steamId = extraData.readUTF8String();
|
||||
if (steamId) {
|
||||
const entityId = parseInt(text, 10) + 1;
|
||||
let userState = state.userInfo.get(userId);
|
||||
|
||||
if (!userState) {
|
||||
userState = {
|
||||
name: '',
|
||||
userId,
|
||||
steamId: '',
|
||||
entityId: 0
|
||||
entityId: entityId
|
||||
};
|
||||
|
||||
state.userInfo.set(userState.userId, userState);
|
||||
}
|
||||
|
||||
userState.name = name;
|
||||
userState.steamId = steamId;
|
||||
userState.entityId = parseInt(text, 10) + 1;
|
||||
state.userInfo.set(userId, userState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
455
src/PacketHandler/TFEntityHandler.ts
Normal file
455
src/PacketHandler/TFEntityHandler.ts
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
import {Vector} from '../Data/Vector';
|
||||
import {PacketEntity, PVS} from '../Data/PacketEntity';
|
||||
import {SendProp} from '../Data/SendProp';
|
||||
import {Building, Dispenser, Sentry, Teleporter} from '../Data/Building';
|
||||
import {CWeaponMedigun} from '../Data/Weapon';
|
||||
import {Match} from '../Data/Match';
|
||||
import {TeamNumber} from '../Data/Team';
|
||||
import {PacketMessage} from '../Data/Message';
|
||||
import {Player} from '../Data/Player';
|
||||
|
||||
export function handleTFEntity(entity: PacketEntity, match: Match, message: PacketMessage) {
|
||||
switch (entity.serverClass.name) {
|
||||
case 'CTFPlayer':
|
||||
/**
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iCaptures": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iDefenses": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iKills": 5,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iDeaths": 17,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iSuicides": 7,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iDominations": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iRevenge": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iBuildingsBuilt": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iBuildingsDestroyed": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iHeadshots": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iBackstabs": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iHealPoints": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iInvulns": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iTeleports": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iDamageDone": 847,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iCrits": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iResupplyPoints": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iKillAssists": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iBonusPoints": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iPoints": 6,
|
||||
* "DT_TFPlayerSharedLocal.m_nDesiredDisguiseTeam": 0,
|
||||
* "DT_TFPlayerSharedLocal.m_nDesiredDisguiseClass": 0,
|
||||
* "DT_TFPlayerShared.m_iKillStreak": 0,
|
||||
* "DT_TFPlayerShared.m_flCloakMeter": 100,
|
||||
*/
|
||||
|
||||
const userInfo = match.getUserInfoForEntity(entity);
|
||||
if (!userInfo) {
|
||||
throw new Error(`No user info for entity ${entity.entityIndex}`);
|
||||
}
|
||||
const player: Player = (match.playerEntityMap.has(entity.entityIndex)) ?
|
||||
match.playerEntityMap.get(entity.entityIndex) as Player :
|
||||
new Player(match, userInfo);
|
||||
if (!match.playerEntityMap.has(entity.entityIndex)) {
|
||||
match.playerEntityMap.set(entity.entityIndex, player);
|
||||
}
|
||||
|
||||
for (const prop of entity.props) {
|
||||
if (prop.definition.ownerTableName === 'm_hMyWeapons') {
|
||||
if (prop.value !== 2097151) {
|
||||
player.weaponIds[parseInt(prop.definition.name, 10)] = prop.value as number;
|
||||
}
|
||||
}
|
||||
if (prop.definition.ownerTableName === 'm_iAmmo') {
|
||||
if (prop.value !== null && prop.value > 0) {
|
||||
player.ammo[parseInt(prop.definition.name, 10)] = prop.value as number;
|
||||
}
|
||||
}
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
switch (propName) {
|
||||
case 'DT_BasePlayer.m_iHealth':
|
||||
player.health = prop.value as number;
|
||||
break;
|
||||
case 'DT_BasePlayer.m_iMaxHealth':
|
||||
player.maxHealth = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFLocalPlayerExclusive.m_vecOrigin':
|
||||
player.position.x = (prop.value as Vector).x;
|
||||
player.position.y = (prop.value as Vector).y;
|
||||
|
||||
// set the view angles for the local player since that prop isn't send
|
||||
player.viewAngle = message.localViewAngles[0].y;
|
||||
break;
|
||||
case 'DT_TFNonLocalPlayerExclusive.m_vecOrigin':
|
||||
player.position.x = (prop.value as Vector).x;
|
||||
player.position.y = (prop.value as Vector).y;
|
||||
break;
|
||||
case 'DT_TFLocalPlayerExclusive.m_vecOrigin[2]':
|
||||
player.position.z = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFNonLocalPlayerExclusive.m_vecOrigin[2]':
|
||||
player.position.z = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFNonLocalPlayerExclusive.m_angEyeAngles[1]':
|
||||
player.viewAngle = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFLocalPlayerExclusive.m_angEyeAngles[1]':
|
||||
player.viewAngle = prop.value as number;
|
||||
break;
|
||||
case 'DT_BasePlayer.m_lifeState':
|
||||
player.lifeState = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseCombatCharacter.m_hActiveWeapon':
|
||||
for (let i = 0; i < player.weapons.length; i++) {
|
||||
if (player.weaponIds[i] === prop.value) {
|
||||
player.activeWeapon = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'CWeaponMedigun':
|
||||
const weapon = match.weaponMap.get(entity.entityIndex) as CWeaponMedigun | undefined;
|
||||
if (weapon && weapon.className === 'CWeaponMedigun') {
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
switch (propName) {
|
||||
case 'DT_WeaponMedigun.m_hHealingTarget':
|
||||
weapon.healTarget = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFWeaponMedigunDataNonLocal.m_flChargeLevel':
|
||||
weapon.chargeLevel = prop.value as number;
|
||||
break;
|
||||
case 'DT_LocalTFWeaponMedigunData.m_flChargeLevel':
|
||||
weapon.chargeLevel = prop.value as number;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case'CTFTeam':
|
||||
if (entity.hasProperty('DT_Team', 'm_iTeamNum')) {
|
||||
const teamId = entity.getProperty('DT_Team', 'm_iTeamNum').value as TeamNumber;
|
||||
if (!match.teams.has(teamId)) {
|
||||
const team = {
|
||||
name: entity.getProperty('DT_Team', 'm_szTeamname').value as string,
|
||||
score: entity.getProperty('DT_Team', 'm_iScore').value as number,
|
||||
roundsWon: entity.getProperty('DT_Team', 'm_iRoundsWon').value as number,
|
||||
players: entity.getProperty('DT_Team', '"player_array"').value as number[],
|
||||
teamNumber: teamId
|
||||
};
|
||||
match.teams.set(teamId, team);
|
||||
match.teamEntityMap.set(entity.entityIndex, team);
|
||||
}
|
||||
} else {
|
||||
const team = match.teamEntityMap.get(entity.entityIndex);
|
||||
if (!team) {
|
||||
throw new Error(`No team with entity id: ${entity.entityIndex}`);
|
||||
}
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
switch (propName) {
|
||||
case 'DT_Team.m_iScore':
|
||||
team.score = prop.value as number;
|
||||
break;
|
||||
case 'DT_Team.m_szTeamname':
|
||||
team.name = prop.value as string;
|
||||
break;
|
||||
case 'DT_Team.m_iRoundsWon':
|
||||
team.roundsWon = prop.value as number;
|
||||
break;
|
||||
case 'DT_Team."player_array"':
|
||||
team.players = prop.value as number[];
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'CObjectSentrygun':
|
||||
if (!match.buildings.has(entity.entityIndex)) {
|
||||
match.buildings.set(entity.entityIndex, {
|
||||
type: 'sentry',
|
||||
ammoRockets: 0,
|
||||
ammoShells: 0,
|
||||
autoAimTarget: 0,
|
||||
builder: 0,
|
||||
health: 0,
|
||||
isBuilding: false,
|
||||
isSapped: false,
|
||||
level: 0,
|
||||
maxHealth: 0,
|
||||
playerControlled: false,
|
||||
position: new Vector(0, 0, 0),
|
||||
shieldLevel: 0,
|
||||
isMini: false,
|
||||
team: 0,
|
||||
angle: 0
|
||||
});
|
||||
}
|
||||
const sentry = match.buildings.get(entity.entityIndex) as Sentry;
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
applyBuildingProp(sentry, prop, propName);
|
||||
switch (propName) {
|
||||
case 'DT_ObjectSentrygun.m_bPlayerControlled':
|
||||
sentry.playerControlled = prop.value as number > 0;
|
||||
break;
|
||||
case 'DT_ObjectSentrygun.m_hAutoAimTarget':
|
||||
sentry.autoAimTarget = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectSentrygun.m_nShieldLevel':
|
||||
sentry.shieldLevel = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectSentrygun.m_iAmmoShells':
|
||||
sentry.ammoShells = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectSentrygun.m_iAmmoRockets':
|
||||
sentry.ammoRockets = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_bMiniBuilding':
|
||||
sentry.isMini = prop.value as number > 1;
|
||||
break;
|
||||
case 'DT_TFNonLocalPlayerExclusive.m_angEyeAngles[1]':
|
||||
sentry.angle = prop.value as number;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (entity.pvs & PVS.LEAVE) {
|
||||
match.buildings.delete(entity.entityIndex);
|
||||
}
|
||||
break;
|
||||
case 'CObjectDispenser':
|
||||
if (!match.buildings.has(entity.entityIndex)) {
|
||||
match.buildings.set(entity.entityIndex, {
|
||||
type: 'dispenser',
|
||||
builder: 0,
|
||||
health: 0,
|
||||
isBuilding: false,
|
||||
isSapped: false,
|
||||
level: 0,
|
||||
maxHealth: 0,
|
||||
position: new Vector(0, 0, 0),
|
||||
team: 0,
|
||||
healing: [],
|
||||
metal: 0,
|
||||
angle: 0
|
||||
});
|
||||
}
|
||||
const dispenser = match.buildings.get(entity.entityIndex) as Dispenser;
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
applyBuildingProp(dispenser, prop, propName);
|
||||
switch (propName) {
|
||||
case 'DT_ObjectDispenser.m_iAmmoMetal':
|
||||
dispenser.metal = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectDispenser."healing_array"':
|
||||
dispenser.healing = prop.value as number[];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (entity.pvs & PVS.LEAVE) {
|
||||
match.buildings.delete(entity.entityIndex);
|
||||
}
|
||||
break;
|
||||
case 'CObjectTeleporter':
|
||||
if (!match.buildings.has(entity.entityIndex)) {
|
||||
match.buildings.set(entity.entityIndex, {
|
||||
type: 'teleporter',
|
||||
builder: 0,
|
||||
health: 0,
|
||||
isBuilding: false,
|
||||
isSapped: false,
|
||||
level: 0,
|
||||
maxHealth: 0,
|
||||
position: new Vector(0, 0, 0),
|
||||
team: 0,
|
||||
isEntrance: false,
|
||||
otherEnd: 0,
|
||||
rechargeTime: 0,
|
||||
rechargeDuration: 0,
|
||||
timesUsed: 0,
|
||||
angle: 0,
|
||||
yawToExit: 0
|
||||
});
|
||||
}
|
||||
const teleporter = match.buildings.get(entity.entityIndex) as Teleporter;
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
applyBuildingProp(teleporter, prop, propName);
|
||||
switch (propName) {
|
||||
case 'DT_ObjectTeleporter.m_flRechargeTime':
|
||||
teleporter.rechargeTime = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectTeleporter.m_flCurrentRechargeDuration':
|
||||
teleporter.rechargeDuration = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectTeleporter.m_iTimesUsed':
|
||||
teleporter.timesUsed = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectTeleporter.m_bMatchBuilding':
|
||||
teleporter.otherEnd = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectTeleporter.m_flYawToExit':
|
||||
teleporter.yawToExit = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_iObjectMode':
|
||||
teleporter.isEntrance = prop.value as number === 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (entity.pvs & PVS.LEAVE) {
|
||||
match.buildings.delete(entity.entityIndex);
|
||||
}
|
||||
break;
|
||||
case 'CTFPlayerResource':
|
||||
for (const prop of entity.props) {
|
||||
const playerId = parseInt(prop.definition.name, 10);
|
||||
const value = prop.value as number;
|
||||
if (!match.playerResources[playerId]) {
|
||||
match.playerResources[playerId] = {
|
||||
alive: false,
|
||||
arenaSpectator: false,
|
||||
bonusPoints: 0,
|
||||
chargeLevel: 0,
|
||||
connected: false,
|
||||
damageAssists: 0,
|
||||
damageBlocked: 0,
|
||||
deaths: 0,
|
||||
dominations: 0,
|
||||
healing: 0,
|
||||
healingAssist: 0,
|
||||
health: 0,
|
||||
killStreak: 0,
|
||||
maxBuffedHealth: 0,
|
||||
maxHealth: 0,
|
||||
nextRespawn: 0,
|
||||
ping: 0,
|
||||
playerClass: 0,
|
||||
playerLevel: 0,
|
||||
score: 0,
|
||||
team: 0,
|
||||
totalScore: 0,
|
||||
damage: 0
|
||||
};
|
||||
}
|
||||
const playerResource = match.playerResources[playerId];
|
||||
switch (prop.definition.ownerTableName) {
|
||||
case 'm_iPing':
|
||||
playerResource.ping = value;
|
||||
break;
|
||||
case 'm_iScore':
|
||||
playerResource.score = value;
|
||||
break;
|
||||
case 'm_iDeaths':
|
||||
playerResource.deaths = value;
|
||||
break;
|
||||
case 'm_bConnected':
|
||||
playerResource.connected = value > 0;
|
||||
break;
|
||||
case 'm_iTeam':
|
||||
playerResource.team = value;
|
||||
break;
|
||||
case'm_bAlive':
|
||||
playerResource.alive = value > 0;
|
||||
break;
|
||||
case 'm_iHealth':
|
||||
playerResource.health = value;
|
||||
break;
|
||||
case 'm_iTotalScore':
|
||||
playerResource.totalScore = value;
|
||||
break;
|
||||
case 'm_iMaxHealth':
|
||||
playerResource.maxHealth = value;
|
||||
break;
|
||||
case 'm_iMaxBuffedHealth':
|
||||
playerResource.maxBuffedHealth = value;
|
||||
break;
|
||||
case 'm_iPlayerClass':
|
||||
playerResource.playerClass = value;
|
||||
break;
|
||||
case 'm_bArenaSpectator':
|
||||
playerResource.arenaSpectator = value > 0;
|
||||
break;
|
||||
case 'm_iActiveDominations':
|
||||
playerResource.dominations = value;
|
||||
break;
|
||||
case 'm_flNextRespawnTime':
|
||||
playerResource.nextRespawn = value;
|
||||
break;
|
||||
case 'm_iChargeLevel':
|
||||
playerResource.chargeLevel = value;
|
||||
break;
|
||||
case 'm_iDamage':
|
||||
playerResource.damage = value;
|
||||
break;
|
||||
case 'm_iDamageAssist':
|
||||
playerResource.damageAssists = value;
|
||||
break;
|
||||
case 'm_iHealing':
|
||||
playerResource.healing = value;
|
||||
break;
|
||||
case 'm_iHealingAssist':
|
||||
playerResource.healingAssist = value;
|
||||
break;
|
||||
case 'm_iDamageBlocked':
|
||||
playerResource.damageBlocked = value;
|
||||
break;
|
||||
case 'm_iBonusPoints':
|
||||
playerResource.bonusPoints = value;
|
||||
break;
|
||||
case 'm_iPlayerLevel':
|
||||
playerResource.playerLevel = value;
|
||||
break;
|
||||
case 'm_iKillstreak':
|
||||
playerResource.killStreak = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'CTeamRoundTimer':
|
||||
break;
|
||||
case 'CLaserDot':
|
||||
// for (const prop of entity.props) {
|
||||
// const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
// switch (propName) {
|
||||
// case 'DT_BaseEntity.m_iParentAttachment':
|
||||
// console.log(prop.value);
|
||||
// process.exit();
|
||||
// break;
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// console.log(match.getSendTable(entity.serverClass.dataTable).flattenedProps);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function applyBuildingProp(building: Building, prop: SendProp, propName: string) {
|
||||
switch (propName) {
|
||||
case 'DT_BaseObject.m_iUpgradeLevel':
|
||||
building.level = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_hBuilder':
|
||||
building.builder = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_iMaxHealth':
|
||||
building.maxHealth = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_iHealth':
|
||||
building.health = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_bBuilding':
|
||||
building.isBuilding = prop.value as number > 0;
|
||||
break;
|
||||
case 'DT_BaseObject.m_bHasSapper':
|
||||
building.isSapped = prop.value as number > 0;
|
||||
break;
|
||||
case 'DT_BaseEntity.m_vecOrigin':
|
||||
building.position = prop.value as Vector;
|
||||
break;
|
||||
case 'DT_BaseEntity.m_iTeamNum':
|
||||
building.team = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseEntity.m_angRotation':
|
||||
building.angle = (prop.value as Vector).y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue