t2-demo-parser 1.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.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +823 -0
  3. package/dist/BitStream.d.ts +143 -0
  4. package/dist/BitStream.d.ts.map +1 -0
  5. package/dist/BitStream.js +369 -0
  6. package/dist/BitStream.js.map +1 -0
  7. package/dist/BitStream.test.d.ts +2 -0
  8. package/dist/BitStream.test.d.ts.map +1 -0
  9. package/dist/BitStream.test.js +419 -0
  10. package/dist/BitStream.test.js.map +1 -0
  11. package/dist/ClassRegistry.d.ts +100 -0
  12. package/dist/ClassRegistry.d.ts.map +1 -0
  13. package/dist/ClassRegistry.js +118 -0
  14. package/dist/ClassRegistry.js.map +1 -0
  15. package/dist/ClassRegistry.test.d.ts +2 -0
  16. package/dist/ClassRegistry.test.d.ts.map +1 -0
  17. package/dist/ClassRegistry.test.js +83 -0
  18. package/dist/ClassRegistry.test.js.map +1 -0
  19. package/dist/DataBlockParsers.d.ts +3 -0
  20. package/dist/DataBlockParsers.d.ts.map +1 -0
  21. package/dist/DataBlockParsers.js +2041 -0
  22. package/dist/DataBlockParsers.js.map +1 -0
  23. package/dist/DemoParser.d.ts +155 -0
  24. package/dist/DemoParser.d.ts.map +1 -0
  25. package/dist/DemoParser.js +943 -0
  26. package/dist/DemoParser.js.map +1 -0
  27. package/dist/DemoParser.test.d.ts +2 -0
  28. package/dist/DemoParser.test.d.ts.map +1 -0
  29. package/dist/DemoParser.test.js +472 -0
  30. package/dist/DemoParser.test.js.map +1 -0
  31. package/dist/EventParsers.d.ts +3 -0
  32. package/dist/EventParsers.d.ts.map +1 -0
  33. package/dist/EventParsers.js +603 -0
  34. package/dist/EventParsers.js.map +1 -0
  35. package/dist/GhostManager.d.ts +13 -0
  36. package/dist/GhostManager.d.ts.map +1 -0
  37. package/dist/GhostManager.js +2002 -0
  38. package/dist/GhostManager.js.map +1 -0
  39. package/dist/GhostTracker.test.d.ts +2 -0
  40. package/dist/GhostTracker.test.d.ts.map +1 -0
  41. package/dist/GhostTracker.test.js +64 -0
  42. package/dist/GhostTracker.test.js.map +1 -0
  43. package/dist/HuffmanProcessor.d.ts +12 -0
  44. package/dist/HuffmanProcessor.d.ts.map +1 -0
  45. package/dist/HuffmanProcessor.js +163 -0
  46. package/dist/HuffmanProcessor.js.map +1 -0
  47. package/dist/LiveParser.d.ts +16 -0
  48. package/dist/LiveParser.d.ts.map +1 -0
  49. package/dist/LiveParser.js +29 -0
  50. package/dist/LiveParser.js.map +1 -0
  51. package/dist/PacketParser.d.ts +104 -0
  52. package/dist/PacketParser.d.ts.map +1 -0
  53. package/dist/PacketParser.js +713 -0
  54. package/dist/PacketParser.js.map +1 -0
  55. package/dist/Timeline.d.ts +101 -0
  56. package/dist/Timeline.d.ts.map +1 -0
  57. package/dist/Timeline.js +251 -0
  58. package/dist/Timeline.js.map +1 -0
  59. package/dist/cli.d.ts +3 -0
  60. package/dist/cli.d.ts.map +1 -0
  61. package/dist/cli.js +372 -0
  62. package/dist/cli.js.map +1 -0
  63. package/dist/dataBlockDataTypes.d.ts +877 -0
  64. package/dist/dataBlockDataTypes.d.ts.map +1 -0
  65. package/dist/dataBlockDataTypes.js +2 -0
  66. package/dist/dataBlockDataTypes.js.map +1 -0
  67. package/dist/dataTypes.d.ts +31 -0
  68. package/dist/dataTypes.d.ts.map +1 -0
  69. package/dist/dataTypes.js +5 -0
  70. package/dist/dataTypes.js.map +1 -0
  71. package/dist/dump.d.ts +3 -0
  72. package/dist/dump.d.ts.map +1 -0
  73. package/dist/dump.js +36 -0
  74. package/dist/dump.js.map +1 -0
  75. package/dist/eventDataTypes.d.ts +199 -0
  76. package/dist/eventDataTypes.d.ts.map +1 -0
  77. package/dist/eventDataTypes.js +2 -0
  78. package/dist/eventDataTypes.js.map +1 -0
  79. package/dist/ghostDataTypes.d.ts +573 -0
  80. package/dist/ghostDataTypes.d.ts.map +1 -0
  81. package/dist/ghostDataTypes.js +2 -0
  82. package/dist/ghostDataTypes.js.map +1 -0
  83. package/dist/index.d.ts +17 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +9 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/types.d.ts +261 -0
  88. package/dist/types.d.ts.map +1 -0
  89. package/dist/types.js +204 -0
  90. package/dist/types.js.map +1 -0
  91. package/package.json +46 -0
