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
@@ -62,6 +62,7 @@ __export(index_exports, {
62
62
  MD2_VERTEX_SHADER: () => MD2_VERTEX_SHADER,
63
63
  MD3_FRAGMENT_SHADER: () => MD3_FRAGMENT_SHADER,
64
64
  MD3_VERTEX_SHADER: () => MD3_VERTEX_SHADER,
65
+ MapAnalyzer: () => MapAnalyzer,
65
66
  Md2Loader: () => Md2Loader,
66
67
  Md2MeshBuffers: () => Md2MeshBuffers,
67
68
  Md2ParseError: () => Md2ParseError,
@@ -295,6 +296,9 @@ var CommandRegistry = class {
295
296
  this.commands.set(name, command);
296
297
  return command;
297
298
  }
299
+ registerCommand(name, callback) {
300
+ this.register(name, callback);
301
+ }
298
302
  get(name) {
299
303
  return this.commands.get(name);
300
304
  }
@@ -310,10 +314,33 @@ var CommandRegistry = class {
310
314
  command.execute(args);
311
315
  return true;
312
316
  }
317
+ this.onConsoleOutput?.(`Unknown command "${name}"`);
313
318
  return false;
314
319
  }
320
+ executeCommand(cmd) {
321
+ this.execute(cmd);
322
+ }
315
323
  tokenize(text) {
316
- return text.trim().split(/\s+/);
324
+ const args = [];
325
+ let currentArg = "";
326
+ let inQuote = false;
327
+ for (let i = 0; i < text.length; i++) {
328
+ const char = text[i];
329
+ if (char === '"') {
330
+ inQuote = !inQuote;
331
+ } else if (char === " " && !inQuote) {
332
+ if (currentArg.length > 0) {
333
+ args.push(currentArg);
334
+ currentArg = "";
335
+ }
336
+ } else {
337
+ currentArg += char;
338
+ }
339
+ }
340
+ if (currentArg.length > 0) {
341
+ args.push(currentArg);
342
+ }
343
+ return args;
317
344
  }
318
345
  list() {
319
346
  return [...this.commands.values()].sort((a, b) => a.name.localeCompare(b.name));
@@ -1508,13 +1535,21 @@ var CvarRegistry = class {
1508
1535
  if (existing) {
1509
1536
  return existing;
1510
1537
  }
1511
- const cvar = new Cvar(def);
1538
+ const originalOnChange = def.onChange;
1539
+ const wrappedOnChange = (cvar2, prev) => {
1540
+ originalOnChange?.(cvar2, prev);
1541
+ this.onCvarChange?.(cvar2.name, cvar2.string);
1542
+ };
1543
+ const cvar = new Cvar({ ...def, onChange: wrappedOnChange });
1512
1544
  this.cvars.set(def.name, cvar);
1513
1545
  return cvar;
1514
1546
  }
1515
1547
  get(name) {
1516
1548
  return this.cvars.get(name);
1517
1549
  }
1550
+ getCvar(name) {
1551
+ return this.get(name);
1552
+ }
1518
1553
  setValue(name, value) {
1519
1554
  const cvar = this.get(name);
1520
1555
  if (!cvar) {
@@ -1523,6 +1558,9 @@ var CvarRegistry = class {
1523
1558
  cvar.set(value);
1524
1559
  return cvar;
1525
1560
  }
1561
+ setCvar(name, value) {
1562
+ this.setValue(name, value);
1563
+ }
1526
1564
  resetAll() {
1527
1565
  for (const cvar of this.cvars.values()) {
1528
1566
  cvar.reset();
@@ -1538,6 +1576,15 @@ var CvarRegistry = class {
1538
1576
  list() {
1539
1577
  return [...this.cvars.values()].sort((a, b) => a.name.localeCompare(b.name));
1540
1578
  }
1579
+ listCvars() {
1580
+ return this.list().map((cvar) => ({
1581
+ name: cvar.name,
1582
+ value: cvar.string,
1583
+ defaultValue: cvar.defaultValue,
1584
+ flags: cvar.flags,
1585
+ description: cvar.description
1586
+ }));
1587
+ }
1541
1588
  };
1542
1589
 
1543
1590
  // src/host.ts
@@ -1738,6 +1785,9 @@ var AudioApi = class {
1738
1785
  stop_entity_sounds(entnum) {
1739
1786
  this.system.stopEntitySounds(entnum);
1740
1787
  }
1788
+ setPlaybackRate(rate) {
1789
+ this.system.setPlaybackRate(rate);
1790
+ }
1741
1791
  set_listener(listener) {
1742
1792
  this.system.setListener(listener);
1743
1793
  }
@@ -4560,6 +4610,7 @@ function spatializeOrigin(origin, listener, masterVolume, attenuation, isListene
4560
4610
  var AudioSystem = class {
4561
4611
  constructor(options) {
4562
4612
  this.activeSources = /* @__PURE__ */ new Map();
4613
+ this.playbackRate = 1;
4563
4614
  this.contextController = options.context;
4564
4615
  this.registry = options.registry;
4565
4616
  this.playerEntity = options.playerEntity;
@@ -4581,6 +4632,15 @@ var AudioSystem = class {
4581
4632
  setSfxVolume(volume) {
4582
4633
  this.sfxVolume = volume;
4583
4634
  }
4635
+ setPlaybackRate(rate) {
4636
+ this.playbackRate = rate;
4637
+ for (const active of this.activeSources.values()) {
4638
+ if (active.source.playbackRate) {
4639
+ active.source.playbackRate.value = rate;
4640
+ }
4641
+ this.updateSourceGain(active);
4642
+ }
4643
+ }
4584
4644
  async ensureRunning() {
4585
4645
  await this.contextController.resume();
4586
4646
  }
@@ -4603,6 +4663,9 @@ var AudioSystem = class {
4603
4663
  const source = ctx.createBufferSource();
4604
4664
  source.buffer = buffer;
4605
4665
  source.loop = request.looping ?? false;
4666
+ if (source.playbackRate) {
4667
+ source.playbackRate.value = this.playbackRate;
4668
+ }
4606
4669
  const origin = request.origin ?? this.listener.origin;
4607
4670
  const gain = ctx.createGain();
4608
4671
  const panner = this.createPanner(ctx, request.attenuation);
@@ -4614,7 +4677,8 @@ var AudioSystem = class {
4614
4677
  const spatial = spatializeOrigin(origin, this.listener, request.volume, request.attenuation, isListenerSound);
4615
4678
  const attenuationScale = request.volume === 0 ? 0 : Math.max(spatial.left, spatial.right) / Math.max(1, request.volume);
4616
4679
  const gainValue = attenuationScale * (request.volume / 255) * this.masterVolume * this.sfxVolume;
4617
- gain.gain.value = gainValue * occlusionScale;
4680
+ const playbackRateMute = Math.abs(this.playbackRate - 1) < 1e-3 ? 1 : 0;
4681
+ gain.gain.value = gainValue * occlusionScale * playbackRateMute;
4618
4682
  const startTimeSec = ctx.currentTime + (request.timeOffsetMs ?? 0) / 1e3;
4619
4683
  const endTimeMs = (request.looping ? Number.POSITIVE_INFINITY : buffer.duration * 1e3) + startTimeSec * 1e3;
4620
4684
  source.connect(panner);
@@ -4762,9 +4826,15 @@ var AudioSystem = class {
4762
4826
  filter.frequency.value = clamp(cutoffHz, 10, 2e4);
4763
4827
  return filter;
4764
4828
  }
4829
+ updateSourceGain(active) {
4830
+ const occlusionScale = active.occlusion?.scale ?? 1;
4831
+ const playbackRateMute = Math.abs(this.playbackRate - 1) < 1e-3 ? 1 : 0;
4832
+ active.gain.gain.value = active.baseGain * occlusionScale * playbackRateMute;
4833
+ }
4765
4834
  applyOcclusion(active, occlusion) {
4766
4835
  const scale = clamp01(occlusion?.gainScale ?? 1);
4767
- active.gain.gain.value = active.baseGain * scale;
4836
+ const playbackRateMute = Math.abs(this.playbackRate - 1) < 1e-3 ? 1 : 0;
4837
+ active.gain.gain.value = active.baseGain * scale * playbackRateMute;
4768
4838
  if (active.occlusion?.filter) {
4769
4839
  const cutoff = occlusion?.lowpassHz ?? 2e4;
4770
4840
  active.occlusion.filter.frequency.value = clamp(cutoff, 10, 2e4);
@@ -12627,6 +12697,165 @@ var NetworkMessageParser = class _NetworkMessageParser {
12627
12697
  }
12628
12698
  };
12629
12699
 
12700
+ // src/demo/analyzer.ts
12701
+ var DemoAnalyzer = class {
12702
+ constructor(buffer) {
12703
+ this.events = [];
12704
+ this.summary = {
12705
+ totalKills: 0,
12706
+ totalDeaths: 0,
12707
+ damageDealt: 0,
12708
+ damageReceived: 0,
12709
+ weaponUsage: /* @__PURE__ */ new Map()
12710
+ };
12711
+ this.header = null;
12712
+ this.configStrings = /* @__PURE__ */ new Map();
12713
+ this.serverInfo = {};
12714
+ this.statistics = null;
12715
+ this.playerStats = /* @__PURE__ */ new Map();
12716
+ // By playerNum
12717
+ this.weaponStats = /* @__PURE__ */ new Map();
12718
+ this.buffer = buffer;
12719
+ }
12720
+ analyze() {
12721
+ const reader = new DemoReader(this.buffer);
12722
+ let currentFrameIndex = -1;
12723
+ let currentTime = 0;
12724
+ let frameDuration = 0.1;
12725
+ let protocolVersion = 0;
12726
+ const handler = {
12727
+ onServerData: (protocol, serverCount, attractLoop, gameDir, playerNum, levelName, tickRate, demoType) => {
12728
+ protocolVersion = protocol;
12729
+ this.header = {
12730
+ protocolVersion: protocol,
12731
+ gameDir,
12732
+ levelName,
12733
+ playerNum,
12734
+ serverCount,
12735
+ spawnCount: serverCount,
12736
+ // Mapping generic arg
12737
+ tickRate,
12738
+ demoType
12739
+ };
12740
+ if (tickRate && tickRate > 0) {
12741
+ frameDuration = 1 / tickRate;
12742
+ }
12743
+ },
12744
+ onConfigString: (index, str) => {
12745
+ this.configStrings.set(index, str);
12746
+ if (index === 0) {
12747
+ this.parseServerInfo(str);
12748
+ }
12749
+ },
12750
+ onSpawnBaseline: (entity) => {
12751
+ },
12752
+ onFrame: (frame) => {
12753
+ },
12754
+ onPrint: (level, msg) => {
12755
+ if (msg.includes("died") || msg.includes("killed")) {
12756
+ this.summary.totalDeaths++;
12757
+ this.recordEvent({
12758
+ type: 4 /* Death */,
12759
+ frame: currentFrameIndex,
12760
+ time: currentTime,
12761
+ description: msg.trim()
12762
+ });
12763
+ }
12764
+ },
12765
+ onCenterPrint: () => {
12766
+ },
12767
+ onStuffText: () => {
12768
+ },
12769
+ onSound: () => {
12770
+ },
12771
+ onTempEntity: () => {
12772
+ },
12773
+ onLayout: () => {
12774
+ },
12775
+ onInventory: () => {
12776
+ },
12777
+ onMuzzleFlash: (ent, weapon) => {
12778
+ this.handleWeaponFire(ent, weapon, currentFrameIndex, currentTime);
12779
+ },
12780
+ onMuzzleFlash2: (ent, weapon) => {
12781
+ this.handleWeaponFire(ent, weapon, currentFrameIndex, currentTime);
12782
+ },
12783
+ onMuzzleFlash3: (ent, weapon) => {
12784
+ this.handleWeaponFire(ent, weapon, currentFrameIndex, currentTime);
12785
+ },
12786
+ onDisconnect: () => {
12787
+ },
12788
+ onReconnect: () => {
12789
+ },
12790
+ onDownload: () => {
12791
+ },
12792
+ // Rerelease specific
12793
+ onDamage: (indicators) => {
12794
+ for (const ind of indicators) {
12795
+ this.recordEvent({
12796
+ type: 2 /* DamageReceived */,
12797
+ frame: currentFrameIndex,
12798
+ time: currentTime,
12799
+ value: ind.damage,
12800
+ position: ind.dir,
12801
+ description: `Took ${ind.damage} damage`
12802
+ });
12803
+ this.summary.damageReceived += ind.damage;
12804
+ }
12805
+ }
12806
+ };
12807
+ while (reader.hasMore()) {
12808
+ const block = reader.readNextBlock();
12809
+ if (!block) break;
12810
+ currentFrameIndex++;
12811
+ currentTime = currentFrameIndex * frameDuration;
12812
+ const parser = new NetworkMessageParser(block.data, handler);
12813
+ parser.setProtocolVersion(protocolVersion);
12814
+ parser.parseMessage();
12815
+ protocolVersion = parser.getProtocolVersion();
12816
+ }
12817
+ this.statistics = {
12818
+ duration: currentTime,
12819
+ frameCount: currentFrameIndex + 1,
12820
+ averageFps: (currentFrameIndex + 1) / (currentTime || 1),
12821
+ mapName: this.header?.levelName || "unknown",
12822
+ playerCount: 1
12823
+ // Default to 1 for SP/client demo
12824
+ };
12825
+ return {
12826
+ events: this.events,
12827
+ summary: this.summary,
12828
+ header: this.header,
12829
+ configStrings: this.configStrings,
12830
+ serverInfo: this.serverInfo,
12831
+ statistics: this.statistics
12832
+ };
12833
+ }
12834
+ handleWeaponFire(ent, weapon, frame, time) {
12835
+ this.recordEvent({
12836
+ type: 0 /* WeaponFire */,
12837
+ frame,
12838
+ time,
12839
+ entityId: ent,
12840
+ value: weapon,
12841
+ description: `Weapon ${weapon} fired by ${ent}`
12842
+ });
12843
+ const count = this.summary.weaponUsage.get(weapon) || 0;
12844
+ this.summary.weaponUsage.set(weapon, count + 1);
12845
+ }
12846
+ recordEvent(event) {
12847
+ this.events.push(event);
12848
+ }
12849
+ parseServerInfo(str) {
12850
+ const parts = str.split("\\");
12851
+ for (let i = 1; i < parts.length; i += 2) {
12852
+ if (i + 1 < parts.length) {
12853
+ this.serverInfo[parts[i]] = parts[i + 1];
12854
+ }
12855
+ }
12856
+ }
12857
+ };
12858
+
12630
12859
  // src/demo/playback.ts
12631
12860
  var PlaybackState = /* @__PURE__ */ ((PlaybackState2) => {
12632
12861
  PlaybackState2[PlaybackState2["Stopped"] = 0] = "Stopped";
@@ -12638,6 +12867,8 @@ var PlaybackState = /* @__PURE__ */ ((PlaybackState2) => {
12638
12867
  var DemoPlaybackController = class {
12639
12868
  constructor() {
12640
12869
  this.reader = null;
12870
+ this.buffer = null;
12871
+ // Keep reference for analysis
12641
12872
  this.state = 0 /* Stopped */;
12642
12873
  this.playbackSpeed = 1;
12643
12874
  this.currentProtocolVersion = 0;
@@ -12653,6 +12884,13 @@ var DemoPlaybackController = class {
12653
12884
  this.snapshotInterval = 100;
12654
12885
  // frames
12655
12886
  this.snapshots = /* @__PURE__ */ new Map();
12887
+ // Analysis Cache
12888
+ this.cachedEvents = null;
12889
+ this.cachedSummary = null;
12890
+ this.cachedHeader = null;
12891
+ this.cachedConfigStrings = null;
12892
+ this.cachedServerInfo = null;
12893
+ this.cachedStatistics = null;
12656
12894
  }
12657
12895
  setHandler(handler) {
12658
12896
  this.handler = handler;
@@ -12661,6 +12899,7 @@ var DemoPlaybackController = class {
12661
12899
  this.callbacks = callbacks;
12662
12900
  }
12663
12901
  loadDemo(buffer) {
12902
+ this.buffer = buffer;
12664
12903
  this.reader = new DemoReader(buffer);
12665
12904
  this.transitionState(0 /* Stopped */);
12666
12905
  this.accumulatedTime = 0;
@@ -12668,6 +12907,12 @@ var DemoPlaybackController = class {
12668
12907
  this.currentFrameIndex = -1;
12669
12908
  this.snapshots.clear();
12670
12909
  this.lastFrameData = null;
12910
+ this.cachedEvents = null;
12911
+ this.cachedSummary = null;
12912
+ this.cachedHeader = null;
12913
+ this.cachedConfigStrings = null;
12914
+ this.cachedServerInfo = null;
12915
+ this.cachedStatistics = null;
12671
12916
  }
12672
12917
  play() {
12673
12918
  if (this.reader && this.state !== 1 /* Playing */) {
@@ -12980,6 +13225,57 @@ var DemoPlaybackController = class {
12980
13225
  this.seek(originalFrame);
12981
13226
  return trajectory;
12982
13227
  }
13228
+ // 3.2.3 Event Log Extraction & 3.3 Metadata
13229
+ getDemoEvents() {
13230
+ this.ensureAnalysis();
13231
+ return this.cachedEvents || [];
13232
+ }
13233
+ filterEvents(type, entityId) {
13234
+ const events = this.getDemoEvents();
13235
+ return events.filter((e) => {
13236
+ if (e.type !== type) return false;
13237
+ if (entityId !== void 0 && e.entityId !== entityId) return false;
13238
+ return true;
13239
+ });
13240
+ }
13241
+ getEventSummary() {
13242
+ this.ensureAnalysis();
13243
+ return this.cachedSummary || {
13244
+ totalKills: 0,
13245
+ totalDeaths: 0,
13246
+ damageDealt: 0,
13247
+ damageReceived: 0,
13248
+ weaponUsage: /* @__PURE__ */ new Map()
13249
+ };
13250
+ }
13251
+ getDemoHeader() {
13252
+ this.ensureAnalysis();
13253
+ return this.cachedHeader;
13254
+ }
13255
+ getDemoConfigStrings() {
13256
+ this.ensureAnalysis();
13257
+ return this.cachedConfigStrings || /* @__PURE__ */ new Map();
13258
+ }
13259
+ getDemoServerInfo() {
13260
+ this.ensureAnalysis();
13261
+ return this.cachedServerInfo || {};
13262
+ }
13263
+ getDemoStatistics() {
13264
+ this.ensureAnalysis();
13265
+ return this.cachedStatistics;
13266
+ }
13267
+ ensureAnalysis() {
13268
+ if (!this.cachedEvents && this.buffer) {
13269
+ const analyzer = new DemoAnalyzer(this.buffer);
13270
+ const result = analyzer.analyze();
13271
+ this.cachedEvents = result.events;
13272
+ this.cachedSummary = result.summary;
13273
+ this.cachedHeader = result.header;
13274
+ this.cachedConfigStrings = result.configStrings;
13275
+ this.cachedServerInfo = result.serverInfo;
13276
+ this.cachedStatistics = result.statistics;
13277
+ }
13278
+ }
12983
13279
  };
12984
13280
 
12985
13281
  // src/demo/recorder.ts
@@ -13328,6 +13624,67 @@ var AssetPreviewGenerator = class {
13328
13624
  }
13329
13625
  };
13330
13626
 
13627
+ // src/assets/mapStatistics.ts
13628
+ var MapAnalyzer = class {
13629
+ constructor(loader) {
13630
+ this.loader = loader;
13631
+ }
13632
+ async getMapStatistics(mapName) {
13633
+ const map = await this.loader.load(mapName);
13634
+ const lightmapCount = map.faces.filter((f) => f.lightOffset !== -1).length;
13635
+ const worldModel = map.models[0];
13636
+ const bounds = worldModel ? {
13637
+ mins: worldModel.mins,
13638
+ maxs: worldModel.maxs
13639
+ } : {
13640
+ mins: [0, 0, 0],
13641
+ maxs: [0, 0, 0]
13642
+ };
13643
+ return {
13644
+ entityCount: map.entities.entities.length,
13645
+ surfaceCount: map.faces.length,
13646
+ lightmapCount,
13647
+ vertexCount: map.vertices.length,
13648
+ bounds
13649
+ };
13650
+ }
13651
+ async getUsedTextures(mapName) {
13652
+ const map = await this.loader.load(mapName);
13653
+ const textures = /* @__PURE__ */ new Set();
13654
+ for (const info of map.texInfo) {
13655
+ if (info.texture) {
13656
+ textures.add(info.texture);
13657
+ }
13658
+ }
13659
+ return Array.from(textures).sort();
13660
+ }
13661
+ async getUsedModels(mapName) {
13662
+ const map = await this.loader.load(mapName);
13663
+ const models = /* @__PURE__ */ new Set();
13664
+ for (const ent of map.entities.entities) {
13665
+ if (ent.properties["model"] && !ent.properties["model"].startsWith("*")) {
13666
+ models.add(ent.properties["model"]);
13667
+ }
13668
+ }
13669
+ return Array.from(models).sort();
13670
+ }
13671
+ async getUsedSounds(mapName) {
13672
+ const map = await this.loader.load(mapName);
13673
+ const sounds = /* @__PURE__ */ new Set();
13674
+ for (const ent of map.entities.entities) {
13675
+ if (ent.properties["noise"]) {
13676
+ sounds.add(ent.properties["noise"]);
13677
+ }
13678
+ for (const [key, value] of Object.entries(ent.properties)) {
13679
+ if ((key === "noise" || key.endsWith("_sound") || key === "sound") && typeof value === "string") {
13680
+ sounds.add(value);
13681
+ }
13682
+ }
13683
+ }
13684
+ return Array.from(sounds).sort();
13685
+ }
13686
+ };
13687
+
13331
13688
  // src/index.ts
13332
13689
  function createEngine(imports) {
13333
13690
  return {
@@ -13385,6 +13742,7 @@ function createEngine(imports) {
13385
13742
  MD2_VERTEX_SHADER,
13386
13743
  MD3_FRAGMENT_SHADER,
13387
13744
  MD3_VERTEX_SHADER,
13745
+ MapAnalyzer,
13388
13746
  Md2Loader,
13389
13747
  Md2MeshBuffers,
13390
13748
  Md2ParseError,