mirror of
https://github.com/demostf/demo.js
synced 2026-06-04 09:04:13 +02:00
use stricter interface for parser state
This commit is contained in:
parent
fb00dc6ea1
commit
c0b175596a
14 changed files with 176 additions and 152 deletions
|
|
@ -4,8 +4,8 @@ import {
|
|||
GameEventValue, GameEventValueType,
|
||||
} from '../../Data/GameEvent';
|
||||
import {GameEvent, GameEventType, GameEventTypeIdMap, GameEventTypeMap} from '../../Data/GameEventTypes';
|
||||
import {Match} from '../../Data/Match';
|
||||
import {GameEventPacket} from '../../Data/Packet';
|
||||
import {ParserState} from '../../Data/ParserState';
|
||||
|
||||
function parseGameEvent<T extends GameEventType>(definition: GameEventDefinition<T>, stream: BitStream) {
|
||||
const values: GameEventTypeMap[T]['values'] = {};
|
||||
|
|
@ -86,11 +86,11 @@ function encodeGameEventValue(value: GameEventValue | null, stream: BitStream, e
|
|||
}
|
||||
}
|
||||
|
||||
export function ParseGameEvent(stream: BitStream, match: Match): GameEventPacket { // 25: game event
|
||||
export function ParseGameEvent(stream: BitStream, state: ParserState): GameEventPacket { // 25: game event
|
||||
const length = stream.readBits(11);
|
||||
const eventData = stream.readBitStream(length);
|
||||
const eventType = eventData.readBits(9);
|
||||
const definition = match.eventDefinitions.get(eventType);
|
||||
const definition = state.eventDefinitions.get(eventType);
|
||||
if (!definition) {
|
||||
throw new Error(`Unknown game event type ${eventType}`);
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ export function ParseGameEvent(stream: BitStream, match: Match): GameEventPacket
|
|||
};
|
||||
}
|
||||
|
||||
export function EncodeGameEvent(packet: GameEventPacket, stream: BitStream, match: Match) {
|
||||
export function EncodeGameEvent(packet: GameEventPacket, stream: BitStream, state: ParserState) {
|
||||
const lengthStart = stream.index;
|
||||
stream.index += 11;
|
||||
const eventId = GameEventTypeIdMap.get(packet.event.name);
|
||||
|
|
@ -113,7 +113,7 @@ export function EncodeGameEvent(packet: GameEventPacket, stream: BitStream, matc
|
|||
const eventDataStart = stream.index;
|
||||
stream.writeBits(eventId, 9);
|
||||
|
||||
const definition = match.eventDefinitions.get(eventId);
|
||||
const definition = state.eventDefinitions.get(eventId);
|
||||
if (typeof definition === 'undefined') {
|
||||
throw new Error(`Unknown game event type ${packet.event.name}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from '../../Data/Match';
|
||||
import {PacketEntitiesPacket} from '../../Data/Packet';
|
||||
import {EntityId, PacketEntity, PVS} from '../../Data/PacketEntity';
|
||||
import {SendProp} from '../../Data/SendProp';
|
||||
import {encodeEntityUpdate, getEntityUpdate} from '../EntityDecoder';
|
||||
import {readUBitVar, writeBitVar} from '../readBitVar';
|
||||
import {isDate} from 'util';
|
||||
import {ServerClass} from '../../Data/ServerClass';
|
||||
import {SendTable} from '../../Data/SendTable';
|
||||
import {getClassBits, getSendTable, ParserState} from '../../Data/ParserState';
|
||||
|
||||
const pvsMap = new Map([
|
||||
[0, PVS.PRESERVE],
|
||||
|
|
@ -37,12 +34,15 @@ function writePVSType(pvs: PVS, stream: BitStream) {
|
|||
stream.writeBits(raw, 2);
|
||||
}
|
||||
|
||||
function readEnterPVS(stream: BitStream, entityId: EntityId, match: Match): PacketEntity {
|
||||
function readEnterPVS(stream: BitStream, entityId: EntityId, state: ParserState): PacketEntity {
|
||||
// https://github.com/PazerOP/DemoLib/blob/5f9467650f942a4a70f9ec689eadcd3e0a051956/TF2Net/NetMessages/NetPacketEntitiesMessage.cs#L198
|
||||
const serverClass = match.serverClasses[stream.readBits(match.classBits)];
|
||||
const classBits = getClassBits(state);
|
||||
const serverClass = state.serverClasses[stream.readBits(classBits)];
|
||||
const serial = stream.readBits(10); // unused serial number
|
||||
|
||||
const cachedBaseLine = match.baseLineCache.get(serverClass);
|
||||
const sendTable = getSendTable(state, serverClass.dataTable);
|
||||
|
||||
const cachedBaseLine = state.baseLineCache.get(serverClass);
|
||||
if (cachedBaseLine) {
|
||||
const result = cachedBaseLine.clone();
|
||||
result.entityIndex = entityId;
|
||||
|
|
@ -50,16 +50,12 @@ function readEnterPVS(stream: BitStream, entityId: EntityId, match: Match): Pack
|
|||
return result;
|
||||
} else {
|
||||
const entity = new PacketEntity(serverClass, entityId, PVS.ENTER);
|
||||
const sendTable = match.getSendTable(serverClass.dataTable);
|
||||
if (!sendTable) {
|
||||
throw new Error('Unknown SendTable for serverclass');
|
||||
}
|
||||
const staticBaseLine = match.staticBaseLines.get(serverClass.id);
|
||||
const staticBaseLine = state.staticBaseLines.get(serverClass.id);
|
||||
if (staticBaseLine) {
|
||||
staticBaseLine.index = 0;
|
||||
const props = getEntityUpdate(sendTable, staticBaseLine);
|
||||
entity.applyPropUpdate(props);
|
||||
match.baseLineCache.set(serverClass, entity.clone());
|
||||
state.baseLineCache.set(serverClass, entity.clone());
|
||||
// if (staticBaseLine.bitsLeft > 7) {
|
||||
// console.log(staticBaseLine.length, staticBaseLine.index);
|
||||
// throw new Error('Unexpected data left at the end of staticBaseline, ' + staticBaseLine.bitsLeft + ' bits left');
|
||||
|
|
@ -70,32 +66,27 @@ 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);
|
||||
function writeEnterPVS(entity: PacketEntity, stream: BitStream, state: ParserState) {
|
||||
const serverClassId = state.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];
|
||||
const serverClass = state.serverClasses[serverClassId];
|
||||
|
||||
stream.writeBits(serverClassId, match.classBits);
|
||||
stream.writeBits(serverClassId, getClassBits(state));
|
||||
stream.writeBits(entity.serialNumber || 0, 10);
|
||||
|
||||
const cachedBaseLine = match.baseLineCache.get(serverClass);
|
||||
const cachedBaseLine = state.baseLineCache.get(serverClass);
|
||||
const propsToEncode = cachedBaseLine ? entity.diffFromBaseLine(cachedBaseLine) : entity.props;
|
||||
|
||||
const sendTable = getSendTable(state, serverClass.dataTable);
|
||||
|
||||
encodeEntityUpdate(propsToEncode, match.getSendTable(serverClass.dataTable), stream);
|
||||
encodeEntityUpdate(propsToEncode, sendTable, stream);
|
||||
}
|
||||
|
||||
function getPacketEntityForExisting(entityId: EntityId, match: Match, pvs: PVS) {
|
||||
const serverClass = match.entityClasses.get(entityId);
|
||||
function getPacketEntityForExisting(entityId: EntityId, state: ParserState, pvs: PVS) {
|
||||
const serverClass = state.entityClasses.get(entityId);
|
||||
if (!serverClass) {
|
||||
throw new Error(`"unknown entity ${entityId} for ${PVS[pvs]}(${pvs})`);
|
||||
}
|
||||
|
|
@ -103,7 +94,7 @@ function getPacketEntityForExisting(entityId: EntityId, match: Match, pvs: PVS)
|
|||
return new PacketEntity(serverClass, entityId, pvs);
|
||||
}
|
||||
|
||||
export function ParsePacketEntities(stream: BitStream, match: Match, skip: boolean = false): PacketEntitiesPacket { // 26: packetEntities
|
||||
export function ParsePacketEntities(stream: BitStream, state: ParserState, skip: boolean = false): PacketEntitiesPacket { // 26: packetEntities
|
||||
// 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
|
||||
|
|
@ -127,25 +118,30 @@ export function ParsePacketEntities(stream: BitStream, match: Match, skip: boole
|
|||
entityId += 1 + diff;
|
||||
const pvs = readPVSType(stream);
|
||||
if (pvs === PVS.ENTER) {
|
||||
const packetEntity = readEnterPVS(stream, entityId, match);
|
||||
const updatedProps = getEntityUpdate(match.getSendTable(packetEntity.serverClass.dataTable), stream);
|
||||
const packetEntity = readEnterPVS(stream, entityId, state);
|
||||
const sendTable = getSendTable(state, packetEntity.serverClass.dataTable);
|
||||
const updatedProps = getEntityUpdate(sendTable, stream);
|
||||
packetEntity.applyPropUpdate(updatedProps);
|
||||
|
||||
if (updatedBaseLine) {
|
||||
// console.log('updated baseline', packetEntity.serverClass.name);
|
||||
const newBaseLine: SendProp[] = [];
|
||||
newBaseLine.concat(packetEntity.props);
|
||||
match.baseLineCache.set(packetEntity.serverClass, packetEntity.clone());
|
||||
state.baseLineCache.set(packetEntity.serverClass, packetEntity.clone());
|
||||
}
|
||||
packetEntity.inPVS = true;
|
||||
receivedEntities.push(packetEntity);
|
||||
} else if (pvs === PVS.PRESERVE) {
|
||||
const packetEntity = getPacketEntityForExisting(entityId, match, pvs);
|
||||
const updatedProps = getEntityUpdate(match.getSendTable(packetEntity.serverClass.dataTable), stream);
|
||||
const packetEntity = getPacketEntityForExisting(entityId, state, pvs);
|
||||
const sendTable = state.sendTables.get(packetEntity.serverClass.dataTable);
|
||||
if (!sendTable) {
|
||||
throw new Error(`Unknown sendTable ${packetEntity.serverClass.dataTable}`);
|
||||
}
|
||||
const updatedProps = getEntityUpdate(sendTable, stream);
|
||||
packetEntity.applyPropUpdate(updatedProps);
|
||||
receivedEntities.push(packetEntity);
|
||||
} else if (match.entityClasses.has(entityId)) {
|
||||
const packetEntity = getPacketEntityForExisting(entityId, match, pvs);
|
||||
} else if (state.entityClasses.has(entityId)) {
|
||||
const packetEntity = getPacketEntityForExisting(entityId, state, pvs);
|
||||
receivedEntities.push(packetEntity);
|
||||
} else {
|
||||
// throw new Error(`No existing entity to update with id ${entityId}`);
|
||||
|
|
@ -171,7 +167,7 @@ export function ParsePacketEntities(stream: BitStream, match: Match, skip: boole
|
|||
};
|
||||
}
|
||||
|
||||
export function EncodePacketEntities(packet: PacketEntitiesPacket, stream: BitStream, match: Match) {
|
||||
export function EncodePacketEntities(packet: PacketEntitiesPacket, stream: BitStream, state: ParserState) {
|
||||
stream.writeBits(packet.maxEntries, 11);
|
||||
const isDelta = packet.removedEntities.length > 0;
|
||||
stream.writeBoolean(isDelta);
|
||||
|
|
@ -196,9 +192,10 @@ export function EncodePacketEntities(packet: PacketEntitiesPacket, stream: BitSt
|
|||
writePVSType(entity.pvs, stream);
|
||||
|
||||
if (entity.pvs === PVS.ENTER) {
|
||||
writeEnterPVS(entity, stream, match);
|
||||
writeEnterPVS(entity, stream, state);
|
||||
} else if (entity.pvs === PVS.PRESERVE) {
|
||||
encodeEntityUpdate(entity.props, match.getSendTable(entity.serverClass.dataTable), stream);
|
||||
const sendTable = getSendTable(state, entity.serverClass.dataTable);
|
||||
encodeEntityUpdate(entity.props, sendTable, stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from '../../Data/Match';
|
||||
import {Packet, VoidPacket} from '../../Data/Packet';
|
||||
import {ParserState} from '../../Data/ParserState';
|
||||
|
||||
export type Parser<P extends Packet> = (stream: BitStream, match?: Match, skip?: boolean) => P;
|
||||
export type Encoder<P extends Packet> = (packet: P, stream: BitStream, match?: Match) => void;
|
||||
export type Parser<P extends Packet> = (stream: BitStream, state?: ParserState, skip?: boolean) => P;
|
||||
export type Encoder<P extends Packet> = (packet: P, stream: BitStream, state?: ParserState) => void;
|
||||
|
||||
export interface PacketHandler<P extends Packet> {
|
||||
parser: Parser<P>,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from '../../Data/Match';
|
||||
import {TempEntitiesPacket} from '../../Data/Packet';
|
||||
import {PacketEntity, PVS} from '../../Data/PacketEntity';
|
||||
import {encodeEntityUpdate, getEntityUpdate} from '../EntityDecoder';
|
||||
import {readVarInt, writeVarInt} from '../readBitVar';
|
||||
import {DynamicBitStream} from '../../DynamicBitStream';
|
||||
import {getClassBits, getSendTable, ParserState} from '../../Data/ParserState';
|
||||
|
||||
export function ParseTempEntities(stream: BitStream, match: Match, skip: boolean = false): TempEntitiesPacket { // 10: classInfo
|
||||
export function ParseTempEntities(stream: BitStream, state: ParserState, skip: boolean = false): TempEntitiesPacket { // 10: classInfo
|
||||
const entityCount = stream.readUint8();
|
||||
const length = readVarInt(stream);
|
||||
const entityData = stream.readBitStream(length);
|
||||
|
|
@ -17,20 +17,21 @@ export function ParseTempEntities(stream: BitStream, match: Match, skip: boolean
|
|||
for (let i = 0; i < entityCount; i++) {
|
||||
const delay = (entityData.readBoolean()) ? entityData.readUint8() / 100 : 0; // unused it seems
|
||||
if (entityData.readBoolean()) {
|
||||
const classId = entityData.readBits(match.classBits);
|
||||
const serverClass = match.serverClasses[classId - 1];
|
||||
const classId = entityData.readBits(getClassBits(state));
|
||||
// no clue why the -1 but it works
|
||||
const serverClass = state.serverClasses[classId - 1];
|
||||
if (!serverClass) {
|
||||
throw new Error(`Unknown serverClass ${classId}`);
|
||||
}
|
||||
// no clue why the -1 but it works
|
||||
const sendTable = match.getSendTable(serverClass.dataTable);
|
||||
const sendTable = getSendTable(state, serverClass.dataTable);
|
||||
entity = new PacketEntity(serverClass, 0, PVS.ENTER);
|
||||
entity.delay = delay;
|
||||
entity.props = getEntityUpdate(sendTable, entityData);
|
||||
entities.push(entity);
|
||||
} else {
|
||||
if (entity) {
|
||||
const updatedProps = getEntityUpdate(match.getSendTable(entity.serverClass.dataTable), entityData);
|
||||
const sendTable = getSendTable(state, entity.serverClass.dataTable);
|
||||
const updatedProps = getEntityUpdate(sendTable, entityData);
|
||||
entity.applyPropUpdate(updatedProps);
|
||||
} else {
|
||||
throw new Error('no entity set to update');
|
||||
|
|
@ -48,7 +49,7 @@ export function ParseTempEntities(stream: BitStream, match: Match, skip: boolean
|
|||
};
|
||||
}
|
||||
|
||||
export function EncodeTempEntities(packet: TempEntitiesPacket, stream: BitStream, match: Match) {
|
||||
export function EncodeTempEntities(packet: TempEntitiesPacket, stream: BitStream, state: ParserState) {
|
||||
stream.writeUint8(packet.entities.length);
|
||||
|
||||
const entityStream = new DynamicBitStream();
|
||||
|
|
@ -62,10 +63,10 @@ export function EncodeTempEntities(packet: TempEntitiesPacket, stream: BitStream
|
|||
|
||||
entityStream.writeBoolean(true);
|
||||
|
||||
const classId = match.serverClasses.findIndex(serverClass => serverClass && serverClass.name === entity.serverClass.name) + 1;
|
||||
entityStream.writeBits(classId, match.classBits);
|
||||
const classId = state.serverClasses.findIndex(serverClass => serverClass && serverClass.name === entity.serverClass.name) + 1;
|
||||
entityStream.writeBits(classId, getClassBits(state));
|
||||
|
||||
const sendTable = match.getSendTable(entity.serverClass.dataTable);
|
||||
const sendTable = getSendTable(state, entity.serverClass.dataTable);
|
||||
|
||||
encodeEntityUpdate(entity.props, sendTable, entityStream);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from '../../Data/Match';
|
||||
import {UpdateStringTablePacket} from '../../Data/Packet';
|
||||
import {encodeStringTableEntries, guessStringTableEntryLength, parseStringTableEntries} from '../StringTableParser';
|
||||
import {ParserState} from '../../Data/ParserState';
|
||||
|
||||
export function ParseUpdateStringTable(stream: BitStream, match: Match): UpdateStringTablePacket { // 12: updateStringTable
|
||||
export function ParseUpdateStringTable(stream: BitStream, state: ParserState): UpdateStringTablePacket { // 12: updateStringTable
|
||||
const tableId = stream.readBits(5);
|
||||
|
||||
const multipleChanged = stream.readBoolean();
|
||||
|
|
@ -13,11 +13,11 @@ export function ParseUpdateStringTable(stream: BitStream, match: Match): UpdateS
|
|||
const data = stream.readBitStream(bitCount);
|
||||
data.index = 0;
|
||||
|
||||
if (!match.stringTables[tableId]) {
|
||||
if (!state.stringTables[tableId]) {
|
||||
throw new Error(`Table not found for update: ${tableId}`);
|
||||
}
|
||||
|
||||
const table = match.stringTables[tableId];
|
||||
const table = state.stringTables[tableId];
|
||||
const updatedEntries = parseStringTableEntries(data, table, changedEntries, table.entries);
|
||||
|
||||
return {
|
||||
|
|
@ -27,7 +27,7 @@ export function ParseUpdateStringTable(stream: BitStream, match: Match): UpdateS
|
|||
};
|
||||
}
|
||||
|
||||
export function EncodeUpdateStringTable(packet: UpdateStringTablePacket, stream: BitStream, match: Match) {
|
||||
export function EncodeUpdateStringTable(packet: UpdateStringTablePacket, stream: BitStream, state: ParserState) {
|
||||
stream.writeBits(packet.tableId, 5);
|
||||
|
||||
const changedEntryCount = packet.entries.filter(entry => entry).length;
|
||||
|
|
@ -38,10 +38,10 @@ export function EncodeUpdateStringTable(packet: UpdateStringTablePacket, stream:
|
|||
stream.writeUint16(changedEntryCount);
|
||||
}
|
||||
|
||||
if (!match.stringTables[packet.tableId]) {
|
||||
if (!state.stringTables[packet.tableId]) {
|
||||
throw new Error(`Table not found for update: ${packet.tableId}`);
|
||||
}
|
||||
const table = match.stringTables[packet.tableId];
|
||||
const table = state.stringTables[packet.tableId];
|
||||
const entryData = new BitStream(new ArrayBuffer(guessStringTableEntryLength(table, packet.entries)));
|
||||
encodeStringTableEntries(entryData, table, packet.entries);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue