mirror of
https://github.com/demostf/demo.js
synced 2026-06-03 16:44:12 +02:00
entity encoder wip
This commit is contained in:
parent
1ebf40dda4
commit
206facfa8d
17 changed files with 733 additions and 174 deletions
|
|
@ -12,7 +12,7 @@ import {EntityId, PacketEntity} from './PacketEntity';
|
|||
import {Player} from './Player';
|
||||
import {PlayerResource} from './PlayerResource';
|
||||
import {SendTable, SendTableName} from './SendTable';
|
||||
import {ServerClass} from './ServerClass';
|
||||
import {ServerClass, ServerClassId} from './ServerClass';
|
||||
import {StringTable} from './StringTable';
|
||||
import {Team, TeamNumber} from './Team';
|
||||
import {UserInfo} from './UserInfo';
|
||||
|
|
@ -31,7 +31,7 @@ export class Match {
|
|||
public rounds: Round[] = [];
|
||||
public startTick: number = 0;
|
||||
public intervalPerTick: number = 0;
|
||||
public staticBaseLines: BitStream[] = [];
|
||||
public staticBaseLines: Map<ServerClassId, BitStream> = new Map();
|
||||
public eventDefinitions: Map<number, GameEventDefinition<GameEventType>> = new Map();
|
||||
public world: World = {
|
||||
boundaryMin: {x: 0, y: 0, z: 0},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {GameEventDefinition} from './GameEvent';
|
||||
import {PacketEntity} from './PacketEntity';
|
||||
import {EntityId, PacketEntity} from './PacketEntity';
|
||||
import {SendTable} from './SendTable';
|
||||
import {ServerClass} from './ServerClass';
|
||||
import {StringTable, StringTableEntry} from './StringTable';
|
||||
|
|
@ -82,13 +82,10 @@ export interface GameEventListPacket extends BasePacket {
|
|||
export interface PacketEntitiesPacket extends BasePacket {
|
||||
packetType: 'packetEntities';
|
||||
entities: PacketEntity[];
|
||||
removedEntities: number[];
|
||||
removedEntities: EntityId[];
|
||||
maxEntries: number;
|
||||
isDelta: boolean;
|
||||
delta: number;
|
||||
baseLine: number;
|
||||
updatedEntries: number;
|
||||
length: number;
|
||||
updatedBaseLine: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,4 +64,11 @@ export class PacketEntity {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public diffFromBaseLine(baseline: PacketEntity): SendProp[] {
|
||||
return this.props.filter(prop => {
|
||||
const baseProp = baseline.getPropByDefinition(prop.definition);
|
||||
return (!baseProp || prop.value !== baseProp.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
export type ServerClassId = number;
|
||||
|
||||
export class ServerClass {
|
||||
public id: number;
|
||||
public id: ServerClassId;
|
||||
public name: string;
|
||||
public dataTable: string;
|
||||
|
||||
constructor(id: number, name: string, dataTable: string) {
|
||||
constructor(id: ServerClassId, name: string, dataTable: string) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.dataTable = dataTable;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ function saveUserData(userData: StringTableEntry, match: Match) {
|
|||
|
||||
function saveInstanceBaseLine(entry: StringTableEntry, match: Match) {
|
||||
if (entry.extraData) {
|
||||
match.staticBaseLines[parseInt(entry.text, 10)] = entry.extraData;
|
||||
match.staticBaseLines.set(parseInt(entry.text, 10), entry.extraData);
|
||||
} else {
|
||||
throw new Error('Missing baseline');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,38 @@ import {Match} from '../../Data/Match';
|
|||
import {PacketEntitiesPacket} from '../../Data/Packet';
|
||||
import {EntityId, PacketEntity, PVS} from '../../Data/PacketEntity';
|
||||
import {SendProp} from '../../Data/SendProp';
|
||||
import {getEntityUpdate} from '../EntityDecoder';
|
||||
import {readUBitVar} from '../readBitVar';
|
||||
import {encodeEntityUpdate, getEntityUpdate} from '../EntityDecoder';
|
||||
import {readUBitVar, writeBitVar} from '../readBitVar';
|
||||
import {isDate} from 'util';
|
||||
import {ServerClass} from '../../Data/ServerClass';
|
||||
import {SendTable} from '../../Data/SendTable';
|
||||
|
||||
const pvsMap = {
|
||||
0: PVS.PRESERVE,
|
||||
2: PVS.ENTER,
|
||||
1: PVS.LEAVE,
|
||||
3: PVS.LEAVE + PVS.DELETE,
|
||||
};
|
||||
const pvsMap = new Map([
|
||||
[0, PVS.PRESERVE],
|
||||
[2, PVS.ENTER],
|
||||
[1, PVS.LEAVE],
|
||||
[3, PVS.LEAVE + PVS.DELETE],
|
||||
]);
|
||||
|
||||
const pvsReverseMap = new Map([
|
||||
[PVS.PRESERVE, 0],
|
||||
[PVS.ENTER, 2],
|
||||
[PVS.LEAVE, 1],
|
||||
[PVS.LEAVE + PVS.DELETE, 3],
|
||||
]);
|
||||
|
||||
function readPVSType(stream: BitStream): PVS {
|
||||
const pvs = stream.readBits(2);
|
||||
// console.log(pvs);
|
||||
return pvsMap[pvs];
|
||||
return pvsMap.get(pvs) as number;
|
||||
}
|
||||
|
||||
function writePVSType(pvs: PVS, stream: BitStream) {
|
||||
const raw = pvsReverseMap.get(pvs);
|
||||
if (!raw) {
|
||||
throw new Error(`Unknown pvs ${pvs}`);
|
||||
}
|
||||
stream.writeBits(raw, 2);
|
||||
}
|
||||
|
||||
function readEnterPVS(stream: BitStream, entityId: EntityId, match: Match): PacketEntity {
|
||||
|
|
@ -36,7 +54,7 @@ function readEnterPVS(stream: BitStream, entityId: EntityId, match: Match): Pack
|
|||
if (!sendTable) {
|
||||
throw new Error('Unknown SendTable for serverclass');
|
||||
}
|
||||
const staticBaseLine = match.staticBaseLines[serverClass.id];
|
||||
const staticBaseLine = match.staticBaseLines.get(serverClass.id);
|
||||
if (staticBaseLine) {
|
||||
staticBaseLine.index = 0;
|
||||
const props = getEntityUpdate(sendTable, staticBaseLine);
|
||||
|
|
@ -52,6 +70,30 @@ function readEnterPVS(stream: BitStream, entityId: EntityId, match: Match): Pack
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PacketEntity} entity
|
||||
* @param {BitStream} stream
|
||||
* @param {Match} match
|
||||
* @returns {SendProp[]} the entities to be encoded
|
||||
*/
|
||||
function writeEnterPVS(entity: PacketEntity, stream: BitStream, match: Match) {
|
||||
const serverClassId = match.serverClasses.findIndex(serverClass => serverClass && entity.serverClass.id === serverClass.id);
|
||||
if (serverClassId === -1) {
|
||||
throw new Error(`Unknown server class ${entity.serverClass.name}(${entity.serverClass.id})`);
|
||||
}
|
||||
// get the instance from the match, not the entity
|
||||
const serverClass = match.serverClasses[serverClassId];
|
||||
|
||||
stream.writeBits(serverClassId, match.classBits);
|
||||
stream.writeBits(entity.serialNumber || 0, 10);
|
||||
|
||||
const cachedBaseLine = match.baseLineCache.get(serverClass);
|
||||
const propsToEncode = cachedBaseLine ? entity.diffFromBaseLine(cachedBaseLine) : entity.props;
|
||||
|
||||
|
||||
encodeEntityUpdate(propsToEncode, match.getSendTable(serverClass.dataTable), stream);
|
||||
}
|
||||
|
||||
function getPacketEntityForExisting(entityId: EntityId, match: Match, pvs: PVS) {
|
||||
const serverClass = match.entityClasses.get(entityId);
|
||||
if (!serverClass) {
|
||||
|
|
@ -62,6 +104,7 @@ function getPacketEntityForExisting(entityId: EntityId, match: Match, pvs: PVS)
|
|||
}
|
||||
|
||||
export function ParsePacketEntities(stream: BitStream, match: Match, skip: boolean = false): PacketEntitiesPacket { // 26: packetEntities
|
||||
const s = stream.index;
|
||||
// https://github.com/skadistats/smoke/blob/master/smoke/replay/handler/svc_packetentities.pyx
|
||||
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/DP/Handler/PacketEntitesHandler.cs
|
||||
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/DP/Entity.cs
|
||||
|
|
@ -104,6 +147,8 @@ export function ParsePacketEntities(stream: BitStream, match: Match, skip: boole
|
|||
} else if (match.entityClasses.has(entityId)) {
|
||||
const packetEntity = getPacketEntityForExisting(entityId, match, pvs);
|
||||
receivedEntities.push(packetEntity);
|
||||
} else {
|
||||
throw new Error(`No existing entity to update with id ${entityId}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,11 +165,52 @@ export function ParsePacketEntities(stream: BitStream, match: Match, skip: boole
|
|||
entities: receivedEntities,
|
||||
removedEntities: removedEntityIds,
|
||||
maxEntries,
|
||||
isDelta,
|
||||
delta,
|
||||
baseLine,
|
||||
updatedEntries,
|
||||
length,
|
||||
updatedBaseLine,
|
||||
};
|
||||
}
|
||||
|
||||
export function EncodePacketEntities(packet: PacketEntitiesPacket, stream: BitStream, match: Match) {
|
||||
stream.writeBits(packet.maxEntries, 11);
|
||||
const isDelta = packet.removedEntities.length > 0;
|
||||
stream.writeBoolean(isDelta);
|
||||
if (isDelta) {
|
||||
stream.writeInt32(packet.delta);
|
||||
}
|
||||
stream.writeBits(packet.baseLine, 1);
|
||||
stream.writeBits(packet.entities.length, 11);
|
||||
|
||||
const lengthStart = stream.index;
|
||||
|
||||
stream.index += 20;
|
||||
const packetDataStart = stream.index;
|
||||
stream.writeBoolean(packet.updatedBaseLine);
|
||||
|
||||
let lastEntityId = -1;
|
||||
|
||||
for (const entity of packet.entities) {
|
||||
const diff = entity.entityIndex - lastEntityId;
|
||||
lastEntityId = entity.entityIndex;
|
||||
writeBitVar(diff - 1, stream);
|
||||
writePVSType(entity.pvs, stream);
|
||||
|
||||
if (entity.pvs === PVS.ENTER) {
|
||||
writeEnterPVS(entity, stream, match);
|
||||
} else if (entity.pvs === PVS.PRESERVE) {
|
||||
encodeEntityUpdate(entity.props, match.getSendTable(entity.serverClass.dataTable), stream);
|
||||
}
|
||||
|
||||
for (const removedEntity of packet.removedEntities) {
|
||||
stream.writeBoolean(true);
|
||||
stream.writeBits(removedEntity, 11);
|
||||
}
|
||||
stream.writeBoolean(false);
|
||||
}
|
||||
|
||||
const packetDataEnd = stream.index;
|
||||
|
||||
stream.index = lengthStart;
|
||||
stream.writeBits(packetDataEnd - packetDataStart, 20);
|
||||
stream.index = packetDataEnd;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ export class SendPropParser {
|
|||
}
|
||||
|
||||
public static readInt(propDefinition: SendPropDefinition, stream: BitStream) {
|
||||
if (!propDefinition.hasFlag) {
|
||||
console.log(propDefinition, propDefinition.hasFlag);
|
||||
}
|
||||
if (propDefinition.hasFlag(SendPropFlag.SPROP_VARINT)) {
|
||||
return readVarInt(stream, !propDefinition.hasFlag(SendPropFlag.SPROP_UNSIGNED));
|
||||
} else {
|
||||
|
|
|
|||
1
src/tests/data/packetEntitiesData.json
Normal file
1
src/tests/data/packetEntitiesData.json
Normal file
File diff suppressed because one or more lines are too long
BIN
src/tests/data/packetEntitiesResult.json.gz
Normal file
BIN
src/tests/data/packetEntitiesResult.json.gz
Normal file
Binary file not shown.
BIN
src/tests/data/packetEntitiesSendTables.json.gz
Normal file
BIN
src/tests/data/packetEntitiesSendTables.json.gz
Normal file
Binary file not shown.
337
src/tests/data/packetEntitiesServerClasses.json
Normal file
337
src/tests/data/packetEntitiesServerClasses.json
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
[
|
||||
{
|
||||
"id": 338,
|
||||
"name": "CWorld",
|
||||
"dataTable": "DT_WORLD"
|
||||
},
|
||||
{
|
||||
"id": 241,
|
||||
"name": "CTFPlayer",
|
||||
"dataTable": "DT_TFPlayer"
|
||||
},
|
||||
{
|
||||
"id": 306,
|
||||
"name": "CTFTeam",
|
||||
"dataTable": "DT_TFTeam"
|
||||
},
|
||||
{
|
||||
"id": 243,
|
||||
"name": "CTFPlayerResource",
|
||||
"dataTable": "DT_TFPlayerResource"
|
||||
},
|
||||
{
|
||||
"id": 229,
|
||||
"name": "CTFObjectiveResource",
|
||||
"dataTable": "DT_TFObjectiveResource"
|
||||
},
|
||||
{
|
||||
"id": 84,
|
||||
"name": "CMonsterResource",
|
||||
"dataTable": "DT_MonsterResource"
|
||||
},
|
||||
{
|
||||
"id": 80,
|
||||
"name": "CMannVsMachineStats",
|
||||
"dataTable": "DT_MannVsMachineStats"
|
||||
},
|
||||
{
|
||||
"id": 331,
|
||||
"name": "CVoteController",
|
||||
"dataTable": "DT_VoteController"
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"name": "CDynamicProp",
|
||||
"dataTable": "DT_DynamicProp"
|
||||
},
|
||||
{
|
||||
"id": 79,
|
||||
"name": "CLightGlow",
|
||||
"dataTable": "DT_LightGlow"
|
||||
},
|
||||
{
|
||||
"id": 119,
|
||||
"name": "CSprite",
|
||||
"dataTable": "DT_Sprite"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "CBaseEntity",
|
||||
"dataTable": "DT_BaseEntity"
|
||||
},
|
||||
{
|
||||
"id": 126,
|
||||
"name": "CTeamRoundTimer",
|
||||
"dataTable": "DT_TeamRoundTimer"
|
||||
},
|
||||
{
|
||||
"id": 127,
|
||||
"name": "CTeamTrainWatcher",
|
||||
"dataTable": "DT_TeamTrainWatcher"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "CBaseAnimating",
|
||||
"dataTable": "DT_BaseAnimating"
|
||||
},
|
||||
{
|
||||
"id": 92,
|
||||
"name": "CParticleSystem",
|
||||
"dataTable": "DT_ParticleSystem"
|
||||
},
|
||||
{
|
||||
"id": 85,
|
||||
"name": "CObjectCartDispenser",
|
||||
"dataTable": "DT_ObjectCartDispenser"
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"name": "CFuncTrackTrain",
|
||||
"dataTable": "DT_FuncTrackTrain"
|
||||
},
|
||||
{
|
||||
"id": 97,
|
||||
"name": "CPhysicsProp",
|
||||
"dataTable": "DT_PhysicsProp"
|
||||
},
|
||||
{
|
||||
"id": 207,
|
||||
"name": "CTFGameRulesProxy",
|
||||
"dataTable": "DT_TFGameRulesProxy"
|
||||
},
|
||||
{
|
||||
"id": 123,
|
||||
"name": "CSun",
|
||||
"dataTable": "DT_Sun"
|
||||
},
|
||||
{
|
||||
"id": 52,
|
||||
"name": "CFogController",
|
||||
"dataTable": "DT_FogController"
|
||||
},
|
||||
{
|
||||
"id": 114,
|
||||
"name": "CShadowControl",
|
||||
"dataTable": "DT_ShadowControl"
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"name": "CEnvTonemapController",
|
||||
"dataTable": "DT_EnvTonemapController"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "CBaseDoor",
|
||||
"dataTable": "DT_BaseDoor"
|
||||
},
|
||||
{
|
||||
"id": 113,
|
||||
"name": "CSceneEntity",
|
||||
"dataTable": "DT_SceneEntity"
|
||||
},
|
||||
{
|
||||
"id": 217,
|
||||
"name": "CTFJar",
|
||||
"dataTable": "DT_TFWeaponJar"
|
||||
},
|
||||
{
|
||||
"id": 64,
|
||||
"name": "CFuncRespawnRoomVisualizer",
|
||||
"dataTable": "DT_FuncRespawnRoomVisualizer"
|
||||
},
|
||||
{
|
||||
"id": 289,
|
||||
"name": "CTFShotgun_Pyro",
|
||||
"dataTable": "DT_TFShotgun_Pyro"
|
||||
},
|
||||
{
|
||||
"id": 292,
|
||||
"name": "CTFShotgunBuildingRescue",
|
||||
"dataTable": "DT_TFShotgunBuildingRescue"
|
||||
},
|
||||
{
|
||||
"id": 308,
|
||||
"name": "CTFViewModel",
|
||||
"dataTable": "DT_TFViewModel"
|
||||
},
|
||||
{
|
||||
"id": 212,
|
||||
"name": "CTFGrenadePipebombProjectile",
|
||||
"dataTable": "DT_TFProjectile_Pipebomb"
|
||||
},
|
||||
{
|
||||
"id": 228,
|
||||
"name": "CTFMinigun",
|
||||
"dataTable": "DT_WeaponMinigun"
|
||||
},
|
||||
{
|
||||
"id": 112,
|
||||
"name": "CRopeKeyframe",
|
||||
"dataTable": "DT_RopeKeyframe"
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"name": "CFuncOccluder",
|
||||
"dataTable": "DT_FuncOccluder"
|
||||
},
|
||||
{
|
||||
"id": 54,
|
||||
"name": "CFunc_LOD",
|
||||
"dataTable": "DT_Func_LOD"
|
||||
},
|
||||
{
|
||||
"id": 198,
|
||||
"name": "CTFCrossbow",
|
||||
"dataTable": "DT_Crossbow"
|
||||
},
|
||||
{
|
||||
"id": 323,
|
||||
"name": "CTFWearable",
|
||||
"dataTable": "DT_TFWearable"
|
||||
},
|
||||
{
|
||||
"id": 295,
|
||||
"name": "CTFSniperRifle",
|
||||
"dataTable": "DT_TFSniperRifle"
|
||||
},
|
||||
{
|
||||
"id": 189,
|
||||
"name": "CTFBonesaw",
|
||||
"dataTable": "DT_TFWeaponBonesaw"
|
||||
},
|
||||
{
|
||||
"id": 196,
|
||||
"name": "CTFClub",
|
||||
"dataTable": "DT_TFWeaponClub"
|
||||
},
|
||||
{
|
||||
"id": 293,
|
||||
"name": "CTFShovel",
|
||||
"dataTable": "DT_TFWeaponShovel"
|
||||
},
|
||||
{
|
||||
"id": 337,
|
||||
"name": "CWeaponMedigun",
|
||||
"dataTable": "DT_WeaponMedigun"
|
||||
},
|
||||
{
|
||||
"id": 282,
|
||||
"name": "CTFRocketLauncher",
|
||||
"dataTable": "DT_WeaponRocketLauncher"
|
||||
},
|
||||
{
|
||||
"id": 244,
|
||||
"name": "CTFPowerupBottle",
|
||||
"dataTable": "DT_TFPowerupBottle"
|
||||
},
|
||||
{
|
||||
"id": 118,
|
||||
"name": "CSpotlightEnd",
|
||||
"dataTable": "DT_SpotlightEnd"
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "CBeam",
|
||||
"dataTable": "DT_Beam"
|
||||
},
|
||||
{
|
||||
"id": 221,
|
||||
"name": "CTFLaserPointer",
|
||||
"dataTable": "DT_TFLaserPointer"
|
||||
},
|
||||
{
|
||||
"id": 222,
|
||||
"name": "CTFLunchBox",
|
||||
"dataTable": "DT_WeaponLunchBox"
|
||||
},
|
||||
{
|
||||
"id": 202,
|
||||
"name": "CTFFists",
|
||||
"dataTable": "DT_TFWeaponFists"
|
||||
},
|
||||
{
|
||||
"id": 180,
|
||||
"name": "CTFAmmoPack",
|
||||
"dataTable": "DT_AmmoPack"
|
||||
},
|
||||
{
|
||||
"id": 273,
|
||||
"name": "CTFRagdoll",
|
||||
"dataTable": "DT_TFRagdoll"
|
||||
},
|
||||
{
|
||||
"id": 86,
|
||||
"name": "CObjectDispenser",
|
||||
"dataTable": "DT_ObjectDispenser"
|
||||
},
|
||||
{
|
||||
"id": 204,
|
||||
"name": "CTFFlameThrower",
|
||||
"dataTable": "DT_WeaponFlameThrower"
|
||||
},
|
||||
{
|
||||
"id": 201,
|
||||
"name": "CTFFireAxe",
|
||||
"dataTable": "DT_TFWeaponFireAxe"
|
||||
},
|
||||
{
|
||||
"id": 276,
|
||||
"name": "CTFRevolver",
|
||||
"dataTable": "DT_WeaponRevolver"
|
||||
},
|
||||
{
|
||||
"id": 314,
|
||||
"name": "CTFWeaponBuilder",
|
||||
"dataTable": "DT_TFWeaponBuilder"
|
||||
},
|
||||
{
|
||||
"id": 89,
|
||||
"name": "CObjectTeleporter",
|
||||
"dataTable": "DT_ObjectTeleporter"
|
||||
},
|
||||
{
|
||||
"id": 317,
|
||||
"name": "CTFWeaponPDA_Engineer_Build",
|
||||
"dataTable": "DT_TFWeaponPDA_Engineer_Build"
|
||||
},
|
||||
{
|
||||
"id": 318,
|
||||
"name": "CTFWeaponPDA_Engineer_Destroy",
|
||||
"dataTable": "DT_TFWeaponPDA_Engineer_Destroy"
|
||||
},
|
||||
{
|
||||
"id": 319,
|
||||
"name": "CTFWeaponPDA_Spy",
|
||||
"dataTable": "DT_TFWeaponPDA_Spy"
|
||||
},
|
||||
{
|
||||
"id": 330,
|
||||
"name": "CVGuiScreen",
|
||||
"dataTable": "DT_VGuiScreen"
|
||||
},
|
||||
{
|
||||
"id": 192,
|
||||
"name": "CTFBuffItem",
|
||||
"dataTable": "DT_TFWeaponBuffItem"
|
||||
},
|
||||
{
|
||||
"id": 88,
|
||||
"name": "CObjectSentrygun",
|
||||
"dataTable": "DT_ObjectSentrygun"
|
||||
},
|
||||
{
|
||||
"id": 329,
|
||||
"name": "CTFWrench",
|
||||
"dataTable": "DT_TFWeaponWrench"
|
||||
},
|
||||
{
|
||||
"id": 315,
|
||||
"name": "CTFWeaponInvis",
|
||||
"dataTable": "DT_TFWeaponInvis"
|
||||
},
|
||||
{
|
||||
"id": 220,
|
||||
"name": "CTFKnife",
|
||||
"dataTable": "DT_TFWeaponKnife"
|
||||
}
|
||||
]
|
||||
124
src/tests/data/tempEntitiesResult.json
Normal file
124
src/tests/data/tempEntitiesResult.json
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
[
|
||||
{
|
||||
"serverClass": {
|
||||
"id": 164,
|
||||
"name": "CTEPlayerAnimEvent",
|
||||
"dataTable": "DT_TEPlayerAnimEvent"
|
||||
},
|
||||
"entityIndex": 0,
|
||||
"props": [
|
||||
{
|
||||
"definition": {
|
||||
"type": 0,
|
||||
"name": "m_iPlayerIndex",
|
||||
"flags": 1,
|
||||
"excludeDTName": null,
|
||||
"lowValue": 0,
|
||||
"highValue": 0,
|
||||
"bitCount": 7,
|
||||
"table": null,
|
||||
"numElements": 0,
|
||||
"arrayProperty": null,
|
||||
"ownerTableName": "DT_TEPlayerAnimEvent"
|
||||
},
|
||||
"value": 17
|
||||
}
|
||||
],
|
||||
"inPVS": false,
|
||||
"pvs": 1,
|
||||
"delay": 0
|
||||
},
|
||||
{
|
||||
"serverClass": {
|
||||
"id": 178,
|
||||
"name": "CTETFParticleEffect",
|
||||
"dataTable": "DT_TETFParticleEffect"
|
||||
},
|
||||
"entityIndex": 0,
|
||||
"props": [
|
||||
{
|
||||
"definition": {
|
||||
"type": 1,
|
||||
"name": "m_vecOrigin[0]",
|
||||
"flags": 32772,
|
||||
"excludeDTName": null,
|
||||
"lowValue": 0,
|
||||
"highValue": 0,
|
||||
"bitCount": 32,
|
||||
"table": null,
|
||||
"numElements": 0,
|
||||
"arrayProperty": null,
|
||||
"ownerTableName": "DT_TETFParticleEffect"
|
||||
},
|
||||
"value": 1004
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"type": 1,
|
||||
"name": "m_vecOrigin[1]",
|
||||
"flags": 32772,
|
||||
"excludeDTName": null,
|
||||
"lowValue": 0,
|
||||
"highValue": 0,
|
||||
"bitCount": 32,
|
||||
"table": null,
|
||||
"numElements": 0,
|
||||
"arrayProperty": null,
|
||||
"ownerTableName": "DT_TETFParticleEffect"
|
||||
},
|
||||
"value": -2016
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"type": 1,
|
||||
"name": "m_vecOrigin[2]",
|
||||
"flags": 32772,
|
||||
"excludeDTName": null,
|
||||
"lowValue": 0,
|
||||
"highValue": 0,
|
||||
"bitCount": 32,
|
||||
"table": null,
|
||||
"numElements": 0,
|
||||
"arrayProperty": null,
|
||||
"ownerTableName": "DT_TETFParticleEffect"
|
||||
},
|
||||
"value": 561
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"type": 0,
|
||||
"name": "m_iParticleSystemIndex",
|
||||
"flags": 1,
|
||||
"excludeDTName": null,
|
||||
"lowValue": 0,
|
||||
"highValue": 0,
|
||||
"bitCount": 16,
|
||||
"table": null,
|
||||
"numElements": 0,
|
||||
"arrayProperty": null,
|
||||
"ownerTableName": "DT_TETFParticleEffect"
|
||||
},
|
||||
"value": 472
|
||||
},
|
||||
{
|
||||
"definition": {
|
||||
"type": 0,
|
||||
"name": "entindex",
|
||||
"flags": 1,
|
||||
"excludeDTName": null,
|
||||
"lowValue": 0,
|
||||
"highValue": 0,
|
||||
"bitCount": 11,
|
||||
"table": null,
|
||||
"numElements": 0,
|
||||
"arrayProperty": null,
|
||||
"ownerTableName": "DT_TETFParticleEffect"
|
||||
},
|
||||
"value": 2047
|
||||
}
|
||||
],
|
||||
"inPVS": false,
|
||||
"pvs": 1,
|
||||
"delay": 0
|
||||
}
|
||||
]
|
||||
|
|
@ -15,32 +15,6 @@ function testDemo(name: string, fastMode: boolean = false) {
|
|||
assert.deepEqual(JSON.parse(JSON.stringify(parsed)), target);
|
||||
}
|
||||
|
||||
function testPackets() {
|
||||
const target = JSON.parse(readFileSync(`${__dirname}/../data/${name}_packets.json`, 'utf8'));
|
||||
const source = readFileSync(`${__dirname}/../data/${name}.dem`);
|
||||
const demo = Demo.fromNodeBuffer(source);
|
||||
const parser = demo.getParser(false);
|
||||
parser.readHeader();
|
||||
const match = parser.match;
|
||||
|
||||
const packets: {[tick: string]: Partial<Packet>[]} = {};
|
||||
|
||||
parser.on('packet', (packet: Packet) => {
|
||||
if (!packets[match.tick]) {
|
||||
packets[match.tick] = [];
|
||||
}
|
||||
const packetData = {};
|
||||
for (const key in packet) {
|
||||
if (packet.hasOwnProperty(key) && !(packet[key] instanceof BitStream)) {
|
||||
packetData[key] = packet[key];
|
||||
}
|
||||
}
|
||||
packets[match.tick].push(packetData);
|
||||
});
|
||||
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(packets)), target);
|
||||
}
|
||||
|
||||
suite('Parse basic demo info', () => {
|
||||
test('Fast mode', () => {
|
||||
testDemo('snakewater', true);
|
||||
|
|
|
|||
145
src/tests/unit/Parser/Packet/PacketEntitiesTest.ts
Normal file
145
src/tests/unit/Parser/Packet/PacketEntitiesTest.ts
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {assertEncoder, assertParser, getStream} from './PacketTest';
|
||||
import {Match} from '../../../../Data/Match';
|
||||
import {hydrateEntity, hydrateTable} from './hydrate';
|
||||
import {ServerClass} from '../../../../Data/ServerClass';
|
||||
import {PacketEntitiesPacket} from '../../../../Data/Packet';
|
||||
import {readFileSync} from 'fs';
|
||||
import {gunzipSync} from 'zlib';
|
||||
import {EncodePacketEntities, ParsePacketEntities} from '../../../../Parser/Packet/PacketEntities';
|
||||
import * as assert from 'assert';
|
||||
|
||||
const data = JSON.parse(readFileSync(__dirname + '/../../../data/packetEntitiesData.json', 'utf8'));
|
||||
const packetData = JSON.parse(gunzipSync(readFileSync(__dirname + '/../../../data/packetEntitiesResult.json.gz')).toString('utf8'));
|
||||
const sendTableData = JSON.parse(gunzipSync(readFileSync(__dirname + '/../../../data/packetEntitiesSendTables.json.gz')).toString('utf8'));
|
||||
const serverClassesData = JSON.parse(readFileSync(__dirname + '/../../../data/packetEntitiesServerClasses.json', 'utf8'));
|
||||
|
||||
const expected: PacketEntitiesPacket = {
|
||||
packetType: 'packetEntities',
|
||||
removedEntities: packetData.removedEntities,
|
||||
updatedBaseLine: packetData.updatedBaseLine,
|
||||
baseLine: packetData.baseLine,
|
||||
delta: packetData.delta,
|
||||
maxEntries: packetData.maxEntries,
|
||||
entities: packetData.entities.map(hydrateEntity)
|
||||
};
|
||||
|
||||
const match = new Match();
|
||||
match.serverClasses.length = 348;
|
||||
for (const serverClass of serverClassesData) {
|
||||
match.serverClasses[serverClass.id] = new ServerClass(serverClass.id, serverClass.name, serverClass.dataTable);
|
||||
}
|
||||
for (const sendTable of sendTableData) {
|
||||
const table = hydrateTable(sendTable);
|
||||
match.sendTables.set(table.name, table);
|
||||
}
|
||||
|
||||
for (const entity of expected.entities) {
|
||||
match.entityClasses.set(entity.entityIndex, entity.serverClass);
|
||||
}
|
||||
|
||||
function parse(stream: BitStream) {
|
||||
return ParsePacketEntities(stream, match);
|
||||
}
|
||||
|
||||
function encode(value: PacketEntitiesPacket, stream: BitStream) {
|
||||
EncodePacketEntities(value, stream, match);
|
||||
}
|
||||
|
||||
const sunEntityData = {
|
||||
'serverClass': {
|
||||
'id': 123,
|
||||
'name': 'CSun',
|
||||
'dataTable': 'DT_Sun'
|
||||
},
|
||||
'entityIndex': 403,
|
||||
'props': [
|
||||
{
|
||||
'definition': {
|
||||
'type': 0,
|
||||
'name': 'm_clrRender',
|
||||
'flags': 1,
|
||||
'excludeDTName': null,
|
||||
'lowValue': 0,
|
||||
'highValue': 0,
|
||||
'bitCount': 32,
|
||||
'table': null,
|
||||
'numElements': 0,
|
||||
'arrayProperty': null,
|
||||
'ownerTableName': 'DT_Sun'
|
||||
},
|
||||
'value': 4276271871
|
||||
},
|
||||
{
|
||||
'definition': {
|
||||
'type': 0,
|
||||
'name': 'm_clrOverlay',
|
||||
'flags': 1,
|
||||
'excludeDTName': null,
|
||||
'lowValue': 0,
|
||||
'highValue': 0,
|
||||
'bitCount': 32,
|
||||
'table': null,
|
||||
'numElements': 0,
|
||||
'arrayProperty': null,
|
||||
'ownerTableName': 'DT_Sun'
|
||||
},
|
||||
'value': 0
|
||||
},
|
||||
{
|
||||
'definition': {
|
||||
'type': 2,
|
||||
'name': 'm_vDirection',
|
||||
'flags': 32,
|
||||
'excludeDTName': null,
|
||||
'lowValue': 0,
|
||||
'highValue': -121121.125,
|
||||
'bitCount': 0,
|
||||
'table': null,
|
||||
'numElements': 0,
|
||||
'arrayProperty': null,
|
||||
'ownerTableName': 'DT_Sun'
|
||||
},
|
||||
'value': {
|
||||
'x': -0.6453346360527601,
|
||||
'y': -0.504152418172936,
|
||||
'z': 0.1880801172447484
|
||||
}
|
||||
}
|
||||
],
|
||||
'inPVS': true,
|
||||
'pvs': 1,
|
||||
'serialNumber': 664
|
||||
};
|
||||
|
||||
suite('PacketEntities', () => {
|
||||
test('Parse packetEntities', () => {
|
||||
const length = 130435;
|
||||
const stream = getStream(data);
|
||||
const start = stream.index;
|
||||
const resultPacket = parse(stream);
|
||||
assert.equal(stream.index - start, length, 'Unexpected number of bits consumed from stream');
|
||||
|
||||
for (let i = 0; i < resultPacket.entities.length; i++) {
|
||||
const resultEntity = resultPacket.entities[i];
|
||||
const expectedEntity = expected.entities[i];
|
||||
assert.deepEqual(resultEntity, expectedEntity);
|
||||
}
|
||||
});
|
||||
|
||||
// test('Encode packetEntities', () => {
|
||||
// assertEncoder(parse, encode, expected, Math.ceil(data.length / 8));
|
||||
// });
|
||||
//
|
||||
// test('Encode small packetEntities', () => {
|
||||
// assertEncoder(parse, encode, {
|
||||
// packetType: 'packetEntities',
|
||||
// removedEntities: [10, 11],
|
||||
// updatedBaseLine: false,
|
||||
// baseLine: 0,
|
||||
// delta: 0,
|
||||
// maxEntries: 16,
|
||||
// entities: [hydrateEntity(sunEntityData)]
|
||||
// }, 259);
|
||||
// });
|
||||
});
|
||||
|
|
@ -5,6 +5,7 @@ import {Match} from '../../../../Data/Match';
|
|||
import {hydrateEntity, hydrateTable} from './hydrate';
|
||||
import {ServerClass} from '../../../../Data/ServerClass';
|
||||
import {TempEntitiesPacket} from '../../../../Data/Packet';
|
||||
import {readFileSync} from 'fs';
|
||||
|
||||
const data = [
|
||||
2,
|
||||
|
|
@ -29,130 +30,7 @@ const data = [
|
|||
252,
|
||||
95];
|
||||
|
||||
const entityData = [
|
||||
{
|
||||
'serverClass': {
|
||||
'id': 164,
|
||||
'name': 'CTEPlayerAnimEvent',
|
||||
'dataTable': 'DT_TEPlayerAnimEvent'
|
||||
},
|
||||
'entityIndex': 0,
|
||||
'props': [
|
||||
{
|
||||
'definition': {
|
||||
'type': 0,
|
||||
'name': 'm_iPlayerIndex',
|
||||
'flags': 1,
|
||||
'excludeDTName': null,
|
||||
'lowValue': 0,
|
||||
'highValue': 0,
|
||||
'bitCount': 7,
|
||||
'table': null,
|
||||
'numElements': 0,
|
||||
'arrayProperty': null,
|
||||
'ownerTableName': 'DT_TEPlayerAnimEvent'
|
||||
},
|
||||
'value': 17
|
||||
}
|
||||
],
|
||||
'inPVS': false,
|
||||
'pvs': 1,
|
||||
'delay': 0
|
||||
},
|
||||
{
|
||||
'serverClass': {
|
||||
'id': 178,
|
||||
'name': 'CTETFParticleEffect',
|
||||
'dataTable': 'DT_TETFParticleEffect'
|
||||
},
|
||||
'entityIndex': 0,
|
||||
'props': [
|
||||
{
|
||||
'definition': {
|
||||
'type': 1,
|
||||
'name': 'm_vecOrigin[0]',
|
||||
'flags': 32772,
|
||||
'excludeDTName': null,
|
||||
'lowValue': 0,
|
||||
'highValue': 0,
|
||||
'bitCount': 32,
|
||||
'table': null,
|
||||
'numElements': 0,
|
||||
'arrayProperty': null,
|
||||
'ownerTableName': 'DT_TETFParticleEffect'
|
||||
},
|
||||
'value': 1004
|
||||
},
|
||||
{
|
||||
'definition': {
|
||||
'type': 1,
|
||||
'name': 'm_vecOrigin[1]',
|
||||
'flags': 32772,
|
||||
'excludeDTName': null,
|
||||
'lowValue': 0,
|
||||
'highValue': 0,
|
||||
'bitCount': 32,
|
||||
'table': null,
|
||||
'numElements': 0,
|
||||
'arrayProperty': null,
|
||||
'ownerTableName': 'DT_TETFParticleEffect'
|
||||
},
|
||||
'value': -2016
|
||||
},
|
||||
{
|
||||
'definition': {
|
||||
'type': 1,
|
||||
'name': 'm_vecOrigin[2]',
|
||||
'flags': 32772,
|
||||
'excludeDTName': null,
|
||||
'lowValue': 0,
|
||||
'highValue': 0,
|
||||
'bitCount': 32,
|
||||
'table': null,
|
||||
'numElements': 0,
|
||||
'arrayProperty': null,
|
||||
'ownerTableName': 'DT_TETFParticleEffect'
|
||||
},
|
||||
'value': 561
|
||||
},
|
||||
{
|
||||
'definition': {
|
||||
'type': 0,
|
||||
'name': 'm_iParticleSystemIndex',
|
||||
'flags': 1,
|
||||
'excludeDTName': null,
|
||||
'lowValue': 0,
|
||||
'highValue': 0,
|
||||
'bitCount': 16,
|
||||
'table': null,
|
||||
'numElements': 0,
|
||||
'arrayProperty': null,
|
||||
'ownerTableName': 'DT_TETFParticleEffect'
|
||||
},
|
||||
'value': 472
|
||||
},
|
||||
{
|
||||
'definition': {
|
||||
'type': 0,
|
||||
'name': 'entindex',
|
||||
'flags': 1,
|
||||
'excludeDTName': null,
|
||||
'lowValue': 0,
|
||||
'highValue': 0,
|
||||
'bitCount': 11,
|
||||
'table': null,
|
||||
'numElements': 0,
|
||||
'arrayProperty': null,
|
||||
'ownerTableName': 'DT_TETFParticleEffect'
|
||||
},
|
||||
'value': 2047
|
||||
}
|
||||
],
|
||||
'inPVS': false,
|
||||
'pvs': 1,
|
||||
'delay': 0
|
||||
}
|
||||
];
|
||||
const entityData = JSON.parse(readFileSync(__dirname + '/../../../data/tempEntitiesResult.json', 'utf8'));
|
||||
const sendTableData = {
|
||||
'name': 'DT_TEPlayerAnimEvent',
|
||||
'props': [
|
||||
|
|
|
|||
|
|
@ -15,15 +15,19 @@ export function hydrateEntity(entityData): PacketEntity {
|
|||
}
|
||||
return prop;
|
||||
});
|
||||
entity.inPVS = entityData.inPVS;
|
||||
if (typeof entityData.delay !== 'undefined') {
|
||||
entity.delay = entityData.delay;
|
||||
}
|
||||
if (typeof entityData.serialNumber !== 'undefined') {
|
||||
entity.serialNumber = entityData.serialNumber;
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
export function propDataDefinition(propData): SendPropDefinition {
|
||||
const prop = new SendPropDefinition(propData.type, propData.name, propData.flags, propData.ownerTableName);
|
||||
prop.arrayProperty = propData.arrayProperty;
|
||||
prop.arrayProperty = propData.arrayProperty ? propDataDefinition(propData.arrayProperty) : null;
|
||||
prop.numElements = propData.numElements;
|
||||
prop.bitCount = propData.bitCount;
|
||||
prop.excludeDTName = propData.excludeDTName;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import * as assert from 'assert';
|
||||
|
||||
export interface EqualOpts {
|
||||
strict?: boolean;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue