quake2ts 0.0.403 → 0.0.407

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 (42) hide show
  1. package/package.json +1 -1
  2. package/packages/client/dist/browser/index.global.js +16 -16
  3. package/packages/client/dist/browser/index.global.js.map +1 -1
  4. package/packages/client/dist/cjs/index.cjs +405 -13
  5. package/packages/client/dist/cjs/index.cjs.map +1 -1
  6. package/packages/client/dist/esm/index.js +404 -13
  7. package/packages/client/dist/esm/index.js.map +1 -1
  8. package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
  9. package/packages/client/dist/types/demo/camera.d.ts +16 -0
  10. package/packages/client/dist/types/demo/camera.d.ts.map +1 -0
  11. package/packages/client/dist/types/index.d.ts +7 -1
  12. package/packages/client/dist/types/index.d.ts.map +1 -1
  13. package/packages/client/dist/types/ui/demo-controls.d.ts +2 -1
  14. package/packages/client/dist/types/ui/demo-controls.d.ts.map +1 -1
  15. package/packages/engine/dist/browser/index.global.js +16 -16
  16. package/packages/engine/dist/browser/index.global.js.map +1 -1
  17. package/packages/engine/dist/cjs/index.cjs +362 -4
  18. package/packages/engine/dist/cjs/index.cjs.map +1 -1
  19. package/packages/engine/dist/esm/index.js +361 -4
  20. package/packages/engine/dist/esm/index.js.map +1 -1
  21. package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
  22. package/packages/engine/dist/types/assets/mapStatistics.d.ts +20 -0
  23. package/packages/engine/dist/types/assets/mapStatistics.d.ts.map +1 -0
  24. package/packages/engine/dist/types/audio/api.d.ts +1 -0
  25. package/packages/engine/dist/types/audio/api.d.ts.map +1 -1
  26. package/packages/engine/dist/types/audio/context.d.ts +1 -0
  27. package/packages/engine/dist/types/audio/context.d.ts.map +1 -1
  28. package/packages/engine/dist/types/audio/system.d.ts +3 -0
  29. package/packages/engine/dist/types/audio/system.d.ts.map +1 -1
  30. package/packages/engine/dist/types/commands.d.ts +3 -0
  31. package/packages/engine/dist/types/commands.d.ts.map +1 -1
  32. package/packages/engine/dist/types/cvars.d.ts +11 -0
  33. package/packages/engine/dist/types/cvars.d.ts.map +1 -1
  34. package/packages/engine/dist/types/demo/analysis.d.ts +33 -0
  35. package/packages/engine/dist/types/demo/analysis.d.ts.map +1 -1
  36. package/packages/engine/dist/types/demo/analyzer.d.ts +28 -0
  37. package/packages/engine/dist/types/demo/analyzer.d.ts.map +1 -0
  38. package/packages/engine/dist/types/demo/playback.d.ts +16 -1
  39. package/packages/engine/dist/types/demo/playback.d.ts.map +1 -1
  40. package/packages/engine/dist/types/index.d.ts +1 -0
  41. package/packages/engine/dist/types/index.d.ts.map +1 -1
  42. package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
@@ -94,6 +94,9 @@ var CommandRegistry = class {
94
94
  this.commands.set(name, command);
95
95
  return command;
96
96
  }
97
+ registerCommand(name, callback) {
98
+ this.register(name, callback);
99
+ }
97
100
  get(name) {
98
101
  return this.commands.get(name);
99
102
  }
@@ -109,10 +112,33 @@ var CommandRegistry = class {
109
112
  command.execute(args);
110
113
  return true;
111
114
  }
115
+ this.onConsoleOutput?.(`Unknown command "${name}"`);
112
116
  return false;
113
117
  }
118
+ executeCommand(cmd) {
119
+ this.execute(cmd);
120
+ }
114
121
  tokenize(text) {
115
- return text.trim().split(/\s+/);
122
+ const args = [];
123
+ let currentArg = "";
124
+ let inQuote = false;
125
+ for (let i = 0; i < text.length; i++) {
126
+ const char = text[i];
127
+ if (char === '"') {
128
+ inQuote = !inQuote;
129
+ } else if (char === " " && !inQuote) {
130
+ if (currentArg.length > 0) {
131
+ args.push(currentArg);
132
+ currentArg = "";
133
+ }
134
+ } else {
135
+ currentArg += char;
136
+ }
137
+ }
138
+ if (currentArg.length > 0) {
139
+ args.push(currentArg);
140
+ }
141
+ return args;
116
142
  }
