t2-demo-parser 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +170 -7
- package/dist/BitStream.d.ts +14 -3
- package/dist/BitStream.d.ts.map +1 -1
- package/dist/BitStream.js +28 -6
- package/dist/BitStream.js.map +1 -1
- package/dist/BitWriter.d.ts +29 -0
- package/dist/BitWriter.d.ts.map +1 -0
- package/dist/BitWriter.js +76 -0
- package/dist/BitWriter.js.map +1 -0
- package/dist/ClassRegistry.d.ts +23 -28
- package/dist/ClassRegistry.d.ts.map +1 -1
- package/dist/ClassRegistry.js +75 -84
- package/dist/ClassRegistry.js.map +1 -1
- package/dist/DataBlockParsers.d.ts.map +1 -1
- package/dist/DataBlockParsers.js +51 -76
- package/dist/DataBlockParsers.js.map +1 -1
- package/dist/DemoParser.d.ts +23 -22
- package/dist/DemoParser.d.ts.map +1 -1
- package/dist/DemoParser.js +218 -266
- package/dist/DemoParser.js.map +1 -1
- package/dist/EventParsers.d.ts.map +1 -1
- package/dist/EventParsers.js +87 -58
- package/dist/EventParsers.js.map +1 -1
- package/dist/GhostManager.d.ts +7 -1
- package/dist/GhostManager.d.ts.map +1 -1
- package/dist/GhostManager.js +231 -146
- package/dist/GhostManager.js.map +1 -1
- package/dist/HuffmanProcessor.d.ts +15 -0
- package/dist/HuffmanProcessor.d.ts.map +1 -1
- package/dist/HuffmanProcessor.js +45 -0
- package/dist/HuffmanProcessor.js.map +1 -1
- package/dist/LiveParser.d.ts +20 -1
- package/dist/LiveParser.d.ts.map +1 -1
- package/dist/LiveParser.js +31 -12
- package/dist/LiveParser.js.map +1 -1
- package/dist/PacketParser.d.ts +22 -2
- package/dist/PacketParser.d.ts.map +1 -1
- package/dist/PacketParser.js +111 -48
- package/dist/PacketParser.js.map +1 -1
- package/dist/Timeline.d.ts +5 -2
- package/dist/Timeline.d.ts.map +1 -1
- package/dist/Timeline.js +25 -12
- package/dist/Timeline.js.map +1 -1
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/dataBlockDataTypes.d.ts +111 -112
- package/dist/dataBlockDataTypes.d.ts.map +1 -1
- package/dist/dataBlockDataTypes.js.map +1 -1
- package/dist/defaultRegistry.d.ts +9 -0
- package/dist/defaultRegistry.d.ts.map +1 -0
- package/dist/defaultRegistry.js +43 -0
- package/dist/defaultRegistry.js.map +1 -0
- package/dist/eventDataTypes.d.ts +74 -72
- package/dist/eventDataTypes.d.ts.map +1 -1
- package/dist/eventDataTypes.js.map +1 -1
- package/dist/ghostDataTypes.d.ts +125 -131
- package/dist/ghostDataTypes.d.ts.map +1 -1
- package/dist/ghostDataTypes.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +51 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -1
- package/package.json +4 -1
- package/dist/BitStream.test.d.ts +0 -2
- package/dist/BitStream.test.d.ts.map +0 -1
- package/dist/BitStream.test.js +0 -419
- package/dist/BitStream.test.js.map +0 -1
- package/dist/ClassRegistry.test.d.ts +0 -2
- package/dist/ClassRegistry.test.d.ts.map +0 -1
- package/dist/ClassRegistry.test.js +0 -83
- package/dist/ClassRegistry.test.js.map +0 -1
- package/dist/DataBlockParsers.test.d.ts +0 -2
- package/dist/DataBlockParsers.test.d.ts.map +0 -1
- package/dist/DataBlockParsers.test.js +0 -294
- package/dist/DataBlockParsers.test.js.map +0 -1
- package/dist/DemoParser.test.d.ts +0 -2
- package/dist/DemoParser.test.d.ts.map +0 -1
- package/dist/DemoParser.test.js +0 -472
- package/dist/DemoParser.test.js.map +0 -1
- package/dist/GhostStateAccumulator.test.d.ts +0 -2
- package/dist/GhostStateAccumulator.test.d.ts.map +0 -1
- package/dist/GhostStateAccumulator.test.js +0 -201
- package/dist/GhostStateAccumulator.test.js.map +0 -1
- package/dist/GhostTracker.test.d.ts +0 -2
- package/dist/GhostTracker.test.d.ts.map +0 -1
- package/dist/GhostTracker.test.js +0 -64
- package/dist/GhostTracker.test.js.map +0 -1
- package/dist/IncrementalParser.test.d.ts +0 -2
- package/dist/IncrementalParser.test.d.ts.map +0 -1
- package/dist/IncrementalParser.test.js +0 -226
- package/dist/IncrementalParser.test.js.map +0 -1
package/dist/GhostManager.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import createDebug from "debug";
|
|
1
2
|
import { MaxTriggerKeys } from "./types.js";
|
|
3
|
+
const debugTracker = createDebug("t2-demo-parser:ghost-tracker");
|
|
2
4
|
// ============================================================
|
|
3
5
|
// Ghost Tracker — tracks ghost lifecycle (create/update/delete)
|
|
4
6
|
// ============================================================
|
|
@@ -10,8 +12,18 @@ export class GhostTracker {
|
|
|
10
12
|
hasGhost(index) {
|
|
11
13
|
return this.ghosts.has(index);
|
|
12
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Record a ghost at `index`. Re-creating an occupied index is normal
|
|
17
|
+
* (the server recycles slots after a delete we may not have seen), but
|
|
18
|
+
* a class change is logged since it is the earliest sign of a tracker
|
|
19
|
+
* that has drifted from the server.
|
|
20
|
+
*/
|
|
13
21
|
createGhost(index, classId, className) {
|
|
14
|
-
const
|
|
22
|
+
const existing = this.ghosts.get(index);
|
|
23
|
+
if (existing && existing.classId !== classId) {
|
|
24
|
+
debugTracker("ghost %d re-created as %s (classId %d), was %s (classId %d)", index, className, classId, existing.className, existing.classId);
|
|
25
|
+
}
|
|
26
|
+
const entry = { classId, className };
|
|
15
27
|
this.ghosts.set(index, entry);
|
|
16
28
|
return entry;
|
|
17
29
|
}
|
|
@@ -61,6 +73,21 @@ function readPackedColorF(bs) {
|
|
|
61
73
|
* Tribes 2 object/datablock references are serialized via FUN_00436ce0/FUN_00436d10
|
|
62
74
|
* as raw 11-bit object ids (nextPow2(0x800) -> bitCount 11).
|
|
63
75
|
*/
|
|
76
|
+
/**
|
|
77
|
+
* Ghost index reference written with GhostIdBitSize (10) bits, e.g.
|
|
78
|
+
* Player mount object, Item collision object, projectile source object
|
|
79
|
+
* in classes that call writeInt(idx, 10) directly.
|
|
80
|
+
*/
|
|
81
|
+
function readGhostRef10(bs) {
|
|
82
|
+
return bs.readInt(10);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Source object + image slot pair written via writeRangedU32(0, 1024)
|
|
86
|
+
* (11 bits, resolveGhost-style) and writeRangedU32(0, 7) (3 bits).
|
|
87
|
+
*/
|
|
88
|
+
function readSourceObjectSlot(bs) {
|
|
89
|
+
return { sourceObject: readObjectRef11(bs), sourceSlot: bs.readInt(3) };
|
|
90
|
+
}
|
|
64
91
|
function readObjectRef11(bs) {
|
|
65
92
|
return bs.readInt(11);
|
|
66
93
|
}
|
|
@@ -237,7 +264,7 @@ function readShapeBaseUpdate(bs, isInitial, conn) {
|
|
|
237
264
|
if (bs.readFlag()) {
|
|
238
265
|
if (bs.readFlag()) {
|
|
239
266
|
// Mounting
|
|
240
|
-
result.mountObject = bs
|
|
267
|
+
result.mountObject = readGhostRef10(bs);
|
|
241
268
|
result.mountNode = bs.readInt(5); // NumMountPointBits=5
|
|
242
269
|
}
|
|
243
270
|
else {
|
|
@@ -326,19 +353,11 @@ function playerReadPacketData(bs, conn) {
|
|
|
326
353
|
}
|
|
327
354
|
if (bs.readFlag()) {
|
|
328
355
|
// Not mounted — read full position/velocity
|
|
329
|
-
const pos =
|
|
330
|
-
x: bs.readF32(),
|
|
331
|
-
y: bs.readF32(),
|
|
332
|
-
z: bs.readF32(),
|
|
333
|
-
};
|
|
356
|
+
const pos = bs.readPoint3F();
|
|
334
357
|
result.position = pos;
|
|
335
358
|
// Update compression point (this IS the compression point)
|
|
336
359
|
conn.compressionPoint = pos;
|
|
337
|
-
result.velocity =
|
|
338
|
-
x: bs.readF32(),
|
|
339
|
-
y: bs.readF32(),
|
|
340
|
-
z: bs.readF32(),
|
|
341
|
-
};
|
|
360
|
+
result.velocity = bs.readPoint3F();
|
|
342
361
|
result.jumpSurfaceLastContact = bs.readInt(4);
|
|
343
362
|
}
|
|
344
363
|
result.headX = bs.readF32();
|
|
@@ -347,20 +366,21 @@ function playerReadPacketData(bs, conn) {
|
|
|
347
366
|
if (bs.readFlag()) {
|
|
348
367
|
// Has control object (e.g., vehicle being piloted)
|
|
349
368
|
// Binary FUN_005dab20: readInt(10) + resolveGhost + obj->readPacketData(conn, stream)
|
|
350
|
-
const controlGhostIndex = bs
|
|
369
|
+
const controlGhostIndex = readGhostRef10(bs);
|
|
351
370
|
result.controlObjectGhost = controlGhostIndex;
|
|
352
|
-
// Recursively call the control object's readPacketData.
|
|
353
|
-
//
|
|
371
|
+
// Recursively call the control object's readPacketData. The engine
|
|
372
|
+
// resolves the ghost and calls its virtual readPacketData with no
|
|
373
|
+
// fallback; if we cannot do the same the rest of the packet is
|
|
374
|
+
// unreadable, so fail loudly rather than continue misaligned.
|
|
354
375
|
const controlGhost = conn.ghostTracker.getGhost(controlGhostIndex);
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
: undefined;
|
|
358
|
-
if (controlParser?.readPacketData) {
|
|
359
|
-
const prevGhostIndex = conn.currentGhostIndex;
|
|
360
|
-
conn.currentGhostIndex = controlGhostIndex;
|
|
361
|
-
result.controlObjectData = controlParser.readPacketData(bs, conn);
|
|
362
|
-
conn.currentGhostIndex = prevGhostIndex;
|
|
376
|
+
if (!controlGhost) {
|
|
377
|
+
throw new Error(`Player readPacketData: piloted ghost ${controlGhostIndex} is not tracked`);
|
|
363
378
|
}
|
|
379
|
+
const controlParser = conn.getGhostParser?.(controlGhost.classId);
|
|
380
|
+
if (!controlParser?.readPacketData) {
|
|
381
|
+
throw new Error(`Player readPacketData: piloted ghost ${controlGhostIndex} (${controlGhost.className}) has no readPacketData parser`);
|
|
382
|
+
}
|
|
383
|
+
result.controlObjectData = controlParser.readPacketData(bs, conn);
|
|
364
384
|
}
|
|
365
385
|
result.disableMove = bs.readFlag();
|
|
366
386
|
result.pilot = bs.readFlag();
|
|
@@ -375,7 +395,6 @@ function vehicleUnpackUpdate(bs, isInitial, conn) {
|
|
|
375
395
|
// Control object shortcut — server writes true and returns early,
|
|
376
396
|
// meaning no further Vehicle data follows in the stream.
|
|
377
397
|
if (bs.readFlag()) {
|
|
378
|
-
result._controlledEarlyReturn = true;
|
|
379
398
|
return result;
|
|
380
399
|
}
|
|
381
400
|
// Steering
|
|
@@ -404,6 +423,37 @@ function vehicleUnpackUpdate(bs, isInitial, conn) {
|
|
|
404
423
|
}
|
|
405
424
|
return result;
|
|
406
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* ShapeBase::readPacketData (FUN_005ee9f0): GameBase (empty) + energy
|
|
428
|
+
* level (F32, via setEnergyLevel) + recharge rate (F32).
|
|
429
|
+
*/
|
|
430
|
+
function shapeBaseReadPacketData(bs, _conn) {
|
|
431
|
+
return {
|
|
432
|
+
energyLevel: bs.readF32(),
|
|
433
|
+
rechargeRate: bs.readF32(),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* GameBase::readPacketData (FUN_005e32c0) is empty. Every GameBase-derived
|
|
438
|
+
* class that does not override it reads nothing when it is the control
|
|
439
|
+
* object.
|
|
440
|
+
*/
|
|
441
|
+
function gameBaseReadPacketData(_bs, _conn) {
|
|
442
|
+
return {};
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Turret::readPacketData (FUN_00655d70): ShapeBase::readPacketData, then a
|
|
446
|
+
* ranged 0..4 state (getBinLog2(getNextPow2(5)) = 3 bits) and three F32s
|
|
447
|
+
* that the engine copies into its current barrel-rotation state.
|
|
448
|
+
*/
|
|
449
|
+
function turretReadPacketData(bs, conn) {
|
|
450
|
+
const base = shapeBaseReadPacketData(bs, conn);
|
|
451
|
+
return {
|
|
452
|
+
...base,
|
|
453
|
+
turretState: bs.readRangedU32(0, 4),
|
|
454
|
+
rotationValues: [bs.readF32(), bs.readF32(), bs.readF32()],
|
|
455
|
+
};
|
|
456
|
+
}
|
|
407
457
|
function vehicleReadPacketData(bs, conn) {
|
|
408
458
|
// Vehicle::readPacketData (binary FUN_0060d740)
|
|
409
459
|
// ShapeBase::readPacketData → GameBase::readPacketData (empty) + energy + recharge
|
|
@@ -412,7 +462,7 @@ function vehicleReadPacketData(bs, conn) {
|
|
|
412
462
|
result.rechargeRate = bs.readF32();
|
|
413
463
|
// Vehicle-specific rigid body state
|
|
414
464
|
result.steering = { x: bs.readF32(), y: bs.readF32() };
|
|
415
|
-
const linPos =
|
|
465
|
+
const linPos = bs.readPoint3F();
|
|
416
466
|
result.linPosition = linPos;
|
|
417
467
|
result.angPosition = {
|
|
418
468
|
x: bs.readF32(),
|
|
@@ -483,7 +533,7 @@ function itemUnpackUpdate(bs, isInitial, conn) {
|
|
|
483
533
|
}
|
|
484
534
|
// ThrowSrcMask
|
|
485
535
|
if (bs.readFlag()) {
|
|
486
|
-
result.collisionObject = bs
|
|
536
|
+
result.collisionObject = readGhostRef10(bs);
|
|
487
537
|
}
|
|
488
538
|
// RotationMask (only if !rotate)
|
|
489
539
|
if (bs.readFlag()) {
|
|
@@ -593,13 +643,12 @@ function debrisUnpackUpdate(bs, isInitial, conn) {
|
|
|
593
643
|
// 2 × string
|
|
594
644
|
result.string0 = bs.readString();
|
|
595
645
|
result.string1 = bs.readString();
|
|
596
|
-
// 3 conditional object references
|
|
646
|
+
// 3 conditional object references (-1 when absent)
|
|
597
647
|
const refs = [];
|
|
598
|
-
for (let i = 0; i <
|
|
648
|
+
for (let i = 0; i < 3; i++) {
|
|
599
649
|
refs.push(bs.readFlag() ? readObjectRef11(bs) : -1);
|
|
600
650
|
}
|
|
601
651
|
result.objectRefs = refs;
|
|
602
|
-
result.objectRef2 = bs.readFlag() ? readObjectRef11(bs) : -1;
|
|
603
652
|
return result;
|
|
604
653
|
}
|
|
605
654
|
function projectileUnpackUpdate(bs, isInitial, conn) {
|
|
@@ -611,51 +660,40 @@ function projectileUnpackUpdate(bs, isInitial, conn) {
|
|
|
611
660
|
// ============================================================
|
|
612
661
|
function bombProjectileUnpackUpdate(bs, _isInitial, _conn) {
|
|
613
662
|
// BombProjectile vtable +0x4c -> FUN_00636050.
|
|
614
|
-
const result =
|
|
615
|
-
// Parent GameBase::unpackUpdate
|
|
616
|
-
if (bs.readFlag()) {
|
|
617
|
-
result.dataBlockId = readObjectRef11(bs);
|
|
618
|
-
}
|
|
619
|
-
if (bs.readFlag()) {
|
|
620
|
-
const hasTargetId = bs.readFlag();
|
|
621
|
-
result.targetId = hasTargetId ? bs.readInt(9) : -1;
|
|
622
|
-
}
|
|
663
|
+
const result = readGameBaseUpdate(bs, _isInitial, _conn);
|
|
623
664
|
if (!bs.readFlag()) {
|
|
624
665
|
if (bs.readFlag()) {
|
|
625
|
-
result.position =
|
|
626
|
-
result.velocity =
|
|
666
|
+
result.position = bs.readPoint3F();
|
|
667
|
+
result.velocity = bs.readPoint3F();
|
|
627
668
|
}
|
|
628
669
|
if (!bs.readFlag()) {
|
|
629
670
|
return result;
|
|
630
671
|
}
|
|
631
|
-
result.endPoint =
|
|
632
|
-
result.endNormal =
|
|
672
|
+
result.endPoint = bs.readPoint3F();
|
|
673
|
+
result.endNormal = bs.readPoint3F();
|
|
633
674
|
return result;
|
|
634
675
|
}
|
|
635
|
-
result.position =
|
|
636
|
-
result.velocity =
|
|
676
|
+
result.position = bs.readPoint3F();
|
|
677
|
+
result.velocity = bs.readPoint3F();
|
|
637
678
|
result.currTick = bs.readInt(12); // nextPow2(0x1000) -> 12 bits
|
|
638
679
|
if (bs.readFlag()) {
|
|
639
680
|
result.resetFlag = true; // FUN_00636ae0 side-effect-only
|
|
640
681
|
}
|
|
641
682
|
if (bs.readFlag()) {
|
|
642
|
-
result.explodePoint =
|
|
643
|
-
result.explodeNormal =
|
|
644
|
-
x: bs.readF32(),
|
|
645
|
-
y: bs.readF32(),
|
|
646
|
-
z: bs.readF32(),
|
|
647
|
-
};
|
|
683
|
+
result.explodePoint = bs.readPoint3F();
|
|
684
|
+
result.explodeNormal = bs.readPoint3F();
|
|
648
685
|
}
|
|
649
686
|
if (bs.readFlag()) {
|
|
650
|
-
|
|
651
|
-
result.sourceSlot
|
|
687
|
+
// nextPow2(0x401) -> 11 bits, nextPow2(8) -> 3 bits
|
|
688
|
+
({ sourceObject: result.sourceObject, sourceSlot: result.sourceSlot } =
|
|
689
|
+
readSourceObjectSlot(bs));
|
|
652
690
|
}
|
|
653
691
|
else {
|
|
654
692
|
result.sourceObject = -1;
|
|
655
693
|
result.sourceSlot = -1;
|
|
656
694
|
}
|
|
657
695
|
if (bs.readFlag()) {
|
|
658
|
-
result.vehicleObject = bs
|
|
696
|
+
result.vehicleObject = readObjectRef11(bs); // nextPow2(0x401) -> 11 bits
|
|
659
697
|
}
|
|
660
698
|
else {
|
|
661
699
|
result.vehicleObject = 0;
|
|
@@ -676,11 +714,11 @@ function grenadeUnpackUpdate(bs, isInitial, conn) {
|
|
|
676
714
|
result.explodeNormal = bs.readPoint3F();
|
|
677
715
|
}
|
|
678
716
|
if (bs.readFlag()) {
|
|
679
|
-
result.sourceObject
|
|
680
|
-
|
|
717
|
+
({ sourceObject: result.sourceObject, sourceSlot: result.sourceSlot } =
|
|
718
|
+
readSourceObjectSlot(bs));
|
|
681
719
|
}
|
|
682
720
|
if (bs.readFlag()) {
|
|
683
|
-
result.vehicleObject = bs
|
|
721
|
+
result.vehicleObject = readObjectRef11(bs); // writeRangedU32(0, 1024)
|
|
684
722
|
}
|
|
685
723
|
}
|
|
686
724
|
else {
|
|
@@ -708,34 +746,22 @@ function seekerUnpackUpdate(bs, isInitial, conn) {
|
|
|
708
746
|
const isExplosion = bs.readFlag();
|
|
709
747
|
if (isExplosion) {
|
|
710
748
|
// FUN_00639370: explode(position, normal) — SeekerProjectile detonation.
|
|
711
|
-
result.explodePosition =
|
|
712
|
-
|
|
713
|
-
y: bs.readF32(),
|
|
714
|
-
z: bs.readF32(),
|
|
715
|
-
};
|
|
716
|
-
result.explodeNormal = {
|
|
717
|
-
x: bs.readF32(),
|
|
718
|
-
y: bs.readF32(),
|
|
719
|
-
z: bs.readF32(),
|
|
720
|
-
};
|
|
749
|
+
result.explodePosition = bs.readPoint3F();
|
|
750
|
+
result.explodeNormal = bs.readPoint3F();
|
|
721
751
|
return result;
|
|
722
752
|
}
|
|
723
|
-
result.position =
|
|
724
|
-
result.velocity =
|
|
753
|
+
result.position = bs.readPoint3F();
|
|
754
|
+
result.velocity = bs.readPoint3F();
|
|
725
755
|
const hasTargetInfo = bs.readFlag();
|
|
726
756
|
if (hasTargetInfo) {
|
|
727
757
|
const hasTargetGhost = bs.readFlag();
|
|
728
758
|
if (!hasTargetGhost) {
|
|
729
|
-
result.targetDirection =
|
|
730
|
-
x: bs.readF32(),
|
|
731
|
-
y: bs.readF32(),
|
|
732
|
-
z: bs.readF32(),
|
|
733
|
-
};
|
|
759
|
+
result.targetDirection = bs.readPoint3F();
|
|
734
760
|
result.targetMode = 1;
|
|
735
761
|
}
|
|
736
762
|
else {
|
|
737
763
|
// getBitCount(getNextPow2(0x401)) -> 11 bits
|
|
738
|
-
result.targetGhost = bs
|
|
764
|
+
result.targetGhost = readObjectRef11(bs);
|
|
739
765
|
result.targetMode = 0;
|
|
740
766
|
}
|
|
741
767
|
}
|
|
@@ -745,13 +771,14 @@ function seekerUnpackUpdate(bs, isInitial, conn) {
|
|
|
745
771
|
return result;
|
|
746
772
|
}
|
|
747
773
|
// 0x0063c010 full-state path
|
|
748
|
-
result.position =
|
|
749
|
-
result.velocity =
|
|
750
|
-
result.orientation =
|
|
774
|
+
result.position = bs.readPoint3F();
|
|
775
|
+
result.velocity = bs.readPoint3F();
|
|
776
|
+
result.orientation = bs.readPoint3F();
|
|
751
777
|
// Optional source object + slot
|
|
752
778
|
if (bs.readFlag()) {
|
|
753
|
-
|
|
754
|
-
result.sourceSlot
|
|
779
|
+
// getBitCount(getNextPow2(0x401)) = 11, getBitCount(getNextPow2(8)) = 3
|
|
780
|
+
({ sourceObject: result.sourceObject, sourceSlot: result.sourceSlot } =
|
|
781
|
+
readSourceObjectSlot(bs));
|
|
755
782
|
}
|
|
756
783
|
else {
|
|
757
784
|
result.sourceObject = -1;
|
|
@@ -761,15 +788,11 @@ function seekerUnpackUpdate(bs, isInitial, conn) {
|
|
|
761
788
|
if (bs.readFlag()) {
|
|
762
789
|
const hasTargetGhost = bs.readFlag();
|
|
763
790
|
if (!hasTargetGhost) {
|
|
764
|
-
result.targetDirection =
|
|
765
|
-
x: bs.readF32(),
|
|
766
|
-
y: bs.readF32(),
|
|
767
|
-
z: bs.readF32(),
|
|
768
|
-
};
|
|
791
|
+
result.targetDirection = bs.readPoint3F();
|
|
769
792
|
result.targetMode = 1;
|
|
770
793
|
}
|
|
771
794
|
else {
|
|
772
|
-
result.targetGhost = bs
|
|
795
|
+
result.targetGhost = readObjectRef11(bs); // getBitCount(getNextPow2(0x401))
|
|
773
796
|
result.targetMode = 0;
|
|
774
797
|
}
|
|
775
798
|
}
|
|
@@ -883,11 +906,7 @@ function cameraReadPacketData(bs, conn) {
|
|
|
883
906
|
// Parent::readPacketData + absolute camera transform basis + mode-specific orbit payload.
|
|
884
907
|
result.energyLevel = bs.readF32();
|
|
885
908
|
result.rechargeRate = bs.readF32();
|
|
886
|
-
const pos =
|
|
887
|
-
x: bs.readF32(),
|
|
888
|
-
y: bs.readF32(),
|
|
889
|
-
z: bs.readF32(),
|
|
890
|
-
};
|
|
909
|
+
const pos = bs.readPoint3F();
|
|
891
910
|
result.position = pos;
|
|
892
911
|
result.rotX = bs.readF32();
|
|
893
912
|
result.rotZ = bs.readF32();
|
|
@@ -902,7 +921,7 @@ function cameraReadPacketData(bs, conn) {
|
|
|
902
921
|
// OrbitObjectMode: observing flag + always-read 10-bit target ghost index.
|
|
903
922
|
if (cameraMode === 3) {
|
|
904
923
|
result.observingClientObject = bs.readFlag();
|
|
905
|
-
result.orbitObjectGhostIndex = bs
|
|
924
|
+
result.orbitObjectGhostIndex = readGhostRef10(bs);
|
|
906
925
|
}
|
|
907
926
|
// OrbitPointMode: NetConnection::readCompressed using current compression point.
|
|
908
927
|
if (cameraMode === 4) {
|
|
@@ -935,7 +954,7 @@ function linearProjectileUnpackUpdate(bs, isInitial, conn) {
|
|
|
935
954
|
result.direction = bs.readNormalVector(14); // binary: 0xe
|
|
936
955
|
result.currTick = bs.readRangedU32(0, 511); // nextPow2(0x200)=9 bits
|
|
937
956
|
if (bs.readFlag()) {
|
|
938
|
-
result.sourceObject = bs
|
|
957
|
+
result.sourceObject = readGhostRef10(bs); // nextPow2(0x400)=10 bits
|
|
939
958
|
result.sourceSlot = bs.readRangedU32(0, 7); // nextPow2(8)=3 bits
|
|
940
959
|
if (bs.readFlag()) {
|
|
941
960
|
result.excessVel = bs.readRangedU32(0, 255); // nextPow2(0x100)=8 bits
|
|
@@ -943,7 +962,7 @@ function linearProjectileUnpackUpdate(bs, isInitial, conn) {
|
|
|
943
962
|
}
|
|
944
963
|
}
|
|
945
964
|
if (bs.readFlag()) {
|
|
946
|
-
result.vehicleObject = bs
|
|
965
|
+
result.vehicleObject = readGhostRef10(bs); // nextPow2(0x400)=10 bits
|
|
947
966
|
}
|
|
948
967
|
}
|
|
949
968
|
}
|
|
@@ -963,9 +982,9 @@ function elfProjectileUnpackUpdate(bs, isInitial, conn) {
|
|
|
963
982
|
const result = readGameBaseUpdate(bs, isInitial, conn);
|
|
964
983
|
if (bs.readFlag()) {
|
|
965
984
|
if (bs.readFlag()) {
|
|
966
|
-
result.sourceObject
|
|
967
|
-
|
|
968
|
-
result.targetObject = bs
|
|
985
|
+
({ sourceObject: result.sourceObject, sourceSlot: result.sourceSlot } =
|
|
986
|
+
readSourceObjectSlot(bs));
|
|
987
|
+
result.targetObject = readObjectRef11(bs); // writeRangedU32(0, 1024)
|
|
969
988
|
}
|
|
970
989
|
}
|
|
971
990
|
return result;
|
|
@@ -979,9 +998,9 @@ function repairProjectileUnpackUpdate(bs, isInitial, conn) {
|
|
|
979
998
|
if (bs.readFlag()) {
|
|
980
999
|
// InitialUpdateMask
|
|
981
1000
|
if (bs.readFlag()) {
|
|
982
|
-
result.sourceObject
|
|
983
|
-
|
|
984
|
-
result.repairingObject = bs
|
|
1001
|
+
({ sourceObject: result.sourceObject, sourceSlot: result.sourceSlot } =
|
|
1002
|
+
readSourceObjectSlot(bs));
|
|
1003
|
+
result.repairingObject = readObjectRef11(bs); // writeRangedU32(0, 1024)
|
|
985
1004
|
}
|
|
986
1005
|
}
|
|
987
1006
|
// Non-initial update: no data
|
|
@@ -999,26 +1018,33 @@ function targetProjectileUnpackUpdate(bs, isInitial, conn) {
|
|
|
999
1018
|
result.endPos = bs.readPoint3F();
|
|
1000
1019
|
result.truncated = bs.readFlag();
|
|
1001
1020
|
if (bs.readFlag()) {
|
|
1002
|
-
result.sourceObject
|
|
1003
|
-
|
|
1021
|
+
({ sourceObject: result.sourceObject, sourceSlot: result.sourceSlot } =
|
|
1022
|
+
readSourceObjectSlot(bs));
|
|
1004
1023
|
result.clientOwned = bs.readFlag();
|
|
1005
1024
|
}
|
|
1006
1025
|
}
|
|
1007
1026
|
else {
|
|
1008
|
-
|
|
1009
|
-
if (bs.readFlag()) {
|
|
1010
|
-
result.sourceObject = bs.readRangedU32(0, 1024);
|
|
1011
|
-
result.sourceSlot = bs.readRangedU32(0, 7);
|
|
1012
|
-
result.clientOwned = bs.readFlag();
|
|
1013
|
-
}
|
|
1014
|
-
else {
|
|
1015
|
-
result.initialPosition = bs.readPoint3F();
|
|
1016
|
-
}
|
|
1017
|
-
result.endPos = bs.readPoint3F();
|
|
1018
|
-
result.truncated = bs.readFlag();
|
|
1027
|
+
readBeamProjectileSwing(bs, result);
|
|
1019
1028
|
}
|
|
1020
1029
|
return result;
|
|
1021
1030
|
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Non-initial update shared by TargetProjectile and SniperProjectile:
|
|
1033
|
+
* either a source object/slot pair or a fresh start point, then the end
|
|
1034
|
+
* point and truncation flag.
|
|
1035
|
+
*/
|
|
1036
|
+
function readBeamProjectileSwing(bs, result) {
|
|
1037
|
+
if (bs.readFlag()) {
|
|
1038
|
+
({ sourceObject: result.sourceObject, sourceSlot: result.sourceSlot } =
|
|
1039
|
+
readSourceObjectSlot(bs));
|
|
1040
|
+
result.clientOwned = bs.readFlag();
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
result.initialPosition = bs.readPoint3F();
|
|
1044
|
+
}
|
|
1045
|
+
result.endPos = bs.readPoint3F();
|
|
1046
|
+
result.truncated = bs.readFlag();
|
|
1047
|
+
}
|
|
1022
1048
|
// ============================================================
|
|
1023
1049
|
// WayPoint ghost parser
|
|
1024
1050
|
// ============================================================
|
|
@@ -1055,10 +1081,10 @@ function spawnSphereUnpackUpdate(bs, isInitial, conn) {
|
|
|
1055
1081
|
// ============================================================
|
|
1056
1082
|
// ForceFieldBare ghost parser
|
|
1057
1083
|
// ============================================================
|
|
1058
|
-
function forceFieldBareUnpackUpdate(bs,
|
|
1084
|
+
function forceFieldBareUnpackUpdate(bs, isInitial, conn) {
|
|
1059
1085
|
// ForceFieldBare::unpackUpdate (FUN_00676d30):
|
|
1060
1086
|
// GameBase parent, then two-level transform flags, then StateChangeMask.
|
|
1061
|
-
const result = readGameBaseUpdate(bs,
|
|
1087
|
+
const result = readGameBaseUpdate(bs, isInitial, conn);
|
|
1062
1088
|
// InitialUpdateMask flag — if true, reads transform+scale (initial path)
|
|
1063
1089
|
if (bs.readFlag()) {
|
|
1064
1090
|
result.transform = bs.readAffineTransform();
|
|
@@ -1104,6 +1130,20 @@ function tsStaticUnpackUpdate(bs, _isInitial, _conn) {
|
|
|
1104
1130
|
// ============================================================
|
|
1105
1131
|
// TerrainBlock ghost parser
|
|
1106
1132
|
// ============================================================
|
|
1133
|
+
/** U32 count + count × U32. The count is only trusted if that many
|
|
1134
|
+
* words can still fit in the stream (a corrupt count would otherwise
|
|
1135
|
+
* spin until memory runs out, since exhausted reads return 0). */
|
|
1136
|
+
function readEmptySquareRuns(bs) {
|
|
1137
|
+
const size = bs.readU32();
|
|
1138
|
+
if (size > bs.getRemainingBits() / 32) {
|
|
1139
|
+
throw new Error(`Invalid TerrainBlock emptySquareRun count: ${size}`);
|
|
1140
|
+
}
|
|
1141
|
+
const runs = [];
|
|
1142
|
+
for (let i = 0; i < size; i++) {
|
|
1143
|
+
runs.push(bs.readU32());
|
|
1144
|
+
}
|
|
1145
|
+
return runs;
|
|
1146
|
+
}
|
|
1107
1147
|
function terrainBlockUnpackUpdate(bs, _isInitial, _conn) {
|
|
1108
1148
|
// TerrainBlock extends SceneObject (NOT GameBase).
|
|
1109
1149
|
// V12 source: terrain/terrData.cc lines 910-941
|
|
@@ -1119,25 +1159,17 @@ function terrainBlockUnpackUpdate(bs, _isInitial, _conn) {
|
|
|
1119
1159
|
result.terrFileName = bs.readString();
|
|
1120
1160
|
result.detailTextureName = bs.readString();
|
|
1121
1161
|
result.squareSize = bs.readU32();
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
emptySquareRuns.push(bs.readU32());
|
|
1126
|
-
}
|
|
1127
|
-
result.emptySquareRuns = emptySquareRuns;
|
|
1128
|
-
result.emptySquareRunCount = size;
|
|
1162
|
+
const runs = readEmptySquareRuns(bs);
|
|
1163
|
+
result.emptySquareRuns = runs;
|
|
1164
|
+
result.emptySquareRunCount = runs.length;
|
|
1129
1165
|
}
|
|
1130
1166
|
else {
|
|
1131
1167
|
// Normal update
|
|
1132
1168
|
if (bs.readFlag()) {
|
|
1133
1169
|
// EmptyMask
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
emptySquareRuns.push(bs.readU32());
|
|
1138
|
-
}
|
|
1139
|
-
result.emptySquareRuns = emptySquareRuns;
|
|
1140
|
-
result.emptySquareRunCount = size;
|
|
1170
|
+
const runs = readEmptySquareRuns(bs);
|
|
1171
|
+
result.emptySquareRuns = runs;
|
|
1172
|
+
result.emptySquareRunCount = runs.length;
|
|
1141
1173
|
}
|
|
1142
1174
|
}
|
|
1143
1175
|
return result;
|
|
@@ -1471,23 +1503,13 @@ function sniperProjectileUnpackUpdate(bs, isInitial, conn) {
|
|
|
1471
1503
|
result.truncated = bs.readFlag();
|
|
1472
1504
|
result.hitWater = bs.readFlag();
|
|
1473
1505
|
if (bs.readFlag()) {
|
|
1474
|
-
result.sourceObject
|
|
1475
|
-
|
|
1506
|
+
({ sourceObject: result.sourceObject, sourceSlot: result.sourceSlot } =
|
|
1507
|
+
readSourceObjectSlot(bs));
|
|
1476
1508
|
result.clientOwned = bs.readFlag();
|
|
1477
1509
|
}
|
|
1478
1510
|
}
|
|
1479
1511
|
else {
|
|
1480
|
-
|
|
1481
|
-
if (bs.readFlag()) {
|
|
1482
|
-
result.sourceObject = bs.readRangedU32(0, 1024);
|
|
1483
|
-
result.sourceSlot = bs.readRangedU32(0, 7);
|
|
1484
|
-
result.clientOwned = bs.readFlag();
|
|
1485
|
-
}
|
|
1486
|
-
else {
|
|
1487
|
-
result.initialPosition = bs.readPoint3F();
|
|
1488
|
-
}
|
|
1489
|
-
result.endPos = bs.readPoint3F();
|
|
1490
|
-
result.truncated = bs.readFlag();
|
|
1512
|
+
readBeamProjectileSwing(bs, result);
|
|
1491
1513
|
}
|
|
1492
1514
|
return result;
|
|
1493
1515
|
}
|
|
@@ -1499,7 +1521,7 @@ function shockLanceProjectileUnpackUpdate(bs, isInitial, conn) {
|
|
|
1499
1521
|
const result = readGameBaseUpdate(bs, isInitial, conn);
|
|
1500
1522
|
// Target (always written)
|
|
1501
1523
|
if (bs.readFlag()) {
|
|
1502
|
-
result.targetObject = bs
|
|
1524
|
+
result.targetObject = readObjectRef11(bs); // writeRangedU32(0, 1024)
|
|
1503
1525
|
}
|
|
1504
1526
|
// Initial update
|
|
1505
1527
|
if (bs.readFlag()) {
|
|
@@ -1507,8 +1529,8 @@ function shockLanceProjectileUnpackUpdate(bs, isInitial, conn) {
|
|
|
1507
1529
|
result.end = bs.readPoint3F();
|
|
1508
1530
|
result.hitObject = bs.readFlag();
|
|
1509
1531
|
if (bs.readFlag()) {
|
|
1510
|
-
result.sourceObject
|
|
1511
|
-
|
|
1532
|
+
({ sourceObject: result.sourceObject, sourceSlot: result.sourceSlot } =
|
|
1533
|
+
readSourceObjectSlot(bs));
|
|
1512
1534
|
}
|
|
1513
1535
|
}
|
|
1514
1536
|
return result;
|
|
@@ -1717,7 +1739,7 @@ function stationFXPersonalUnpackUpdate(bs, isInitial, conn) {
|
|
|
1717
1739
|
if (bs.readFlag()) {
|
|
1718
1740
|
// InitialUpdateMask
|
|
1719
1741
|
if (bs.readFlag()) {
|
|
1720
|
-
result.stationObject = bs
|
|
1742
|
+
result.stationObject = readObjectRef11(bs); // writeRangedU32(0, 1024)
|
|
1721
1743
|
}
|
|
1722
1744
|
}
|
|
1723
1745
|
return result;
|
|
@@ -1768,7 +1790,7 @@ export function registerGhostParsers(registry) {
|
|
|
1768
1790
|
registry.catalogGhost({
|
|
1769
1791
|
name: "ShapeBase",
|
|
1770
1792
|
unpackUpdate: readShapeBaseUpdate,
|
|
1771
|
-
readPacketData:
|
|
1793
|
+
readPacketData: shapeBaseReadPacketData,
|
|
1772
1794
|
});
|
|
1773
1795
|
registry.catalogGhost({
|
|
1774
1796
|
name: "Vehicle",
|
|
@@ -1824,6 +1846,7 @@ export function registerGhostParsers(registry) {
|
|
|
1824
1846
|
registry.catalogGhost({
|
|
1825
1847
|
name: "Turret",
|
|
1826
1848
|
unpackUpdate: turretUnpackUpdate,
|
|
1849
|
+
readPacketData: turretReadPacketData,
|
|
1827
1850
|
});
|
|
1828
1851
|
registry.catalogGhost({
|
|
1829
1852
|
name: "InteriorInstance",
|
|
@@ -1966,5 +1989,67 @@ export function registerGhostParsers(registry) {
|
|
|
1966
1989
|
name: "StationFXVehicle",
|
|
1967
1990
|
unpackUpdate: stationFXPersonalUnpackUpdate,
|
|
1968
1991
|
});
|
|
1992
|
+
applyInheritedReadPacketData(registry);
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Ghost classes that inherit readPacketData rather than overriding it, from
|
|
1996
|
+
* the build 25034 vtables (slot +0x108) cross-checked against the V12
|
|
1997
|
+
* class hierarchy. The engine dispatches virtually, so a StaticShape used
|
|
1998
|
+
* as the control object reads ShapeBase's two floats and a Trigger reads
|
|
1999
|
+
* nothing. Classes absent from both lists are not GameBase-derived and
|
|
2000
|
+
* have no readPacketData at all.
|
|
2001
|
+
*/
|
|
2002
|
+
const SHAPEBASE_READ_PACKET_DATA_HEIRS = [
|
|
2003
|
+
"AIObjective",
|
|
2004
|
+
"BeaconObject",
|
|
2005
|
+
"Item",
|
|
2006
|
+
"MissionMarker",
|
|
2007
|
+
"ScopeAlwaysShape",
|
|
2008
|
+
"SpawnSphere",
|
|
2009
|
+
"StaticShape",
|
|
2010
|
+
"WayPoint",
|
|
2011
|
+
];
|
|
2012
|
+
const GAMEBASE_READ_PACKET_DATA_HEIRS = [
|
|
2013
|
+
"BombProjectile",
|
|
2014
|
+
"Debris",
|
|
2015
|
+
"ELFProjectile",
|
|
2016
|
+
"EnergyProjectile",
|
|
2017
|
+
"FireballAtmosphere",
|
|
2018
|
+
"FlareProjectile",
|
|
2019
|
+
"ForceFieldBare",
|
|
2020
|
+
"GameBase",
|
|
2021
|
+
"GrenadeProjectile",
|
|
2022
|
+
"Lightning",
|
|
2023
|
+
"LinearFlareProjectile",
|
|
2024
|
+
"LinearProjectile",
|
|
2025
|
+
"ParticleEmissionDummy",
|
|
2026
|
+
"Precipitation",
|
|
2027
|
+
"Projectile",
|
|
2028
|
+
"RepairProjectile",
|
|
2029
|
+
"SeekerProjectile",
|
|
2030
|
+
"ShockLanceProjectile",
|
|
2031
|
+
"Shockwave",
|
|
2032
|
+
"SniperProjectile",
|
|
2033
|
+
"Splash",
|
|
2034
|
+
"StationFXPersonal",
|
|
2035
|
+
"StationFXVehicle",
|
|
2036
|
+
"TargetProjectile",
|
|
2037
|
+
"TracerProjectile",
|
|
2038
|
+
"Trigger",
|
|
2039
|
+
];
|
|
2040
|
+
function applyInheritedReadPacketData(registry) {
|
|
2041
|
+
const catalog = registry.getGhostCatalog();
|
|
2042
|
+
for (const name of SHAPEBASE_READ_PACKET_DATA_HEIRS) {
|
|
2043
|
+
const entry = catalog.get(name);
|
|
2044
|
+
if (entry && !entry.readPacketData) {
|
|
2045
|
+
entry.readPacketData = shapeBaseReadPacketData;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
for (const name of GAMEBASE_READ_PACKET_DATA_HEIRS) {
|
|
2049
|
+
const entry = catalog.get(name);
|
|
2050
|
+
if (entry && !entry.readPacketData) {
|
|
2051
|
+
entry.readPacketData = gameBaseReadPacketData;
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
1969
2054
|
}
|
|
1970
2055
|
//# sourceMappingURL=GhostManager.js.map
|