@@ -0,0 +1,2041 @@
1
+ // ============================================================
2
+ // Helper functions
3
+ // ============================================================
4
+ /**
5
+ * Read a DataBlock reference: flag(1b) + readClassId (11 bits).
6
+ * From decompiled binary: uses FUN_00436d10 (readClassId) which reads
7
+ * getBitCount(getNextPow2(0x800)) = 11 bits.
8
+ * If flag is false, sets objectId to -1 (0xFFFFFFFF).
9
+ */
10
+ function readDataBlockRef(bs) {
11
+ return bs.readFlag() ? bs.readInt(11) : null;
12
+ }
13
+ /**
14
+ * Read a ranged signed 32-bit integer.
15
+ * Encodes value as unsigned offset from min.
16
+ */
17
+ function readRangedS32(bs, min, max) {
18
+ return bs.readRangedU32(0, max - min) + min;
19
+ }
20
+ /**
21
+ * Read a packed ColorF: 4 bytes (32 bits total), each converted to [0..1] float.
22
+ * From decompiled binary: FUN_0043f040 calls FUN_0043efe0 which reads 4 × stream->read(1, &byte).
23
+ * Each byte is multiplied by 1/255.0 to produce the float value.
24
+ * NOTE: This is NOT 4×F32 (128 bits) — it's 4×U8 (32 bits).
25
+ */
26
+ function readColorF(bs) {
27
+ return {
28
+ r: bs.readInt(8) / 255,
29
+ g: bs.readInt(8) / 255,
30
+ b: bs.readInt(8) / 255,
31
+ a: bs.readInt(8) / 255,
32
+ };
33
+ }
34
+ /**
35
+ * Read a DataBlock boolean field: 8 bits (1 byte), as written by stream->write(sizeof(bool), &val).
36
+ * NOTE: This is different from readFlag() which reads 1 bit via BitStream::readFlag().
37
+ * Many DataBlock fields use write(sizeof(bool)) instead of writeFlag().
38
+ */
39
+ function readBool(bs) {
40
+ return bs.readInt(8) !== 0;
41
+ }
42
+ /**
43
+ * Read a ranged float: min + readInt(bits) / ((1 << bits) - 1) * (max - min).
44
+ */
45
+ function readRangedF32(bs, min, max, bits) {
46
+ return min + (bs.readInt(bits) / ((1 << bits) - 1)) * (max - min);
47
+ }
48
+ // ============================================================
49
+ // Base class parsers (inheritance chain)
50
+ // ============================================================
51
+ // SimDataBlock → GameBaseData: 0 bits on wire (no-op)
52
+ /**
53
+ * ShapeBaseData::unpackData — critical base class for most game objects.
54
+ *
55
+ * Verified against decompiled binary FUN_005e63e0 (Tribes2.exe).
56
+ * NOTE: The field order differs from V12 engine reference source.
57
+ * The Tribes 2 binary groups booleans and DataBlock refs differently,
58
+ * and the sensor color uses 4×U8 (not 4×F32), among other differences.
59
+ */
60
+ function shapeBaseDataUnpack(bs) {
61
+ const result = {};
62
+ // computeCRC — flag; if true: U32 mCRC
63
+ // Binary: readFlag → this+0xc0; if true: read(4) → this+0xbc
64
+ if (bs.readFlag()) {
65
+ result.crc = bs.readU32();
66
+ }
67
+ // shapeName — readString → this+0x44
68
+ result.shapeName = bs.readString();
69
+ // 9 conditional F32 fields (flag + F32 if non-default)
70
+ // Binary: each is readFlag, if true: read(4) → field offset
71
+ if (bs.readFlag())
72
+ result.mass = bs.readF32(); // → 0x74
73
+ if (bs.readFlag())
74
+ result.drag = bs.readF32(); // → 0x78
75
+ if (bs.readFlag())
76
+ result.density = bs.readF32(); // → 0x7c
77
+ if (bs.readFlag())
78
+ result.maxEnergy = bs.readF32(); // → 0x80
79
+ if (bs.readFlag())
80
+ result.cameraMaxDist = bs.readF32(); // → 0xa4
81
+ if (bs.readFlag())
82
+ result.cameraMinDist = bs.readF32(); // → 0xa8
83
+ if (bs.readFlag())
84
+ result.cameraDefaultFov = bs.readF32(); // → 0xac
85
+ if (bs.readFlag())
86
+ result.cameraMinFov = bs.readF32(); // → 0xb0
87
+ if (bs.readFlag())
88
+ result.cameraMaxFov = bs.readF32(); // → 0xb4
89
+ // debrisShapeName — readString → this+0x48
90
+ result.debrisShapeName = bs.readString();
91
+ // sensorRadius — flag; if true: readInt(10) + 4×U8 (RGBA bytes)
92
+ // Binary: readInt(10) → this+0x15c (cast to float), then 4 × read(1) → 0x160-0x163
93
+ if (bs.readFlag()) {
94
+ result.sensorRadius = bs.readInt(10);
95
+ result.sensorColor = {
96
+ r: bs.readInt(8),
97
+ g: bs.readInt(8),
98
+ b: bs.readInt(8),
99
+ a: bs.readInt(8),
100
+ };
101
+ }
102
+ // heat — flag; if true: F32 → this+0x50
103
+ if (bs.readFlag())
104
+ result.heat = bs.readF32();
105
+ // cmdCategory — readString → this+0x164
106
+ result.cmdCategory = bs.readString();
107
+ // cmdMiniIconName — readString → this+0x168
108
+ // NOTE: V12 has cmdIcon (DataBlockRef) here, but binary has readString.
109
+ result.cmdMiniIconName = bs.readString();
110
+ // 3 boolean flags: canControl, canObserve, observeThroughObject
111
+ // Binary: 3 readFlags → this+0x16c, 0x16d, 0x16e
112
+ result.canControl = bs.readFlag();
113
+ result.canObserve = bs.readFlag();
114
+ result.observeThroughObject = bs.readFlag();
115
+ // 3 more boolean flags (grouped together in binary, different from V12)
116
+ // Binary: this+0x325, 0x329, 0x32a
117
+ result.emap = bs.readFlag(); // 0x325
118
+ result.isInvincible = bs.readFlag(); // 0x329
119
+ result.renderWhenDestroyed = bs.readFlag(); // 0x32a
120
+ // 4 DataBlock refs using readClassId (FUN_00436d10 = readInt(11))
121
+ // Binary: each is readFlag + readClassId → this+0x70, 0x60, 0x68, 0x58
122
+ result.cmdIcon = readDataBlockRef(bs); // 0x6c/0x70
123
+ result.explosion = readDataBlockRef(bs); // 0x5c/0x60
124
+ result.underwaterExplosion = readDataBlockRef(bs); // 0x64/0x68
125
+ result.debris = readDataBlockRef(bs); // 0x54/0x58
126
+ // 3 more boolean flags
127
+ // Binary: this+0x32b, 0x326, 0x327
128
+ result.inheritEnergyFromMount = bs.readFlag(); // 0x32b
129
+ result.firstPersonOnly = bs.readFlag(); // 0x326
130
+ result.useEyePoint = bs.readFlag(); // 0x327
131
+ // shieldEffectLifetimeMS — U32 → this+0x94
132
+ result.shieldEffectLifetimeMS = bs.readU32();
133
+ // shieldEffectScale — flag; if true: 3×F32
134
+ // Binary: readFlag, if true: read(4) × 3 → this+0x98, 0x9c, 0xa0
135
+ // NOTE: This field is NOT in the V12 reference source — Tribes 2 addition.
136
+ if (bs.readFlag()) {
137
+ result.shieldEffectScale = {
138
+ x: bs.readF32(),
139
+ y: bs.readF32(),
140
+ z: bs.readF32(),
141
+ };
142
+ }
143
+ // HUD images loop (8 iterations, NumHudRenderImages=8)
144
+ // Binary: do-while uVar4 < 8 at offset this + uVar4*4 + 0x174
145
+ const hudImages = [];
146
+ for (let i = 0; i < 8; i++) {
147
+ const hasFriendly = bs.readFlag();
148
+ if (!hasFriendly)
149
+ continue;
150
+ const friendlyName = bs.readString(); // → this + i*4 + 0x174
151
+ const hasEnemy = bs.readFlag();
152
+ const enemyName = hasEnemy ? bs.readString() : undefined; // → this + i*4 + 0x194
153
+ const img = {
154
+ friendlyName,
155
+ enemyName,
156
+ renderCenter: bs.readFlag(), // → this + i + 0x1f4
157
+ renderModulated: bs.readFlag(), // → this + i + 0x1fc
158
+ renderAlways: bs.readFlag(), // → this + i + 0x204
159
+ renderDistance: bs.readFlag(), // → this + i + 0x20c
160
+ renderName: bs.readFlag(), // → this + i + 0x214
161
+ };
162
+ hudImages.push(img);
163
+ }
164
+ if (hudImages.length > 0)
165
+ result.hudImages = hudImages;
166
+ return result;
167
+ }
168
+ // ============================================================
169
+ // ShapeBaseImageData (weapon mount data — 31 states)
170
+ // ============================================================
171
+ function shapeBaseImageDataUnpack(bs) {
172
+ // Extends GameBaseData directly (0 parent bits)
173
+ const result = {};
174
+ // computeCRC — flag; if true: U32
175
+ if (bs.readFlag()) {
176
+ result.crc = bs.readU32();
177
+ }
178
+ // shapeName
179
+ result.shapeName = bs.readString();
180
+ // mountPoint — U32
181
+ result.mountPoint = bs.readU32();
182
+ // offsetTransform — flag(isIdentity); if NOT identity: readAffineTransform
183
+ if (!bs.readFlag()) {
184
+ result.offset = bs.readAffineTransform();
185
+ }
186
+ // firstPerson — flag
187
+ result.firstPerson = bs.readFlag();
188
+ // mass — F32
189
+ result.mass = bs.readF32();
190
+ // usesEnergy — flag
191
+ result.usesEnergy = bs.readFlag();
192
+ // minEnergy — F32
193
+ result.minEnergy = bs.readF32();
194
+ // hasFlash — flag
195
+ result.hasFlash = bs.readFlag();
196
+ // projectile — readDataBlockRef
197
+ result.projectile = readDataBlockRef(bs);
198
+ // muzzleFlash — readDataBlockRef (confirmed at offset 0xce4 in binary)
199
+ result.muzzleFlash = readDataBlockRef(bs);
200
+ // isSeeker — flag; if true: 4×F32 + flag + F32
201
+ result.isSeeker = bs.readFlag();
202
+ if (result.isSeeker) {
203
+ result.seekerRadius = bs.readF32();
204
+ result.maxSeekAngle = bs.readF32();
205
+ result.seekerLockTime = bs.readF32();
206
+ result.seekerFreeTime = bs.readF32();
207
+ result.isTargetLockRequired = bs.readFlag();
208
+ result.maxLockRange = bs.readF32();
209
+ }
210
+ // cloakable — flag
211
+ result.cloakable = bs.readFlag();
212
+ // lightType — readRangedU32(0, 3); if != 0: F32 + S32 + 4×writeFloat(7)
213
+ result.lightType = bs.readRangedU32(0, 3);
214
+ if (result.lightType !== 0) {
215
+ result.lightRadius = bs.readF32();
216
+ result.lightTime = bs.readS32();
217
+ result.lightColor = {
218
+ r: bs.readFloat(7),
219
+ g: bs.readFloat(7),
220
+ b: bs.readFloat(7),
221
+ a: bs.readFloat(7),
222
+ };
223
+ }
224
+ // shellExitDir — 3×F32 (mathWrite Point3F)
225
+ result.shellExitDir = {
226
+ x: bs.readF32(),
227
+ y: bs.readF32(),
228
+ z: bs.readF32(),
229
+ };
230
+ // shellExitVariance — F32
231
+ result.shellExitVariance = bs.readF32();
232
+ // shellVelocity — F32
233
+ result.shellVelocity = bs.readF32();
234
+ // casing — readDataBlockRef
235
+ result.casing = readDataBlockRef(bs);
236
+ // accuFire flag (confirmed at offset 0xc88 in binary, before state loop)
237
+ result.accuFire = bs.readFlag();
238
+ // State loop (31 iterations, MaxStates=31)
239
+ const states = [];
240
+ for (let i = 0; i < 31; i++) {
241
+ const hasName = bs.readFlag();
242
+ if (!hasName)
243
+ continue;
244
+ const name = bs.readString();
245
+ // 11 transition values: each readInt(5)
246
+ const transitionOnAmmo = bs.readInt(5);
247
+ const transitionOnNoAmmo = bs.readInt(5);
248
+ const transitionOnTarget = bs.readInt(5);
249
+ const transitionOnNoTarget = bs.readInt(5);
250
+ const transitionOnWet = bs.readInt(5);
251
+ const transitionOnNotWet = bs.readInt(5);
252
+ const transitionOnTriggerUp = bs.readInt(5);
253
+ const transitionOnTriggerDown = bs.readInt(5);
254
+ const transitionOnTimeout = bs.readInt(5);
255
+ const transitionGeneric0In = bs.readInt(5);
256
+ const transitionGeneric0Out = bs.readInt(5);
257
+ // timeoutValue — flag(!=default) then F32
258
+ const timeoutValue = bs.readFlag() ? bs.readF32() : undefined;
259
+ // waitForTimeout — flag
260
+ const waitForTimeout = bs.readFlag();
261
+ // fire — flag
262
+ const fire = bs.readFlag();
263
+ // ejectShell — flag
264
+ const ejectShell = bs.readFlag();
265
+ // scaleAnimation — flag
266
+ const scaleAnimation = bs.readFlag();
267
+ // direction — flag
268
+ const direction = bs.readFlag();
269
+ // reload — flag (confirmed at offset 0xe2a in binary, between direction and energyDrain)
270
+ const reload = bs.readFlag();
271
+ // energyDrain — flag(!=default) then F32
272
+ const energyDrain = bs.readFlag() ? bs.readF32() : undefined;
273
+ // loaded — readInt(3)
274
+ const loaded = bs.readInt(3);
275
+ // spin — readInt(3)
276
+ const spin = bs.readInt(3);
277
+ // recoil — readInt(3)
278
+ const recoil = bs.readInt(3);
279
+ // sequence — flag(!=default) then readSignedInt(16)
280
+ const sequence = bs.readFlag() ? bs.readSignedInt(16) : undefined;
281
+ // sequenceVis — flag(!=default) then readSignedInt(16)
282
+ const sequenceVis = bs.readFlag() ? bs.readSignedInt(16) : undefined;
283
+ // flashSequence — flag
284
+ const flashSequence = bs.readFlag();
285
+ // ignoreLoadedForReady — flag
286
+ const ignoreLoadedForReady = bs.readFlag();
287
+ // emitter — readDataBlockRef; if present: F32 + S32
288
+ const emitter = readDataBlockRef(bs);
289
+ const emitterTime = emitter !== null ? bs.readF32() : undefined;
290
+ const emitterNode = emitter !== null ? bs.readS32() : undefined;
291
+ // sound — readDataBlockRef
292
+ const sound = readDataBlockRef(bs);
293
+ const state = {
294
+ name,
295
+ transitionOnAmmo,
296
+ transitionOnNoAmmo,
297
+ transitionOnTarget,
298
+ transitionOnNoTarget,
299
+ transitionOnWet,
300
+ transitionOnNotWet,
301
+ transitionOnTriggerUp,
302
+ transitionOnTriggerDown,
303
+ transitionOnTimeout,
304
+ transitionGeneric0In,
305
+ transitionGeneric0Out,
306
+ timeoutValue,
307
+ waitForTimeout,
308
+ fire,
309
+ ejectShell,
310
+ scaleAnimation,
311
+ direction,
312
+ reload,
313
+ energyDrain,
314
+ loaded,
315
+ spin,
316
+ recoil,
317
+ sequence,
318
+ sequenceVis,
319
+ flashSequence,
320
+ ignoreLoadedForReady,
321
+ emitter,
322
+ emitterTime,
323
+ emitterNode,
324
+ sound,
325
+ };
326
+ states.push(state);
327
+ }
328
+ result.states = states;
329
+ return result;
330
+ }
331
+ // ============================================================
332
+ // PlayerData (extends ShapeBaseData)
333
+ // ============================================================
334
+ function playerDataUnpack(bs) {
335
+ const result = shapeBaseDataUnpack(bs);
336
+ // Verified against decompiled binary FUN_005cfa40 (Tribes2.exe).
337
+ // Binary layout: flag + 13 F32s + 2 optional DB refs + 9 F32s + 1 F32 +
338
+ // 8 F32s + readInt(7) + 6 F32s + 9 F32s + 1 F32 + 32 sounds +
339
+ // boxSize(3) + DB refs + F32s + 11 ground impact F32s
340
+ // 1. readFlag → 0x340 (renderFirstPerson — NOT F32!)
341
+ result.renderFirstPerson = bs.readFlag();
342
+ // 2. 13× F32 (offsets 0x334-0x360)
343
+ result.minLookAngle = bs.readF32(); // 0x334
344
+ result.maxLookAngle = bs.readF32(); // 0x338
345
+ result.maxFreelookAngle = bs.readF32(); // 0x33c
346
+ result.maxTimeScale = bs.readF32(); // 0x330
347
+ result.maxStepHeight = bs.readF32(); // 0x398
348
+ result.runForce = bs.readF32(); // 0x344
349
+ result.runEnergyDrain = bs.readF32(); // 0x348
350
+ result.minRunEnergy = bs.readF32(); // 0x34c
351
+ result.maxForwardSpeed = bs.readF32(); // 0x350
352
+ result.maxBackwardSpeed = bs.readF32(); // 0x354
353
+ result.maxSideSpeed = bs.readF32(); // 0x358
354
+ result.maxUnderwaterForwardSpeed = bs.readF32(); // 0x35c
355
+ result.maxUnderwaterBackwardSpeed = bs.readF32(); // 0x360
356
+ // [0x364 hardcoded to 0, not from stream]
357
+ // 3. 2 optional DataBlock refs (readClassId pattern)
358
+ result.maxUnderwaterSideSpeedRef = readDataBlockRef(bs); // 0x368
359
+ if (bs.readFlag()) {
360
+ result.runSurfaceAngleRef = bs.readInt(11); // 0x370 (no else/-1)
361
+ }
362
+ // 4. 9× F32 (offsets 0x374-0x394)
363
+ result.runSurfaceAngle = bs.readF32(); // 0x374
364
+ result.recoverDelay = bs.readF32(); // 0x378 (S32 on wire = 4 bytes)
365
+ result.recoverRunForceScale = bs.readF32(); // 0x37c
366
+ result.jumpForce = bs.readF32(); // 0x380
367
+ result.jumpEnergyDrain = bs.readF32(); // 0x384
368
+ result.minJumpEnergy = bs.readF32(); // 0x388
369
+ result.minJumpSpeed = bs.readF32(); // 0x38c
370
+ result.maxJumpSpeed = bs.readF32(); // 0x390
371
+ result.jumpSurfaceAngle = bs.readF32(); // 0x394
372
+ // 5. 1× F32 (offset 0x39c)
373
+ result.minJetEnergy = bs.readF32(); // 0x39c
374
+ // 6. 8× F32 (offsets 0x3b8-0x3d4)
375
+ result.splashVelocity = bs.readF32(); // 0x3b8
376
+ result.splashAngle = bs.readF32(); // 0x3bc
377
+ result.splashFreqMod = bs.readF32(); // 0x3c0
378
+ result.splashVelEpsilon = bs.readF32(); // 0x3c4
379
+ result.bubbleEmitTime = bs.readF32(); // 0x3c8
380
+ result.medSplashSoundVel = bs.readF32(); // 0x3cc
381
+ result.hardSplashSoundVel = bs.readF32(); // 0x3d0
382
+ result.exitSplashSoundVel = bs.readF32(); // 0x3d4
383
+ // 7. readInt(7) → jumpDelay (offset 0x3d8)
384
+ result.jumpDelay = bs.readInt(7);
385
+ // 8. 6× F32 (offsets 0x3a0-0x3b4)
386
+ result.horizMaxSpeed = bs.readF32(); // 0x3a0
387
+ result.horizResistSpeed = bs.readF32(); // 0x3a4
388
+ result.horizResistFactor = bs.readF32(); // 0x3a8
389
+ result.upMaxSpeed = bs.readF32(); // 0x3ac
390
+ result.upResistSpeed = bs.readF32(); // 0x3b0
391
+ result.upResistFactor = bs.readF32(); // 0x3b4
392
+ // 9. 9× F32 (offsets 0xd14-0xd34, far offsets = Tribes 2-specific fields)
393
+ result.jetEnergyDrain = bs.readF32(); // 0xd14
394
+ result.canJet = bs.readF32(); // 0xd18
395
+ result.maxJetHorizontalPercentage = bs.readF32(); // 0xd1c
396
+ result.maxJetForwardSpeed = bs.readF32(); // 0xd20
397
+ result.jetForce = bs.readF32(); // 0xd24
398
+ result.minJetSpeed = bs.readF32(); // 0xd28
399
+ result.maxDamage = bs.readF32(); // 0xd2c
400
+ result.minImpactDamageSpeed = bs.readF32(); // 0xd30
401
+ result.impactDamageScale = bs.readF32(); // 0xd34
402
+ // 10. 1× F32 (offset 0x3f4)
403
+ result.footSplashHeight = bs.readF32(); // 0x3f4
404
+ // 11. Sound loop: 32 iterations (MaxSounds=0x20 in binary)
405
+ // Binary: zeros 0x428+i*4, then flag + readClassId → 0x4a8+i*4
406
+ const sounds = [];
407
+ for (let i = 0; i < 32; i++) {
408
+ if (bs.readFlag()) {
409
+ sounds.push(bs.readInt(11));
410
+ }
411
+ else {
412
+ sounds.push(null);
413
+ }
414
+ }
415
+ result.sounds = sounds;
416
+ // 12. boxSize 3×F32 (offsets 0x528, 0x52c, 0x530)
417
+ result.boxSize = {
418
+ x: bs.readF32(),
419
+ y: bs.readF32(),
420
+ z: bs.readF32(),
421
+ };
422
+ // 13. footPuffEmitter + 2 F32s (offset 0xcf0, 0xcf4, 0xcf8)
423
+ result.footPuffEmitter = readDataBlockRef(bs);
424
+ result.footPuffNumParts = bs.readF32();
425
+ result.footPuffRadius = bs.readF32();
426
+ // 14. decalData + 1 F32 (offset 0xd00, 0x3f8)
427
+ result.decalData = readDataBlockRef(bs);
428
+ result.decalOffset = bs.readF32();
429
+ // 15. dustEmitter + splash (offsets 0xd08, 0xd10)
430
+ result.dustEmitter = readDataBlockRef(bs);
431
+ result.splash = readDataBlockRef(bs);
432
+ // 16. 3 splashEmitters (offsets 0xd44 + i*4)
433
+ const splashEmitters = [];
434
+ for (let i = 0; i < 3; i++) {
435
+ splashEmitters.push(readDataBlockRef(bs));
436
+ }
437
+ result.splashEmitters = splashEmitters;
438
+ // 17. 11× F32 for ground impact shake (offsets 0x3fc-0x424)
439
+ // V12 has 9 fields; binary has 11 (2 extra Tribes 2-specific)
440
+ result.groundImpactMinSpeed = bs.readF32(); // 0x3fc
441
+ result.groundImpactShakeFreq = {
442
+ x: bs.readF32(), // 0x400
443
+ y: bs.readF32(), // 0x404
444
+ z: bs.readF32(), // 0x408
445
+ };
446
+ result.groundImpactShakeAmp = {
447
+ x: bs.readF32(), // 0x40c
448
+ y: bs.readF32(), // 0x410
449
+ z: bs.readF32(), // 0x414
450
+ };
451
+ result.groundImpactShakeDuration = bs.readF32(); // 0x418
452
+ result.groundImpactShakeFalloff = bs.readF32(); // 0x41c
453
+ result.boundingRadius = bs.readF32(); // 0x420 (Tribes 2 extra)
454
+ result.moveBubbleSize = bs.readF32(); // 0x424 (Tribes 2 extra)
455
+ return result;
456
+ }
457
+ // ============================================================
458
+ // VehicleData (extends ShapeBaseData)
459
+ // ============================================================
460
+ function vehicleDataUnpack(bs) {
461
+ const result = shapeBaseDataUnpack(bs);
462
+ // Verified against decompiled binary FUN_00609450 (Tribes2.exe).
463
+ // 1. 2×F32 (body restitution/friction) → offsets 0x33c, 0x340
464
+ result.bodyRestitution = bs.readF32();
465
+ result.bodyFriction = bs.readF32();
466
+ // 2. Loop of 2: impact sound DataBlock refs → offsets 0x334+i*4
467
+ const impactSounds = [];
468
+ for (let i = 0; i < 2; i++) {
469
+ impactSounds.push(readDataBlockRef(bs));
470
+ }
471
+ result.impactSounds = impactSounds;
472
+ // 3. ~20 F32s for vehicle physics
473
+ result.minImpactSpeed = bs.readF32(); // 0x37c
474
+ result.softImpactSpeed = bs.readF32(); // 0x380
475
+ result.hardImpactSpeed = bs.readF32(); // 0x384 (900 dec)
476
+ result.minRollSpeed = bs.readF32(); // 0x388
477
+ result.maxSteeringAngle = bs.readF32(); // 0x38c
478
+ result.maxDrag = bs.readF32(); // 0x3a4
479
+ result.minDrag = bs.readF32(); // 0x3a0
480
+ result.jetForce = bs.readF32(); // 0x3a8
481
+ result.jetEnergyDrain = bs.readF32(); // 0x3ac
482
+ result.minJetEnergy = bs.readF32(); // 0x3b0
483
+ result.cameraOffset = bs.readF32(); // 0x39c
484
+ result.cameraLag = bs.readF32(); // 0x398
485
+ result.triggerDustHeight = bs.readF32(); // 0x3c8
486
+ result.dustHeight = bs.readF32(); // 0x3cc
487
+ result.numDmgEmitterAreas = bs.readF32(); // 0x408
488
+ result.exitSplashSoundVelocity = bs.readF32(); // 0x36c
489
+ result.softSplashSoundVelocity = bs.readF32(); // 0x370
490
+ result.mediumSplashSoundVelocity = bs.readF32(); // 0x374
491
+ result.hardSplashSoundVelocity = bs.readF32(); // 0x378
492
+ // 4. Loop of 5: water impact sound DataBlock refs → offsets 0x358+i*4
493
+ const waterSounds = [];
494
+ for (let i = 0; i < 5; i++) {
495
+ waterSounds.push(readDataBlockRef(bs));
496
+ }
497
+ result.waterSounds = waterSounds;
498
+ // 5. dustEmitter DataBlock ref → offset 0x3c4
499
+ result.dustEmitter = readDataBlockRef(bs);
500
+ // 6. Loop of 3: damage emitter DataBlock refs → offsets 0x3dc+i*4
501
+ const damageEmitters = [];
502
+ for (let i = 0; i < 3; i++) {
503
+ damageEmitters.push(readDataBlockRef(bs));
504
+ }
505
+ result.damageEmitters = damageEmitters;
506
+ // 7. Loop of 2: splash emitter DataBlock refs → offsets 0x414+i*4
507
+ const splashEmitters = [];
508
+ for (let i = 0; i < 2; i++) {
509
+ splashEmitters.push(readDataBlockRef(bs));
510
+ }
511
+ result.splashEmitters = splashEmitters;
512
+ // 8. 2×3 F32 damage emitter offsets (2 sets of x,y,z) → offsets 0x3e8-0x3f4
513
+ result.damageEmitterOffset0 = {
514
+ x: bs.readF32(),
515
+ y: bs.readF32(),
516
+ z: bs.readF32(),
517
+ };
518
+ result.damageEmitterOffset1 = {
519
+ x: bs.readF32(),
520
+ y: bs.readF32(),
521
+ z: bs.readF32(),
522
+ };
523
+ // 9. 2 F32 damage level tolerance → offsets 0x400, 0x404
524
+ result.damageLevelTolerance0 = bs.readF32();
525
+ result.damageLevelTolerance1 = bs.readF32();
526
+ // 10. 2 F32 splash params → offsets 0x41c, 0x420
527
+ result.splashFreqMod = bs.readF32();
528
+ result.splashVelEpsilon = bs.readF32();
529
+ // 11. 2 F32 collision damage → offsets 0x390, 0x394
530
+ result.collDamageThresholdVel = bs.readF32();
531
+ result.collDamageMultiplier = bs.readF32();
532
+ return result;
533
+ }
534
+ // ============================================================
535
+ // FlyingVehicleData (extends VehicleData)
536
+ // ============================================================
537
+ function flyingVehicleDataUnpack(bs) {
538
+ const result = vehicleDataUnpack(bs);
539
+ // 2 sound refs
540
+ result.jetActivateSound = readDataBlockRef(bs);
541
+ result.jetDeactivateSound = readDataBlockRef(bs);
542
+ // 4 jet emitter refs
543
+ const jetEmitters = [];
544
+ for (let i = 0; i < 4; i++) {
545
+ jetEmitters.push(readDataBlockRef(bs));
546
+ }
547
+ result.jetEmitters = jetEmitters;
548
+ // Verified against decompiled binary FUN_0060fc40 — 16 F32s
549
+ result.maneuveringForce = bs.readF32();
550
+ result.horizontalSurfaceForce = bs.readF32();
551
+ result.verticalSurfaceForce = bs.readF32();
552
+ result.autoInputDamping = bs.readF32();
553
+ result.steeringForce = bs.readF32();
554
+ result.steeringRollForce = bs.readF32();
555
+ result.rollForce = bs.readF32();
556
+ result.autoAngularForce = bs.readF32();
557
+ result.rotationalDrag = bs.readF32();
558
+ result.maxAutoSpeed = bs.readF32();
559
+ result.autoLinearForce = bs.readF32();
560
+ result.hoverHeight = bs.readF32();
561
+ result.createHoverHeight = bs.readF32();
562
+ result.minTrailSpeed = bs.readF32();
563
+ result.vertThrustMultiple = bs.readF32();
564
+ result.maxForwardSpeed = bs.readF32();
565
+ return result;
566
+ }
567
+ // ============================================================
568
+ // HoverVehicleData (extends VehicleData)
569
+ // ============================================================
570
+ function hoverVehicleDataUnpack(bs) {
571
+ const result = vehicleDataUnpack(bs);
572
+ // 17 F32s
573
+ result.dragForce = bs.readF32();
574
+ result.mainThrustForce = bs.readF32();
575
+ result.reverseThrustForce = bs.readF32();
576
+ result.strafeThrustForce = bs.readF32();
577
+ result.turboFactor = bs.readF32();
578
+ result.stabLenMin = bs.readF32();
579
+ result.stabLenMax = bs.readF32();
580
+ result.stabSpringConstant = bs.readF32();
581
+ result.stabDampingConstant = bs.readF32();
582
+ result.gyroDrag = bs.readF32();
583
+ result.normalForce = bs.readF32();
584
+ result.restorativeForce = bs.readF32();
585
+ result.steeringForce = bs.readF32();
586
+ result.rollForce = bs.readF32();
587
+ result.pitchForce = bs.readF32();
588
+ result.floatingThrustFactor = bs.readF32();
589
+ result.brakingForce = bs.readF32();
590
+ // 3×F32 (dustTrailOffset)
591
+ result.dustTrailOffset = {
592
+ x: bs.readF32(),
593
+ y: bs.readF32(),
594
+ z: bs.readF32(),
595
+ };
596
+ // 2 F32s
597
+ result.dustTrailFreqMod = bs.readF32();
598
+ result.triggerTrailHeight = bs.readF32();
599
+ // 3 sound refs
600
+ result.floatSound = readDataBlockRef(bs);
601
+ result.thrustSound = readDataBlockRef(bs);
602
+ result.turboSound = readDataBlockRef(bs);
603
+ // 3 jet emitter refs
604
+ const jetEmitters = [];
605
+ for (let i = 0; i < 3; i++) {
606
+ jetEmitters.push(readDataBlockRef(bs));
607
+ }
608
+ result.jetEmitters = jetEmitters;
609
+ // dustTrailEmitter ref
610
+ result.dustTrailEmitter = readDataBlockRef(bs);
611
+ // 3 F32s
612
+ result.mainThrustEmitterFactor = bs.readF32();
613
+ result.strafeThrustEmitterFactor = bs.readF32();
614
+ result.reverseThrustEmitterFactor = bs.readF32();
615
+ return result;
616
+ }
617
+ // ============================================================
618
+ // WheeledVehicleData (extends VehicleData)
619
+ // ============================================================
620
+ function wheeledVehicleDataUnpack(bs) {
621
+ const result = vehicleDataUnpack(bs);
622
+ // 9 tire F32s
623
+ result.tireRadius = bs.readF32();
624
+ result.tireStaticFriction = bs.readF32();
625
+ result.tireKineticFriction = bs.readF32();
626
+ result.tireRestitution = bs.readF32();
627
+ result.tireLateralForce = bs.readF32();
628
+ result.tireLateralDamping = bs.readF32();
629
+ result.tireLateralRelaxation = bs.readF32();
630
+ result.tireLongitudinalForce = bs.readF32();
631
+ result.tireLongitudinalDamping = bs.readF32();
632
+ // tire emitter ref
633
+ result.tireEmitter = readDataBlockRef(bs);
634
+ // 4 sound refs
635
+ result.jetSound = readDataBlockRef(bs);
636
+ result.engineSound = readDataBlockRef(bs);
637
+ result.squealSound = readDataBlockRef(bs);
638
+ result.wadeSound = readDataBlockRef(bs);
639
+ // 11 F32s
640
+ result.spring = bs.readF32();
641
+ result.springDamping = bs.readF32();
642
+ result.springLength = bs.readF32();
643
+ result.brakeTorque = bs.readF32();
644
+ result.engineTorque = bs.readF32();
645
+ result.engineBrake = bs.readF32();
646
+ result.maxWheelSpeed = bs.readF32();
647
+ result.steeringAngle = bs.readF32();
648
+ result.steeringReturn = bs.readF32();
649
+ result.steeringDamping = bs.readF32();
650
+ result.powerSteeringFactor = bs.readF32();
651
+ return result;
652
+ }
653
+ // ============================================================
654
+ // StaticShapeData (extends ShapeBaseData)
655
+ // ============================================================
656
+ function staticShapeDataUnpack(bs) {
657
+ const result = shapeBaseDataUnpack(bs);
658
+ result.noIndividualDamage = bs.readFlag();
659
+ result.dynamicTypeField = bs.readS32();
660
+ return result;
661
+ }
662
+ // ============================================================
663
+ // TurretData (extends StaticShapeData)
664
+ // ============================================================
665
+ function turretDataUnpack(bs) {
666
+ const result = staticShapeDataUnpack(bs);
667
+ // Verified against decompiled binary FUN_00654190
668
+ result.thetaMin = bs.readF32(); // 0x33c
669
+ result.thetaMax = bs.readF32(); // 0x340
670
+ result.thetaNull = bs.readF32(); // 0x344
671
+ result.neverUpdateControl = bs.readFlag(); // 0x34c
672
+ result.primaryAxis = bs.readRangedU32(0, 3); // 0x348 (FUN_0043f120(4) = 2 bits)
673
+ result.maxCapacitorEnergy = bs.readF32(); // 0x35c
674
+ result.capacitorRechargeRate = bs.readF32(); // 0x360
675
+ return result;
676
+ }
677
+ // ============================================================
678
+ // TurretImageData (extends ShapeBaseImageData)
679
+ // ============================================================
680
+ function turretImageDataUnpack(bs) {
681
+ const result = shapeBaseImageDataUnpack(bs);
682
+ // Verified against decompiled binary FUN_00654850
683
+ result.activationMS = bs.readInt(8); // << 5 on store (0x1c04)
684
+ result.deactivateDelayMS = bs.readInt(8); // << 5 on store (0x1c08)
685
+ result.degPerSecTheta = bs.readRangedU32(0, 1080); // 0x1c10 (FUN_0043f120(0x439))
686
+ result.degPerSecPhi = bs.readRangedU32(0, 1080); // 0x1c14
687
+ result.dontFireInsideDamageRadius = bs.readFlag(); // 0x1c1c
688
+ result.damageRadius = bs.readF32(); // 0x1c20
689
+ result.useCapacitor = bs.readFlag(); // 0x1c24 (NOT a DataBlockRef)
690
+ return result;
691
+ }
692
+ // ============================================================
693
+ // ItemData (extends ShapeBaseData)
694
+ // ============================================================
695
+ function itemDataUnpack(bs) {
696
+ const result = shapeBaseDataUnpack(bs);
697
+ // 2×readFloat(10)
698
+ result.friction = bs.readFloat(10);
699
+ result.elasticity = bs.readFloat(10);
700
+ // flag(sticky)
701
+ result.sticky = bs.readFlag();
702
+ // flag(gravityMod!=1) then readFloat(10)
703
+ if (bs.readFlag())
704
+ result.gravityMod = bs.readFloat(10);
705
+ // flag(maxVelocity!=-1) then F32
706
+ if (bs.readFlag())
707
+ result.maxVelocity = bs.readF32();
708
+ // flag(hasLight); if true: readInt(2) + 4×readFloat(7) + S32 + F32 + flag
709
+ if (bs.readFlag()) {
710
+ result.lightType = bs.readInt(2);
711
+ result.lightColor = {
712
+ r: bs.readFloat(7),
713
+ g: bs.readFloat(7),
714
+ b: bs.readFloat(7),
715
+ a: bs.readFloat(7),
716
+ };
717
+ result.lightTime = bs.readS32();
718
+ result.lightRadius = bs.readF32();
719
+ result.lightOnlyStatic = bs.readFlag();
720
+ }
721
+ return result;
722
+ }
723
+ // ============================================================
724
+ // ProjectileData (extends GameBaseData — 0 parent bits)
725
+ // ============================================================
726
+ function projectileDataUnpack(bs) {
727
+ const result = {};
728
+ // readString (projectileName)
729
+ result.projectileShapeName = bs.readString();
730
+ // 2×S32
731
+ result.faceViewerLinkTime = bs.readS32();
732
+ result.lifetime = bs.readS32();
733
+ // flag(faceViewer)
734
+ result.faceViewer = bs.readFlag();
735
+ // flag(nonDefaultScale); if true: 3×F32
736
+ if (bs.readFlag()) {
737
+ result.scale = {
738
+ x: bs.readF32(),
739
+ y: bs.readF32(),
740
+ z: bs.readF32(),
741
+ };
742
+ }
743
+ // 9 readDataBlockRef fields in pack order (verified against decompiled
744
+ // Tribes2.exe FUN_00631010 / FUN_006303f0 struct offsets).
745
+ result.baseEmitter = readDataBlockRef(bs); // 0xe8 ParticleEmitterData
746
+ result.delayEmitter = readDataBlockRef(bs); // 0xec ParticleEmitterData
747
+ result.bubbleEmitter = readDataBlockRef(bs); // 0xf0 ParticleEmitterData
748
+ result.explosion = readDataBlockRef(bs); // 0xf4 ExplosionData
749
+ result.underwaterExplosion = readDataBlockRef(bs); // 0xf8 ExplosionData
750
+ result.splash = readDataBlockRef(bs); // 0xfc SplashData
751
+ result.sound = readDataBlockRef(bs); // 0x100 AudioProfile (in-flight)
752
+ result.wetFireSound = readDataBlockRef(bs); // 0x104 AudioProfile
753
+ result.fireSound = readDataBlockRef(bs); // 0x108 AudioProfile
754
+ // 6 decal refs (loop)
755
+ const decals = [];
756
+ for (let i = 0; i < 6; i++) {
757
+ decals.push(readDataBlockRef(bs));
758
+ }
759
+ result.decals = decals;
760
+ // flag(hasLight); if true: readFloat(8) + 3×readFloat(7)
761
+ if (bs.readFlag()) {
762
+ result.lightRadius = bs.readFloat(8);
763
+ result.lightColor = {
764
+ r: bs.readFloat(7),
765
+ g: bs.readFloat(7),
766
+ b: bs.readFloat(7),
767
+ };
768
+ }
769
+ // flag(hasUnderwaterColor); if true: 3×readFloat(7)
770
+ if (bs.readFlag()) {
771
+ result.underwaterLightColor = {
772
+ r: bs.readFloat(7),
773
+ g: bs.readFloat(7),
774
+ b: bs.readFloat(7),
775
+ };
776
+ }
777
+ // explodeOnWaterImpact: 8-bit bool via stream->write(sizeof(bool))
778
+ // (confirmed in decompiled binary FUN_00631360: read(1,&var) at offset 0xcc)
779
+ result.explodeOnWaterImpact = readBool(bs);
780
+ // depthTolerance: F32
781
+ result.depthTolerance = bs.readF32();
782
+ return result;
783
+ }
784
+ // ============================================================
785
+ // LinearProjectileData (extends ProjectileData)
786
+ // ============================================================
787
+ function linearProjectileDataUnpack(bs) {
788
+ const result = projectileDataUnpack(bs);
789
+ // 2×F32
790
+ result.dryVelocity = bs.readF32();
791
+ result.wetVelocity = bs.readF32();
792
+ // 2×U32
793
+ result.fizzleTime = bs.readU32();
794
+ result.fizzleType = bs.readU32();
795
+ // flag
796
+ result.hardRetarget = bs.readFlag();
797
+ // 2×readRangedU32(0,90)
798
+ result.inheritedVelocityScale = bs.readRangedU32(0, 90);
799
+ result.lifetimeMS = bs.readRangedU32(0, 90);
800
+ // 2×U32
801
+ result.collideWithOwnerTimeMS = bs.readU32();
802
+ result.proximityRadius = bs.readU32();
803
+ // flag
804
+ result.tracerProjectile = bs.readFlag();
805
+ return result;
806
+ }
807
+ // ============================================================
808
+ // GrenadeProjectileData (extends ProjectileData)
809
+ // ============================================================
810
+ function grenadeProjectileDataUnpack(bs) {
811
+ const result = projectileDataUnpack(bs);
812
+ result.armingDelayMS = bs.readS32();
813
+ result.muzzleVelocity = bs.readF32();
814
+ result.grenadeElasticity = bs.readF32();
815
+ result.grenadeFriction = bs.readF32();
816
+ result.drag = bs.readF32();
817
+ result.density = bs.readF32();
818
+ result.gravityMod = bs.readF32();
819
+ result.lifetimeMS = bs.readS32();
820
+ return result;
821
+ }
822
+ // ============================================================
823
+ // SeekerProjectileData (extends ProjectileData)
824
+ // ============================================================
825
+ function seekerProjectileDataUnpack(bs) {
826
+ const result = projectileDataUnpack(bs);
827
+ // Verified against decompiled binary (lines 430494-430580)
828
+ // 10 F32/S32 (offsets 0x138-0x164)
829
+ result.lifetimeMS = bs.readS32(); // 0x138
830
+ result.muzzleVelocity = bs.readF32(); // 0x13c
831
+ result.turningSpeed = bs.readF32(); // 0x140
832
+ result.proximityRadius = bs.readF32(); // 0x14c
833
+ result.terrainAvoidanceSpeed = bs.readF32(); // 0x150
834
+ result.terrainScanAhead = bs.readF32(); // 0x154
835
+ result.terrainHeightFail = bs.readF32(); // 0x158
836
+ result.terrainAvoidanceRadius = bs.readF32(); // 0x15c
837
+ result.flareDistance = bs.readF32(); // 0x160
838
+ result.flareAngle = bs.readF32(); // 0x164
839
+ // useFlechette — 8-bit bool via stream->read(1, &var) at offset 0x168
840
+ // (confirmed: read(1, &cStack_21) = 1 BYTE = 8 bits, NOT readF32)
841
+ result.useFlechette = readBool(bs);
842
+ // 2 F32 (offsets 0x144, 0x148)
843
+ result.maxVelocity = bs.readF32();
844
+ result.acceleration = bs.readF32();
845
+ // 2 S32 (offsets 0x16c, 0x180)
846
+ result.flechetteDelayMs = bs.readS32();
847
+ result.exhaustTimeMs = bs.readS32();
848
+ // 2 readString (offsets 0x184, 0x190)
849
+ result.exhaustNodeName = bs.readString();
850
+ result.casingShapeName = bs.readString();
851
+ // 3 conditional DataBlock refs (offsets 0x18c, 0x174, 0x17c)
852
+ result.casingDebris = readDataBlockRef(bs);
853
+ result.puffEmitter = readDataBlockRef(bs);
854
+ result.exhaustEmitter = readDataBlockRef(bs);
855
+ return result;
856
+ }
857
+ // ============================================================
858
+ // SniperProjectileData (extends ProjectileData)
859
+ // ============================================================
860
+ function sniperProjectileDataUnpack(bs) {
861
+ const result = projectileDataUnpack(bs);
862
+ // Verified against decompiled binary FUN_00641d40
863
+ // 2×F32 (offsets 0x138, 0x13c)
864
+ result.maxRifleRange = bs.readF32();
865
+ result.rifleHeadMultiplier = bs.readF32();
866
+ // ColorF via FUN_0043f040 (packed 4×U8 = 32 bits at offset 0x140)
867
+ result.beamColor = readColorF(bs);
868
+ // 9×F32 (offsets 0x150, 0x154, 0x158, 0x168, 0x15c, 0x160, 0x164, 0x16c, 0x170)
869
+ result.fadeTime = bs.readF32();
870
+ result.startBeamWidth = bs.readF32();
871
+ result.endBeamWidth = bs.readF32();
872
+ result.pulseBeamWidth = bs.readF32();
873
+ result.beamFlareAngle = bs.readF32();
874
+ result.minFlareSize = bs.readF32();
875
+ result.maxFlareSize = bs.readF32();
876
+ result.pulseSpeed = bs.readF32();
877
+ result.pulseLength = bs.readF32();
878
+ // ColorF via FUN_0043f040 (packed 4×U8 = 32 bits at offset 0x178)
879
+ result.lightColor = readColorF(bs);
880
+ // F32 (offset 0x174)
881
+ result.lightRadius = bs.readF32();
882
+ // 12 readString (ST_NUM_TEX=0xc)
883
+ const textures = [];
884
+ for (let i = 0; i < 12; i++) {
885
+ textures.push(bs.readString());
886
+ }
887
+ result.textures = textures;
888
+ return result;
889
+ }
890
+ // ============================================================
891
+ // ShockLanceProjectileData (extends ProjectileData)
892
+ // ============================================================
893
+ function shockLanceProjectileDataUnpack(bs) {
894
+ // Verified against decompiled binary FUN_0064e840
895
+ const result = projectileDataUnpack(bs);
896
+ // 7 F32 (offsets 0x138, 0x13c, 0x160, 0x164, 0x168, 0x16c, 0x170)
897
+ result.zapDuration = bs.readF32();
898
+ result.boltLength = bs.readF32();
899
+ result.numParts = bs.readF32();
900
+ result.lightningFreq = bs.readF32();
901
+ result.lightningDensity = bs.readF32();
902
+ result.lightningAmp = bs.readF32();
903
+ result.lightningWidth = bs.readF32();
904
+ // DataBlockRef (shockwave at offset 0x190)
905
+ result.shockwave = readDataBlockRef(bs);
906
+ // 2×4 loop: startWidth[2], endWidth[2], boltSpeed[2], texWrap[2]
907
+ // (offsets 0x140+i*4, 0x148+i*4, 0x150+i*4, 0x158+i*4)
908
+ const startWidth = [];
909
+ const endWidth = [];
910
+ const boltSpeed = [];
911
+ const texWrap = [];
912
+ for (let i = 0; i < 2; i++) {
913
+ startWidth.push(bs.readF32());
914
+ endWidth.push(bs.readF32());
915
+ boltSpeed.push(bs.readF32());
916
+ texWrap.push(bs.readF32());
917
+ }
918
+ result.startWidth = startWidth;
919
+ result.endWidth = endWidth;
920
+ result.boltSpeed = boltSpeed;
921
+ result.texWrap = texWrap;
922
+ // 4 readString (texture[4] at offset 0x174)
923
+ const slTextures = [];
924
+ for (let i = 0; i < 4; i++) {
925
+ slTextures.push(bs.readString());
926
+ }
927
+ result.textures = slTextures;
928
+ // DataBlockRef (emitter at offset 0x188)
929
+ result.emitter = readDataBlockRef(bs);
930
+ return result;
931
+ }
932
+ // ============================================================
933
+ // ELFProjectileData (extends ProjectileData)
934
+ // ============================================================
935
+ function elfProjectileDataUnpack(bs) {
936
+ const result = projectileDataUnpack(bs);
937
+ // 6×F32
938
+ result.beamRange = bs.readF32();
939
+ result.beamDrainRate = bs.readF32();
940
+ result.muzzleVelocity = bs.readF32();
941
+ result.proximityRadius = bs.readF32();
942
+ result.startWidth = bs.readF32();
943
+ result.endWidth = bs.readF32();
944
+ // 3 readString
945
+ result.mainBeamTexture = bs.readString();
946
+ result.innerBeamTexture = bs.readString();
947
+ result.flareTexture = bs.readString();
948
+ // 1 readDataBlockRef
949
+ result.hitEmitter = readDataBlockRef(bs);
950
+ return result;
951
+ }
952
+ // ============================================================
953
+ // RepairProjectileData (extends ProjectileData)
954
+ // ============================================================
955
+ function repairProjectileDataUnpack(bs) {
956
+ const result = projectileDataUnpack(bs);
957
+ // 8×F32
958
+ result.beamRange = bs.readF32();
959
+ result.beamRepairRate = bs.readF32();
960
+ result.muzzleVelocity = bs.readF32();
961
+ result.proximityRadius = bs.readF32();
962
+ result.startWidth = bs.readF32();
963
+ result.endWidth = bs.readF32();
964
+ result.startBeamWidth = bs.readF32();
965
+ result.endBeamWidth = bs.readF32();
966
+ // 2 readString
967
+ result.mainBeamTexture = bs.readString();
968
+ result.innerBeamTexture = bs.readString();
969
+ return result;
970
+ }
971
+ // ============================================================
972
+ // TargetProjectileData (extends ProjectileData)
973
+ // ============================================================
974
+ function targetProjectileDataUnpack(bs) {
975
+ const result = projectileDataUnpack(bs);
976
+ // Verified against decompiled binary FUN_006477f0
977
+ // F32 (offset 0x138)
978
+ result.maxRifleRange = bs.readF32();
979
+ // ColorF via FUN_0043f040 (packed 4×U8 = 32 bits at offset 0x13c)
980
+ result.beamColor = readColorF(bs);
981
+ // 7×F32 (offsets 0x14c, 0x15c, 0x150, 0x154, 0x158, 0x160, 0x164)
982
+ result.startBeamWidth = bs.readF32();
983
+ result.pulseBeamWidth = bs.readF32();
984
+ result.beamFlareAngle = bs.readF32();
985
+ result.minFlareSize = bs.readF32();
986
+ result.maxFlareSize = bs.readF32();
987
+ result.pulseSpeed = bs.readF32();
988
+ result.pulseLength = bs.readF32();
989
+ // 4 readString (textureName[4] at offset 0x16c)
990
+ const targetTextures = [];
991
+ for (let i = 0; i < 4; i++) {
992
+ targetTextures.push(bs.readString());
993
+ }
994
+ result.textures = targetTextures;
995
+ return result;
996
+ }
997
+ // ============================================================
998
+ // TracerProjectileData (extends LinearProjectileData)
999
+ // ============================================================
1000
+ function tracerProjectileDataUnpack(bs) {
1001
+ // Verified against decompiled binary FUN_00640160
1002
+ const result = linearProjectileDataUnpack(bs);
1003
+ // 3 F32 (offsets 0x168, 0x184, 0x170)
1004
+ result.tracerLength = bs.readF32();
1005
+ result.tracerAlpha = bs.readF32();
1006
+ result.tracerMinPixels = bs.readF32();
1007
+ // readBool 8-bit (offset 0x16c)
1008
+ result.crossViewFraction = readBool(bs);
1009
+ // ColorF via FUN_0043f040 (packed 4×U8 at offset 0x174)
1010
+ result.tracerColor = readColorF(bs);
1011
+ // 2 F32 (offsets 0x188, 0x18c)
1012
+ result.tracerWidth = bs.readF32();
1013
+ result.muzzleVelocity = bs.readF32();
1014
+ // readBool 8-bit (offset 0x190)
1015
+ result.proximityRadius = readBool(bs);
1016
+ // 2 readString (loop count=2 at offset 0x194)
1017
+ result.textureName0 = bs.readString();
1018
+ result.textureName1 = bs.readString();
1019
+ return result;
1020
+ }
1021
+ // ============================================================
1022
+ // EnergyProjectileData (extends GrenadeProjectileData)
1023
+ // ============================================================
1024
+ function energyProjectileDataUnpack(bs) {
1025
+ // Verified against decompiled binary FUN_00694d80 — parent is FUN_00633cf0 (GrenadeProjectileData)
1026
+ const result = grenadeProjectileDataUnpack(bs);
1027
+ // 7×F32
1028
+ result.energyDrainPerSecond = bs.readF32();
1029
+ result.energyMinDrain = bs.readF32();
1030
+ result.beamWidth = bs.readF32();
1031
+ result.beamRange = bs.readF32();
1032
+ result.numSegments = bs.readF32();
1033
+ result.texRepeat = bs.readF32();
1034
+ result.beamFlareAngle = bs.readF32();
1035
+ // 2 readString
1036
+ result.beamTexture = bs.readString();
1037
+ result.flareTexture = bs.readString();
1038
+ return result;
1039
+ }
1040
+ // ============================================================
1041
+ // LinearFlareProjectileData (extends LinearProjectileData)
1042
+ // ============================================================
1043
+ function linearFlareProjectileDataUnpack(bs) {
1044
+ // Verified against decompiled binary FUN_0063dc80
1045
+ const result = linearProjectileDataUnpack(bs);
1046
+ // F32 (offset 0x168)
1047
+ result.numFlares = bs.readF32();
1048
+ // ColorF via FUN_0043f040 (packed 4×U8 at offset 0x178)
1049
+ result.flareColor = readColorF(bs);
1050
+ // 2 readString (offsets 0x188, 0x18c)
1051
+ result.flareTexture = bs.readString();
1052
+ result.smokeTexture = bs.readString();
1053
+ // 3×F32 loop (offset 0x16c, count=3)
1054
+ result.size = bs.readF32();
1055
+ result.flareModTexture = bs.readF32();
1056
+ result.smokeSize = bs.readF32();
1057
+ return result;
1058
+ }
1059
+ // ============================================================
1060
+ // BombProjectileData (extends GrenadeProjectileData)
1061
+ // ============================================================
1062
+ function bombProjectileDataUnpack(bs) {
1063
+ const result = grenadeProjectileDataUnpack(bs);
1064
+ // 6×F32
1065
+ result.smokeDist = bs.readF32();
1066
+ result.noSmoke = bs.readF32();
1067
+ result.boomTime = bs.readF32();
1068
+ result.casingDist = bs.readF32();
1069
+ result.smokeCushion = bs.readF32();
1070
+ result.noSmokeCounter = bs.readF32();
1071
+ // 2 readString
1072
+ result.smokeTexture = bs.readString();
1073
+ result.bombTexture = bs.readString();
1074
+ return result;
1075
+ }
1076
+ // ============================================================
1077
+ // FlareProjectileData (extends GrenadeProjectileData)
1078
+ // ============================================================
1079
+ function flareProjectileDataUnpack(bs) {
1080
+ // Verified against decompiled binary FUN_006872f0
1081
+ const result = grenadeProjectileDataUnpack(bs);
1082
+ // F32 (offset 0x158)
1083
+ result.size = bs.readF32();
1084
+ // readBool 8-bit (offset 0x160, read(1,...))
1085
+ result.useLensFlare = readBool(bs);
1086
+ // 2 readString loop (offset 0x164, count=2)
1087
+ result.flareTexture = bs.readString();
1088
+ result.lensFlareTexture = bs.readString();
1089
+ return result;
1090
+ }
1091
+ // ============================================================
1092
+ // ExplosionData (extends GameBaseData)
1093
+ // ============================================================
1094
+ function explosionDataUnpack(bs) {
1095
+ const result = {};
1096
+ // readString
1097
+ result.dtsFileName = bs.readString();
1098
+ // 2 readDataBlockRef
1099
+ result.soundProfile = readDataBlockRef(bs);
1100
+ result.particleEmitter = readDataBlockRef(bs);
1101
+ // readInt(14) + F32 + flag
1102
+ result.particleDensity = bs.readInt(14);
1103
+ result.particleRadius = bs.readF32();
1104
+ result.faceViewer = bs.readFlag();
1105
+ // flag(scale); if true: 3×readInt(16)
1106
+ if (bs.readFlag()) {
1107
+ result.explosionScale = {
1108
+ x: bs.readInt(16),
1109
+ y: bs.readInt(16),
1110
+ z: bs.readInt(16),
1111
+ };
1112
+ }
1113
+ // readInt(14) + 4×readRangedU32 + 2×readRangedU32(0,1000) + readInt(14) + readRangedU32(0,10000)
1114
+ result.playSpeed = bs.readInt(14);
1115
+ result.debrisThetaMin = bs.readRangedU32(0, 180);
1116
+ result.debrisThetaMax = bs.readRangedU32(0, 180);
1117
+ result.debrisPhiMin = bs.readRangedU32(0, 360);
1118
+ result.debrisPhiMax = bs.readRangedU32(0, 360);
1119
+ result.debrisMinVelocity = bs.readRangedU32(0, 1000);
1120
+ result.debrisMaxVelocity = bs.readRangedU32(0, 1000);
1121
+ result.debrisNum = bs.readInt(14);
1122
+ result.debrisVariance = bs.readRangedU32(0, 10000);
1123
+ // 4×readInt(16) + F32 + 2 flags
1124
+ result.delayMS = bs.readInt(16);
1125
+ result.delayVariance = bs.readInt(16);
1126
+ result.lifetimeMS = bs.readInt(16);
1127
+ result.lifetimeVariance = bs.readInt(16);
1128
+ result.offset = bs.readF32();
1129
+ result.shakeCamera = bs.readFlag();
1130
+ result.hasLight = bs.readFlag();
1131
+ // 9×F32
1132
+ result.camShakeFreq = {
1133
+ x: bs.readF32(),
1134
+ y: bs.readF32(),
1135
+ z: bs.readF32(),
1136
+ };
1137
+ result.camShakeAmp = {
1138
+ x: bs.readF32(),
1139
+ y: bs.readF32(),
1140
+ z: bs.readF32(),
1141
+ };
1142
+ result.camShakeDuration = bs.readF32();
1143
+ result.camShakeRadius = bs.readF32();
1144
+ result.camShakeFalloff = bs.readF32();
1145
+ // readDataBlockRef(shockwave) + 1 debris ref + 4 emitter refs + 5 sub-explosion refs
1146
+ result.shockwave = readDataBlockRef(bs);
1147
+ result.debris = readDataBlockRef(bs);
1148
+ const emitters = [];
1149
+ for (let i = 0; i < 4; i++) {
1150
+ emitters.push(readDataBlockRef(bs));
1151
+ }
1152
+ result.emitters = emitters;
1153
+ const subExplosions = [];
1154
+ for (let i = 0; i < 5; i++) {
1155
+ subExplosions.push(readDataBlockRef(bs));
1156
+ }
1157
+ result.subExplosions = subExplosions;
1158
+ // readRangedU32(0,4) count; loop: readFloat(8) times + 3×readRangedU32(0,16000) sizes
1159
+ const timeCount = bs.readRangedU32(0, 4);
1160
+ const times = [];
1161
+ for (let i = 0; i < timeCount; i++) {
1162
+ times.push(bs.readFloat(8));
1163
+ }
1164
+ result.times = times;
1165
+ const sizes = [];
1166
+ for (let i = 0; i < timeCount; i++) {
1167
+ sizes.push({
1168
+ x: bs.readRangedU32(0, 16000),
1169
+ y: bs.readRangedU32(0, 16000),
1170
+ z: bs.readRangedU32(0, 16000),
1171
+ });
1172
+ }
1173
+ result.sizes = sizes;
1174
+ return result;
1175
+ }
1176
+ // ============================================================
1177
+ // DebrisData (extends GameBaseData)
1178
+ // ============================================================
1179
+ function debrisDataUnpack(bs) {
1180
+ // Field order confirmed from decompiled binary FUN_006844d0
1181
+ const result = {};
1182
+ result.elasticity = bs.readF32(); // 0x50
1183
+ result.friction = bs.readF32(); // 0x4c
1184
+ result.numBounces = bs.readS32(); // 0x5c
1185
+ result.bounceVariance = bs.readS32(); // 0x60
1186
+ result.minSpinSpeed = bs.readF32(); // 0x64
1187
+ result.maxSpinSpeed = bs.readF32(); // 0x68
1188
+ // 4 bools via stream->write(sizeof(bool)) = 8 bits each (0x6c-0x6f)
1189
+ result.render2D = readBool(bs);
1190
+ result.explodeOnMaxBounce = readBool(bs);
1191
+ result.staticOnMaxBounce = readBool(bs);
1192
+ result.snapOnMaxBounce = readBool(bs);
1193
+ result.lifetime = bs.readF32(); // 0x54
1194
+ result.lifetimeVariance = bs.readF32(); // 0x58
1195
+ // "Written twice" bug: same offsets 0x64/0x68 written again
1196
+ result.minSpinSpeed_dup = bs.readF32();
1197
+ result.maxSpinSpeed_dup = bs.readF32();
1198
+ result.velocity = bs.readF32(); // 0x44
1199
+ result.velocityVariance = bs.readF32(); // 0x48
1200
+ // 2 bools via stream->write(sizeof(bool)) = 8 bits each (0x70, 0x71)
1201
+ result.useRadiusMass = readBool(bs);
1202
+ result.fade = readBool(bs);
1203
+ result.baseRadius = bs.readF32(); // 0x74
1204
+ result.gravModifier = bs.readF32(); // 0x78
1205
+ result.terminalVelocity = bs.readF32(); // 0x7c
1206
+ result.ignoreWater = readBool(bs); // 0x80 (8-bit bool)
1207
+ // 2 readString
1208
+ result.shapeFileName = bs.readString(); // 0x8c
1209
+ result.skinName = bs.readString(); // 0x84
1210
+ // 2 emitter refs (loop at 0xa4, 0xa8) + 1 explosion ref (0x94)
1211
+ result.emitter0 = readDataBlockRef(bs);
1212
+ result.emitter1 = readDataBlockRef(bs);
1213
+ result.explosion = readDataBlockRef(bs);
1214
+ return result;
1215
+ }
1216
+ // ============================================================
1217
+ // SplashData (extends GameBaseData)
1218
+ // ============================================================
1219
+ function splashDataUnpack(bs) {
1220
+ const result = {};
1221
+ // 3×F32 (scale)
1222
+ result.scale = {
1223
+ x: bs.readF32(),
1224
+ y: bs.readF32(),
1225
+ z: bs.readF32(),
1226
+ };
1227
+ // ~15 F32/S32 values
1228
+ result.delayMS = bs.readS32();
1229
+ result.delayVariance = bs.readS32();
1230
+ result.lifetimeMS = bs.readS32();
1231
+ result.lifetimeVariance = bs.readS32();
1232
+ result.width = bs.readF32();
1233
+ result.numSegments = bs.readS32();
1234
+ result.velocity = bs.readF32();
1235
+ result.height = bs.readF32();
1236
+ result.acceleration = bs.readF32();
1237
+ result.texWrap = bs.readF32();
1238
+ result.texFactor = bs.readF32();
1239
+ result.ejectionFreq = bs.readF32();
1240
+ result.ejectionAngle = bs.readF32();
1241
+ result.ringLifetime = bs.readF32();
1242
+ result.startRadius = bs.readF32();
1243
+ // explosion ref + 3 emitter refs
1244
+ result.explosion = readDataBlockRef(bs);
1245
+ const emitters = [];
1246
+ for (let i = 0; i < 3; i++) {
1247
+ emitters.push(readDataBlockRef(bs));
1248
+ }
1249
+ result.emitters = emitters;
1250
+ // 4× ColorF (4×U8 = 32 bits each, via FUN_0043f040)
1251
+ const colors = [];
1252
+ for (let i = 0; i < 4; i++) {
1253
+ colors.push(readColorF(bs));
1254
+ }
1255
+ result.colors = colors;
1256
+ // 4×F32 times
1257
+ const times = [];
1258
+ for (let i = 0; i < 4; i++) {
1259
+ times.push(bs.readF32());
1260
+ }
1261
+ result.times = times;
1262
+ // 2 readString
1263
+ result.textureName = bs.readString();
1264
+ result.foamTexture = bs.readString();
1265
+ return result;
1266
+ }
1267
+ // ============================================================
1268
+ // ShockwaveData (extends GameBaseData)
1269
+ // ============================================================
1270
+ function shockwaveDataUnpack(bs) {
1271
+ const result = {};
1272
+ // 3×F32 (scale)
1273
+ result.scale = {
1274
+ x: bs.readF32(),
1275
+ y: bs.readF32(),
1276
+ z: bs.readF32(),
1277
+ };
1278
+ // ~17 F32/S32/bool values
1279
+ result.delayMS = bs.readS32();
1280
+ result.delayVariance = bs.readS32();
1281
+ result.lifetimeMS = bs.readS32();
1282
+ result.lifetimeVariance = bs.readS32();
1283
+ result.width = bs.readF32();
1284
+ result.numSegments = bs.readS32();
1285
+ result.numVertSegments = bs.readS32();
1286
+ result.velocity = bs.readF32();
1287
+ result.height = bs.readF32();
1288
+ result.verticalCurve = bs.readF32();
1289
+ result.acceleration = bs.readF32();
1290
+ result.texWrap = bs.readF32();
1291
+ // 5 bools via stream->write(sizeof(bool)) = 8 bits each
1292
+ // (confirmed at offsets 0x98-0x9c in binary, using read(1,&var) pattern)
1293
+ result.is2D = readBool(bs);
1294
+ result.orientToNormal = readBool(bs);
1295
+ result.mapToTerrain = readBool(bs);
1296
+ result.renderBottom = readBool(bs);
1297
+ result.renderSquare = readBool(bs);
1298
+ // 3 emitter refs
1299
+ const emitters = [];
1300
+ for (let i = 0; i < 3; i++) {
1301
+ emitters.push(readDataBlockRef(bs));
1302
+ }
1303
+ result.emitters = emitters;
1304
+ // 4× ColorF (4×U8 = 32 bits each, via FUN_0043f040)
1305
+ const colors = [];
1306
+ for (let i = 0; i < 4; i++) {
1307
+ colors.push(readColorF(bs));
1308
+ }
1309
+ result.colors = colors;
1310
+ // 4×F32 times
1311
+ const times = [];
1312
+ for (let i = 0; i < 4; i++) {
1313
+ times.push(bs.readF32());
1314
+ }
1315
+ result.times = times;
1316
+ // 2 readString
1317
+ result.textureName = bs.readString();
1318
+ result.mapToTexture = bs.readString();
1319
+ return result;
1320
+ }
1321
+ // ============================================================
1322
+ // ParticleEmitterData (extends GameBaseData)
1323
+ // ============================================================
1324
+ function particleEmitterDataUnpack(bs) {
1325
+ const result = {};
1326
+ // 4 readInt(various)
1327
+ result.ejectionPeriodMS = bs.readInt(10);
1328
+ result.periodVarianceMS = bs.readInt(10);
1329
+ result.ejectionVelocity = bs.readInt(16);
1330
+ result.velocityVariance = bs.readInt(14);
1331
+ // flag then readInt(16)
1332
+ if (bs.readFlag()) {
1333
+ result.ejectionOffset = bs.readInt(16);
1334
+ }
1335
+ // 2×readRangedU32(0,180)
1336
+ result.thetaMin = bs.readRangedU32(0, 180);
1337
+ result.thetaMax = bs.readRangedU32(0, 180);
1338
+ // 2 conditional readRangedU32(0,360)
1339
+ if (bs.readFlag())
1340
+ result.phiReferenceVel = bs.readRangedU32(0, 360);
1341
+ if (bs.readFlag())
1342
+ result.phiVariance = bs.readRangedU32(0, 360);
1343
+ // 3 flags
1344
+ result.overrideAdvances = bs.readFlag();
1345
+ result.orientParticles = bs.readFlag();
1346
+ result.orientOnVelocity = bs.readFlag();
1347
+ // 2×readInt(10)
1348
+ result.lifetimeMS = bs.readInt(10);
1349
+ result.lifetimeVarianceMS = bs.readInt(10);
1350
+ // 2 flags
1351
+ result.useEmitterSizes = bs.readFlag();
1352
+ result.useEmitterColors = bs.readFlag();
1353
+ // U32 count (4 bytes) + per-particle: flag(1b) + conditional readClassId(11b)
1354
+ // Verified against decompiled binary FUN_006222a0:
1355
+ // read(4, &count) → U32 count
1356
+ // for each: readFlag → if true: FUN_00436d10 (readClassId, 11 bits); else: 0xFFFFFFFF
1357
+ const particleCount = bs.readU32();
1358
+ const particles = [];
1359
+ for (let i = 0; i < particleCount && i < 16; i++) {
1360
+ particles.push(readDataBlockRef(bs));
1361
+ }
1362
+ result.particles = particles;
1363
+ return result;
1364
+ }
1365
+ // ============================================================
1366
+ // ParticleData (extends GameBaseData)
1367
+ // ============================================================
1368
+ function particleDataUnpack(bs) {
1369
+ const result = {};
1370
+ // readFloat(10) + flag then F32
1371
+ result.dragCoefficient = bs.readFloat(10);
1372
+ if (bs.readFlag())
1373
+ result.windCoefficient = bs.readF32();
1374
+ // readSignedFloat(12)
1375
+ result.gravityCoefficient = bs.readSignedFloat(12);
1376
+ // readFloat(9) + flag then F32
1377
+ result.inheritedVelFactor = bs.readFloat(9);
1378
+ if (bs.readFlag())
1379
+ result.constantAcceleration = bs.readF32();
1380
+ // 2×readInt(10)
1381
+ result.lifetimeMS = bs.readInt(10);
1382
+ result.lifetimeVarianceMS = bs.readInt(10);
1383
+ // flag then F32 (spinSpeed at offset 0x58, conditional)
1384
+ if (bs.readFlag())
1385
+ result.spinSpeed = bs.readF32();
1386
+ // flag then 2×readInt(11) (spinRandomMin/Max at 0x5c/0x60)
1387
+ // Verified against decompiled binary FUN_006232d0:
1388
+ // spinRandom conditional checks BOTH defaults, reads 11-bit values (subtract 1000)
1389
+ if (bs.readFlag()) {
1390
+ result.spinRandomMin = bs.readInt(11);
1391
+ result.spinRandomMax = bs.readInt(11);
1392
+ }
1393
+ // useInvAlpha — always read (1-bit flag at offset 0x64)
1394
+ // NOT inside the spinRandom conditional (confirmed in binary: separate readFlag at line 416229)
1395
+ result.useInvAlpha = bs.readFlag();
1396
+ // readInt(2) count(+1); per key: 4×readFloat(7) + readFloat(14) + readFloat(8)
1397
+ const numKeys = bs.readInt(2) + 1;
1398
+ const keys = [];
1399
+ for (let i = 0; i < numKeys; i++) {
1400
+ keys.push({
1401
+ r: bs.readFloat(7),
1402
+ g: bs.readFloat(7),
1403
+ b: bs.readFloat(7),
1404
+ a: bs.readFloat(7),
1405
+ size: bs.readFloat(14),
1406
+ time: bs.readFloat(8),
1407
+ });
1408
+ }
1409
+ result.keys = keys;
1410
+ // readInt(6) texCount; per tex: readString
1411
+ const texCount = bs.readInt(6);
1412
+ const textures = [];
1413
+ for (let i = 0; i < texCount && i < 50; i++) {
1414
+ textures.push(bs.readString());
1415
+ }
1416
+ result.textures = textures;
1417
+ return result;
1418
+ }
1419
+ // ============================================================
1420
+ // AudioDescription (extends SimDataBlock — 0 parent bits)
1421
+ // ============================================================
1422
+ function audioDescriptionUnpack(bs) {
1423
+ const result = {};
1424
+ // readFloat(6)
1425
+ result.volume = bs.readFloat(6);
1426
+ // flag(looping); if true: 3×S32
1427
+ result.isLooping = bs.readFlag();
1428
+ if (result.isLooping) {
1429
+ result.loopCount = bs.readS32();
1430
+ result.minLoopGap = bs.readS32();
1431
+ result.maxLoopGap = bs.readS32();
1432
+ }
1433
+ // flag(is3D); if true: 2×F32 + 2×readInt(9) + readFloat(6) + readNormalVector(8) + F32
1434
+ // Verified: decompiled FUN_0040c1a0 has NO isStreaming flag between isLooping and is3D.
1435
+ // Offsets: isLooping=0x40, is3D=0x41 — consecutive with no gap.
1436
+ result.is3D = bs.readFlag();
1437
+ if (result.is3D) {
1438
+ result.referenceDistance = bs.readF32();
1439
+ result.maxDistance = bs.readF32();
1440
+ result.coneInsideAngle = bs.readInt(9);
1441
+ result.coneOutsideAngle = bs.readInt(9);
1442
+ result.coneOutsideVolume = bs.readFloat(6);
1443
+ result.coneVector = bs.readNormalVector(8);
1444
+ result.environmentLevel = bs.readF32();
1445
+ }
1446
+ // readInt(3) — type
1447
+ result.type = bs.readInt(3);
1448
+ return result;
1449
+ }
1450
+ // ============================================================
1451
+ // AudioProfile (extends SimDataBlock — 0 parent bits)
1452
+ // ============================================================
1453
+ function audioProfileUnpack(bs) {
1454
+ const result = {};
1455
+ // Verified: decompiled FUN_0040c7f0 reads 3 DataBlock refs + 1 string.
1456
+ // Offsets: 0x48 (description), 0x4c (effect/environment), 0x50 (sampleEnvironment)
1457
+ result.description = readDataBlockRef(bs);
1458
+ result.environment = readDataBlockRef(bs);
1459
+ result.sampleEnvironment = readDataBlockRef(bs);
1460
+ // readString (filename, .wav appended on read)
1461
+ result.filename = bs.readString();
1462
+ return result;
1463
+ }
1464
+ // ============================================================
1465
+ // AudioEnvironment (extends SimDataBlock — 0 parent bits)
1466
+ // ============================================================
1467
+ function audioEnvironmentUnpack(bs) {
1468
+ // Verified against decompiled binary FUN_0040b530
1469
+ const result = {};
1470
+ // flag(useRoom) → offset 0x3c
1471
+ result.useRoom = bs.readFlag();
1472
+ if (result.useRoom) {
1473
+ // readRangedU32(0, 26) → offset 0x40 (FUN_0043f120(0x1b=27), rangeSize=27, max=26)
1474
+ result.room = bs.readRangedU32(0, 26);
1475
+ }
1476
+ else {
1477
+ // 13 fields inside !useRoom branch (binary offsets 0x44-0x7c)
1478
+ result.roomHF = readRangedS32(bs, -10000, 0); // 0x44
1479
+ result.reflections = readRangedS32(bs, -10000, 10000); // 0x48
1480
+ result.reverb = readRangedS32(bs, -10000, 2000); // 0x4c
1481
+ result.roomRolloffFactor = readRangedF32(bs, 0.1, 10, 8); // 0x50
1482
+ result.decayTime = readRangedF32(bs, 0.1, 20, 8); // 0x54
1483
+ result.decayHFRatio = readRangedF32(bs, 0.1, 20, 8); // 0x58
1484
+ result.reflectionsDelay = readRangedF32(bs, 0, 0.3, 9); // 0x5c
1485
+ result.reverbDelay = readRangedF32(bs, 0, 0.1, 7); // 0x60
1486
+ result.roomVolume = readRangedS32(bs, -10000, 0); // 0x64
1487
+ result.effectVolume = readRangedF32(bs, 0, 1, 9); // 0x6c (9 bits, confirmed in binary)
1488
+ result.damping = readRangedF32(bs, 0, 2, 10); // 0x70 (10 bits, confirmed in binary)
1489
+ result.environmentSize = readRangedF32(bs, 1, 100, 8); // 0x74 (8 bits, confirmed in binary)
1490
+ result.environmentDiffusion = readRangedF32(bs, 0, 1, 10); // 0x78 (10 bits, confirmed in binary)
1491
+ // NOTE: No airAbsorption field in binary (V12 has it, Tribes 2 binary does not)
1492
+ result.flags = bs.readInt(6); // 0x7c
1493
+ }
1494
+ // Trailing field: always present after both branches (binary line 23471, offset 0x68)
1495
+ result.effectVolumeHF = readRangedF32(bs, 0, 1, 8);
1496
+ return result;
1497
+ }
1498
+ // ============================================================
1499
+ // AudioSampleEnvironment (extends SimDataBlock — 0 parent bits)
1500
+ // ============================================================
1501
+ function audioSampleEnvironmentUnpack(bs) {
1502
+ const result = {};
1503
+ // Series of rangedS32 and rangedF32 values + readInt(3)
1504
+ result.direct = readRangedS32(bs, -10000, 1000);
1505
+ result.directHF = readRangedS32(bs, -10000, 0);
1506
+ result.room = readRangedS32(bs, -10000, 1000);
1507
+ result.roomHF = readRangedS32(bs, -10000, 0);
1508
+ result.obstruction = readRangedF32(bs, 0, 1, 9);
1509
+ result.obstructionLFRatio = readRangedF32(bs, 0, 1, 8);
1510
+ result.occlusion = readRangedF32(bs, 0, 1, 9);
1511
+ result.occlusionLFRatio = readRangedF32(bs, 0, 1, 8);
1512
+ result.occlusionRoomRatio = readRangedF32(bs, 0, 10, 9);
1513
+ result.roomRolloff = readRangedF32(bs, 0, 10, 9);
1514
+ result.airAbsorption = readRangedF32(bs, 0, 10, 9);
1515
+ result.outsideVolumeHF = readRangedS32(bs, -10000, 0);
1516
+ result.flags = bs.readInt(3);
1517
+ return result;
1518
+ }
1519
+ // ============================================================
1520
+ // DecalData (extends SimDataBlock — 0 parent bits)
1521
+ // ============================================================
1522
+ function decalDataUnpack(bs) {
1523
+ return {
1524
+ sizeX: bs.readF32(),
1525
+ sizeY: bs.readF32(),
1526
+ textureName: bs.readString(),
1527
+ };
1528
+ }
1529
+ // ============================================================
1530
+ // CameraData (extends ShapeBaseData) — no additional fields
1531
+ // ============================================================
1532
+ function cameraDataUnpack(bs) {
1533
+ return shapeBaseDataUnpack(bs);
1534
+ }
1535
+ // ============================================================
1536
+ // SensorData — empty body
1537
+ // ============================================================
1538
+ function sensorDataUnpack(_bs) {
1539
+ return {};
1540
+ }
1541
+ // ============================================================
1542
+ // TriggerData — S32(tickPeriodMS)
1543
+ // ============================================================
1544
+ function triggerDataUnpack(bs) {
1545
+ return { tickPeriodMS: bs.readS32() };
1546
+ }
1547
+ // ============================================================
1548
+ // ForceFieldBareData
1549
+ // ============================================================
1550
+ function forceFieldBareDataUnpack(bs) {
1551
+ // Verified against decompiled binary FUN_00675580
1552
+ const result = {};
1553
+ // 3 S32/F32 (offsets 0x44, 0x48, 0x4c)
1554
+ result.fadeMS = bs.readS32();
1555
+ result.baseTranslucency = bs.readF32();
1556
+ result.powerOffTranslucency = bs.readF32();
1557
+ // 2 readFlag (offsets 0x8c, 0x8d — 1-bit flags, inline bit extraction)
1558
+ result.fadeInOnly = bs.readFlag();
1559
+ result.triggerEnable = bs.readFlag();
1560
+ // 2 ColorF via FUN_0043f040 (packed 4×U8 = 32 bits each, offsets 0x90, 0xa0)
1561
+ result.color1 = readColorF(bs);
1562
+ result.color2 = readColorF(bs);
1563
+ // Offsets 0x84, 0x88, 0x80, 0x78, 0x7c — named to match TorqueScript fields.
1564
+ // framesPerSec and numFrames are S32 (integers); the rest are F32.
1565
+ result.framesPerSec = bs.readS32(); // 0x84
1566
+ result.numFrames = bs.readS32(); // 0x88
1567
+ result.scrollSpeed = bs.readF32(); // 0x80
1568
+ result.umapping = bs.readF32(); // 0x78
1569
+ result.vmapping = bs.readF32(); // 0x7c
1570
+ // 5 readString (loop at 0x50+i*4)
1571
+ result.texture0 = bs.readString();
1572
+ result.texture1 = bs.readString();
1573
+ result.texture2 = bs.readString();
1574
+ result.texture3 = bs.readString();
1575
+ result.texture4 = bs.readString();
1576
+ return result;
1577
+ }
1578
+ // ============================================================
1579
+ // ParticleEmissionDummyData — F32(timeMultiple)
1580
+ // ============================================================
1581
+ function particleEmissionDummyDataUnpack(bs) {
1582
+ return { timeMultiple: bs.readF32() };
1583
+ }
1584
+ // ============================================================
1585
+ // CommanderIconData — 5 readString (NumImages=5)
1586
+ // ============================================================
1587
+ function commanderIconDataUnpack(bs) {
1588
+ return {
1589
+ baseImage: bs.readString(),
1590
+ activeImage: bs.readString(),
1591
+ inactiveImage: bs.readString(),
1592
+ selectImage: bs.readString(),
1593
+ hilightImage: bs.readString(),
1594
+ };
1595
+ }
1596
+ // ============================================================
1597
+ // PrecipitationData
1598
+ // ============================================================
1599
+ function precipitationDataUnpack(bs) {
1600
+ // Verified against decompiled binary FUN_006805d0
1601
+ const result = {};
1602
+ // DataBlockRef (soundProfile at offset 0x48)
1603
+ result.soundProfile = readDataBlockRef(bs);
1604
+ // S32 (offset 0x4c)
1605
+ result.numDrops = bs.readS32();
1606
+ // F32 (maxSize at offset 0x50 — before string)
1607
+ result.maxSize = bs.readF32();
1608
+ // readString (materialList at offset 0x5c)
1609
+ result.materialList = bs.readString();
1610
+ // 13×F32 (offsets 0x54, 0x58, 0x60, 0x64, 0x68, 0x6c, 0x70, 0x74, 0x78, 0x7c, 0x80, 0x84, 0x88)
1611
+ result.sizeX = bs.readF32();
1612
+ result.sizeY = bs.readF32();
1613
+ result.movingBoxPer = bs.readF32();
1614
+ result.divHeightVal = bs.readF32();
1615
+ result.sizeBigBox = bs.readF32();
1616
+ result.topBoxSpeed = bs.readF32();
1617
+ result.frontBoxSpeed = bs.readF32();
1618
+ result.topBoxDrawPer = bs.readF32();
1619
+ result.bottomDrawHeight = bs.readF32();
1620
+ result.skipIfPer = bs.readF32();
1621
+ result.bottomSpeedPer = bs.readF32();
1622
+ result.frontSpeedPer = bs.readF32();
1623
+ result.frontRadiusPer = bs.readF32();
1624
+ return result;
1625
+ }
1626
+ // ============================================================
1627
+ // FireballAtmosphereData
1628
+ // ============================================================
1629
+ function fireballAtmosphereDataUnpack(bs) {
1630
+ return { emitter: readDataBlockRef(bs) };
1631
+ }
1632
+ // ============================================================
1633
+ // LightningData
1634
+ // ============================================================
1635
+ function lightningDataUnpack(bs) {
1636
+ const result = {};
1637
+ // 8 readDataBlockRef (strikeSound profiles)
1638
+ const strikeSounds = [];
1639
+ for (let i = 0; i < 8; i++) {
1640
+ strikeSounds.push(readDataBlockRef(bs));
1641
+ }
1642
+ result.strikeSounds = strikeSounds;
1643
+ // 8 readString (strikeTextures)
1644
+ const strikeTextures = [];
1645
+ for (let i = 0; i < 8; i++) {
1646
+ strikeTextures.push(bs.readString());
1647
+ }
1648
+ result.strikeTextures = strikeTextures;
1649
+ // readDataBlockRef (thunder sound)
1650
+ result.thunderSound = readDataBlockRef(bs);
1651
+ return result;
1652
+ }
1653
+ // ============================================================
1654
+ // StationFXVehicleData
1655
+ // ============================================================
1656
+ function stationFXVehicleDataUnpack(bs) {
1657
+ // Field order confirmed from decompiled binary FUN_0069d570
1658
+ const result = {};
1659
+ // 11 F32 fields (offsets 0x44-0x6c)
1660
+ result.glowTopHeight = bs.readF32(); // 0x44
1661
+ result.glowBottomHeight = bs.readF32(); // 0x48
1662
+ result.glowTopRadius = bs.readF32(); // 0x4c
1663
+ result.glowBottomRadius = bs.readF32(); // 0x50
1664
+ result.numGlowSegments = bs.readF32(); // 0x54
1665
+ result.glowFadeTime = bs.readF32(); // 0x58
1666
+ result.armLightDelay = bs.readF32(); // 0x5c
1667
+ result.armLightLifetime = bs.readF32(); // 0x60
1668
+ result.armLightFadeTime = bs.readF32(); // 0x64
1669
+ result.lifetime = bs.readF32(); // 0x68
1670
+ result.numArcSegments = bs.readF32(); // 0x6c
1671
+ // ColorF via FUN_0043f040 (4×U8 = 32 bits at offset 0x70)
1672
+ result.sphereColor = readColorF(bs);
1673
+ // 6 F32 fields (offsets 0x80-0x94)
1674
+ result.spherePhiSegments = bs.readF32(); // 0x80
1675
+ result.sphereThetaSegments = bs.readF32(); // 0x84
1676
+ result.sphereRadius = bs.readF32(); // 0x88
1677
+ result.scale = {
1678
+ x: bs.readF32(), // 0x8c
1679
+ y: bs.readF32(), // 0x90
1680
+ z: bs.readF32(), // 0x94
1681
+ };
1682
+ // 11 strings
1683
+ result.glowTexture = bs.readString(); // 0x98
1684
+ // 4×(2 readString) — pad textures
1685
+ result.padTexture00 = bs.readString();
1686
+ result.padTexture01 = bs.readString();
1687
+ result.padTexture10 = bs.readString();
1688
+ result.padTexture11 = bs.readString();
1689
+ result.padTexture20 = bs.readString();
1690
+ result.padTexture21 = bs.readString();
1691
+ result.padTexture30 = bs.readString();
1692
+ result.padTexture31 = bs.readString();
1693
+ // 2 readString
1694
+ result.lightStartColor = bs.readString();
1695
+ result.lightEndColor = bs.readString();
1696
+ return result;
1697
+ }
1698
+ // ============================================================
1699
+ // StationFXPersonalData
1700
+ // ============================================================
1701
+ function stationFXPersonalDataUnpack(bs) {
1702
+ const result = {};
1703
+ // ~10 F32
1704
+ result.glowTopRadius = bs.readF32();
1705
+ result.glowBottomRadius = bs.readF32();
1706
+ result.glowTopHeight = bs.readF32();
1707
+ result.glowBottomHeight = bs.readF32();
1708
+ result.numGlowSegments = bs.readF32();
1709
+ result.numGlowPanels = bs.readF32();
1710
+ result.topAlpha = bs.readF32();
1711
+ result.bottomAlpha = bs.readF32();
1712
+ result.glowSpeed = bs.readF32();
1713
+ result.scrollSpeed = bs.readF32();
1714
+ // 2 readString
1715
+ result.glowTexture = bs.readString();
1716
+ result.padTexture = bs.readString();
1717
+ // 2 readString
1718
+ result.lightStartColor = bs.readString();
1719
+ result.lightEndColor = bs.readString();
1720
+ return result;
1721
+ }
1722
+ // ============================================================
1723
+ // CannedChatItem (game-specific, Tribes 2 only)
1724
+ // From Tribes 2 script reference: single readString (chatText)
1725
+ // ============================================================
1726
+ function cannedChatItemUnpack(bs) {
1727
+ return { chatText: bs.readString() };
1728
+ }
1729
+ // ============================================================
1730
+ // MissionMarkerData (extends ShapeBaseData — no additional fields)
1731
+ // ============================================================
1732
+ function missionMarkerDataUnpack(bs) {
1733
+ return shapeBaseDataUnpack(bs);
1734
+ }
1735
+ // ============================================================
1736
+ // GameBaseData (extends SimDataBlock — 0 bits on wire)
1737
+ // ============================================================
1738
+ function gameBaseDataUnpack(_bs) {
1739
+ return {};
1740
+ }
1741
+ // ============================================================
1742
+ // SimDataBlock — 0 bits on wire (base class)
1743
+ // ============================================================
1744
+ function simDataBlockUnpack(_bs) {
1745
+ return {};
1746
+ }
1747
+ // ============================================================
1748
+ // TSShapeConstructor (extends SimDataBlock)
1749
+ // readString(shape) + readInt(7) count + count × readString
1750
+ // NumSequenceBits = 7
1751
+ // ============================================================
1752
+ function tsShapeConstructorUnpack(bs) {
1753
+ const result = {};
1754
+ result.shape = bs.readString();
1755
+ const count = bs.readInt(7);
1756
+ const sequences = [];
1757
+ for (let i = 0; i < count && i < 128; i++) {
1758
+ sequences.push(bs.readString());
1759
+ }
1760
+ result.sequences = sequences;
1761
+ return result;
1762
+ }
1763
+ // ============================================================
1764
+ // EffectProfile (classId 139)
1765
+ // From decompiled binary FUN_0050dde0.
1766
+ // Parent: SimDataBlock (no-op)
1767
+ // ============================================================
1768
+ function effectProfileUnpack(bs) {
1769
+ const result = {};
1770
+ result.minDistance = bs.readF32(); // 0x40
1771
+ result.maxDistance = bs.readF32(); // 0x44
1772
+ result.audioScale = bs.readF32(); // 0x48
1773
+ result.directional = readBool(bs); // 0x4c, _read(1) = 8-bit bool
1774
+ result.effectName = bs.readString(); // 0x3c
1775
+ return result;
1776
+ }
1777
+ // ============================================================
1778
+ // JetEffectData (classId 150)
1779
+ // From decompiled binary FUN_0069fe60.
1780
+ // Parent: GameBaseData → SimDataBlock (no-op chain)
1781
+ // ============================================================
1782
+ function jetEffectDataUnpack(bs) {
1783
+ const result = {};
1784
+ result.coolColor = readColorF(bs); // 0x44, FUN_0043f040 = packed 4×U8
1785
+ result.hotColor = readColorF(bs); // 0x54, FUN_0043f040 = packed 4×U8
1786
+ result.activateTime = bs.readF32(); // 0x64
1787
+ result.deactivateTime = bs.readF32(); // 0x68
1788
+ result.length = bs.readF32(); // 0x6c
1789
+ result.width = bs.readF32(); // 0x70
1790
+ result.speed = bs.readF32(); // 0x74
1791
+ result.stretch = bs.readF32(); // 0x78
1792
+ result.yOffset = bs.readF32(); // 0x7c
1793
+ // texture[0]: loop of 1 iteration, flag + conditional string
1794
+ if (bs.readFlag()) {
1795
+ result.texture = bs.readString(); // 0x80
1796
+ }
1797
+ return result;
1798
+ }
1799
+ // ============================================================
1800
+ // RunningLightData (classId 162)
1801
+ // From decompiled binary FUN_006a0af0.
1802
+ // Parent: GameBaseData → SimDataBlock (no-op chain)
1803
+ // ============================================================
1804
+ function runningLightDataUnpack(bs) {
1805
+ const result = {};
1806
+ result.radius = bs.readF32(); // 0x48
1807
+ result.color = readColorF(bs); // 0x4c, FUN_0043f040 = packed 4×U8
1808
+ result.type = bs.readF32(); // 0x44, stored as F32 (bool/int on wire)
1809
+ result.length = bs.readF32(); // 0x78
1810
+ result.nodeName = bs.readString(); // 0x5c
1811
+ result.direction = {
1812
+ x: bs.readF32(),
1813
+ y: bs.readF32(),
1814
+ z: bs.readF32(),
1815
+ };
1816
+ result.offset = {
1817
+ x: bs.readF32(),
1818
+ y: bs.readF32(),
1819
+ z: bs.readF32(),
1820
+ };
1821
+ // texture[0]: loop of 1 iteration, flag + conditional string
1822
+ if (bs.readFlag()) {
1823
+ result.texture = bs.readString(); // 0x7c
1824
+ }
1825
+ return result;
1826
+ }
1827
+ // ============================================================
1828
+ // Registration
1829
+ // ============================================================
1830
+ export function registerDataBlockParsers(registry) {
1831
+ // Base types
1832
+ registry.catalogDataBlock({
1833
+ name: "ShapeBaseData",
1834
+ unpackData: shapeBaseDataUnpack,
1835
+ });
1836
+ registry.catalogDataBlock({
1837
+ name: "ShapeBaseImageData",
1838
+ unpackData: shapeBaseImageDataUnpack,
1839
+ });
1840
+ // Player/Vehicle types
1841
+ registry.catalogDataBlock({ name: "PlayerData", unpackData: playerDataUnpack });
1842
+ registry.catalogDataBlock({
1843
+ name: "VehicleData",
1844
+ unpackData: vehicleDataUnpack,
1845
+ });
1846
+ registry.catalogDataBlock({
1847
+ name: "FlyingVehicleData",
1848
+ unpackData: flyingVehicleDataUnpack,
1849
+ });
1850
+ registry.catalogDataBlock({
1851
+ name: "HoverVehicleData",
1852
+ unpackData: hoverVehicleDataUnpack,
1853
+ });
1854
+ registry.catalogDataBlock({
1855
+ name: "WheeledVehicleData",
1856
+ unpackData: wheeledVehicleDataUnpack,
1857
+ });
1858
+ // Static/Turret types
1859
+ registry.catalogDataBlock({
1860
+ name: "StaticShapeData",
1861
+ unpackData: staticShapeDataUnpack,
1862
+ });
1863
+ registry.catalogDataBlock({ name: "TurretData", unpackData: turretDataUnpack });
1864
+ registry.catalogDataBlock({
1865
+ name: "TurretImageData",
1866
+ unpackData: turretImageDataUnpack,
1867
+ });
1868
+ // Item
1869
+ registry.catalogDataBlock({ name: "ItemData", unpackData: itemDataUnpack });
1870
+ // Projectile types
1871
+ registry.catalogDataBlock({
1872
+ name: "ProjectileData",
1873
+ unpackData: projectileDataUnpack,
1874
+ });
1875
+ registry.catalogDataBlock({
1876
+ name: "LinearProjectileData",
1877
+ unpackData: linearProjectileDataUnpack,
1878
+ });
1879
+ registry.catalogDataBlock({
1880
+ name: "GrenadeProjectileData",
1881
+ unpackData: grenadeProjectileDataUnpack,
1882
+ });
1883
+ registry.catalogDataBlock({
1884
+ name: "SeekerProjectileData",
1885
+ unpackData: seekerProjectileDataUnpack,
1886
+ });
1887
+ registry.catalogDataBlock({
1888
+ name: "SniperProjectileData",
1889
+ unpackData: sniperProjectileDataUnpack,
1890
+ });
1891
+ registry.catalogDataBlock({
1892
+ name: "ShockLanceProjectileData",
1893
+ unpackData: shockLanceProjectileDataUnpack,
1894
+ });
1895
+ registry.catalogDataBlock({
1896
+ name: "ELFProjectileData",
1897
+ unpackData: elfProjectileDataUnpack,
1898
+ });
1899
+ registry.catalogDataBlock({
1900
+ name: "RepairProjectileData",
1901
+ unpackData: repairProjectileDataUnpack,
1902
+ });
1903
+ registry.catalogDataBlock({
1904
+ name: "TargetProjectileData",
1905
+ unpackData: targetProjectileDataUnpack,
1906
+ });
1907
+ registry.catalogDataBlock({
1908
+ name: "TracerProjectileData",
1909
+ unpackData: tracerProjectileDataUnpack,
1910
+ });
1911
+ registry.catalogDataBlock({
1912
+ name: "EnergyProjectileData",
1913
+ unpackData: energyProjectileDataUnpack,
1914
+ });
1915
+ registry.catalogDataBlock({
1916
+ name: "LinearFlareProjectileData",
1917
+ unpackData: linearFlareProjectileDataUnpack,
1918
+ });
1919
+ registry.catalogDataBlock({
1920
+ name: "BombProjectileData",
1921
+ unpackData: bombProjectileDataUnpack,
1922
+ });
1923
+ registry.catalogDataBlock({
1924
+ name: "FlareProjectileData",
1925
+ unpackData: flareProjectileDataUnpack,
1926
+ });
1927
+ // Effects
1928
+ registry.catalogDataBlock({
1929
+ name: "ExplosionData",
1930
+ unpackData: explosionDataUnpack,
1931
+ });
1932
+ registry.catalogDataBlock({ name: "DebrisData", unpackData: debrisDataUnpack });
1933
+ registry.catalogDataBlock({ name: "SplashData", unpackData: splashDataUnpack });
1934
+ registry.catalogDataBlock({
1935
+ name: "ShockwaveData",
1936
+ unpackData: shockwaveDataUnpack,
1937
+ });
1938
+ registry.catalogDataBlock({
1939
+ name: "ParticleEmitterData",
1940
+ unpackData: particleEmitterDataUnpack,
1941
+ });
1942
+ registry.catalogDataBlock({
1943
+ name: "ParticleData",
1944
+ unpackData: particleDataUnpack,
1945
+ });
1946
+ // Audio
1947
+ registry.catalogDataBlock({
1948
+ name: "AudioDescription",
1949
+ unpackData: audioDescriptionUnpack,
1950
+ });
1951
+ registry.catalogDataBlock({
1952
+ name: "AudioProfile",
1953
+ unpackData: audioProfileUnpack,
1954
+ });
1955
+ registry.catalogDataBlock({
1956
+ name: "AudioEnvironment",
1957
+ unpackData: audioEnvironmentUnpack,
1958
+ });
1959
+ registry.catalogDataBlock({
1960
+ name: "AudioSampleEnvironment",
1961
+ unpackData: audioSampleEnvironmentUnpack,
1962
+ });
1963
+ // Misc
1964
+ registry.catalogDataBlock({ name: "DecalData", unpackData: decalDataUnpack });
1965
+ registry.catalogDataBlock({
1966
+ name: "CameraData",
1967
+ unpackData: cameraDataUnpack,
1968
+ });
1969
+ registry.catalogDataBlock({ name: "SensorData", unpackData: sensorDataUnpack });
1970
+ registry.catalogDataBlock({
1971
+ name: "TriggerData",
1972
+ unpackData: triggerDataUnpack,
1973
+ });
1974
+ registry.catalogDataBlock({
1975
+ name: "ForceFieldBareData",
1976
+ unpackData: forceFieldBareDataUnpack,
1977
+ });
1978
+ registry.catalogDataBlock({
1979
+ name: "ParticleEmissionDummyData",
1980
+ unpackData: particleEmissionDummyDataUnpack,
1981
+ });
1982
+ registry.catalogDataBlock({
1983
+ name: "CommanderIconData",
1984
+ unpackData: commanderIconDataUnpack,
1985
+ });
1986
+ registry.catalogDataBlock({
1987
+ name: "PrecipitationData",
1988
+ unpackData: precipitationDataUnpack,
1989
+ });
1990
+ registry.catalogDataBlock({
1991
+ name: "FireballAtmosphereData",
1992
+ unpackData: fireballAtmosphereDataUnpack,
1993
+ });
1994
+ registry.catalogDataBlock({
1995
+ name: "LightningData",
1996
+ unpackData: lightningDataUnpack,
1997
+ });
1998
+ registry.catalogDataBlock({
1999
+ name: "StationFXVehicleData",
2000
+ unpackData: stationFXVehicleDataUnpack,
2001
+ });
2002
+ registry.catalogDataBlock({
2003
+ name: "StationFXPersonalData",
2004
+ unpackData: stationFXPersonalDataUnpack,
2005
+ });
2006
+ // Classes added from game-specific analysis
2007
+ registry.catalogDataBlock({
2008
+ name: "CannedChatItem",
2009
+ unpackData: cannedChatItemUnpack,
2010
+ });
2011
+ registry.catalogDataBlock({
2012
+ name: "MissionMarkerData",
2013
+ unpackData: missionMarkerDataUnpack,
2014
+ });
2015
+ registry.catalogDataBlock({
2016
+ name: "GameBaseData",
2017
+ unpackData: gameBaseDataUnpack,
2018
+ });
2019
+ registry.catalogDataBlock({
2020
+ name: "SimDataBlock",
2021
+ unpackData: simDataBlockUnpack,
2022
+ });
2023
+ registry.catalogDataBlock({
2024
+ name: "TSShapeConstructor",
2025
+ unpackData: tsShapeConstructorUnpack,
2026
+ });
2027
+ // Previously missing parsers (verified against decompiled binary)
2028
+ registry.catalogDataBlock({
2029
+ name: "EffectProfile",
2030
+ unpackData: effectProfileUnpack,
2031
+ });
2032
+ registry.catalogDataBlock({
2033
+ name: "JetEffectData",
2034
+ unpackData: jetEffectDataUnpack,
2035
+ });
2036
+ registry.catalogDataBlock({
2037
+ name: "RunningLightData",
2038
+ unpackData: runningLightDataUnpack,
2039
+ });
2040
+ }
2041
+ //# sourceMappingURL=DataBlockParsers.js.map