117
143
  list() {
118
144
  return [...this.commands.values()].sort((a, b) => a.name.localeCompare(b.name));
@@ -1307,13 +1333,21 @@ var CvarRegistry = class {
1307
1333
  if (existing) {
1308
1334
  return existing;
1309
1335
  }
1310
- const cvar = new Cvar(def);
1336
+ const originalOnChange = def.onChange;
1337
+ const wrappedOnChange = (cvar2, prev) => {
1338
+ originalOnChange?.(cvar2, prev);
1339
+ this.onCvarChange?.(cvar2.name, cvar2.string);
1340
+ };
1341
+ const cvar = new Cvar({ ...def, onChange: wrappedOnChange });
1311
1342
  this.cvars.set(def.name, cvar);
1312
1343
  return cvar;
1313
1344
  }
1314
1345
  get(name) {
1315
1346
  return this.cvars.get(name);
1316
1347
  }
1348
+ getCvar(name) {
1349
+ return this.get(name);
1350
+ }
1317
1351
  setValue(name, value) {
1318
1352
  const cvar = this.get(name);
1319
1353
  if (!cvar) {
@@ -1322,6 +1356,9 @@ var CvarRegistry = class {
1322
1356
  cvar.set(value);
1323
1357
  return cvar;
1324
1358
  }
1359
+ setCvar(name, value) {
1360
+ this.setValue(name, value);
1361
+ }
1325
1362
  resetAll() {
1326
1363
  for (const cvar of this.cvars.values()) {
1327
1364
  cvar.reset();
@@ -1337,6 +1374,15 @@ var CvarRegistry = class {
1337
1374
  list() {
1338
1375
  return [...this.cvars.values()].sort((a, b) => a.name.localeCompare(b.name));
1339
1376
  }
1377
+ listCvars() {
1378
+ return this.list().map((cvar) => ({
1379
+ name: cvar.name,
1380
+ value: cvar.string,
1381
+ defaultValue: cvar.defaultValue,
1382
+ flags: cvar.flags,
1383
+ description: cvar.description
1384
+ }));
1385
+ }
1340
1386
  };
1341
1387
 
1342
1388
  // src/host.ts
@@ -1537,6 +1583,9 @@ var AudioApi = class {
1537
1583
  stop_entity_sounds(entnum) {
1538
1584
  this.system.stopEntitySounds(entnum);
1539
1585
  }
1586
+ setPlaybackRate(rate) {
1587
+ this.system.setPlaybackRate(rate);
1588
+ }
1540
1589
  set_listener(listener) {
1541
1590
  this.system.setListener(listener);
1542
1591
  }
@@ -4359,6 +4408,7 @@ function spatializeOrigin(origin, listener, masterVolume, attenuation, isListene
4359
4408
  var AudioSystem = class {
4360
4409
  constructor(options) {
4361
4410
  this.activeSources = /* @__PURE__ */ new Map();
4411
+ this.playbackRate = 1;
4362
4412
  this.contextController = options.context;
4363
4413
  this.registry = options.registry;
4364
4414
  this.playerEntity = options.playerEntity;
@@ -4380,6 +4430,15 @@ var AudioSystem = class {
4380
4430
  setSfxVolume(volume) {
4381
4431
  this.sfxVolume = volume;
4382
4432
  }
4433
+ setPlaybackRate(rate) {
4434
+ this.playbackRate = rate;
4435
+ for (const active of this.activeSources.values()) {
4436
+ if (active.source.playbackRate) {
4437
+ active.source.playbackRate.value = rate;
4438
+ }
4439
+ this.updateSourceGain(active);
4440
+ }
4441
+ }
4383
4442
  async ensureRunning() {
4384
4443
  await this.contextController.resume();
4385
4444
  }
@@ -4402,6 +4461,9 @@ var AudioSystem = class {
4402
4461
  const source = ctx.createBufferSource();
4403
4462
  source.buffer = buffer;
4404
4463
  source.loop = request.looping ?? false;
4464
+ if (source.playbackRate) {
4465
+ source.playbackRate.value = this.playbackRate;
4466
+ }
4405
4467
  const origin = request.origin ?? this.listener.origin;
4406
4468
  const gain = ctx.createGain();
4407
4469
  const panner = this.createPanner(ctx, request.attenuation);
@@ -4413,7 +4475,8 @@ var AudioSystem = class {
4413
4475
  const spatial = spatializeOrigin(origin, this.listener, request.volume, request.attenuation, isListenerSound);
4414
4476
  const attenuationScale = request.volume === 0 ? 0 : Math.max(spatial.left, spatial.right) / Math.max(1, request.volume);
4415
4477
  const gainValue = attenuationScale * (request.volume / 255) * this.masterVolume * this.sfxVolume;
4416
- gain.gain.value = gainValue * occlusionScale;
4478
+ const playbackRateMute = Math.abs(this.playbackRate - 1) < 1e-3 ? 1 : 0;
4479
+ gain.gain.value = gainValue * occlusionScale * playbackRateMute;
4417
4480
  const startTimeSec = ctx.currentTime + (request.timeOffsetMs ?? 0) / 1e3;
4418
4481
  const endTimeMs = (request.looping ? Number.POSITIVE_INFINITY : buffer.duration * 1e3) + startTimeSec * 1e3;
4419
4482
  source.connect(panner);
@@ -4561,9 +4624,15 @@ var AudioSystem = class {
4561
4624
  filter.frequency.value = clamp(cutoffHz, 10, 2e4);
4562
4625
  return filter;
4563
4626
  }
4627
+ updateSourceGain(active) {
4628
+ const occlusionScale = active.occlusion?.scale ?? 1;
4629
+ const playbackRateMute = Math.abs(this.playbackRate - 1) < 1e-3 ? 1 : 0;
4630
+ active.gain.gain.value = active.baseGain * occlusionScale * playbackRateMute;
4631
+ }
4564
4632
  applyOcclusion(active, occlusion) {
4565
4633
  const scale = clamp01(occlusion?.gainScale ?? 1);
4566
- active.gain.gain.value = active.baseGain * scale;
4634
+ const playbackRateMute = Math.abs(this.playbackRate - 1) < 1e-3 ? 1 : 0;
4635
+ active.gain.gain.value = active.baseGain * scale * playbackRateMute;
4567
4636
  if (active.occlusion?.filter) {
4568
4637
  const cutoff = occlusion?.lowpassHz ?? 2e4;
4569
4638
  active.occlusion.filter.frequency.value = clamp(cutoff, 10, 2e4);
@@ -12426,6 +12495,165 @@ var NetworkMessageParser = class _NetworkMessageParser {
12426
12495
  }
12427
12496
  };
12428
12497
 
12498
+ // src/demo/analyzer.ts
12499
+ var DemoAnalyzer = class {
12500
+ constructor(buffer) {
12501
+ this.events = [];
12502
+ this.summary = {
12503
+ totalKills: 0,
12504
+ totalDeaths: 0,
12505
+ damageDealt: 0,
12506
+ damageReceived: 0,
12507
+ weaponUsage: /* @__PURE__ */ new Map()
12508
+ };
12509
+ this.header = null;
12510
+ this.configStrings = /* @__PURE__ */ new Map();
12511
+ this.serverInfo = {};
12512
+ this.statistics = null;
12513
+ this.playerStats = /* @__PURE__ */ new Map();
12514
+ // By playerNum
12515
+ this.weaponStats = /* @__PURE__ */ new Map();
12516
+ this.buffer = buffer;
12517
+ }
12518
+ analyze() {
12519
+ const reader = new DemoReader(this.buffer);
12520
+ let currentFrameIndex = -1;
12521
+ let currentTime = 0;
12522
+ let frameDuration = 0.1;
12523
+ let protocolVersion = 0;
12524
+ const handler = {
12525
+ onServerData: (protocol, serverCount, attractLoop, gameDir, playerNum, levelName, tickRate, demoType) => {
12526
+ protocolVersion = protocol;
12527
+ this.header = {
12528
+ protocolVersion: protocol,
12529
+ gameDir,
12530
+ levelName,
12531
+ playerNum,
12532
+ serverCount,
12533
+ spawnCount: serverCount,
12534
+ // Mapping generic arg
12535
+ tickRate,
12536
+ demoType
12537
+ };
12538
+ if (tickRate && tickRate > 0) {
12539
+ frameDuration = 1 / tickRate;
12540
+ }
12541
+ },
12542
+ onConfigString: (index, str) => {
12543
+ this.configStrings.set(index, str);
12544
+ if (index === 0) {
12545
+ this.parseServerInfo(str);
12546
+ }
12547
+ },
12548
+ onSpawnBaseline: (entity) => {
12549
+ },
12550
+ onFrame: (frame) => {
12551
+ },
12552
+ onPrint: (level, msg) => {
12553
+ if (msg.includes("died") || msg.includes("killed")) {
12554
+ this.summary.totalDeaths++;
12555
+ this.recordEvent({
12556
+ type: 4 /* Death */,
12557
+ frame: currentFrameIndex,
12558
+ time: currentTime,
12559
+ description: msg.trim()
12560
+ });
12561
+ }
12562
+ },
12563
+ onCenterPrint: () => {
12564
+ },
12565
+ onStuffText: () => {
12566
+ },
12567
+ onSound: () => {
12568
+ },
12569
+ onTempEntity: () => {
12570
+ },
12571
+ onLayout: () => {
12572
+ },
12573
+ onInventory: () => {
12574
+ },
12575
+ onMuzzleFlash: (ent, weapon) => {
12576
+ this.handleWeaponFire(ent, weapon, currentFrameIndex, currentTime);
12577
+ },
12578
+ onMuzzleFlash2: (ent, weapon) => {
12579
+ this.handleWeaponFire(ent, weapon, currentFrameIndex, currentTime);
12580
+ },
12581
+ onMuzzleFlash3: (ent, weapon) => {
12582
+ this.handleWeaponFire(ent, weapon, currentFrameIndex, currentTime);
12583
+ },
12584
+ onDisconnect: () => {
12585
+ },
12586
+ onReconnect: () => {
12587
+ },
12588
+ onDownload: () => {
12589
+ },
12590
+ // Rerelease specific
12591
+ onDamage: (indicators) => {
12592
+ for (const ind of indicators) {
12593
+ this.recordEvent({
12594
+ type: 2 /* DamageReceived */,
12595
+ frame: currentFrameIndex,
12596
+ time: currentTime,
12597
+ value: ind.damage,
12598
+ position: ind.dir,
12599
+ description: `Took ${ind.damage} damage`
12600
+ });
12601
+ this.summary.damageReceived += ind.damage;
12602
+ }
12603
+ }
12604
+ };
12605
+ while (reader.hasMore()) {
12606
+ const block = reader.readNextBlock();
12607
+ if (!block) break;
12608
+ currentFrameIndex++;
12609
+ currentTime = currentFrameIndex * frameDuration;
12610
+ const parser = new NetworkMessageParser(block.data, handler);
12611
+ parser.setProtocolVersion(protocolVersion);
12612
+ parser.parseMessage();
12613
+ protocolVersion = parser.getProtocolVersion();
12614
+ }
12615
+ this.statistics = {
12616
+ duration: currentTime,
12617
+ frameCount: currentFrameIndex + 1,
12618
+ averageFps: (currentFrameIndex + 1) / (currentTime || 1),
12619
+ mapName: this.header?.levelName || "unknown",
12620
+ playerCount: 1
12621
+ // Default to 1 for SP/client demo
12622
+ };
12623
+ return {
12624
+ events: this.events,
12625
+ summary: this.summary,
12626
+ header: this.header,
12627
+ configStrings: this.configStrings,
12628
+ serverInfo: this.serverInfo,
12629
+ statistics: this.statistics
12630
+ };
12631
+ }
12632
+ handleWeaponFire(ent, weapon, frame, time) {
12633
+ this.recordEvent({
12634
+ type: 0 /* WeaponFire */,
12635
+ frame,
12636
+ time,
12637
+ entityId: ent,
12638
+ value: weapon,
12639
+ description: `Weapon ${weapon} fired by ${ent}`
12640
+ });
12641
+ const count = this.summary.weaponUsage.get(weapon) || 0;
12642
+ this.summary.weaponUsage.set(weapon, count + 1);
12643
+ }
12644
+ recordEvent(event) {
12645
+ this.events.push(event);
12646
+ }
12647
+ parseServerInfo(str) {
12648
+ const parts = str.split("\\");
12649
+ for (let i = 1; i < parts.length; i += 2) {
12650
+ if (i + 1 < parts.length) {
12651
+ this.serverInfo[parts[i]] = parts[i + 1];
12652
+ }
12653
+ }
12654
+ }
12655
+ };
12656
+
12429
12657
  // src/demo/playback.ts
12430
12658
  var PlaybackState = /* @__PURE__ */ ((PlaybackState2) => {
12431
12659
  PlaybackState2[PlaybackState2["Stopped"] = 0] = "Stopped";
@@ -12437,6 +12665,8 @@ var PlaybackState = /* @__PURE__ */ ((PlaybackState2) => {
12437
12665
  var DemoPlaybackController = class {
12438
12666
  constructor() {
12439
12667
  this.reader = null;
12668
+ this.buffer = null;
12669
+ // Keep reference for analysis
12440
12670
  this.state = 0 /* Stopped */;
12441
12671
  this.playbackSpeed = 1;
12442
12672
  this.currentProtocolVersion = 0;
@@ -12452,6 +12682,13 @@ var DemoPlaybackController = class {
12452
12682
  this.snapshotInterval = 100;
12453
12683
  // frames
12454
12684
  this.snapshots = /* @__PURE__ */ new Map();
12685
+ // Analysis Cache
12686
+ this.cachedEvents = null;
12687
+ this.cachedSummary = null;
12688
+ this.cachedHeader = null;
12689
+ this.cachedConfigStrings = null;
12690
+ this.cachedServerInfo = null;
12691
+ this.cachedStatistics = null;
12455
12692
  }
12456
12693
  setHandler(handler) {
12457
12694
  this.handler = handler;
@@ -12460,6 +12697,7 @@ var DemoPlaybackController = class {
12460
12697
  this.callbacks = callbacks;
12461
12698
  }
12462
12699
  loadDemo(buffer) {
12700
+ this.buffer = buffer;
12463
12701
  this.reader = new DemoReader(buffer);
12464
12702
  this.transitionState(0 /* Stopped */);
12465
12703
  this.accumulatedTime = 0;
@@ -12467,6 +12705,12 @@ var DemoPlaybackController = class {
12467
12705
  this.currentFrameIndex = -1;
12468
12706
  this.snapshots.clear();
12469
12707
  this.lastFrameData = null;
12708
+ this.cachedEvents = null;
12709
+ this.cachedSummary = null;
12710
+ this.cachedHeader = null;
12711
+ this.cachedConfigStrings = null;
12712
+ this.cachedServerInfo = null;
12713
+ this.cachedStatistics = null;
12470
12714
  }
12471
12715
  play() {
12472
12716
  if (this.reader && this.state !== 1 /* Playing */) {
@@ -12779,6 +13023,57 @@ var DemoPlaybackController = class {
12779
13023
  this.seek(originalFrame);
12780
13024
  return trajectory;
12781
13025
  }
13026
+ // 3.2.3 Event Log Extraction & 3.3 Metadata
13027
+ getDemoEvents() {
13028
+ this.ensureAnalysis();
13029
+ return this.cachedEvents || [];
13030
+ }
13031
+ filterEvents(type, entityId) {
13032
+ const events = this.getDemoEvents();
13033
+ return events.filter((e) => {
13034
+ if (e.type !== type) return false;
13035
+ if (entityId !== void 0 && e.entityId !== entityId) return false;
13036
+ return true;
13037
+ });
13038
+ }
13039
+ getEventSummary() {
13040
+ this.ensureAnalysis();
13041
+ return this.cachedSummary || {
13042
+ totalKills: 0,
13043
+ totalDeaths: 0,
13044
+ damageDealt: 0,
13045
+ damageReceived: 0,
13046
+ weaponUsage: /* @__PURE__ */ new Map()
13047
+ };
13048
+ }
13049
+ getDemoHeader() {
13050
+ this.ensureAnalysis();
13051
+ return this.cachedHeader;
13052
+ }
13053
+ getDemoConfigStrings() {
13054
+ this.ensureAnalysis();
13055
+ return this.cachedConfigStrings || /* @__PURE__ */ new Map();
13056
+ }
13057
+ getDemoServerInfo() {
13058
+ this.ensureAnalysis();
13059
+ return this.cachedServerInfo || {};
13060
+ }
13061
+ getDemoStatistics() {
13062
+ this.ensureAnalysis();
13063
+ return this.cachedStatistics;
13064
+ }
13065
+ ensureAnalysis() {
13066
+ if (!this.cachedEvents && this.buffer) {
13067
+ const analyzer = new DemoAnalyzer(this.buffer);
13068
+ const result = analyzer.analyze();
13069
+ this.cachedEvents = result.events;
13070
+ this.cachedSummary = result.summary;
13071
+ this.cachedHeader = result.header;
13072
+ this.cachedConfigStrings = result.configStrings;
13073
+ this.cachedServerInfo = result.serverInfo;
13074
+ this.cachedStatistics = result.statistics;
13075
+ }
13076
+ }
12782
13077
  };
12783
13078
 
12784
13079
  // src/demo/recorder.ts
@@ -13127,6 +13422,67 @@ var AssetPreviewGenerator = class {
13127
13422
  }
13128
13423
  };
13129
13424
 
13425
+ // src/assets/mapStatistics.ts
13426
+ var MapAnalyzer = class {
13427
+ constructor(loader) {
13428
+ this.loader = loader;
13429
+ }
13430
+ async getMapStatistics(mapName) {
13431
+ const map = await this.loader.load(mapName);
13432
+ const lightmapCount = map.faces.filter((f) => f.lightOffset !== -1).length;
13433
+ const worldModel = map.models[0];
13434
+ const bounds = worldModel ? {
13435
+ mins: worldModel.mins,
13436
+ maxs: worldModel.maxs
13437
+ } : {
13438
+ mins: [0, 0, 0],
13439
+ maxs: [0, 0, 0]
13440
+ };
13441
+ return {
13442
+ entityCount: map.entities.entities.length,
13443
+ surfaceCount: map.faces.length,
13444
+ lightmapCount,
13445
+ vertexCount: map.vertices.length,
13446
+ bounds
13447
+ };
13448
+ }
13449
+ async getUsedTextures(mapName) {
13450
+ const map = await this.loader.load(mapName);
13451
+ const textures = /* @__PURE__ */ new Set();
13452
+ for (const info of map.texInfo) {
13453
+ if (info.texture) {
13454
+ textures.add(info.texture);
13455
+ }
13456
+ }
13457
+ return Array.from(textures).sort();
13458
+ }
13459
+ async getUsedModels(mapName) {
13460
+ const map = await this.loader.load(mapName);
13461
+ const models = /* @__PURE__ */ new Set();
13462
+ for (const ent of map.entities.entities) {
13463
+ if (ent.properties["model"] && !ent.properties["model"].startsWith("*")) {
13464
+ models.add(ent.properties["model"]);
13465
+ }
13466
+ }
13467
+ return Array.from(models).sort();
13468
+ }
13469
+ async getUsedSounds(mapName) {
13470
+ const map = await this.loader.load(mapName);
13471
+ const sounds = /* @__PURE__ */ new Set();
13472
+ for (const ent of map.entities.entities) {
13473
+ if (ent.properties["noise"]) {
13474
+ sounds.add(ent.properties["noise"]);
13475
+ }
13476
+ for (const [key, value] of Object.entries(ent.properties)) {
13477
+ if ((key === "noise" || key.endsWith("_sound") || key === "sound") && typeof value === "string") {
13478
+ sounds.add(value);
13479
+ }
13480
+ }
13481
+ }
13482
+ return Array.from(sounds).sort();
13483
+ }
13484
+ };
13485
+
13130
13486
  // src/index.ts
13131
13487
  function createEngine(imports) {
13132
13488
  return {
@@ -13183,6 +13539,7 @@ export {
13183
13539
  MD2_VERTEX_SHADER,
13184
13540
  MD3_FRAGMENT_SHADER,
13185
13541
  MD3_VERTEX_SHADER,
13542
+ MapAnalyzer,
13186
13543
  Md2Loader,
13187
13544
  Md2MeshBuffers,
13188
13545
  Md2ParseError,