quake2ts 0.0.7 → 0.0.39

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 (177) hide show
  1. package/README.md +425 -0
  2. package/apps/viewer/dist/browser/index.global.js +1 -1
  3. package/apps/viewer/dist/browser/index.global.js.map +1 -1
  4. package/apps/viewer/dist/cjs/index.cjs +2097 -295
  5. package/apps/viewer/dist/cjs/index.cjs.map +1 -1
  6. package/apps/viewer/dist/esm/index.js +2097 -295
  7. package/apps/viewer/dist/esm/index.js.map +1 -1
  8. package/apps/viewer/dist/tsconfig.tsbuildinfo +1 -1
  9. package/apps/viewer/dist/types/index.d.ts +1 -1
  10. package/package.json +1 -1
  11. package/packages/client/dist/browser/index.global.js +1 -1
  12. package/packages/client/dist/browser/index.global.js.map +1 -1
  13. package/packages/client/dist/cjs/index.cjs +1200 -13
  14. package/packages/client/dist/cjs/index.cjs.map +1 -1
  15. package/packages/client/dist/esm/index.js +1186 -12
  16. package/packages/client/dist/esm/index.js.map +1 -1
  17. package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
  18. package/packages/client/dist/types/index.d.ts +14 -6
  19. package/packages/client/dist/types/index.d.ts.map +1 -1
  20. package/packages/client/dist/types/input/bindings.d.ts +18 -0
  21. package/packages/client/dist/types/input/bindings.d.ts.map +1 -0
  22. package/packages/client/dist/types/input/command-buffer.d.ts +15 -0
  23. package/packages/client/dist/types/input/command-buffer.d.ts.map +1 -0
  24. package/packages/client/dist/types/input/controller.d.ts +125 -0
  25. package/packages/client/dist/types/input/controller.d.ts.map +1 -0
  26. package/packages/client/dist/types/prediction.d.ts +38 -0
  27. package/packages/client/dist/types/prediction.d.ts.map +1 -0
  28. package/packages/client/dist/types/view-effects.d.ts +41 -0
  29. package/packages/client/dist/types/view-effects.d.ts.map +1 -0
  30. package/packages/engine/dist/browser/index.global.js +257 -1
  31. package/packages/engine/dist/browser/index.global.js.map +1 -1
  32. package/packages/engine/dist/cjs/index.cjs +2408 -2
  33. package/packages/engine/dist/cjs/index.cjs.map +1 -1
  34. package/packages/engine/dist/esm/index.js +2340 -2
  35. package/packages/engine/dist/esm/index.js.map +1 -1
  36. package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
  37. package/packages/engine/dist/types/assets/animation.d.ts +33 -0
  38. package/packages/engine/dist/types/assets/animation.d.ts.map +1 -0
  39. package/packages/engine/dist/types/assets/audio.d.ts +21 -0
  40. package/packages/engine/dist/types/assets/audio.d.ts.map +1 -0
  41. package/packages/engine/dist/types/assets/bsp.d.ts +1 -1
  42. package/packages/engine/dist/types/assets/bsp.d.ts.map +1 -1
  43. package/packages/engine/dist/types/assets/ingestion.d.ts +31 -0
  44. package/packages/engine/dist/types/assets/ingestion.d.ts.map +1 -1
  45. package/packages/engine/dist/types/assets/manager.d.ts +43 -0
  46. package/packages/engine/dist/types/assets/manager.d.ts.map +1 -0
  47. package/packages/engine/dist/types/assets/md3.d.ts +69 -0
  48. package/packages/engine/dist/types/assets/md3.d.ts.map +1 -0
  49. package/packages/engine/dist/types/assets/ogg.d.ts +12 -0
  50. package/packages/engine/dist/types/assets/ogg.d.ts.map +1 -0
  51. package/packages/engine/dist/types/assets/pakIndexStore.d.ts +19 -0
  52. package/packages/engine/dist/types/assets/pakIndexStore.d.ts.map +1 -0
  53. package/packages/engine/dist/types/assets/pakValidation.d.ts +28 -0
  54. package/packages/engine/dist/types/assets/pakValidation.d.ts.map +1 -0
  55. package/packages/engine/dist/types/assets/pcx.d.ts +13 -0
  56. package/packages/engine/dist/types/assets/pcx.d.ts.map +1 -0
  57. package/packages/engine/dist/types/assets/texture.d.ts +29 -0
  58. package/packages/engine/dist/types/assets/texture.d.ts.map +1 -0
  59. package/packages/engine/dist/types/assets/wal.d.ts +21 -0
  60. package/packages/engine/dist/types/assets/wal.d.ts.map +1 -0
  61. package/packages/engine/dist/types/assets/wav.d.ts +11 -0
  62. package/packages/engine/dist/types/assets/wav.d.ts.map +1 -0
  63. package/packages/engine/dist/types/audio/api.d.ts +29 -0
  64. package/packages/engine/dist/types/audio/api.d.ts.map +1 -0
  65. package/packages/engine/dist/types/audio/channels.d.ts +15 -0
  66. package/packages/engine/dist/types/audio/channels.d.ts.map +1 -0
  67. package/packages/engine/dist/types/audio/constants.d.ts +24 -0
  68. package/packages/engine/dist/types/audio/constants.d.ts.map +1 -0
  69. package/packages/engine/dist/types/audio/context.d.ts +67 -0
  70. package/packages/engine/dist/types/audio/context.d.ts.map +1 -0
  71. package/packages/engine/dist/types/audio/music.d.ts +42 -0
  72. package/packages/engine/dist/types/audio/music.d.ts.map +1 -0
  73. package/packages/engine/dist/types/audio/precache.d.ts +28 -0
  74. package/packages/engine/dist/types/audio/precache.d.ts.map +1 -0
  75. package/packages/engine/dist/types/audio/registry.d.ts +13 -0
  76. package/packages/engine/dist/types/audio/registry.d.ts.map +1 -0
  77. package/packages/engine/dist/types/audio/spatialization.d.ts +14 -0
  78. package/packages/engine/dist/types/audio/spatialization.d.ts.map +1 -0
  79. package/packages/engine/dist/types/audio/system.d.ts +101 -0
  80. package/packages/engine/dist/types/audio/system.d.ts.map +1 -0
  81. package/packages/engine/dist/types/configstrings.d.ts +1 -0
  82. package/packages/engine/dist/types/configstrings.d.ts.map +1 -1
  83. package/packages/engine/dist/types/index.d.ts +26 -1
  84. package/packages/engine/dist/types/index.d.ts.map +1 -1
  85. package/packages/engine/dist/types/render/bspPipeline.d.ts +42 -0
  86. package/packages/engine/dist/types/render/bspPipeline.d.ts.map +1 -0
  87. package/packages/engine/dist/types/render/bspTraversal.d.ts +11 -0
  88. package/packages/engine/dist/types/render/bspTraversal.d.ts.map +1 -0
  89. package/packages/engine/dist/types/render/culling.d.ts +8 -0
  90. package/packages/engine/dist/types/render/culling.d.ts.map +1 -0
  91. package/packages/engine/dist/types/render/md2Pipeline.d.ts +51 -0
  92. package/packages/engine/dist/types/render/md2Pipeline.d.ts.map +1 -0
  93. package/packages/engine/dist/types/render/resources.d.ts +10 -0
  94. package/packages/engine/dist/types/render/resources.d.ts.map +1 -1
  95. package/packages/engine/dist/types/render/skybox.d.ts +26 -0
  96. package/packages/engine/dist/types/render/skybox.d.ts.map +1 -0
  97. package/packages/game/dist/browser/index.global.js +1 -1
  98. package/packages/game/dist/browser/index.global.js.map +1 -1
  99. package/packages/game/dist/cjs/index.cjs +2926 -116
  100. package/packages/game/dist/cjs/index.cjs.map +1 -1
  101. package/packages/game/dist/esm/index.js +2863 -115
  102. package/packages/game/dist/esm/index.js.map +1 -1
  103. package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
  104. package/packages/game/dist/types/ai/constants.d.ts +13 -0
  105. package/packages/game/dist/types/ai/constants.d.ts.map +1 -0
  106. package/packages/game/dist/types/ai/index.d.ts +4 -0
  107. package/packages/game/dist/types/ai/index.d.ts.map +1 -0
  108. package/packages/game/dist/types/ai/movement.d.ts +20 -0
  109. package/packages/game/dist/types/ai/movement.d.ts.map +1 -0
  110. package/packages/game/dist/types/ai/perception.d.ts +21 -0
  111. package/packages/game/dist/types/ai/perception.d.ts.map +1 -0
  112. package/packages/game/dist/types/checksum.d.ts +3 -0
  113. package/packages/game/dist/types/checksum.d.ts.map +1 -0
  114. package/packages/game/dist/types/combat/armor.d.ts +39 -0
  115. package/packages/game/dist/types/combat/armor.d.ts.map +1 -0
  116. package/packages/game/dist/types/combat/damage.d.ts +52 -0
  117. package/packages/game/dist/types/combat/damage.d.ts.map +1 -0
  118. package/packages/game/dist/types/combat/damageFlags.d.ts +15 -0
  119. package/packages/game/dist/types/combat/damageFlags.d.ts.map +1 -0
  120. package/packages/game/dist/types/combat/damageMods.d.ts +79 -0
  121. package/packages/game/dist/types/combat/damageMods.d.ts.map +1 -0
  122. package/packages/game/dist/types/combat/index.d.ts +6 -0
  123. package/packages/game/dist/types/combat/index.d.ts.map +1 -0
  124. package/packages/game/dist/types/combat/specialDamage.d.ts +88 -0
  125. package/packages/game/dist/types/combat/specialDamage.d.ts.map +1 -0
  126. package/packages/game/dist/types/entities/entity.d.ts +46 -2
  127. package/packages/game/dist/types/entities/entity.d.ts.map +1 -1
  128. package/packages/game/dist/types/entities/index.d.ts +6 -2
  129. package/packages/game/dist/types/entities/index.d.ts.map +1 -1
  130. package/packages/game/dist/types/entities/pool.d.ts +9 -0
  131. package/packages/game/dist/types/entities/pool.d.ts.map +1 -1
  132. package/packages/game/dist/types/entities/spawn.d.ts +27 -0
  133. package/packages/game/dist/types/entities/spawn.d.ts.map +1 -0
  134. package/packages/game/dist/types/entities/system.d.ts +32 -1
  135. package/packages/game/dist/types/entities/system.d.ts.map +1 -1
  136. package/packages/game/dist/types/entities/thinkScheduler.d.ts +6 -0
  137. package/packages/game/dist/types/entities/thinkScheduler.d.ts.map +1 -1
  138. package/packages/game/dist/types/entities/triggers.d.ts +3 -0
  139. package/packages/game/dist/types/entities/triggers.d.ts.map +1 -0
  140. package/packages/game/dist/types/entities/utils.d.ts +4 -0
  141. package/packages/game/dist/types/entities/utils.d.ts.map +1 -0
  142. package/packages/game/dist/types/index.d.ts +5 -0
  143. package/packages/game/dist/types/index.d.ts.map +1 -1
  144. package/packages/game/dist/types/inventory/ammo.d.ts +17 -0
  145. package/packages/game/dist/types/inventory/ammo.d.ts.map +1 -0
  146. package/packages/game/dist/types/inventory/index.d.ts +2 -0
  147. package/packages/game/dist/types/inventory/index.d.ts.map +1 -0
  148. package/packages/game/dist/types/level.d.ts +1 -0
  149. package/packages/game/dist/types/level.d.ts.map +1 -1
  150. package/packages/game/dist/types/save/index.d.ts +4 -0
  151. package/packages/game/dist/types/save/index.d.ts.map +1 -0
  152. package/packages/game/dist/types/save/rerelease.d.ts +25 -0
  153. package/packages/game/dist/types/save/rerelease.d.ts.map +1 -0
  154. package/packages/game/dist/types/save/save.d.ts +49 -0
  155. package/packages/game/dist/types/save/save.d.ts.map +1 -0
  156. package/packages/game/dist/types/save/storage.d.ts +37 -0
  157. package/packages/game/dist/types/save/storage.d.ts.map +1 -0
  158. package/packages/shared/dist/browser/index.global.js +1 -1
  159. package/packages/shared/dist/browser/index.global.js.map +1 -1
  160. package/packages/shared/dist/cjs/index.cjs +638 -9
  161. package/packages/shared/dist/cjs/index.cjs.map +1 -1
  162. package/packages/shared/dist/esm/index.js +616 -9
  163. package/packages/shared/dist/esm/index.js.map +1 -1
  164. package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
  165. package/packages/shared/dist/types/bsp/collision.d.ts +56 -0
  166. package/packages/shared/dist/types/bsp/collision.d.ts.map +1 -1
  167. package/packages/shared/dist/types/bsp/contents.d.ts +1 -0
  168. package/packages/shared/dist/types/bsp/contents.d.ts.map +1 -1
  169. package/packages/shared/dist/types/index.d.ts +2 -0
  170. package/packages/shared/dist/types/index.d.ts.map +1 -1
  171. package/packages/shared/dist/types/math/random.d.ts +11 -0
  172. package/packages/shared/dist/types/math/random.d.ts.map +1 -1
  173. package/packages/shared/dist/types/protocol/contracts.d.ts +17 -0
  174. package/packages/shared/dist/types/protocol/contracts.d.ts.map +1 -0
  175. package/packages/shared/dist/types/protocol/usercmd.d.ts +30 -0
  176. package/packages/shared/dist/types/protocol/usercmd.d.ts.map +1 -0
  177. package/packages/tools/dist/tsconfig.tsbuildinfo +1 -1
@@ -20,21 +20,333 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ AIFlags: () => AIFlags,
24
+ AMMO_TYPE_COUNT: () => AMMO_TYPE_COUNT,
25
+ ARMOR_INFO: () => ARMOR_INFO,
26
+ AmmoType: () => AmmoType,
27
+ ArmorType: () => ArmorType,
28
+ DamageFlags: () => DamageFlags,
29
+ DamageMod: () => DamageMod,
23
30
  DeadFlag: () => DeadFlag,
24
31
  ENTITY_FIELD_METADATA: () => ENTITY_FIELD_METADATA,
25
32
  Entity: () => Entity,
33
+ EntityDamageFlags: () => EntityDamageFlags,
26
34
  EntitySystem: () => EntitySystem,
35
+ EnvironmentalFlags: () => EnvironmentalFlags,
36
+ FL_NOVISIBLE: () => FL_NOVISIBLE,
27
37
  MoveType: () => MoveType,
38
+ ORDERED_DAMAGE_MODS: () => ORDERED_DAMAGE_MODS,
39
+ RANGE_MELEE: () => RANGE_MELEE,
40
+ RANGE_MID: () => RANGE_MID,
41
+ RANGE_NEAR: () => RANGE_NEAR,
42
+ RangeCategory: () => RangeCategory,
43
+ SAVE_FORMAT_VERSION: () => SAVE_FORMAT_VERSION,
44
+ SPAWNFLAG_MONSTER_AMBUSH: () => SPAWNFLAG_MONSTER_AMBUSH,
45
+ SaveStorage: () => SaveStorage,
46
+ ServerFlags: () => ServerFlags,
28
47
  Solid: () => Solid,
29
- createGame: () => createGame
48
+ SpawnRegistry: () => SpawnRegistry,
49
+ T_Damage: () => T_Damage,
50
+ T_RadiusDamage: () => T_RadiusDamage,
51
+ TraceMask: () => TraceMask,
52
+ ai_charge: () => ai_charge,
53
+ ai_face: () => ai_face,
54
+ ai_move: () => ai_move,
55
+ ai_run: () => ai_run,
56
+ ai_stand: () => ai_stand,
57
+ ai_turn: () => ai_turn,
58
+ ai_walk: () => ai_walk,
59
+ applyCrushDamage: () => applyCrushDamage,
60
+ applyEntityKeyValues: () => applyEntityKeyValues,
61
+ applyEnvironmentalDamage: () => applyEnvironmentalDamage,
62
+ applyFallingDamage: () => applyFallingDamage,
63
+ applyPowerArmor: () => applyPowerArmor,
64
+ applyRegularArmor: () => applyRegularArmor,
65
+ applySaveFile: () => applySaveFile,
66
+ calculateFallingDamage: () => calculateFallingDamage,
67
+ changeYaw: () => changeYaw,
68
+ clampAmmoCounts: () => clampAmmoCounts,
69
+ classifyRange: () => classifyRange,
70
+ createBaseAmmoCaps: () => createBaseAmmoCaps,
71
+ createDefaultSpawnRegistry: () => createDefaultSpawnRegistry,
72
+ createGame: () => createGame,
73
+ createSaveFile: () => createSaveFile,
74
+ damageModName: () => damageModName,
75
+ facingIdeal: () => facingIdeal,
76
+ hasAnyDamageFlag: () => hasAnyDamageFlag,
77
+ hashGameState: () => hashGameState,
78
+ infront: () => infront,
79
+ isZeroVector: () => isZeroVector,
80
+ killBox: () => killBox,
81
+ parseEntityLump: () => parseEntityLump,
82
+ parseRereleaseSave: () => parseRereleaseSave,
83
+ parseSaveFile: () => parseSaveFile,
84
+ rangeTo: () => rangeTo,
85
+ registerDefaultSpawns: () => registerDefaultSpawns,
86
+ setMovedir: () => setMovedir,
87
+ spawnEntitiesFromText: () => spawnEntitiesFromText,
88
+ spawnEntityFromDictionary: () => spawnEntityFromDictionary,
89
+ summarizeRereleaseSave: () => summarizeRereleaseSave,
90
+ visible: () => visible,
91
+ walkMove: () => walkMove
30
92
  });
31
93
  module.exports = __toCommonJS(index_exports);
32
94
 
33
95
  // ../shared/dist/esm/index.js
34
96
  var ZERO_VEC3 = { x: 0, y: 0, z: 0 };
35
97
  var DEG_TO_RAD = Math.PI / 180;
98
+ function addVec3(a, b) {
99
+ return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z };
100
+ }
101
+ function subtractVec3(a, b) {
102
+ return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
103
+ }
104
+ function scaleVec3(a, scalar) {
105
+ return { x: a.x * scalar, y: a.y * scalar, z: a.z * scalar };
106
+ }
107
+ function dotVec3(a, b) {
108
+ return a.x * b.x + a.y * b.y + a.z * b.z;
109
+ }
110
+ function lengthSquaredVec3(a) {
111
+ return dotVec3(a, a);
112
+ }
113
+ function lengthVec3(a) {
114
+ return Math.sqrt(lengthSquaredVec3(a));
115
+ }
116
+ function normalizeVec3(a) {
117
+ const len = lengthVec3(a);
118
+ return len === 0 ? a : scaleVec3(a, 1 / len);
119
+ }
120
+ function closestPointToBox(point, mins, maxs) {
121
+ return {
122
+ x: point.x < mins.x ? mins.x : point.x > maxs.x ? maxs.x : point.x,
123
+ y: point.y < mins.y ? mins.y : point.y > maxs.y ? maxs.y : point.y,
124
+ z: point.z < mins.z ? mins.z : point.z > maxs.z ? maxs.z : point.z
125
+ };
126
+ }
127
+ function distanceBetweenBoxesSquared(aMins, aMaxs, bMins, bMaxs) {
128
+ let lengthSq = 0;
129
+ if (aMaxs.x < bMins.x) {
130
+ const d = aMaxs.x - bMins.x;
131
+ lengthSq += d * d;
132
+ } else if (aMins.x > bMaxs.x) {
133
+ const d = aMins.x - bMaxs.x;
134
+ lengthSq += d * d;
135
+ }
136
+ if (aMaxs.y < bMins.y) {
137
+ const d = aMaxs.y - bMins.y;
138
+ lengthSq += d * d;
139
+ } else if (aMins.y > bMaxs.y) {
140
+ const d = aMins.y - bMaxs.y;
141
+ lengthSq += d * d;
142
+ }
143
+ if (aMaxs.z < bMins.z) {
144
+ const d = aMaxs.z - bMins.z;
145
+ lengthSq += d * d;
146
+ } else if (aMins.z > bMaxs.z) {
147
+ const d = aMins.z - bMaxs.z;
148
+ lengthSq += d * d;
149
+ }
150
+ return lengthSq;
151
+ }
152
+ function boxesIntersect(a, b) {
153
+ return a.mins.x <= b.maxs.x && a.maxs.x >= b.mins.x && a.mins.y <= b.maxs.y && a.maxs.y >= b.mins.y && a.mins.z <= b.maxs.z && a.maxs.z >= b.mins.z;
154
+ }
155
+ var PITCH = 0;
156
+ var YAW = 1;
157
+ var ROLL = 2;
36
158
  var DEG2RAD_FACTOR = Math.PI / 180;
37
159
  var RAD2DEG_FACTOR = 180 / Math.PI;
160
+ function axisComponent(vec, axis) {
161
+ switch (axis) {
162
+ case PITCH:
163
+ return vec.x;
164
+ case YAW:
165
+ return vec.y;
166
+ case ROLL:
167
+ default:
168
+ return vec.z;
169
+ }
170
+ }
171
+ function degToRad(degrees) {
172
+ return degrees * DEG2RAD_FACTOR;
173
+ }
174
+ function radToDeg(radians) {
175
+ return radians * RAD2DEG_FACTOR;
176
+ }
177
+ function angleMod(angle) {
178
+ const value = angle % 360;
179
+ return value < 0 ? 360 + value : value;
180
+ }
181
+ function angleVectors(angles) {
182
+ const yaw = degToRad(axisComponent(angles, YAW));
183
+ const pitch = degToRad(axisComponent(angles, PITCH));
184
+ const roll = degToRad(axisComponent(angles, ROLL));
185
+ const sy = Math.sin(yaw);
186
+ const cy = Math.cos(yaw);
187
+ const sp = Math.sin(pitch);
188
+ const cp = Math.cos(pitch);
189
+ const sr = Math.sin(roll);
190
+ const cr = Math.cos(roll);
191
+ const forward = {
192
+ x: cp * cy,
193
+ y: cp * sy,
194
+ z: -sp
195
+ };
196
+ const right = {
197
+ x: -sr * sp * cy - cr * -sy,
198
+ y: -sr * sp * sy - cr * cy,
199
+ z: -sr * cp
200
+ };
201
+ const up = {
202
+ x: cr * sp * cy - sr * -sy,
203
+ y: cr * sp * sy - sr * cy,
204
+ z: cr * cp
205
+ };
206
+ return { forward, right, up };
207
+ }
208
+ function vectorToYaw(vec) {
209
+ const pitch = axisComponent(vec, PITCH);
210
+ const yawAxis = axisComponent(vec, YAW);
211
+ if (pitch === 0) {
212
+ if (yawAxis === 0) {
213
+ return 0;
214
+ }
215
+ return yawAxis > 0 ? 90 : 270;
216
+ }
217
+ const yaw = radToDeg(Math.atan2(yawAxis, pitch));
218
+ return yaw < 0 ? yaw + 360 : yaw;
219
+ }
220
+ var STATE_SIZE = 624;
221
+ var MIDDLE_WORD = 397;
222
+ var MATRIX_A = 2567483615;
223
+ var UPPER_MASK = 2147483648;
224
+ var LOWER_MASK = 2147483647;
225
+ var TWO_POW_32 = 4294967296;
226
+ var MersenneTwister19937 = class {
227
+ constructor(seed = 5489) {
228
+ this.state = new Uint32Array(STATE_SIZE);
229
+ this.index = STATE_SIZE;
230
+ this.seed(seed);
231
+ }
232
+ seed(seed) {
233
+ this.state[0] = seed >>> 0;
234
+ for (let i = 1; i < STATE_SIZE; i++) {
235
+ const prev = this.state[i - 1] ^ this.state[i - 1] >>> 30;
236
+ const next = Math.imul(prev >>> 0, 1812433253) + i;
237
+ this.state[i] = next >>> 0;
238
+ }
239
+ this.index = STATE_SIZE;
240
+ }
241
+ nextUint32() {
242
+ if (this.index >= STATE_SIZE) {
243
+ this.twist();
244
+ }
245
+ let y = this.state[this.index++];
246
+ y ^= y >>> 11;
247
+ y ^= y << 7 & 2636928640;
248
+ y ^= y << 15 & 4022730752;
249
+ y ^= y >>> 18;
250
+ return y >>> 0;
251
+ }
252
+ twist() {
253
+ for (let i = 0; i < STATE_SIZE; i++) {
254
+ const y = this.state[i] & UPPER_MASK | this.state[(i + 1) % STATE_SIZE] & LOWER_MASK;
255
+ let next = this.state[(i + MIDDLE_WORD) % STATE_SIZE] ^ y >>> 1;
256
+ if ((y & 1) !== 0) {
257
+ next ^= MATRIX_A;
258
+ }
259
+ this.state[i] = next >>> 0;
260
+ }
261
+ this.index = 0;
262
+ }
263
+ getState() {
264
+ return {
265
+ index: this.index,
266
+ state: Array.from(this.state)
267
+ };
268
+ }
269
+ setState(snapshot) {
270
+ if (snapshot.state.length !== STATE_SIZE) {
271
+ throw new Error(`Expected ${STATE_SIZE} MT state values, received ${snapshot.state.length}`);
272
+ }
273
+ this.index = snapshot.index;
274
+ this.state = Uint32Array.from(snapshot.state, (value) => value >>> 0);
275
+ }
276
+ };
277
+ var RandomGenerator = class {
278
+ constructor(options = {}) {
279
+ this.mt = new MersenneTwister19937(options.seed);
280
+ }
281
+ /** Uniform float in [0, 1). */
282
+ frandom() {
283
+ return this.mt.nextUint32() / TWO_POW_32;
284
+ }
285
+ /** Uniform float in [min, max). */
286
+ frandomRange(minInclusive, maxExclusive) {
287
+ return minInclusive + (maxExclusive - minInclusive) * this.frandom();
288
+ }
289
+ /** Uniform float in [0, max). */
290
+ frandomMax(maxExclusive) {
291
+ return this.frandomRange(0, maxExclusive);
292
+ }
293
+ /** Uniform float in [-1, 1). */
294
+ crandom() {
295
+ return this.frandomRange(-1, 1);
296
+ }
297
+ /** Uniform float in (-1, 1). */
298
+ crandomOpen() {
299
+ const epsilon = Number.EPSILON;
300
+ return this.frandomRange(-1 + epsilon, 1);
301
+ }
302
+ /** Raw uint32 sample. */
303
+ irandomUint32() {
304
+ return this.mt.nextUint32();
305
+ }
306
+ /** Uniform integer in [min, max). */
307
+ irandomRange(minInclusive, maxExclusive) {
308
+ if (maxExclusive - minInclusive <= 1) {
309
+ return minInclusive;
310
+ }
311
+ const span = maxExclusive - minInclusive;
312
+ const limit = TWO_POW_32 - TWO_POW_32 % span;
313
+ let sample;
314
+ do {
315
+ sample = this.mt.nextUint32();
316
+ } while (sample >= limit);
317
+ return minInclusive + sample % span;
318
+ }
319
+ /** Uniform integer in [0, max). */
320
+ irandom(maxExclusive) {
321
+ if (maxExclusive <= 0) {
322
+ return 0;
323
+ }
324
+ return this.irandomRange(0, maxExclusive);
325
+ }
326
+ /** Uniform time in milliseconds [min, max). */
327
+ randomTimeRange(minMs, maxMs) {
328
+ if (maxMs <= minMs) {
329
+ return minMs;
330
+ }
331
+ return this.irandomRange(minMs, maxMs);
332
+ }
333
+ /** Uniform time in milliseconds [0, max). */
334
+ randomTime(maxMs) {
335
+ return this.irandom(maxMs);
336
+ }
337
+ randomIndex(container) {
338
+ return this.irandom(container.length);
339
+ }
340
+ getState() {
341
+ return { mt: this.mt.getState() };
342
+ }
343
+ setState(snapshot) {
344
+ this.mt.setState(snapshot.mt);
345
+ }
346
+ };
347
+ function createRandomGenerator(options) {
348
+ return new RandomGenerator(options);
349
+ }
38
350
  var CONTENTS_SOLID = 1 << 0;
39
351
  var CONTENTS_WINDOW = 1 << 1;
40
352
  var CONTENTS_AUX = 1 << 2;
@@ -87,6 +399,16 @@ var MASK_NAV_SOLID = CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WINDOW;
87
399
  var MASK_LADDER_NAV_SOLID = CONTENTS_SOLID | CONTENTS_WINDOW;
88
400
  var MASK_WALK_NAV_SOLID = CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WINDOW | CONTENTS_MONSTERCLIP;
89
401
  var MASK_PROJECTILE = MASK_SHOT | CONTENTS_PROJECTILECLIP;
402
+ var MAX_CHECKCOUNT = Number.MAX_SAFE_INTEGER - 1;
403
+ var CvarFlags = /* @__PURE__ */ ((CvarFlags2) => {
404
+ CvarFlags2[CvarFlags2["None"] = 0] = "None";
405
+ CvarFlags2[CvarFlags2["Archive"] = 1] = "Archive";
406
+ CvarFlags2[CvarFlags2["UserInfo"] = 2] = "UserInfo";
407
+ CvarFlags2[CvarFlags2["ServerInfo"] = 4] = "ServerInfo";
408
+ CvarFlags2[CvarFlags2["Latch"] = 8] = "Latch";
409
+ CvarFlags2[CvarFlags2["Cheat"] = 16] = "Cheat";
410
+ return CvarFlags2;
411
+ })(CvarFlags || {});
90
412
  var MAX_CLIENTS = 256;
91
413
  var MAX_LIGHTSTYLES = 256;
92
414
  var MAX_MODELS = 8192;
@@ -123,6 +445,13 @@ var ConfigStringIndex = ((ConfigStringIndex2) => {
123
445
  return ConfigStringIndex2;
124
446
  })(ConfigStringIndex || {});
125
447
  var MAX_CONFIGSTRINGS = ConfigStringIndex.MaxConfigStrings;
448
+ var WaterLevel = /* @__PURE__ */ ((WaterLevel3) => {
449
+ WaterLevel3[WaterLevel3["None"] = 0] = "None";
450
+ WaterLevel3[WaterLevel3["Feet"] = 1] = "Feet";
451
+ WaterLevel3[WaterLevel3["Waist"] = 2] = "Waist";
452
+ WaterLevel3[WaterLevel3["Under"] = 3] = "Under";
453
+ return WaterLevel3;
454
+ })(WaterLevel || {});
126
455
  var WATERJUMP_CLEAR = 8 | 16 | 32 | 1024;
127
456
 
128
457
  // src/entities/entity.ts
@@ -146,6 +475,22 @@ var Solid = /* @__PURE__ */ ((Solid2) => {
146
475
  Solid2[Solid2["Bsp"] = 3] = "Bsp";
147
476
  return Solid2;
148
477
  })(Solid || {});
478
+ var ServerFlags = /* @__PURE__ */ ((ServerFlags2) => {
479
+ ServerFlags2[ServerFlags2["None"] = 0] = "None";
480
+ ServerFlags2[ServerFlags2["NoClient"] = 1] = "NoClient";
481
+ ServerFlags2[ServerFlags2["DeadMonster"] = 2] = "DeadMonster";
482
+ ServerFlags2[ServerFlags2["Monster"] = 4] = "Monster";
483
+ ServerFlags2[ServerFlags2["Player"] = 8] = "Player";
484
+ ServerFlags2[ServerFlags2["Bot"] = 16] = "Bot";
485
+ ServerFlags2[ServerFlags2["NoBots"] = 32] = "NoBots";
486
+ ServerFlags2[ServerFlags2["Respawning"] = 64] = "Respawning";
487
+ ServerFlags2[ServerFlags2["Projectile"] = 128] = "Projectile";
488
+ ServerFlags2[ServerFlags2["Instanced"] = 256] = "Instanced";
489
+ ServerFlags2[ServerFlags2["Door"] = 512] = "Door";
490
+ ServerFlags2[ServerFlags2["NoCull"] = 1024] = "NoCull";
491
+ ServerFlags2[ServerFlags2["Hull"] = 2048] = "Hull";
492
+ return ServerFlags2;
493
+ })(ServerFlags || {});
149
494
  var DeadFlag = /* @__PURE__ */ ((DeadFlag2) => {
150
495
  DeadFlag2[DeadFlag2["Alive"] = 0] = "Alive";
151
496
  DeadFlag2[DeadFlag2["Dying"] = 1] = "Dying";
@@ -157,6 +502,7 @@ var ZERO = { ...ZERO_VEC3 };
157
502
  function copyVec3() {
158
503
  return { ...ZERO };
159
504
  }
505
+ var DEFAULT_MONSTER_INFO = Object.freeze({ aiflags: 0 });
160
506
  var Entity = class {
161
507
  constructor(index) {
162
508
  this.inUse = false;
@@ -165,17 +511,20 @@ var Entity = class {
165
511
  this.linkNext = null;
166
512
  this.classname = "";
167
513
  this.spawnflags = 0;
514
+ this.inventory = {};
168
515
  this.origin = copyVec3();
169
516
  this.old_origin = copyVec3();
170
517
  this.velocity = copyVec3();
171
518
  this.avelocity = copyVec3();
172
519
  this.angles = copyVec3();
520
+ this.viewheight = 0;
173
521
  this.mins = copyVec3();
174
522
  this.maxs = copyVec3();
175
523
  this.size = copyVec3();
176
524
  this.mass = 0;
177
525
  this.gravity = 1;
178
526
  this.movetype = 0 /* None */;
527
+ this.movedir = copyVec3();
179
528
  this.modelindex = 0;
180
529
  this.frame = 0;
181
530
  this.skin = 0;
@@ -185,20 +534,35 @@ var Entity = class {
185
534
  this.max_health = 0;
186
535
  this.takedamage = false;
187
536
  this.dmg = 0;
537
+ this.speed = 0;
188
538
  this.deadflag = 0 /* Alive */;
539
+ this.count = 0;
540
+ this.wait = 0;
541
+ this.delay = 0;
542
+ this.timestamp = 0;
543
+ this.sounds = 0;
544
+ this.noise_index = 0;
545
+ this.fly_sound_debounce_time = 0;
189
546
  this.enemy = null;
190
547
  this.movetarget = null;
548
+ this.target_ent = null;
191
549
  this.goalentity = null;
192
550
  this.ideal_yaw = 0;
193
551
  this.yaw_speed = 0;
552
+ this.search_time = 0;
553
+ this.attack_finished_time = 0;
554
+ this.pain_finished_time = 0;
555
+ this.trail_time = 0;
194
556
  this.groundentity = null;
195
557
  this.groundentity_linkcount = 0;
196
558
  this.waterlevel = 0;
197
559
  this.watertype = 0;
198
560
  this.nextthink = 0;
561
+ this.activator = null;
199
562
  this.solid = 0 /* Not */;
200
563
  this.flags = 0;
201
564
  this.svflags = 0;
565
+ this.monsterinfo = { ...DEFAULT_MONSTER_INFO };
202
566
  this.index = index;
203
567
  }
204
568
  reset() {
@@ -210,19 +574,26 @@ var Entity = class {
210
574
  this.spawnflags = 0;
211
575
  this.target = void 0;
212
576
  this.targetname = void 0;
577
+ this.killtarget = void 0;
213
578
  this.team = void 0;
214
579
  this.message = void 0;
580
+ this.pathtarget = void 0;
581
+ this.model = void 0;
582
+ this.item = void 0;
583
+ this.inventory = {};
215
584
  this.origin = copyVec3();
216
585
  this.old_origin = copyVec3();
217
586
  this.velocity = copyVec3();
218
587
  this.avelocity = copyVec3();
219
588
  this.angles = copyVec3();
589
+ this.viewheight = 0;
220
590
  this.mins = copyVec3();
221
591
  this.maxs = copyVec3();
222
592
  this.size = copyVec3();
223
593
  this.mass = 0;
224
594
  this.gravity = 1;
225
595
  this.movetype = 0 /* None */;
596
+ this.movedir = copyVec3();
226
597
  this.modelindex = 0;
227
598
  this.frame = 0;
228
599
  this.skin = 0;
@@ -232,12 +603,25 @@ var Entity = class {
232
603
  this.max_health = 0;
233
604
  this.takedamage = false;
234
605
  this.dmg = 0;
606
+ this.speed = 0;
235
607
  this.deadflag = 0 /* Alive */;
608
+ this.count = 0;
609
+ this.wait = 0;
610
+ this.delay = 0;
611
+ this.timestamp = 0;
612
+ this.sounds = 0;
613
+ this.noise_index = 0;
614
+ this.fly_sound_debounce_time = 0;
236
615
  this.enemy = null;
237
616
  this.movetarget = null;
617
+ this.target_ent = null;
238
618
  this.goalentity = null;
239
619
  this.ideal_yaw = 0;
240
620
  this.yaw_speed = 0;
621
+ this.search_time = 0;
622
+ this.attack_finished_time = 0;
623
+ this.pain_finished_time = 0;
624
+ this.trail_time = 0;
241
625
  this.groundentity = null;
242
626
  this.groundentity_linkcount = 0;
243
627
  this.waterlevel = 0;
@@ -248,9 +632,11 @@ var Entity = class {
248
632
  this.use = void 0;
249
633
  this.pain = void 0;
250
634
  this.die = void 0;
635
+ this.activator = null;
251
636
  this.solid = 0 /* Not */;
252
637
  this.flags = 0;
253
638
  this.svflags = 0;
639
+ this.monsterinfo = { ...DEFAULT_MONSTER_INFO };
254
640
  }
255
641
  };
256
642
  var ENTITY_FIELD_METADATA = [
@@ -258,19 +644,26 @@ var ENTITY_FIELD_METADATA = [
258
644
  { name: "spawnflags", type: "int", save: true },
259
645
  { name: "target", type: "string", save: true },
260
646
  { name: "targetname", type: "string", save: true },
647
+ { name: "killtarget", type: "string", save: true },
261
648
  { name: "team", type: "string", save: true },
262
649
  { name: "message", type: "string", save: true },
650
+ { name: "pathtarget", type: "string", save: true },
651
+ { name: "model", type: "string", save: true },
652
+ { name: "item", type: "string", save: true },
653
+ { name: "inventory", type: "inventory", save: true },
263
654
  { name: "origin", type: "vec3", save: true },
264
655
  { name: "old_origin", type: "vec3", save: true },
265
656
  { name: "velocity", type: "vec3", save: true },
266
657
  { name: "avelocity", type: "vec3", save: true },
267
658
  { name: "angles", type: "vec3", save: true },
659
+ { name: "viewheight", type: "int", save: true },
268
660
  { name: "mins", type: "vec3", save: true },
269
661
  { name: "maxs", type: "vec3", save: true },
270
662
  { name: "size", type: "vec3", save: true },
271
663
  { name: "mass", type: "int", save: true },
272
664
  { name: "gravity", type: "float", save: true },
273
665
  { name: "movetype", type: "int", save: true },
666
+ { name: "movedir", type: "vec3", save: true },
274
667
  { name: "modelindex", type: "int", save: true },
275
668
  { name: "frame", type: "int", save: true },
276
669
  { name: "skin", type: "int", save: true },
@@ -280,12 +673,25 @@ var ENTITY_FIELD_METADATA = [
280
673
  { name: "max_health", type: "int", save: true },
281
674
  { name: "takedamage", type: "boolean", save: true },
282
675
  { name: "dmg", type: "int", save: true },
676
+ { name: "speed", type: "float", save: true },
283
677
  { name: "deadflag", type: "int", save: true },
678
+ { name: "count", type: "int", save: true },
679
+ { name: "wait", type: "float", save: true },
680
+ { name: "delay", type: "float", save: true },
681
+ { name: "timestamp", type: "float", save: true },
682
+ { name: "sounds", type: "int", save: true },
683
+ { name: "noise_index", type: "int", save: true },
684
+ { name: "fly_sound_debounce_time", type: "float", save: true },
284
685
  { name: "enemy", type: "entity", save: true },
285
686
  { name: "movetarget", type: "entity", save: true },
687
+ { name: "target_ent", type: "entity", save: true },
286
688
  { name: "goalentity", type: "entity", save: true },
287
689
  { name: "ideal_yaw", type: "float", save: true },
288
690
  { name: "yaw_speed", type: "float", save: true },
691
+ { name: "search_time", type: "float", save: true },
692
+ { name: "attack_finished_time", type: "float", save: true },
693
+ { name: "pain_finished_time", type: "float", save: true },
694
+ { name: "trail_time", type: "float", save: true },
289
695
  { name: "groundentity", type: "entity", save: true },
290
696
  { name: "groundentity_linkcount", type: "int", save: true },
291
697
  { name: "waterlevel", type: "int", save: true },
@@ -373,6 +779,17 @@ var EntityPool = class {
373
779
  entity.freePending = true;
374
780
  this.pendingFree.push(entity.index);
375
781
  }
782
+ freeImmediate(entity) {
783
+ if (entity.index === WORLD_INDEX) {
784
+ throw new Error("Cannot free world entity");
785
+ }
786
+ if (!entity.inUse) {
787
+ return;
788
+ }
789
+ this.unlink(entity);
790
+ entity.reset();
791
+ this.freeList.push(entity.index);
792
+ }
376
793
  flushFreeList() {
377
794
  if (this.pendingFree.length === 0) {
378
795
  return;
@@ -384,6 +801,69 @@ var EntityPool = class {
384
801
  }
385
802
  this.pendingFree.length = 0;
386
803
  }
804
+ createSnapshot() {
805
+ const activeOrder = Array.from(this, (entity) => entity.index);
806
+ return {
807
+ capacity: this.entities.length,
808
+ activeOrder,
809
+ freeList: [...this.freeList],
810
+ pendingFree: [...this.pendingFree]
811
+ };
812
+ }
813
+ restore(snapshot) {
814
+ if (snapshot.capacity !== this.entities.length) {
815
+ throw new Error(`Snapshot capacity ${snapshot.capacity} does not match pool capacity ${this.entities.length}`);
816
+ }
817
+ const seen = /* @__PURE__ */ new Set();
818
+ const noteIndex = (index, label) => {
819
+ if (index < 0 || index >= this.entities.length) {
820
+ throw new Error(`Invalid entity index ${index} in ${label}`);
821
+ }
822
+ if (seen.has(index)) {
823
+ throw new Error(`Duplicate entity index ${index} in snapshot`);
824
+ }
825
+ seen.add(index);
826
+ };
827
+ for (const index of snapshot.activeOrder) {
828
+ noteIndex(index, "activeOrder");
829
+ }
830
+ for (const index of snapshot.freeList) {
831
+ noteIndex(index, "freeList");
832
+ }
833
+ for (const index of snapshot.pendingFree) {
834
+ noteIndex(index, "pendingFree");
835
+ }
836
+ this.activeHead = null;
837
+ this.freeList.length = 0;
838
+ this.pendingFree.length = 0;
839
+ for (const entity of this.entities) {
840
+ entity.reset();
841
+ }
842
+ for (let i = snapshot.activeOrder.length - 1; i >= 0; i -= 1) {
843
+ const entity = this.entities[snapshot.activeOrder[i]];
844
+ entity.inUse = true;
845
+ this.link(entity);
846
+ }
847
+ for (const index of snapshot.pendingFree) {
848
+ const entity = this.entities[index];
849
+ entity.inUse = false;
850
+ entity.freePending = true;
851
+ entity.linkNext = null;
852
+ entity.linkPrevious = null;
853
+ this.pendingFree.push(index);
854
+ }
855
+ for (const index of snapshot.freeList) {
856
+ const entity = this.entities[index];
857
+ entity.inUse = false;
858
+ entity.freePending = false;
859
+ entity.linkNext = null;
860
+ entity.linkPrevious = null;
861
+ this.freeList.push(index);
862
+ }
863
+ if (!snapshot.activeOrder.includes(WORLD_INDEX)) {
864
+ throw new Error("Snapshot must include the world entity as active");
865
+ }
866
+ }
387
867
  link(entity) {
388
868
  entity.linkNext = this.activeHead;
389
869
  if (this.activeHead) {
@@ -427,6 +907,19 @@ var ThinkScheduler = class {
427
907
  }
428
908
  }
429
909
  }
910
+ snapshot() {
911
+ return this.queue.map(({ time, entity }) => ({ time, entityIndex: entity.index }));
912
+ }
913
+ restore(entries, resolver) {
914
+ this.queue.length = 0;
915
+ for (const entry of entries) {
916
+ const entity = resolver(entry.entityIndex);
917
+ if (!entity) {
918
+ continue;
919
+ }
920
+ this.schedule(entity, entry.time);
921
+ }
922
+ }
430
923
  runDueThinks(currentTimeSeconds) {
431
924
  while (this.queue.length > 0) {
432
925
  const next = this.queue[0];
@@ -467,8 +960,41 @@ function computeBounds(entity) {
467
960
  function boundsIntersect(a, b) {
468
961
  return !(a.min.x > b.max.x || a.max.x < b.min.x || a.min.y > b.max.y || a.max.y < b.min.y || a.min.z > b.max.z || a.max.z < b.min.z);
469
962
  }
963
+ var SERIALIZABLE_FIELDS = ENTITY_FIELD_METADATA.filter(
964
+ (field) => field.save
965
+ );
966
+ var DESCRIPTORS = new Map(SERIALIZABLE_FIELDS.map((descriptor) => [descriptor.name, descriptor]));
967
+ function serializeVec3(vec) {
968
+ return [vec.x, vec.y, vec.z];
969
+ }
970
+ function deserializeVec3(value) {
971
+ const vec = value;
972
+ if (!Array.isArray(vec) || vec.length !== 3) {
973
+ throw new Error("Invalid vec3 serialization");
974
+ }
975
+ const [x, y, z] = vec;
976
+ return { x, y, z };
977
+ }
978
+ function assignField(entity, name, value) {
979
+ entity[name] = value;
980
+ }
981
+ function serializeInventory(inventory) {
982
+ return { ...inventory };
983
+ }
984
+ function deserializeInventory(value) {
985
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
986
+ throw new Error("Invalid inventory serialization");
987
+ }
988
+ const parsed = {};
989
+ for (const [key, entry] of Object.entries(value)) {
990
+ parsed[key] = Number(entry);
991
+ }
992
+ return parsed;
993
+ }
470
994
  var EntitySystem = class {
471
995
  constructor(maxEntities) {
996
+ this.targetNameIndex = /* @__PURE__ */ new Map();
997
+ this.random = createRandomGenerator();
472
998
  this.currentTimeSeconds = 0;
473
999
  this.pool = new EntityPool(maxEntities);
474
1000
  this.thinkScheduler = new ThinkScheduler();
@@ -491,20 +1017,175 @@ var EntitySystem = class {
491
1017
  return this.pool.spawn();
492
1018
  }
493
1019
  free(entity) {
1020
+ this.unregisterTarget(entity);
494
1021
  this.thinkScheduler.cancel(entity);
495
1022
  this.pool.deferFree(entity);
496
1023
  }
1024
+ freeImmediate(entity) {
1025
+ this.unregisterTarget(entity);
1026
+ this.thinkScheduler.cancel(entity);
1027
+ this.pool.freeImmediate(entity);
1028
+ }
497
1029
  scheduleThink(entity, nextThinkSeconds) {
498
1030
  this.thinkScheduler.schedule(entity, nextThinkSeconds);
499
1031
  }
500
1032
  beginFrame(timeSeconds) {
501
1033
  this.currentTimeSeconds = timeSeconds;
502
1034
  }
1035
+ finalizeSpawn(entity) {
1036
+ if (!entity.inUse || entity.freePending) {
1037
+ return;
1038
+ }
1039
+ this.registerTarget(entity);
1040
+ }
1041
+ findByClassname(classname) {
1042
+ const matches = [];
1043
+ for (const entity of this.pool) {
1044
+ if (entity.classname === classname && entity.inUse && !entity.freePending) {
1045
+ matches.push(entity);
1046
+ }
1047
+ }
1048
+ return matches;
1049
+ }
1050
+ findByTargetName(targetname) {
1051
+ const matches = this.targetNameIndex.get(targetname);
1052
+ if (!matches) {
1053
+ return [];
1054
+ }
1055
+ return Array.from(matches).filter((entity) => entity.inUse && !entity.freePending);
1056
+ }
1057
+ pickTarget(targetname) {
1058
+ if (!targetname) {
1059
+ return null;
1060
+ }
1061
+ const matches = this.findByTargetName(targetname);
1062
+ if (matches.length === 0) {
1063
+ return null;
1064
+ }
1065
+ const choice = this.random.randomIndex(matches);
1066
+ return matches[choice] ?? null;
1067
+ }
1068
+ killBox(entity) {
1069
+ const targetBounds = computeBounds(entity);
1070
+ for (const other of this.pool) {
1071
+ if (other === entity || other === this.pool.world) {
1072
+ continue;
1073
+ }
1074
+ if (!other.inUse || other.freePending || other.solid === 0 /* Not */) {
1075
+ continue;
1076
+ }
1077
+ if (other.svflags & 2 /* DeadMonster */) {
1078
+ continue;
1079
+ }
1080
+ if (!boundsIntersect(targetBounds, computeBounds(other))) {
1081
+ continue;
1082
+ }
1083
+ other.health = 0;
1084
+ other.deadflag = 2 /* Dead */;
1085
+ this.free(other);
1086
+ }
1087
+ }
1088
+ useTargets(entity, activator = null) {
1089
+ if (entity.delay > 0) {
1090
+ const delayed = this.spawn();
1091
+ delayed.classname = "DelayedUse";
1092
+ delayed.target = entity.target;
1093
+ delayed.killtarget = entity.killtarget;
1094
+ delayed.message = entity.message;
1095
+ delayed.think = (self) => {
1096
+ this.useTargetsImmediate(self, activator ?? entity);
1097
+ this.free(self);
1098
+ };
1099
+ this.scheduleThink(delayed, this.currentTimeSeconds + entity.delay);
1100
+ return;
1101
+ }
1102
+ this.useTargetsImmediate(entity, activator ?? entity);
1103
+ }
503
1104
  runFrame() {
504
1105
  this.thinkScheduler.runDueThinks(this.currentTimeSeconds);
505
1106
  this.runTouches();
506
1107
  this.pool.flushFreeList();
507
1108
  }
1109
+ createSnapshot() {
1110
+ const entities = [];
1111
+ for (const entity of this.pool) {
1112
+ const fields = {};
1113
+ for (const descriptor of SERIALIZABLE_FIELDS) {
1114
+ const value = entity[descriptor.name];
1115
+ switch (descriptor.type) {
1116
+ case "vec3":
1117
+ fields[descriptor.name] = serializeVec3(value);
1118
+ break;
1119
+ case "entity":
1120
+ fields[descriptor.name] = value?.index ?? null;
1121
+ break;
1122
+ case "inventory":
1123
+ fields[descriptor.name] = serializeInventory(value);
1124
+ break;
1125
+ default:
1126
+ fields[descriptor.name] = value ?? null;
1127
+ break;
1128
+ }
1129
+ }
1130
+ entities.push({
1131
+ index: entity.index,
1132
+ fields
1133
+ });
1134
+ }
1135
+ return {
1136
+ timeSeconds: this.currentTimeSeconds,
1137
+ pool: this.pool.createSnapshot(),
1138
+ entities,
1139
+ thinks: this.thinkScheduler.snapshot()
1140
+ };
1141
+ }
1142
+ restore(snapshot) {
1143
+ this.currentTimeSeconds = snapshot.timeSeconds;
1144
+ this.pool.restore(snapshot.pool);
1145
+ const indexToEntity = /* @__PURE__ */ new Map();
1146
+ for (const entity of this.pool) {
1147
+ indexToEntity.set(entity.index, entity);
1148
+ }
1149
+ const pendingEntityRefs = [];
1150
+ for (const serialized of snapshot.entities) {
1151
+ const entity = indexToEntity.get(serialized.index);
1152
+ if (!entity) {
1153
+ continue;
1154
+ }
1155
+ for (const [name, value] of Object.entries(serialized.fields)) {
1156
+ const descriptor = DESCRIPTORS.get(name);
1157
+ if (!descriptor || value === void 0) {
1158
+ continue;
1159
+ }
1160
+ switch (descriptor.type) {
1161
+ case "vec3":
1162
+ assignField(entity, name, deserializeVec3(value));
1163
+ break;
1164
+ case "entity":
1165
+ pendingEntityRefs.push({
1166
+ entity,
1167
+ name: descriptor.name,
1168
+ targetIndex: value
1169
+ });
1170
+ break;
1171
+ case "inventory":
1172
+ assignField(entity, name, deserializeInventory(value));
1173
+ break;
1174
+ case "boolean":
1175
+ assignField(entity, name, Boolean(value));
1176
+ break;
1177
+ default:
1178
+ assignField(entity, name, value);
1179
+ break;
1180
+ }
1181
+ }
1182
+ }
1183
+ for (const ref of pendingEntityRefs) {
1184
+ const target = ref.targetIndex === null ? null : indexToEntity.get(ref.targetIndex) ?? null;
1185
+ assignField(ref.entity, ref.name, target);
1186
+ }
1187
+ this.thinkScheduler.restore(snapshot.thinks, (index) => indexToEntity.get(index));
1188
+ }
508
1189
  runTouches() {
509
1190
  const world = this.pool.world;
510
1191
  const activeEntities = [];
@@ -512,7 +1193,7 @@ var EntitySystem = class {
512
1193
  if (entity === world) {
513
1194
  continue;
514
1195
  }
515
- if (!entity.inUse || entity.freePending) {
1196
+ if (!entity.inUse || entity.freePending || entity.solid === 0 /* Not */) {
516
1197
  continue;
517
1198
  }
518
1199
  activeEntities.push(entity);
@@ -541,113 +1222,904 @@ var EntitySystem = class {
541
1222
  }
542
1223
  }
543
1224
  }
544
- };
545
-
546
- // src/loop.ts
547
- var orderedStageNames = [
548
- "prep",
549
- "simulate",
550
- "finish"
551
- ];
552
- var GameFrameLoop = class {
553
- constructor(initialStages) {
554
- this.timeMs = 0;
555
- this.frame = 0;
556
- this.stageHandlers = {
557
- prep: [],
558
- simulate: [],
559
- finish: []
560
- };
561
- this.stageCounts = {
562
- prep: 0,
563
- simulate: 0,
564
- finish: 0
565
- };
566
- this.stageCompactionNeeded = {
567
- prep: false,
568
- simulate: false,
569
- finish: false
570
- };
571
- if (initialStages) {
572
- for (const stageName of orderedStageNames) {
573
- const handler = initialStages[stageName];
574
- if (handler) {
575
- this.addStage(stageName, handler);
576
- }
577
- }
1225
+ registerTarget(entity) {
1226
+ if (!entity.targetname) {
1227
+ return;
578
1228
  }
579
- }
580
- addStage(stage, handler) {
581
- const handlers = this.stageHandlers[stage];
582
- handlers.push(handler);
583
- this.stageCounts[stage] += 1;
584
- return () => {
585
- const index = handlers.indexOf(handler);
586
- if (index >= 0 && handlers[index]) {
587
- handlers[index] = void 0;
588
- this.stageCounts[stage] -= 1;
589
- this.stageCompactionNeeded[stage] = true;
590
- }
591
- };
592
- }
593
- reset(startTimeMs) {
594
- this.timeMs = startTimeMs;
595
- this.frame = 0;
596
- }
597
- advance(step) {
598
- const previousTimeMs = this.timeMs;
599
- this.timeMs = previousTimeMs + step.deltaMs;
600
- this.frame = step.frame;
601
- const context = {
602
- ...step,
603
- timeMs: this.timeMs,
604
- previousTimeMs,
605
- deltaSeconds: step.deltaMs / 1e3
606
- };
607
- this.runStage("prep", context);
608
- if (this.stageCounts.simulate === 0) {
609
- throw new Error("GameFrameLoop requires at least one simulate stage");
1229
+ let bucket = this.targetNameIndex.get(entity.targetname);
1230
+ if (!bucket) {
1231
+ bucket = /* @__PURE__ */ new Set();
1232
+ this.targetNameIndex.set(entity.targetname, bucket);
610
1233
  }
611
- this.runStage("simulate", context);
612
- this.runStage("finish", context);
613
- return context;
1234
+ bucket.add(entity);
614
1235
  }
615
- runStage(stage, context) {
616
- const handlers = this.stageHandlers[stage];
617
- for (let i = 0; i < handlers.length; i += 1) {
618
- const handler = handlers[i];
619
- if (!handler) {
620
- continue;
621
- }
622
- handler(context);
1236
+ unregisterTarget(entity) {
1237
+ if (!entity.targetname) {
1238
+ return;
623
1239
  }
624
- if (this.stageCompactionNeeded[stage]) {
625
- this.compactStageHandlers(stage);
1240
+ const bucket = this.targetNameIndex.get(entity.targetname);
1241
+ if (!bucket) {
1242
+ return;
1243
+ }
1244
+ bucket.delete(entity);
1245
+ if (bucket.size === 0) {
1246
+ this.targetNameIndex.delete(entity.targetname);
626
1247
  }
627
1248
  }
628
- compactStageHandlers(stage) {
629
- const handlers = this.stageHandlers[stage];
630
- let writeIndex = 0;
631
- for (let readIndex = 0; readIndex < handlers.length; readIndex += 1) {
632
- const handler = handlers[readIndex];
633
- if (handler) {
634
- handlers[writeIndex] = handler;
635
- writeIndex += 1;
1249
+ useTargetsImmediate(entity, activator) {
1250
+ if (entity.target) {
1251
+ for (const target of this.findByTargetName(entity.target)) {
1252
+ if (target === entity) {
1253
+ continue;
1254
+ }
1255
+ target.use?.(target, entity, activator);
1256
+ }
1257
+ }
1258
+ if (entity.killtarget) {
1259
+ for (const victim of this.findByTargetName(entity.killtarget)) {
1260
+ if (victim === entity) {
1261
+ continue;
1262
+ }
1263
+ this.free(victim);
636
1264
  }
637
1265
  }
638
- handlers.length = writeIndex;
639
- this.stageCompactionNeeded[stage] = false;
640
- }
641
- get time() {
642
- return this.timeMs;
643
- }
644
- get frameNumber() {
645
- return this.frame;
646
1266
  }
647
1267
  };
648
1268
 
649
- // src/level.ts
650
- var ZERO_STATE = {
1269
+ // src/entities/utils.ts
1270
+ var VEC_UP = { x: 0, y: -1, z: 0 };
1271
+ var MOVEDIR_UP = { x: 0, y: 0, z: 1 };
1272
+ var VEC_DOWN = { x: 0, y: -2, z: 0 };
1273
+ var MOVEDIR_DOWN = { x: 0, y: 0, z: -1 };
1274
+ function vecEquals(a, b) {
1275
+ return a.x === b.x && a.y === b.y && a.z === b.z;
1276
+ }
1277
+ function isZeroVector(vector) {
1278
+ return vecEquals(vector, ZERO_VEC3);
1279
+ }
1280
+ function setMovedir(angles) {
1281
+ if (vecEquals(angles, VEC_UP)) {
1282
+ return { ...MOVEDIR_UP };
1283
+ }
1284
+ if (vecEquals(angles, VEC_DOWN)) {
1285
+ return { ...MOVEDIR_DOWN };
1286
+ }
1287
+ return { ...angleVectors(angles).forward };
1288
+ }
1289
+
1290
+ // src/entities/triggers.ts
1291
+ var FRAME_TIME_SECONDS = 1 / 40;
1292
+ var THINK_INTERVAL = 0.1;
1293
+ var HURT_INTERVAL = 0.1;
1294
+ var TRIGGER_SPAWNFLAGS = {
1295
+ Monster: 1 << 0,
1296
+ NotPlayer: 1 << 1,
1297
+ Triggered: 1 << 2,
1298
+ Toggle: 1 << 3,
1299
+ Latched: 1 << 4,
1300
+ Clip: 1 << 5
1301
+ };
1302
+ var RELAY_SPAWNFLAGS = {
1303
+ NoSound: 1 << 0
1304
+ };
1305
+ var COUNTER_SPAWNFLAGS = {
1306
+ NoMessage: 1 << 0
1307
+ };
1308
+ var PUSH_SPAWNFLAGS = {
1309
+ Once: 1 << 0,
1310
+ Plus: 1 << 1,
1311
+ Silent: 1 << 2,
1312
+ StartOff: 1 << 3,
1313
+ Clip: 1 << 4
1314
+ };
1315
+ var HURT_SPAWNFLAGS = {
1316
+ StartOff: 1 << 0,
1317
+ Toggle: 1 << 1,
1318
+ Silent: 1 << 2,
1319
+ NoProtection: 1 << 3,
1320
+ Slow: 1 << 4,
1321
+ NoPlayers: 1 << 5,
1322
+ NoMonsters: 1 << 6,
1323
+ Clip: 1 << 7
1324
+ };
1325
+ var TELEPORT_SPAWNFLAGS = {
1326
+ StartOn: 1 << 3
1327
+ };
1328
+ var GRAVITY_SPAWNFLAGS = {
1329
+ Toggle: 1 << 0,
1330
+ StartOff: 1 << 1,
1331
+ Clip: 1 << 2
1332
+ };
1333
+ var MONSTERJUMP_SPAWNFLAGS = {
1334
+ Toggle: 1 << 0,
1335
+ StartOff: 1 << 1,
1336
+ Clip: 1 << 2
1337
+ };
1338
+ function trainResume(train, entities) {
1339
+ if (!train.think) {
1340
+ train.think = (self) => {
1341
+ self.nextthink = 0;
1342
+ };
1343
+ }
1344
+ entities.scheduleThink(train, entities.timeSeconds + FRAME_TIME_SECONDS);
1345
+ }
1346
+ function initTrigger(entity) {
1347
+ entity.movetype = 0 /* None */;
1348
+ entity.solid = 1 /* Trigger */;
1349
+ entity.svflags |= 1 /* NoClient */;
1350
+ entity.movedir = setMovedir(entity.angles);
1351
+ entity.angles = { x: 0, y: 0, z: 0 };
1352
+ }
1353
+ function multiWait(self) {
1354
+ self.nextthink = 0;
1355
+ self.think = void 0;
1356
+ }
1357
+ function canActivate(trigger, other) {
1358
+ if (trigger.solid === 0 /* Not */) {
1359
+ return false;
1360
+ }
1361
+ if (other.svflags & 8 /* Player */) {
1362
+ if (trigger.spawnflags & TRIGGER_SPAWNFLAGS.NotPlayer) {
1363
+ return false;
1364
+ }
1365
+ } else if (other.svflags & 4 /* Monster */) {
1366
+ if ((trigger.spawnflags & TRIGGER_SPAWNFLAGS.Monster) === 0) {
1367
+ return false;
1368
+ }
1369
+ } else {
1370
+ return false;
1371
+ }
1372
+ if (!isZeroVector(trigger.movedir)) {
1373
+ const forward = angleVectors(other.angles).forward;
1374
+ const dot = forward.x * trigger.movedir.x + forward.y * trigger.movedir.y + forward.z * trigger.movedir.z;
1375
+ if (dot < 0) {
1376
+ return false;
1377
+ }
1378
+ }
1379
+ return true;
1380
+ }
1381
+ function multiTrigger(self, entities) {
1382
+ if (self.nextthink > entities.timeSeconds) {
1383
+ return;
1384
+ }
1385
+ entities.useTargets(self, self.activator);
1386
+ if (self.wait > 0) {
1387
+ self.think = multiWait;
1388
+ entities.scheduleThink(self, entities.timeSeconds + self.wait);
1389
+ } else {
1390
+ self.touch = void 0;
1391
+ self.think = (entity) => {
1392
+ entities.free(entity);
1393
+ };
1394
+ entities.scheduleThink(self, entities.timeSeconds + FRAME_TIME_SECONDS);
1395
+ }
1396
+ }
1397
+ function touchMulti(self, other, entities) {
1398
+ if (!other) {
1399
+ return;
1400
+ }
1401
+ if (!canActivate(self, other)) {
1402
+ return;
1403
+ }
1404
+ self.activator = other;
1405
+ multiTrigger(self, entities);
1406
+ }
1407
+ function useMulti(self, _other, activator, entities) {
1408
+ if (self.spawnflags & TRIGGER_SPAWNFLAGS.Toggle) {
1409
+ self.solid = self.solid === 1 /* Trigger */ ? 0 /* Not */ : 1 /* Trigger */;
1410
+ return;
1411
+ }
1412
+ self.activator = activator;
1413
+ multiTrigger(self, entities);
1414
+ }
1415
+ function triggerEnable(self) {
1416
+ self.solid = 1 /* Trigger */;
1417
+ }
1418
+ function registerTriggerMultiple(registry) {
1419
+ registry.register("trigger_multiple", (entity, context) => {
1420
+ initTrigger(entity);
1421
+ if (entity.wait === 0) {
1422
+ entity.wait = 0.2;
1423
+ }
1424
+ if (entity.spawnflags & TRIGGER_SPAWNFLAGS.Latched) {
1425
+ }
1426
+ if (entity.spawnflags & (TRIGGER_SPAWNFLAGS.Triggered | TRIGGER_SPAWNFLAGS.Toggle)) {
1427
+ entity.solid = 0 /* Not */;
1428
+ entity.use = (self, other, activator) => {
1429
+ triggerEnable(self);
1430
+ useMulti(self, other, activator ?? other, context.entities);
1431
+ };
1432
+ } else {
1433
+ entity.use = (self, other, activator) => useMulti(self, other, activator ?? other, context.entities);
1434
+ }
1435
+ entity.touch = (self, other) => touchMulti(self, other, context.entities);
1436
+ });
1437
+ }
1438
+ function registerTriggerOnce(registry) {
1439
+ registry.register("trigger_once", (entity, context) => {
1440
+ entity.wait = -1;
1441
+ registry.get("trigger_multiple")?.(entity, context);
1442
+ });
1443
+ }
1444
+ function registerTriggerRelay(registry) {
1445
+ registry.register("trigger_relay", (entity, context) => {
1446
+ if (entity.spawnflags & RELAY_SPAWNFLAGS.NoSound) {
1447
+ entity.noise_index = -1;
1448
+ }
1449
+ entity.use = (self, _other, activator) => {
1450
+ context.entities.useTargets(self, activator ?? self);
1451
+ };
1452
+ });
1453
+ }
1454
+ function registerTriggerAlways(registry) {
1455
+ registry.register("trigger_always", (entity, context) => {
1456
+ if (entity.delay === 0) {
1457
+ entity.delay = 0.2;
1458
+ }
1459
+ context.entities.useTargets(entity, entity);
1460
+ });
1461
+ }
1462
+ function counterUse(self, _other, activator, entities) {
1463
+ if (self.count === 0) {
1464
+ return;
1465
+ }
1466
+ self.count -= 1;
1467
+ if (self.count > 0) {
1468
+ return;
1469
+ }
1470
+ self.activator = activator;
1471
+ multiTrigger(self, entities);
1472
+ }
1473
+ function registerTriggerCounter(registry) {
1474
+ registry.register("trigger_counter", (entity, context) => {
1475
+ entity.wait = -1;
1476
+ if (entity.count === 0) {
1477
+ entity.count = 2;
1478
+ }
1479
+ entity.use = (self, other, activator) => counterUse(self, other, activator ?? other, context.entities);
1480
+ if (!(entity.spawnflags & COUNTER_SPAWNFLAGS.NoMessage)) {
1481
+ entity.message = entity.message ?? "sequence complete";
1482
+ }
1483
+ });
1484
+ }
1485
+ function triggerKeyUse(self, activator, entities, warn) {
1486
+ if (!self.item || !activator) {
1487
+ return;
1488
+ }
1489
+ const available = activator.inventory[self.item] ?? 0;
1490
+ if (available <= 0) {
1491
+ if (self.timestamp > entities.timeSeconds) {
1492
+ return;
1493
+ }
1494
+ self.timestamp = entities.timeSeconds + 5;
1495
+ warn(`Missing required key item: ${self.item}`);
1496
+ return;
1497
+ }
1498
+ activator.inventory[self.item] = available - 1;
1499
+ if (activator.inventory[self.item] <= 0) {
1500
+ delete activator.inventory[self.item];
1501
+ }
1502
+ entities.useTargets(self, activator);
1503
+ self.use = void 0;
1504
+ }
1505
+ function registerTriggerKey(registry) {
1506
+ registry.register("trigger_key", (entity, context) => {
1507
+ const requiredItem = context.keyValues.item;
1508
+ if (!requiredItem) {
1509
+ context.warn("trigger_key requires an item");
1510
+ context.free(entity);
1511
+ return;
1512
+ }
1513
+ entity.item = requiredItem;
1514
+ entity.use = (self, other, activator) => triggerKeyUse(self, activator ?? other, context.entities, context.warn);
1515
+ });
1516
+ }
1517
+ function triggerPushTouch(self, other, entities) {
1518
+ if (!other) {
1519
+ return;
1520
+ }
1521
+ if (other.classname === "grenade" || other.health > 0) {
1522
+ const scale = (self.speed || 1e3) * 10;
1523
+ other.velocity = {
1524
+ x: self.movedir.x * scale,
1525
+ y: self.movedir.y * scale,
1526
+ z: self.movedir.z * scale
1527
+ };
1528
+ }
1529
+ if (self.spawnflags & PUSH_SPAWNFLAGS.Once) {
1530
+ entities.free(self);
1531
+ }
1532
+ }
1533
+ function toggleSolid(self) {
1534
+ self.solid = self.solid === 0 /* Not */ ? 1 /* Trigger */ : 0 /* Not */;
1535
+ }
1536
+ function triggerPushInactive(self, entities, touchHandler) {
1537
+ if (self.delay > entities.timeSeconds) {
1538
+ entities.scheduleThink(self, entities.timeSeconds + THINK_INTERVAL);
1539
+ return;
1540
+ }
1541
+ self.touch = touchHandler;
1542
+ self.think = (entity) => triggerPushActive(entity, entities, touchHandler);
1543
+ self.delay = entities.timeSeconds + self.wait;
1544
+ entities.scheduleThink(self, entities.timeSeconds + THINK_INTERVAL);
1545
+ }
1546
+ function triggerPushActive(self, entities, touchHandler) {
1547
+ if (self.delay > entities.timeSeconds) {
1548
+ entities.scheduleThink(self, entities.timeSeconds + THINK_INTERVAL);
1549
+ return;
1550
+ }
1551
+ self.touch = void 0;
1552
+ self.think = (entity) => triggerPushInactive(entity, entities, touchHandler);
1553
+ self.delay = entities.timeSeconds + self.wait;
1554
+ entities.scheduleThink(self, entities.timeSeconds + THINK_INTERVAL);
1555
+ }
1556
+ function registerTriggerPush(registry) {
1557
+ registry.register("trigger_push", (entity, context) => {
1558
+ initTrigger(entity);
1559
+ const touchHandler = (self, other) => triggerPushTouch(self, other, context.entities);
1560
+ entity.touch = touchHandler;
1561
+ if (!entity.speed) {
1562
+ entity.speed = 1e3;
1563
+ }
1564
+ if (entity.spawnflags & PUSH_SPAWNFLAGS.Plus) {
1565
+ if (!entity.wait) {
1566
+ entity.wait = 10;
1567
+ }
1568
+ entity.delay = context.entities.timeSeconds + entity.wait;
1569
+ entity.think = (self) => triggerPushActive(self, context.entities, touchHandler);
1570
+ context.entities.scheduleThink(entity, context.entities.timeSeconds + THINK_INTERVAL);
1571
+ }
1572
+ if (entity.targetname) {
1573
+ entity.use = (self) => {
1574
+ toggleSolid(self);
1575
+ self.touch = self.solid === 1 /* Trigger */ ? touchHandler : void 0;
1576
+ };
1577
+ if (entity.spawnflags & PUSH_SPAWNFLAGS.StartOff) {
1578
+ entity.solid = 0 /* Not */;
1579
+ entity.touch = void 0;
1580
+ }
1581
+ } else if (entity.spawnflags & PUSH_SPAWNFLAGS.StartOff) {
1582
+ context.warn("trigger_push is START_OFF but not targeted.");
1583
+ entity.touch = void 0;
1584
+ entity.solid = 3 /* Bsp */;
1585
+ entity.movetype = 2 /* Push */;
1586
+ }
1587
+ });
1588
+ }
1589
+ function hurtTouch(self, other, entities) {
1590
+ if (!other) {
1591
+ return;
1592
+ }
1593
+ if (!other.takedamage && other.classname !== "grenade") {
1594
+ return;
1595
+ }
1596
+ if (self.spawnflags & HURT_SPAWNFLAGS.NoMonsters && other.svflags & 4 /* Monster */) {
1597
+ return;
1598
+ }
1599
+ if (self.spawnflags & HURT_SPAWNFLAGS.NoPlayers && other.svflags & 8 /* Player */) {
1600
+ return;
1601
+ }
1602
+ if (self.timestamp > entities.timeSeconds) {
1603
+ return;
1604
+ }
1605
+ self.timestamp = entities.timeSeconds + (self.spawnflags & HURT_SPAWNFLAGS.Slow ? 1 : HURT_INTERVAL);
1606
+ const damage = self.dmg || 5;
1607
+ other.health -= damage;
1608
+ }
1609
+ function registerTriggerHurt(registry) {
1610
+ registry.register("trigger_hurt", (entity, context) => {
1611
+ initTrigger(entity);
1612
+ entity.dmg = entity.dmg || 5;
1613
+ const touchHandler = (self, other) => hurtTouch(self, other, context.entities);
1614
+ entity.touch = touchHandler;
1615
+ if (entity.spawnflags & HURT_SPAWNFLAGS.StartOff) {
1616
+ entity.solid = 0 /* Not */;
1617
+ entity.touch = void 0;
1618
+ }
1619
+ if (entity.spawnflags & HURT_SPAWNFLAGS.Toggle) {
1620
+ entity.use = (self) => {
1621
+ toggleSolid(self);
1622
+ self.touch = self.solid === 1 /* Trigger */ ? touchHandler : void 0;
1623
+ };
1624
+ }
1625
+ });
1626
+ }
1627
+ function teleportTouch(self, other, entities, warn) {
1628
+ if (!other) {
1629
+ return;
1630
+ }
1631
+ if (self.delay > 0) {
1632
+ return;
1633
+ }
1634
+ const destination = entities.pickTarget(self.target);
1635
+ if (!destination) {
1636
+ warn("trigger_teleport target not found");
1637
+ return;
1638
+ }
1639
+ const destOrigin = {
1640
+ x: destination.origin.x,
1641
+ y: destination.origin.y,
1642
+ z: destination.origin.z + 10
1643
+ };
1644
+ other.origin = { ...destOrigin };
1645
+ other.old_origin = { ...destOrigin };
1646
+ other.velocity = { x: 0, y: 0, z: 0 };
1647
+ other.groundentity = null;
1648
+ other.angles = { ...destination.angles };
1649
+ entities.killBox(other);
1650
+ }
1651
+ function registerTriggerTeleport(registry) {
1652
+ registry.register("trigger_teleport", (entity, context) => {
1653
+ if (!entity.wait) {
1654
+ entity.wait = 0.2;
1655
+ }
1656
+ initTrigger(entity);
1657
+ if (entity.targetname) {
1658
+ entity.use = (self) => {
1659
+ self.delay = self.delay > 0 ? 0 : 1;
1660
+ };
1661
+ if ((entity.spawnflags & TELEPORT_SPAWNFLAGS.StartOn) === 0) {
1662
+ entity.delay = 1;
1663
+ }
1664
+ }
1665
+ entity.touch = (self, other) => teleportTouch(self, other, context.entities, context.warn);
1666
+ });
1667
+ }
1668
+ function gravityTouch(self, other) {
1669
+ if (!other) {
1670
+ return;
1671
+ }
1672
+ if (self.spawnflags & GRAVITY_SPAWNFLAGS.Clip) {
1673
+ }
1674
+ other.gravity = self.gravity;
1675
+ }
1676
+ function registerTriggerGravity(registry) {
1677
+ registry.register("trigger_gravity", (entity, context) => {
1678
+ const gravityText = context.keyValues.gravity;
1679
+ if (!gravityText) {
1680
+ context.warn("trigger_gravity requires a gravity value");
1681
+ context.free(entity);
1682
+ return;
1683
+ }
1684
+ initTrigger(entity);
1685
+ entity.gravity = Number.parseFloat(gravityText) || 0;
1686
+ const touchHandler = (self, other) => gravityTouch(self, other);
1687
+ entity.touch = touchHandler;
1688
+ if (entity.spawnflags & GRAVITY_SPAWNFLAGS.StartOff) {
1689
+ entity.solid = 0 /* Not */;
1690
+ entity.touch = void 0;
1691
+ }
1692
+ if (entity.spawnflags & GRAVITY_SPAWNFLAGS.Toggle) {
1693
+ entity.use = (self) => {
1694
+ toggleSolid(self);
1695
+ self.touch = self.solid === 1 /* Trigger */ ? touchHandler : void 0;
1696
+ };
1697
+ }
1698
+ });
1699
+ }
1700
+ function monsterJumpTouch(self, other) {
1701
+ if (!other) {
1702
+ return;
1703
+ }
1704
+ if ((other.flags & (1 /* Fly */ | 2 /* Swim */)) !== 0) {
1705
+ return;
1706
+ }
1707
+ if (other.svflags & 2 /* DeadMonster */) {
1708
+ return;
1709
+ }
1710
+ if ((other.svflags & 4 /* Monster */) === 0) {
1711
+ return;
1712
+ }
1713
+ other.velocity = {
1714
+ x: self.movedir.x * self.speed,
1715
+ y: self.movedir.y * self.speed,
1716
+ z: other.velocity.z
1717
+ };
1718
+ if (!other.groundentity) {
1719
+ return;
1720
+ }
1721
+ other.groundentity = null;
1722
+ other.velocity = { x: other.velocity.x, y: other.velocity.y, z: self.movedir.z };
1723
+ }
1724
+ function triggerElevatorUse(self, other, entities, warn) {
1725
+ if (!self.movetarget) {
1726
+ return;
1727
+ }
1728
+ if (self.movetarget.nextthink > 0) {
1729
+ return;
1730
+ }
1731
+ if (!other?.pathtarget) {
1732
+ warn("trigger_elevator used with no pathtarget");
1733
+ return;
1734
+ }
1735
+ const target = entities.pickTarget(other.pathtarget);
1736
+ if (!target) {
1737
+ warn(`trigger_elevator used with bad pathtarget: ${other.pathtarget}`);
1738
+ return;
1739
+ }
1740
+ self.movetarget.target_ent = target;
1741
+ trainResume(self.movetarget, entities);
1742
+ }
1743
+ function triggerElevatorInit(self, entities, warn) {
1744
+ if (!self.target) {
1745
+ warn("trigger_elevator has no target");
1746
+ return;
1747
+ }
1748
+ const target = entities.pickTarget(self.target);
1749
+ if (!target) {
1750
+ warn(`trigger_elevator unable to find target ${self.target}`);
1751
+ return;
1752
+ }
1753
+ self.movetarget = target;
1754
+ if (target.classname !== "func_train") {
1755
+ warn(`trigger_elevator target ${self.target} is not a train`);
1756
+ return;
1757
+ }
1758
+ self.use = (entity, other, activator) => triggerElevatorUse(entity, other ?? activator ?? null, entities, warn);
1759
+ self.svflags |= 1 /* NoClient */;
1760
+ }
1761
+ function registerTriggerElevator(registry) {
1762
+ registry.register("trigger_elevator", (entity, context) => {
1763
+ entity.think = (self) => triggerElevatorInit(self, context.entities, context.warn);
1764
+ context.entities.scheduleThink(entity, context.entities.timeSeconds + FRAME_TIME_SECONDS);
1765
+ });
1766
+ }
1767
+ function registerTriggerMonsterJump(registry) {
1768
+ registry.register("trigger_monsterjump", (entity, context) => {
1769
+ const heightText = context.keyValues.height;
1770
+ const height = heightText ? Number.parseFloat(heightText) || 0 : 200;
1771
+ if (entity.angles.y === 0) {
1772
+ entity.angles = { ...entity.angles, y: 360 };
1773
+ }
1774
+ if (!entity.speed) {
1775
+ entity.speed = 200;
1776
+ }
1777
+ initTrigger(entity);
1778
+ entity.movedir = { ...entity.movedir, z: height };
1779
+ const touchHandler = (self, other) => monsterJumpTouch(self, other);
1780
+ entity.touch = touchHandler;
1781
+ if (entity.spawnflags & MONSTERJUMP_SPAWNFLAGS.StartOff) {
1782
+ entity.solid = 0 /* Not */;
1783
+ entity.touch = void 0;
1784
+ }
1785
+ if (entity.spawnflags & MONSTERJUMP_SPAWNFLAGS.Toggle) {
1786
+ entity.use = (self) => {
1787
+ toggleSolid(self);
1788
+ self.touch = self.solid === 1 /* Trigger */ ? touchHandler : void 0;
1789
+ };
1790
+ }
1791
+ });
1792
+ }
1793
+ function registerTriggerSpawns(registry) {
1794
+ registerTriggerMultiple(registry);
1795
+ registerTriggerOnce(registry);
1796
+ registerTriggerRelay(registry);
1797
+ registerTriggerAlways(registry);
1798
+ registerTriggerCounter(registry);
1799
+ registerTriggerKey(registry);
1800
+ registerTriggerPush(registry);
1801
+ registerTriggerHurt(registry);
1802
+ registerTriggerTeleport(registry);
1803
+ registerTriggerGravity(registry);
1804
+ registerTriggerElevator(registry);
1805
+ registerTriggerMonsterJump(registry);
1806
+ }
1807
+
1808
+ // src/entities/spawn.ts
1809
+ var FIELD_LOOKUP = new Map(
1810
+ ENTITY_FIELD_METADATA.map((field) => [field.name, field])
1811
+ );
1812
+ function parseVec3(text) {
1813
+ const parts = text.trim().split(/\s+/);
1814
+ const [x = 0, y = 0, z = 0] = parts.map((part) => Number.parseFloat(part)).map((value) => Number.isNaN(value) ? 0 : value);
1815
+ return { x, y, z };
1816
+ }
1817
+ function parseBoolean(text) {
1818
+ const normalized = text.trim().toLowerCase();
1819
+ return normalized === "1" || normalized === "true" || normalized === "yes";
1820
+ }
1821
+ function parseValue(type, value) {
1822
+ switch (type) {
1823
+ case "int":
1824
+ return Number.parseInt(value, 10) || 0;
1825
+ case "float":
1826
+ return Number.parseFloat(value) || 0;
1827
+ case "boolean":
1828
+ return parseBoolean(value);
1829
+ case "vec3":
1830
+ return parseVec3(value);
1831
+ case "string":
1832
+ return value;
1833
+ case "entity":
1834
+ case "callback":
1835
+ return void 0;
1836
+ default:
1837
+ return value;
1838
+ }
1839
+ }
1840
+ function applyEntityKeyValues(entity, values) {
1841
+ if ("angle" in values && !("angles" in values)) {
1842
+ entity.angles = { x: 0, y: Number.parseFloat(values.angle) || 0, z: 0 };
1843
+ }
1844
+ for (const [key, rawValue] of Object.entries(values)) {
1845
+ if (key.startsWith("_")) {
1846
+ continue;
1847
+ }
1848
+ const descriptor = FIELD_LOOKUP.get(key);
1849
+ if (!descriptor) {
1850
+ continue;
1851
+ }
1852
+ const parsed = parseValue(descriptor.type, rawValue);
1853
+ if (parsed !== void 0) {
1854
+ entity[descriptor.name] = parsed;
1855
+ }
1856
+ }
1857
+ entity.size = {
1858
+ x: entity.maxs.x - entity.mins.x,
1859
+ y: entity.maxs.y - entity.mins.y,
1860
+ z: entity.maxs.z - entity.mins.z
1861
+ };
1862
+ }
1863
+ function parseQuoted(text, start) {
1864
+ let index = start;
1865
+ let result = "";
1866
+ while (index < text.length) {
1867
+ const char = text[index];
1868
+ if (char === '"') {
1869
+ return { value: result, nextIndex: index + 1 };
1870
+ }
1871
+ result += char;
1872
+ index += 1;
1873
+ }
1874
+ throw new Error("Unterminated quoted string in entity lump");
1875
+ }
1876
+ function consumeWhitespace(text, start) {
1877
+ let index = start;
1878
+ while (index < text.length && /\s/.test(text[index] ?? "")) {
1879
+ index += 1;
1880
+ }
1881
+ return index;
1882
+ }
1883
+ function parseToken(text, start) {
1884
+ const index = consumeWhitespace(text, start);
1885
+ if (index >= text.length) {
1886
+ return { token: null, nextIndex: index };
1887
+ }
1888
+ const current = text[index];
1889
+ if (current === "{" || current === "}") {
1890
+ return { token: current, nextIndex: index + 1 };
1891
+ }
1892
+ if (current !== '"') {
1893
+ throw new Error(`Unexpected token in entity lump: ${current}`);
1894
+ }
1895
+ const quoted = parseQuoted(text, index + 1);
1896
+ return { token: quoted.value, nextIndex: quoted.nextIndex };
1897
+ }
1898
+ function parseEntityLump(text) {
1899
+ const entities = [];
1900
+ let index = 0;
1901
+ while (index < text.length) {
1902
+ const open = parseToken(text, index);
1903
+ index = open.nextIndex;
1904
+ if (open.token === null) {
1905
+ break;
1906
+ }
1907
+ if (open.token !== "{") {
1908
+ throw new Error("Expected { at start of entity definition");
1909
+ }
1910
+ const entity = {};
1911
+ while (true) {
1912
+ const keyToken = parseToken(text, index);
1913
+ index = keyToken.nextIndex;
1914
+ if (keyToken.token === null) {
1915
+ throw new Error("EOF reached while parsing entity");
1916
+ }
1917
+ if (keyToken.token === "}") {
1918
+ break;
1919
+ }
1920
+ const valueToken = parseToken(text, index);
1921
+ index = valueToken.nextIndex;
1922
+ if (valueToken.token === null || valueToken.token === "{" || valueToken.token === "}") {
1923
+ throw new Error("Malformed entity key/value pair");
1924
+ }
1925
+ if (!keyToken.token.startsWith("_")) {
1926
+ entity[keyToken.token] = valueToken.token;
1927
+ }
1928
+ }
1929
+ entities.push(entity);
1930
+ }
1931
+ return entities;
1932
+ }
1933
+ var SpawnRegistry = class {
1934
+ constructor() {
1935
+ this.registry = /* @__PURE__ */ new Map();
1936
+ }
1937
+ register(classname, spawn) {
1938
+ this.registry.set(classname, spawn);
1939
+ }
1940
+ get(classname) {
1941
+ return this.registry.get(classname);
1942
+ }
1943
+ };
1944
+ function defaultWarn(message) {
1945
+ void message;
1946
+ }
1947
+ function spawnEntityFromDictionary(dictionary, options) {
1948
+ const warn = options.onWarning ?? defaultWarn;
1949
+ const classname = dictionary.classname;
1950
+ if (!classname) {
1951
+ warn("Encountered entity with no classname");
1952
+ return null;
1953
+ }
1954
+ const isWorld = classname === "worldspawn";
1955
+ const entity = isWorld ? options.entities.world : options.entities.spawn();
1956
+ applyEntityKeyValues(entity, dictionary);
1957
+ const context = {
1958
+ keyValues: dictionary,
1959
+ entities: options.entities,
1960
+ warn,
1961
+ free(target) {
1962
+ options.entities.freeImmediate(target);
1963
+ }
1964
+ };
1965
+ const spawnFunc = options.registry.get(classname);
1966
+ if (!spawnFunc) {
1967
+ warn(`${classname} does not have a spawn function`);
1968
+ if (!isWorld) {
1969
+ options.entities.freeImmediate(entity);
1970
+ }
1971
+ return null;
1972
+ }
1973
+ spawnFunc(entity, context);
1974
+ if (!entity.inUse) {
1975
+ return null;
1976
+ }
1977
+ options.entities.finalizeSpawn(entity);
1978
+ return entity;
1979
+ }
1980
+ function spawnEntitiesFromText(text, options) {
1981
+ const parsed = parseEntityLump(text);
1982
+ const spawned = [];
1983
+ for (const dictionary of parsed) {
1984
+ const entity = spawnEntityFromDictionary(dictionary, options);
1985
+ if (entity) {
1986
+ spawned.push(entity);
1987
+ }
1988
+ }
1989
+ return spawned;
1990
+ }
1991
+ function registerDefaultSpawns(registry) {
1992
+ registry.register("worldspawn", (entity) => {
1993
+ entity.movetype = 2 /* Push */;
1994
+ entity.solid = 3 /* Bsp */;
1995
+ entity.modelindex = entity.modelindex || 1;
1996
+ });
1997
+ registry.register("info_player_start", () => {
1998
+ });
1999
+ registry.register("info_player_deathmatch", () => {
2000
+ });
2001
+ registry.register("info_player_coop", () => {
2002
+ });
2003
+ registry.register("info_null", (entity, context) => {
2004
+ context.free(entity);
2005
+ });
2006
+ registry.register("info_notnull", () => {
2007
+ });
2008
+ registry.register("info_teleport_destination", () => {
2009
+ });
2010
+ registerTriggerSpawns(registry);
2011
+ }
2012
+ function createDefaultSpawnRegistry() {
2013
+ const registry = new SpawnRegistry();
2014
+ registerDefaultSpawns(registry);
2015
+ return registry;
2016
+ }
2017
+
2018
+ // src/loop.ts
2019
+ var orderedStageNames = [
2020
+ "prep",
2021
+ "simulate",
2022
+ "finish"
2023
+ ];
2024
+ var GameFrameLoop = class {
2025
+ constructor(initialStages) {
2026
+ this.timeMs = 0;
2027
+ this.frame = 0;
2028
+ this.stageHandlers = {
2029
+ prep: [],
2030
+ simulate: [],
2031
+ finish: []
2032
+ };
2033
+ this.stageCounts = {
2034
+ prep: 0,
2035
+ simulate: 0,
2036
+ finish: 0
2037
+ };
2038
+ this.stageCompactionNeeded = {
2039
+ prep: false,
2040
+ simulate: false,
2041
+ finish: false
2042
+ };
2043
+ if (initialStages) {
2044
+ for (const stageName of orderedStageNames) {
2045
+ const handler = initialStages[stageName];
2046
+ if (handler) {
2047
+ this.addStage(stageName, handler);
2048
+ }
2049
+ }
2050
+ }
2051
+ }
2052
+ addStage(stage, handler) {
2053
+ const handlers = this.stageHandlers[stage];
2054
+ handlers.push(handler);
2055
+ this.stageCounts[stage] += 1;
2056
+ return () => {
2057
+ const index = handlers.indexOf(handler);
2058
+ if (index >= 0 && handlers[index]) {
2059
+ handlers[index] = void 0;
2060
+ this.stageCounts[stage] -= 1;
2061
+ this.stageCompactionNeeded[stage] = true;
2062
+ }
2063
+ };
2064
+ }
2065
+ reset(startTimeMs) {
2066
+ this.timeMs = startTimeMs;
2067
+ this.frame = 0;
2068
+ }
2069
+ advance(step) {
2070
+ const previousTimeMs = this.timeMs;
2071
+ this.timeMs = previousTimeMs + step.deltaMs;
2072
+ this.frame = step.frame;
2073
+ const context = {
2074
+ ...step,
2075
+ timeMs: this.timeMs,
2076
+ previousTimeMs,
2077
+ deltaSeconds: step.deltaMs / 1e3
2078
+ };
2079
+ this.runStage("prep", context);
2080
+ if (this.stageCounts.simulate === 0) {
2081
+ throw new Error("GameFrameLoop requires at least one simulate stage");
2082
+ }
2083
+ this.runStage("simulate", context);
2084
+ this.runStage("finish", context);
2085
+ return context;
2086
+ }
2087
+ runStage(stage, context) {
2088
+ const handlers = this.stageHandlers[stage];
2089
+ for (let i = 0; i < handlers.length; i += 1) {
2090
+ const handler = handlers[i];
2091
+ if (!handler) {
2092
+ continue;
2093
+ }
2094
+ handler(context);
2095
+ }
2096
+ if (this.stageCompactionNeeded[stage]) {
2097
+ this.compactStageHandlers(stage);
2098
+ }
2099
+ }
2100
+ compactStageHandlers(stage) {
2101
+ const handlers = this.stageHandlers[stage];
2102
+ let writeIndex = 0;
2103
+ for (let readIndex = 0; readIndex < handlers.length; readIndex += 1) {
2104
+ const handler = handlers[readIndex];
2105
+ if (handler) {
2106
+ handlers[writeIndex] = handler;
2107
+ writeIndex += 1;
2108
+ }
2109
+ }
2110
+ handlers.length = writeIndex;
2111
+ this.stageCompactionNeeded[stage] = false;
2112
+ }
2113
+ get time() {
2114
+ return this.timeMs;
2115
+ }
2116
+ get frameNumber() {
2117
+ return this.frame;
2118
+ }
2119
+ };
2120
+
2121
+ // src/level.ts
2122
+ var ZERO_STATE = {
651
2123
  frameNumber: 0,
652
2124
  timeSeconds: 0,
653
2125
  previousTimeSeconds: 0,
@@ -655,30 +2127,1306 @@ var ZERO_STATE = {
655
2127
  };
656
2128
  var LevelClock = class {
657
2129
  constructor() {
658
- this.state = ZERO_STATE;
2130
+ this.state = ZERO_STATE;
2131
+ }
2132
+ start(startTimeMs) {
2133
+ const startSeconds = startTimeMs / 1e3;
2134
+ this.state = {
2135
+ frameNumber: 0,
2136
+ timeSeconds: startSeconds,
2137
+ previousTimeSeconds: startSeconds,
2138
+ deltaSeconds: 0
2139
+ };
2140
+ }
2141
+ tick(context) {
2142
+ this.state = {
2143
+ frameNumber: context.frame,
2144
+ timeSeconds: context.timeMs / 1e3,
2145
+ previousTimeSeconds: context.previousTimeMs / 1e3,
2146
+ deltaSeconds: context.deltaSeconds
2147
+ };
2148
+ return this.state;
2149
+ }
2150
+ get current() {
2151
+ return this.state;
2152
+ }
2153
+ restore(state) {
2154
+ this.state = { ...state };
2155
+ }
2156
+ };
2157
+
2158
+ // src/ai/constants.ts
2159
+ var RANGE_MELEE = 20;
2160
+ var RANGE_NEAR = 440;
2161
+ var RANGE_MID = 940;
2162
+ var FL_NOVISIBLE = 1 << 24;
2163
+ var SPAWNFLAG_MONSTER_AMBUSH = 1 << 0;
2164
+ var AIFlags = /* @__PURE__ */ ((AIFlags2) => {
2165
+ AIFlags2[AIFlags2["Pathing"] = 1073741824] = "Pathing";
2166
+ return AIFlags2;
2167
+ })(AIFlags || {});
2168
+ var TraceMask = /* @__PURE__ */ ((TraceMask2) => {
2169
+ TraceMask2[TraceMask2["Opaque"] = 1] = "Opaque";
2170
+ TraceMask2[TraceMask2["Window"] = 2] = "Window";
2171
+ return TraceMask2;
2172
+ })(TraceMask || {});
2173
+
2174
+ // src/ai/movement.ts
2175
+ function yawVector(yawDegrees, distance) {
2176
+ if (distance === 0) {
2177
+ return { x: 0, y: 0, z: 0 };
2178
+ }
2179
+ const radians = degToRad(yawDegrees);
2180
+ return {
2181
+ x: Math.cos(radians) * distance,
2182
+ y: Math.sin(radians) * distance,
2183
+ z: 0
2184
+ };
2185
+ }
2186
+ function walkMove(self, yawDegrees, distance) {
2187
+ const delta = yawVector(yawDegrees, distance);
2188
+ const origin = self.origin;
2189
+ origin.x += delta.x;
2190
+ origin.y += delta.y;
2191
+ origin.z += delta.z;
2192
+ return true;
2193
+ }
2194
+ function changeYaw(self, deltaSeconds) {
2195
+ const current = angleMod(self.angles.y);
2196
+ const ideal = self.ideal_yaw;
2197
+ if (current === ideal) {
2198
+ self.angles.y = current;
2199
+ return;
2200
+ }
2201
+ const speed = self.yaw_speed * deltaSeconds * 10;
2202
+ let move = ideal - current;
2203
+ if (ideal > current) {
2204
+ if (move >= 180) move -= 360;
2205
+ } else if (move <= -180) {
2206
+ move += 360;
2207
+ }
2208
+ if (move > speed) move = speed;
2209
+ else if (move < -speed) move = -speed;
2210
+ self.angles.y = angleMod(current + move);
2211
+ }
2212
+ function facingIdeal(self) {
2213
+ const delta = angleMod(self.angles.y - self.ideal_yaw);
2214
+ const hasPathing = (self.monsterinfo.aiflags & 1073741824 /* Pathing */) !== 0;
2215
+ if (hasPathing) {
2216
+ return !(delta > 5 && delta < 355);
2217
+ }
2218
+ return !(delta > 45 && delta < 315);
2219
+ }
2220
+ function ai_move(self, distance) {
2221
+ walkMove(self, self.angles.y, distance);
2222
+ }
2223
+ function setIdealYawTowards(self, target) {
2224
+ if (!target) return;
2225
+ const toTarget = {
2226
+ x: target.origin.x - self.origin.x,
2227
+ y: target.origin.y - self.origin.y,
2228
+ z: target.origin.z - self.origin.z
2229
+ };
2230
+ self.ideal_yaw = vectorToYaw(toTarget);
2231
+ }
2232
+ function ai_stand(self, deltaSeconds) {
2233
+ changeYaw(self, deltaSeconds);
2234
+ }
2235
+ function ai_walk(self, distance, deltaSeconds) {
2236
+ setIdealYawTowards(self, self.goalentity);
2237
+ changeYaw(self, deltaSeconds);
2238
+ if (distance !== 0) {
2239
+ walkMove(self, self.angles.y, distance);
2240
+ }
2241
+ }
2242
+ function ai_turn(self, distance, deltaSeconds) {
2243
+ if (distance !== 0) {
2244
+ walkMove(self, self.angles.y, distance);
2245
+ }
2246
+ changeYaw(self, deltaSeconds);
2247
+ }
2248
+ function ai_run(self, distance, deltaSeconds) {
2249
+ setIdealYawTowards(self, self.enemy ?? self.goalentity);
2250
+ changeYaw(self, deltaSeconds);
2251
+ if (distance !== 0) {
2252
+ walkMove(self, self.angles.y, distance);
2253
+ }
2254
+ }
2255
+ function ai_face(self, enemy, distance, deltaSeconds) {
2256
+ if (enemy) {
2257
+ setIdealYawTowards(self, enemy);
2258
+ }
2259
+ changeYaw(self, deltaSeconds);
2260
+ if (distance !== 0) {
2261
+ walkMove(self, self.angles.y, distance);
2262
+ }
2263
+ }
2264
+ function ai_charge(self, distance, deltaSeconds) {
2265
+ setIdealYawTowards(self, self.enemy);
2266
+ changeYaw(self, deltaSeconds);
2267
+ if (distance !== 0) {
2268
+ walkMove(self, self.angles.y, distance);
2269
+ }
2270
+ }
2271
+
2272
+ // src/ai/perception.ts
2273
+ var RangeCategory = /* @__PURE__ */ ((RangeCategory2) => {
2274
+ RangeCategory2["Melee"] = "melee";
2275
+ RangeCategory2["Near"] = "near";
2276
+ RangeCategory2["Mid"] = "mid";
2277
+ RangeCategory2["Far"] = "far";
2278
+ return RangeCategory2;
2279
+ })(RangeCategory || {});
2280
+ function absBounds(entity) {
2281
+ return {
2282
+ mins: {
2283
+ x: entity.origin.x + entity.mins.x,
2284
+ y: entity.origin.y + entity.mins.y,
2285
+ z: entity.origin.z + entity.mins.z
2286
+ },
2287
+ maxs: {
2288
+ x: entity.origin.x + entity.maxs.x,
2289
+ y: entity.origin.y + entity.maxs.y,
2290
+ z: entity.origin.z + entity.maxs.z
2291
+ }
2292
+ };
2293
+ }
2294
+ function rangeTo(self, other) {
2295
+ const a = absBounds(self);
2296
+ const b = absBounds(other);
2297
+ const distanceSquared = distanceBetweenBoxesSquared(a.mins, a.maxs, b.mins, b.maxs);
2298
+ return Math.sqrt(distanceSquared);
2299
+ }
2300
+ function classifyRange(distance) {
2301
+ if (distance <= RANGE_MELEE) {
2302
+ return "melee" /* Melee */;
2303
+ }
2304
+ if (distance <= RANGE_NEAR) {
2305
+ return "near" /* Near */;
2306
+ }
2307
+ if (distance <= RANGE_MID) {
2308
+ return "mid" /* Mid */;
2309
+ }
2310
+ return "far" /* Far */;
2311
+ }
2312
+ function infront(self, other) {
2313
+ const { forward } = angleVectors(self.angles);
2314
+ const direction = normalizeVec3(subtractVec3(other.origin, self.origin));
2315
+ const dot = dotVec3(direction, forward);
2316
+ if ((self.spawnflags & SPAWNFLAG_MONSTER_AMBUSH) !== 0 && self.trail_time === 0 && self.enemy === null) {
2317
+ return dot > 0.15;
2318
+ }
2319
+ return dot > -0.3;
2320
+ }
2321
+ function visible(self, other, trace, options) {
2322
+ if ((other.flags & FL_NOVISIBLE) !== 0) {
2323
+ return false;
2324
+ }
2325
+ const start = { x: self.origin.x, y: self.origin.y, z: self.origin.z + self.viewheight };
2326
+ const end = { x: other.origin.x, y: other.origin.y, z: other.origin.z + other.viewheight };
2327
+ const mask = options?.throughGlass ? 1 /* Opaque */ : 1 /* Opaque */ | 2 /* Window */;
2328
+ const result = trace(start, end, self, mask);
2329
+ return result.fraction === 1 || result.entity === other;
2330
+ }
2331
+
2332
+ // src/checksum.ts
2333
+ var FNV_OFFSET_BASIS = 2166136261;
2334
+ var FNV_PRIME = 16777619;
2335
+ function hashBytes(hash, bytes) {
2336
+ let h = hash >>> 0;
2337
+ for (let i = 0; i < bytes.length; i += 1) {
2338
+ h ^= bytes[i] & 255;
2339
+ h = Math.imul(h, FNV_PRIME) >>> 0;
2340
+ }
2341
+ return h >>> 0;
2342
+ }
2343
+ function hashNumber(hash, value) {
2344
+ const buffer = new ArrayBuffer(8);
2345
+ const view = new DataView(buffer);
2346
+ view.setFloat64(0, value, true);
2347
+ const bytes = new Uint8Array(buffer);
2348
+ return hashBytes(hash, bytes);
2349
+ }
2350
+ function hashString(hash, value) {
2351
+ const bytes = new Uint8Array(value.length);
2352
+ for (let i = 0; i < value.length; i += 1) {
2353
+ bytes[i] = value.charCodeAt(i) & 255;
2354
+ }
2355
+ return hashBytes(hash, bytes);
2356
+ }
2357
+ function hashGameState(state) {
2358
+ let hash = FNV_OFFSET_BASIS;
2359
+ hash = hashNumber(hash, state.gravity.x);
2360
+ hash = hashNumber(hash, state.gravity.y);
2361
+ hash = hashNumber(hash, state.gravity.z);
2362
+ hash = hashNumber(hash, state.origin.x);
2363
+ hash = hashNumber(hash, state.origin.y);
2364
+ hash = hashNumber(hash, state.origin.z);
2365
+ hash = hashNumber(hash, state.velocity.x);
2366
+ hash = hashNumber(hash, state.velocity.y);
2367
+ hash = hashNumber(hash, state.velocity.z);
2368
+ hash = hashNumber(hash, state.level.frameNumber);
2369
+ hash = hashNumber(hash, state.level.timeSeconds);
2370
+ hash = hashNumber(hash, state.level.previousTimeSeconds);
2371
+ hash = hashNumber(hash, state.level.deltaSeconds);
2372
+ hash = hashNumber(hash, state.entities.activeCount);
2373
+ hash = hashString(hash, state.entities.worldClassname);
2374
+ return hash >>> 0;
2375
+ }
2376
+
2377
+ // src/save/save.ts
2378
+ var SAVE_FORMAT_VERSION = 1;
2379
+ var MIN_SUPPORTED_VERSION = 1;
2380
+ function ensureObject(value, label) {
2381
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2382
+ throw new Error(`${label} must be an object`);
2383
+ }
2384
+ return value;
2385
+ }
2386
+ function ensureNumber(value, label) {
2387
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2388
+ throw new Error(`${label} must be a finite number`);
2389
+ }
2390
+ return value;
2391
+ }
2392
+ function ensureNumberOrDefault(value, label, defaultValue) {
2393
+ if (value === void 0) {
2394
+ return defaultValue;
2395
+ }
2396
+ return ensureNumber(value, label);
2397
+ }
2398
+ function ensureString(value, label) {
2399
+ if (typeof value !== "string") {
2400
+ throw new Error(`${label} must be a string`);
2401
+ }
2402
+ return value;
2403
+ }
2404
+ function ensureNumberArray(value, label) {
2405
+ if (!Array.isArray(value)) {
2406
+ throw new Error(`${label} must be an array`);
2407
+ }
2408
+ for (const element of value) {
2409
+ ensureNumber(element, `${label} element`);
2410
+ }
2411
+ return value;
2412
+ }
2413
+ function parseLevelState(raw) {
2414
+ if (raw === void 0) {
2415
+ return { frameNumber: 0, timeSeconds: 0, previousTimeSeconds: 0, deltaSeconds: 0 };
2416
+ }
2417
+ const level = ensureObject(raw, "level");
2418
+ return {
2419
+ frameNumber: ensureNumberOrDefault(level.frameNumber, "level.frameNumber", 0),
2420
+ timeSeconds: ensureNumberOrDefault(level.timeSeconds, "level.timeSeconds", 0),
2421
+ previousTimeSeconds: ensureNumberOrDefault(level.previousTimeSeconds, "level.previousTimeSeconds", 0),
2422
+ deltaSeconds: ensureNumberOrDefault(level.deltaSeconds, "level.deltaSeconds", 0)
2423
+ };
2424
+ }
2425
+ function parseRngState(raw) {
2426
+ if (raw === void 0) {
2427
+ return new RandomGenerator().getState();
2428
+ }
2429
+ const rng = ensureObject(raw, "rng");
2430
+ const mt = ensureObject(rng.mt, "rng.mt");
2431
+ const state = ensureNumberArray(mt.state, "rng.mt.state");
2432
+ return {
2433
+ mt: {
2434
+ index: ensureNumber(mt.index, "rng.mt.index"),
2435
+ state
2436
+ }
2437
+ };
2438
+ }
2439
+ function parseThinkEntries(raw) {
2440
+ if (raw === void 0) {
2441
+ return [];
2442
+ }
2443
+ if (!Array.isArray(raw)) {
2444
+ throw new Error("thinks must be an array");
2445
+ }
2446
+ return raw.map((entry, idx) => {
2447
+ const think = ensureObject(entry, `thinks[${idx}]`);
2448
+ return {
2449
+ time: ensureNumber(think.time, `thinks[${idx}].time`),
2450
+ entityIndex: ensureNumber(think.entityIndex, `thinks[${idx}].entityIndex`)
2451
+ };
2452
+ });
2453
+ }
2454
+ function parseEntityFields(raw) {
2455
+ if (raw === void 0) {
2456
+ return {};
2457
+ }
2458
+ const fields = ensureObject(raw, "entity.fields");
2459
+ const parsed = {};
2460
+ for (const [name, value] of Object.entries(fields)) {
2461
+ if (value === null) {
2462
+ parsed[name] = null;
2463
+ continue;
2464
+ }
2465
+ switch (typeof value) {
2466
+ case "number":
2467
+ case "string":
2468
+ case "boolean":
2469
+ parsed[name] = value;
2470
+ break;
2471
+ default: {
2472
+ if (!Array.isArray(value)) {
2473
+ const object = ensureObject(value, name);
2474
+ const inventory = {};
2475
+ for (const [entryName, entryValue] of Object.entries(object)) {
2476
+ inventory[entryName] = ensureNumber(entryValue, `${name}.${entryName}`);
2477
+ }
2478
+ parsed[name] = inventory;
2479
+ break;
2480
+ }
2481
+ if (Array.isArray(value) && value.length === 3) {
2482
+ const [x, y, z] = value;
2483
+ parsed[name] = [
2484
+ ensureNumber(x, `${name}[0]`),
2485
+ ensureNumber(y, `${name}[1]`),
2486
+ ensureNumber(z, `${name}[2]`)
2487
+ ];
2488
+ break;
2489
+ }
2490
+ throw new Error(`Unsupported entity field value for ${name}`);
2491
+ }
2492
+ }
2493
+ }
2494
+ return parsed;
2495
+ }
2496
+ function parseEntities(raw) {
2497
+ if (!Array.isArray(raw)) {
2498
+ throw new Error("entities must be an array");
2499
+ }
2500
+ return raw.map((entry, idx) => {
2501
+ const entity = ensureObject(entry, `entities[${idx}]`);
2502
+ return {
2503
+ index: ensureNumber(entity.index, `entities[${idx}].index`),
2504
+ fields: parseEntityFields(entity.fields)
2505
+ };
2506
+ });
2507
+ }
2508
+ function parsePool(raw) {
2509
+ const pool = ensureObject(raw, "pool");
2510
+ return {
2511
+ capacity: ensureNumber(pool.capacity, "pool.capacity"),
2512
+ activeOrder: ensureNumberArray(pool.activeOrder, "pool.activeOrder"),
2513
+ freeList: ensureNumberArray(pool.freeList, "pool.freeList"),
2514
+ pendingFree: ensureNumberArray(pool.pendingFree, "pool.pendingFree")
2515
+ };
2516
+ }
2517
+ function parseEntitySnapshot(raw) {
2518
+ const snapshot = ensureObject(raw, "entities");
2519
+ return {
2520
+ timeSeconds: ensureNumber(snapshot.timeSeconds, "entities.timeSeconds"),
2521
+ pool: parsePool(snapshot.pool),
2522
+ entities: parseEntities(snapshot.entities),
2523
+ thinks: parseThinkEntries(snapshot.thinks)
2524
+ };
2525
+ }
2526
+ function parseCvars(raw) {
2527
+ if (raw === void 0) {
2528
+ return [];
2529
+ }
2530
+ if (!Array.isArray(raw)) {
2531
+ throw new Error("cvars must be an array");
2532
+ }
2533
+ return raw.map((entry, idx) => {
2534
+ const cvar = ensureObject(entry, `cvars[${idx}]`);
2535
+ return {
2536
+ name: ensureString(cvar.name, `cvars[${idx}].name`),
2537
+ value: ensureString(cvar.value, `cvars[${idx}].value`),
2538
+ flags: ensureNumber(cvar.flags, `cvars[${idx}].flags`)
2539
+ };
2540
+ });
2541
+ }
2542
+ function parseConfigstrings(raw) {
2543
+ if (raw === void 0) {
2544
+ return [];
2545
+ }
2546
+ if (!Array.isArray(raw)) {
2547
+ throw new Error("configstrings must be an array");
2548
+ }
2549
+ return raw.map((value, idx) => ensureString(value, `configstrings[${idx}]`));
2550
+ }
2551
+ function parseGameState(raw) {
2552
+ if (raw === void 0) {
2553
+ return {};
2554
+ }
2555
+ return ensureObject(raw, "gameState");
2556
+ }
2557
+ function serializeCvars(registry) {
2558
+ if (!registry) {
2559
+ return [];
2560
+ }
2561
+ return registry.list().filter((cvar) => (cvar.flags & CvarFlags.Archive) !== 0).map((cvar) => ({ name: cvar.name, value: cvar.string, flags: cvar.flags }));
2562
+ }
2563
+ function applyCvars(entries, registry) {
2564
+ if (!registry) {
2565
+ return;
2566
+ }
2567
+ for (const entry of entries) {
2568
+ const existing = registry.get(entry.name);
2569
+ if (existing) {
2570
+ existing.set(entry.value);
2571
+ } else {
2572
+ registry.register({ name: entry.name, defaultValue: entry.value, flags: entry.flags });
2573
+ }
2574
+ }
2575
+ }
2576
+ function cloneRngState(state) {
2577
+ return {
2578
+ mt: {
2579
+ index: state.mt.index,
2580
+ state: [...state.mt.state]
2581
+ }
2582
+ };
2583
+ }
2584
+ function createSaveFile(options) {
2585
+ const {
2586
+ map,
2587
+ difficulty,
2588
+ playtimeSeconds,
2589
+ levelState,
2590
+ entitySystem,
2591
+ rngState,
2592
+ configstrings = [],
2593
+ cvars,
2594
+ gameState = {},
2595
+ timestamp = Date.now()
2596
+ } = options;
2597
+ return {
2598
+ version: SAVE_FORMAT_VERSION,
2599
+ timestamp,
2600
+ map,
2601
+ difficulty,
2602
+ playtimeSeconds,
2603
+ gameState: { ...gameState },
2604
+ level: { ...levelState },
2605
+ rng: cloneRngState(rngState),
2606
+ entities: entitySystem.createSnapshot(),
2607
+ cvars: serializeCvars(cvars),
2608
+ configstrings: [...configstrings]
2609
+ };
2610
+ }
2611
+ function parseSaveFile(serialized, options = {}) {
2612
+ const { allowNewerVersion = true } = options;
2613
+ const raw = typeof serialized === "string" ? JSON.parse(serialized) : serialized;
2614
+ const save = ensureObject(raw, "save");
2615
+ const versionValue = save.version ?? SAVE_FORMAT_VERSION;
2616
+ const version = ensureNumber(versionValue, "version");
2617
+ if (version < MIN_SUPPORTED_VERSION) {
2618
+ throw new Error(`Unsupported save version ${version}`);
2619
+ }
2620
+ if (version > SAVE_FORMAT_VERSION && !allowNewerVersion) {
2621
+ throw new Error(`Save version ${version} is newer than supported ${SAVE_FORMAT_VERSION}`);
2622
+ }
2623
+ return {
2624
+ version,
2625
+ timestamp: ensureNumber(save.timestamp, "timestamp"),
2626
+ map: ensureString(save.map, "map"),
2627
+ difficulty: ensureNumber(save.difficulty, "difficulty"),
2628
+ playtimeSeconds: ensureNumber(save.playtimeSeconds, "playtimeSeconds"),
2629
+ gameState: parseGameState(save.gameState),
2630
+ level: parseLevelState(save.level),
2631
+ rng: parseRngState(save.rng),
2632
+ entities: parseEntitySnapshot(save.entities),
2633
+ cvars: parseCvars(save.cvars),
2634
+ configstrings: parseConfigstrings(save.configstrings)
2635
+ };
2636
+ }
2637
+ function applySaveFile(save, targets) {
2638
+ targets.levelClock.restore(save.level);
2639
+ targets.entitySystem.restore(save.entities);
2640
+ targets.rng.setState(save.rng);
2641
+ applyCvars(save.cvars, targets.cvars);
2642
+ }
2643
+
2644
+ // src/save/rerelease.ts
2645
+ function ensureObject2(value, label) {
2646
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2647
+ throw new Error(`${label} must be an object`);
2648
+ }
2649
+ return value;
2650
+ }
2651
+ function ensureNumber2(value, label) {
2652
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2653
+ throw new Error(`${label} must be a finite number`);
2654
+ }
2655
+ return value;
2656
+ }
2657
+ function ensureArray(value, label) {
2658
+ if (!Array.isArray(value)) {
2659
+ throw new Error(`${label} must be an array`);
2660
+ }
2661
+ return value;
2662
+ }
2663
+ function parseEntityMap(raw) {
2664
+ const map = /* @__PURE__ */ new Map();
2665
+ for (const [rawIndex, value] of Object.entries(raw)) {
2666
+ const index = Number(rawIndex);
2667
+ if (!Number.isInteger(index)) {
2668
+ throw new Error(`entities[${rawIndex}] must use an integer key`);
2669
+ }
2670
+ map.set(index, ensureObject2(value, `entities[${rawIndex}]`));
2671
+ }
2672
+ return map;
2673
+ }
2674
+ function parseRereleaseSave(raw) {
2675
+ const root = ensureObject2(raw, "rerelease save");
2676
+ const saveVersion = ensureNumber2(root.save_version, "save_version");
2677
+ const hasGame = "game" in root;
2678
+ const hasLevel = "level" in root;
2679
+ if (hasGame === hasLevel) {
2680
+ throw new Error("save must contain either game or level data");
2681
+ }
2682
+ if (hasGame) {
2683
+ const game = ensureObject2(root.game, "game");
2684
+ const clients = ensureArray(root.clients, "clients").map(
2685
+ (entry, idx) => ensureObject2(entry, `clients[${idx}]`)
2686
+ );
2687
+ const maxclients = game.maxclients !== void 0 ? ensureNumber2(game.maxclients, "game.maxclients") : clients.length;
2688
+ if (clients.length !== maxclients) {
2689
+ throw new Error(`clients length ${clients.length} does not match game.maxclients ${maxclients}`);
2690
+ }
2691
+ return { saveVersion, game, clients };
2692
+ }
2693
+ const level = ensureObject2(root.level, "level");
2694
+ const entitiesRoot = ensureObject2(root.entities, "entities");
2695
+ const entities = parseEntityMap(entitiesRoot);
2696
+ return { saveVersion, level, entities };
2697
+ }
2698
+ function summarizeRereleaseSave(raw) {
2699
+ const parsed = parseRereleaseSave(raw);
2700
+ if ("game" in parsed) {
2701
+ const maxClients = parsed.game.maxclients ?? parsed.clients.length;
2702
+ return {
2703
+ version: parsed.saveVersion,
2704
+ kind: "game",
2705
+ maxClients,
2706
+ clientCount: parsed.clients.length
2707
+ };
2708
+ }
2709
+ let highestEntityIndex = -1;
2710
+ for (const index of parsed.entities.keys()) {
2711
+ if (index > highestEntityIndex) {
2712
+ highestEntityIndex = index;
2713
+ }
2714
+ }
2715
+ return {
2716
+ version: parsed.saveVersion,
2717
+ kind: "level",
2718
+ entityCount: parsed.entities.size,
2719
+ highestEntityIndex: highestEntityIndex >= 0 ? highestEntityIndex : void 0
2720
+ };
2721
+ }
2722
+
2723
+ // src/save/storage.ts
2724
+ var TEXT_ENCODER_CTOR = globalThis.TextEncoder;
2725
+ function cloneSave(save) {
2726
+ return parseSaveFile({ ...save }, { allowNewerVersion: true });
2727
+ }
2728
+ function estimateSizeBytes(save) {
2729
+ if (TEXT_ENCODER_CTOR) {
2730
+ const encoder = new TEXT_ENCODER_CTOR();
2731
+ return encoder.encode(JSON.stringify(save)).length;
2732
+ }
2733
+ return JSON.stringify(save).length;
2734
+ }
2735
+ var MemorySaveAdapter = class {
2736
+ constructor() {
2737
+ this.records = /* @__PURE__ */ new Map();
659
2738
  }
660
- start(startTimeMs) {
661
- const startSeconds = startTimeMs / 1e3;
662
- this.state = {
663
- frameNumber: 0,
664
- timeSeconds: startSeconds,
665
- previousTimeSeconds: startSeconds,
666
- deltaSeconds: 0
2739
+ async init() {
2740
+ return Promise.resolve();
2741
+ }
2742
+ async put(record) {
2743
+ const copy = {
2744
+ id: record.id,
2745
+ metadata: { ...record.metadata },
2746
+ save: cloneSave(record.save)
667
2747
  };
2748
+ this.records.set(record.id, copy);
668
2749
  }
669
- tick(context) {
670
- this.state = {
671
- frameNumber: context.frame,
672
- timeSeconds: context.timeMs / 1e3,
673
- previousTimeSeconds: context.previousTimeMs / 1e3,
674
- deltaSeconds: context.deltaSeconds
2750
+ async get(id) {
2751
+ const record = this.records.get(id);
2752
+ if (!record) {
2753
+ return null;
2754
+ }
2755
+ return {
2756
+ id: record.id,
2757
+ metadata: { ...record.metadata },
2758
+ save: cloneSave(record.save)
675
2759
  };
676
- return this.state;
677
2760
  }
678
- get current() {
679
- return this.state;
2761
+ async delete(id) {
2762
+ return this.records.delete(id);
2763
+ }
2764
+ async list() {
2765
+ return Array.from(this.records.values()).map((record) => ({
2766
+ id: record.id,
2767
+ metadata: { ...record.metadata },
2768
+ save: cloneSave(record.save)
2769
+ }));
2770
+ }
2771
+ };
2772
+ var IndexedDbSaveAdapter = class {
2773
+ constructor(indexedDB, dbName, storeName) {
2774
+ this.indexedDB = indexedDB;
2775
+ this.dbName = dbName;
2776
+ this.storeName = storeName;
2777
+ this.db = null;
2778
+ }
2779
+ async init() {
2780
+ if (this.db) {
2781
+ return;
2782
+ }
2783
+ this.db = await new Promise((resolve, reject) => {
2784
+ const request = this.indexedDB.open(this.dbName, 1);
2785
+ request.onupgradeneeded = () => {
2786
+ request.result.createObjectStore(this.storeName, { keyPath: "id" });
2787
+ };
2788
+ request.onerror = () => reject(request.error ?? new Error("Failed to open IndexedDB"));
2789
+ request.onsuccess = () => resolve(request.result);
2790
+ });
2791
+ }
2792
+ async runTransaction(mode, operation) {
2793
+ await this.init();
2794
+ const database = this.db;
2795
+ return new Promise((resolve, reject) => {
2796
+ const transaction = database.transaction(this.storeName, mode);
2797
+ const store = transaction.objectStore(this.storeName);
2798
+ const request = operation(store);
2799
+ request.onsuccess = () => resolve(request.result);
2800
+ request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed"));
2801
+ });
2802
+ }
2803
+ async put(record) {
2804
+ await this.runTransaction("readwrite", (store) => store.put(record));
2805
+ }
2806
+ async get(id) {
2807
+ const record = await this.runTransaction("readonly", (store) => store.get(id));
2808
+ if (!record) {
2809
+ return null;
2810
+ }
2811
+ return {
2812
+ id: record.id,
2813
+ metadata: { ...record.metadata },
2814
+ save: cloneSave(record.save)
2815
+ };
2816
+ }
2817
+ async delete(id) {
2818
+ const existing = await this.get(id);
2819
+ if (!existing) {
2820
+ return false;
2821
+ }
2822
+ await this.runTransaction("readwrite", (store) => store.delete(id));
2823
+ return true;
2824
+ }
2825
+ async list() {
2826
+ const all = await this.runTransaction("readonly", (store) => store.getAll());
2827
+ return all.map((record) => ({
2828
+ id: record.id,
2829
+ metadata: { ...record.metadata },
2830
+ save: cloneSave(record.save)
2831
+ }));
2832
+ }
2833
+ };
2834
+ var _SaveStorage = class _SaveStorage {
2835
+ constructor(options = {}) {
2836
+ const { dbName = _SaveStorage.DEFAULT_DB_NAME, storeName = _SaveStorage.DEFAULT_STORE } = options;
2837
+ const indexedDBFactory = options.indexedDB ?? globalThis.indexedDB;
2838
+ if (indexedDBFactory) {
2839
+ this.adapter = new IndexedDbSaveAdapter(indexedDBFactory, dbName, storeName);
2840
+ } else {
2841
+ this.adapter = new MemorySaveAdapter();
2842
+ }
2843
+ }
2844
+ async save(slotId, save, options = {}) {
2845
+ const normalized = cloneSave(save);
2846
+ const metadata = {
2847
+ id: slotId,
2848
+ name: options.name ?? slotId,
2849
+ map: normalized.map,
2850
+ difficulty: normalized.difficulty,
2851
+ playtimeSeconds: normalized.playtimeSeconds,
2852
+ timestamp: normalized.timestamp,
2853
+ version: normalized.version,
2854
+ bytes: estimateSizeBytes(normalized)
2855
+ };
2856
+ await this.adapter.init();
2857
+ await this.adapter.put({ id: slotId, metadata, save: normalized });
2858
+ return metadata;
2859
+ }
2860
+ async load(slotId, options = {}) {
2861
+ await this.adapter.init();
2862
+ const record = await this.adapter.get(slotId);
2863
+ if (!record) {
2864
+ throw new Error(`Save slot ${slotId} not found`);
2865
+ }
2866
+ return parseSaveFile(record.save, options);
2867
+ }
2868
+ async delete(slotId) {
2869
+ await this.adapter.init();
2870
+ return this.adapter.delete(slotId);
2871
+ }
2872
+ async list() {
2873
+ await this.adapter.init();
2874
+ const records = await this.adapter.list();
2875
+ return records.map((record) => ({ ...record.metadata })).sort((a, b) => b.timestamp - a.timestamp || a.id.localeCompare(b.id));
2876
+ }
2877
+ async quickSave(save) {
2878
+ return this.save(_SaveStorage.QUICK_SLOT, save, { name: "Quick Save" });
2879
+ }
2880
+ async quickLoad(options = {}) {
2881
+ return this.load(_SaveStorage.QUICK_SLOT, options);
2882
+ }
2883
+ };
2884
+ _SaveStorage.DEFAULT_DB_NAME = "quake2ts-saves";
2885
+ _SaveStorage.DEFAULT_STORE = "saves";
2886
+ _SaveStorage.QUICK_SLOT = "quicksave";
2887
+ var SaveStorage = _SaveStorage;
2888
+
2889
+ // src/combat/damageFlags.ts
2890
+ var DamageFlags = /* @__PURE__ */ ((DamageFlags2) => {
2891
+ DamageFlags2[DamageFlags2["NONE"] = 0] = "NONE";
2892
+ DamageFlags2[DamageFlags2["RADIUS"] = 1] = "RADIUS";
2893
+ DamageFlags2[DamageFlags2["NO_ARMOR"] = 2] = "NO_ARMOR";
2894
+ DamageFlags2[DamageFlags2["ENERGY"] = 4] = "ENERGY";
2895
+ DamageFlags2[DamageFlags2["NO_KNOCKBACK"] = 8] = "NO_KNOCKBACK";
2896
+ DamageFlags2[DamageFlags2["BULLET"] = 16] = "BULLET";
2897
+ DamageFlags2[DamageFlags2["NO_PROTECTION"] = 32] = "NO_PROTECTION";
2898
+ DamageFlags2[DamageFlags2["DESTROY_ARMOR"] = 64] = "DESTROY_ARMOR";
2899
+ DamageFlags2[DamageFlags2["NO_REG_ARMOR"] = 128] = "NO_REG_ARMOR";
2900
+ DamageFlags2[DamageFlags2["NO_POWER_ARMOR"] = 256] = "NO_POWER_ARMOR";
2901
+ DamageFlags2[DamageFlags2["NO_INDICATOR"] = 512] = "NO_INDICATOR";
2902
+ return DamageFlags2;
2903
+ })(DamageFlags || {});
2904
+ function hasAnyDamageFlag(flags, mask) {
2905
+ return (flags & mask) !== 0;
2906
+ }
2907
+
2908
+ // src/combat/armor.ts
2909
+ var ArmorType = /* @__PURE__ */ ((ArmorType2) => {
2910
+ ArmorType2["BODY"] = "body";
2911
+ ArmorType2["COMBAT"] = "combat";
2912
+ ArmorType2["JACKET"] = "jacket";
2913
+ return ArmorType2;
2914
+ })(ArmorType || {});
2915
+ var ARMOR_INFO = {
2916
+ ["jacket" /* JACKET */]: {
2917
+ baseCount: 25,
2918
+ maxCount: 50,
2919
+ normalProtection: 0.3,
2920
+ energyProtection: 0
2921
+ },
2922
+ ["combat" /* COMBAT */]: {
2923
+ baseCount: 50,
2924
+ maxCount: 100,
2925
+ normalProtection: 0.6,
2926
+ energyProtection: 0.3
2927
+ },
2928
+ ["body" /* BODY */]: {
2929
+ baseCount: 100,
2930
+ maxCount: 200,
2931
+ normalProtection: 0.8,
2932
+ energyProtection: 0.6
680
2933
  }
681
2934
  };
2935
+ function applyRegularArmor(damage, flags, state) {
2936
+ if (damage <= 0 || hasAnyDamageFlag(flags, 2 /* NO_ARMOR */ | 128 /* NO_REG_ARMOR */) || !state.armorType || state.armorCount <= 0) {
2937
+ return { saved: 0, remainingArmor: state.armorCount };
2938
+ }
2939
+ const info = ARMOR_INFO[state.armorType];
2940
+ const protection = hasAnyDamageFlag(flags, 4 /* ENERGY */) ? info.energyProtection : info.normalProtection;
2941
+ let saved = Math.ceil(protection * damage);
2942
+ if (saved >= state.armorCount) {
2943
+ saved = state.armorCount;
2944
+ }
2945
+ if (saved <= 0) {
2946
+ return { saved: 0, remainingArmor: state.armorCount };
2947
+ }
2948
+ return { saved, remainingArmor: state.armorCount - saved };
2949
+ }
2950
+ function applyPowerArmor(damage, flags, hitPoint, _hitNormal, state, options = {}) {
2951
+ if (state.health <= 0 || damage <= 0) {
2952
+ return { saved: 0, remainingCells: state.cellCount };
2953
+ }
2954
+ if (hasAnyDamageFlag(flags, 2 /* NO_ARMOR */ | 256 /* NO_POWER_ARMOR */)) {
2955
+ return { saved: 0, remainingCells: state.cellCount };
2956
+ }
2957
+ if (!state.type || state.cellCount <= 0) {
2958
+ return { saved: 0, remainingCells: state.cellCount };
2959
+ }
2960
+ const { forward } = angleVectors(state.angles);
2961
+ const toImpact = {
2962
+ x: hitPoint.x - state.origin.x,
2963
+ y: hitPoint.y - state.origin.y,
2964
+ z: hitPoint.z - state.origin.z
2965
+ };
2966
+ const toImpactLength = Math.hypot(toImpact.x, toImpact.y, toImpact.z);
2967
+ if (state.type === "screen" && toImpactLength > 0) {
2968
+ const dir = {
2969
+ x: toImpact.x / toImpactLength,
2970
+ y: toImpact.y / toImpactLength,
2971
+ z: toImpact.z / toImpactLength
2972
+ };
2973
+ const dot = dir.x * forward.x + dir.y * forward.y + dir.z * forward.z;
2974
+ if (dot <= 0.3) {
2975
+ return { saved: 0, remainingCells: state.cellCount };
2976
+ }
2977
+ }
2978
+ const ctfMode = options.ctfMode ?? false;
2979
+ const damagePerCell = state.type === "screen" ? 1 : ctfMode ? 1 : 2;
2980
+ let adjustedDamage = state.type === "screen" ? damage / 3 : 2 * damage / 3;
2981
+ adjustedDamage = Math.max(1, adjustedDamage);
2982
+ let saved = state.cellCount * damagePerCell;
2983
+ if (hasAnyDamageFlag(flags, 4 /* ENERGY */)) {
2984
+ saved = Math.max(1, Math.floor(saved / 2));
2985
+ }
2986
+ if (saved > adjustedDamage) {
2987
+ saved = Math.floor(adjustedDamage);
2988
+ }
2989
+ let powerUsed = saved / damagePerCell;
2990
+ if (hasAnyDamageFlag(flags, 4 /* ENERGY */)) {
2991
+ powerUsed *= 2;
2992
+ }
2993
+ powerUsed = Math.max(1, Math.floor(powerUsed));
2994
+ const cellsSpent = Math.max(damagePerCell, powerUsed);
2995
+ const remainingCells = Math.max(0, state.cellCount - cellsSpent);
2996
+ return { saved, remainingCells };
2997
+ }
2998
+
2999
+ // src/combat/damage.ts
3000
+ var EntityDamageFlags = /* @__PURE__ */ ((EntityDamageFlags2) => {
3001
+ EntityDamageFlags2[EntityDamageFlags2["GODMODE"] = 1] = "GODMODE";
3002
+ EntityDamageFlags2[EntityDamageFlags2["IMMORTAL"] = 2] = "IMMORTAL";
3003
+ EntityDamageFlags2[EntityDamageFlags2["NO_KNOCKBACK"] = 4] = "NO_KNOCKBACK";
3004
+ EntityDamageFlags2[EntityDamageFlags2["NO_DAMAGE_EFFECTS"] = 8] = "NO_DAMAGE_EFFECTS";
3005
+ return EntityDamageFlags2;
3006
+ })(EntityDamageFlags || {});
3007
+ function applyKnockback(targ, attacker, dir, knockback, dflags) {
3008
+ const hasNoKnockback = hasAnyDamageFlag(dflags, 8 /* NO_KNOCKBACK */) || ((targ.flags ?? 0) & 4 /* NO_KNOCKBACK */) !== 0;
3009
+ if (hasNoKnockback || knockback === 0) {
3010
+ return { x: 0, y: 0, z: 0 };
3011
+ }
3012
+ const mass = Math.max(50, targ.mass ?? 200);
3013
+ const normalized = normalizeVec3(dir);
3014
+ const scale = attacker === targ ? 1600 : 500;
3015
+ const delta = scaleVec3(normalized, scale * knockback / mass);
3016
+ targ.velocity = addVec3(targ.velocity, delta);
3017
+ return delta;
3018
+ }
3019
+ function applyProtection(targ, point, normal, damage, dflags) {
3020
+ let take = damage;
3021
+ let psave = 0;
3022
+ let asave = 0;
3023
+ let remainingCells;
3024
+ let remainingArmor;
3025
+ if (targ.powerArmor) {
3026
+ const result = applyPowerArmor(damage, dflags, point, normal, targ.powerArmor);
3027
+ psave = result.saved;
3028
+ remainingCells = result.remainingCells;
3029
+ take -= psave;
3030
+ }
3031
+ if (targ.regularArmor) {
3032
+ const result = applyRegularArmor(take, dflags, targ.regularArmor);
3033
+ asave = result.saved;
3034
+ remainingArmor = result.remainingArmor;
3035
+ take -= asave;
3036
+ }
3037
+ return [Math.max(0, take), psave, asave, remainingCells, remainingArmor];
3038
+ }
3039
+ function targetCenter(ent) {
3040
+ if (ent.mins && ent.maxs) {
3041
+ return {
3042
+ x: ent.origin.x + (ent.mins.x + ent.maxs.x) * 0.5,
3043
+ y: ent.origin.y + (ent.mins.y + ent.maxs.y) * 0.5,
3044
+ z: ent.origin.z + (ent.mins.z + ent.maxs.z) * 0.5
3045
+ };
3046
+ }
3047
+ return ent.origin;
3048
+ }
3049
+ function T_Damage(targ, inflictor, attacker, dir, point, normal, damage, knockback, dflags, mod) {
3050
+ if (!targ.takedamage || damage <= 0) {
3051
+ return null;
3052
+ }
3053
+ const protectedByGod = !hasAnyDamageFlag(dflags, 32 /* NO_PROTECTION */) && ((targ.flags ?? 0) & 1 /* GODMODE */) !== 0;
3054
+ if (protectedByGod) {
3055
+ return {
3056
+ take: 0,
3057
+ psave: 0,
3058
+ asave: damage,
3059
+ knocked: { x: 0, y: 0, z: 0 },
3060
+ killed: false
3061
+ };
3062
+ }
3063
+ const knocked = applyKnockback(targ, attacker, dir, knockback, dflags);
3064
+ const [take, psave, asave, remainingCells, remainingArmor] = applyProtection(targ, point, normal, damage, dflags);
3065
+ if (targ.powerArmor && remainingCells !== void 0) {
3066
+ targ.powerArmor.cellCount = remainingCells;
3067
+ }
3068
+ if (targ.regularArmor) {
3069
+ targ.regularArmor.armorCount = remainingArmor ?? targ.regularArmor.armorCount;
3070
+ }
3071
+ let actualTake = take;
3072
+ if (actualTake > 0) {
3073
+ targ.health -= actualTake;
3074
+ }
3075
+ const killed = targ.health <= 0;
3076
+ if (killed) {
3077
+ if (targ.flags && targ.flags & 2 /* IMMORTAL */) {
3078
+ targ.health = Math.max(1, targ.health);
3079
+ } else if (targ.die) {
3080
+ targ.die(targ, inflictor, attacker, actualTake, point, mod);
3081
+ }
3082
+ } else if (actualTake > 0 && targ.pain) {
3083
+ targ.pain(targ, attacker, knockback, actualTake, mod);
3084
+ }
3085
+ return { take: actualTake, psave, asave, knocked, killed, remainingCells, remainingArmor };
3086
+ }
3087
+ function T_RadiusDamage(entities, inflictor, attacker, damage, ignore, radius, dflags, mod, options = {}) {
3088
+ const hits = [];
3089
+ const inflictorCenter = targetCenter(inflictor);
3090
+ const canDamage = options.canDamage ?? (() => true);
3091
+ for (const ent of entities) {
3092
+ if (ent === ignore || !ent.takedamage || !canDamage(ent, inflictor)) {
3093
+ continue;
3094
+ }
3095
+ const entCenter = ent.mins && ent.maxs ? closestPointToBox(inflictorCenter, addVec3(ent.origin, ent.mins), addVec3(ent.origin, ent.maxs)) : targetCenter(ent);
3096
+ const toTarget = subtractVec3(inflictorCenter, entCenter);
3097
+ const distance = lengthVec3(toTarget);
3098
+ if (radius > 0 && distance > radius) {
3099
+ continue;
3100
+ }
3101
+ const points = damage - 0.5 * distance;
3102
+ if (points <= 0) {
3103
+ continue;
3104
+ }
3105
+ const adjustedDamage = ent === attacker ? points * 0.5 : points;
3106
+ const dir = normalizeVec3(subtractVec3(ent.origin, inflictorCenter));
3107
+ const result = T_Damage(ent, inflictor, attacker, dir, entCenter, dir, adjustedDamage, adjustedDamage, dflags | 1 /* RADIUS */, mod);
3108
+ hits.push({ target: ent, result, appliedDamage: adjustedDamage });
3109
+ }
3110
+ return hits;
3111
+ }
3112
+
3113
+ // src/combat/damageMods.ts
3114
+ var DamageMod = /* @__PURE__ */ ((DamageMod2) => {
3115
+ DamageMod2[DamageMod2["UNKNOWN"] = 0] = "UNKNOWN";
3116
+ DamageMod2[DamageMod2["BLASTER"] = 1] = "BLASTER";
3117
+ DamageMod2[DamageMod2["SHOTGUN"] = 2] = "SHOTGUN";
3118
+ DamageMod2[DamageMod2["SSHOTGUN"] = 3] = "SSHOTGUN";
3119
+ DamageMod2[DamageMod2["MACHINEGUN"] = 4] = "MACHINEGUN";
3120
+ DamageMod2[DamageMod2["CHAINGUN"] = 5] = "CHAINGUN";
3121
+ DamageMod2[DamageMod2["GRENADE"] = 6] = "GRENADE";
3122
+ DamageMod2[DamageMod2["G_SPLASH"] = 7] = "G_SPLASH";
3123
+ DamageMod2[DamageMod2["ROCKET"] = 8] = "ROCKET";
3124
+ DamageMod2[DamageMod2["R_SPLASH"] = 9] = "R_SPLASH";
3125
+ DamageMod2[DamageMod2["HYPERBLASTER"] = 10] = "HYPERBLASTER";
3126
+ DamageMod2[DamageMod2["RAILGUN"] = 11] = "RAILGUN";
3127
+ DamageMod2[DamageMod2["BFG_LASER"] = 12] = "BFG_LASER";
3128
+ DamageMod2[DamageMod2["BFG_BLAST"] = 13] = "BFG_BLAST";
3129
+ DamageMod2[DamageMod2["BFG_EFFECT"] = 14] = "BFG_EFFECT";
3130
+ DamageMod2[DamageMod2["HANDGRENADE"] = 15] = "HANDGRENADE";
3131
+ DamageMod2[DamageMod2["HG_SPLASH"] = 16] = "HG_SPLASH";
3132
+ DamageMod2[DamageMod2["WATER"] = 17] = "WATER";
3133
+ DamageMod2[DamageMod2["SLIME"] = 18] = "SLIME";
3134
+ DamageMod2[DamageMod2["LAVA"] = 19] = "LAVA";
3135
+ DamageMod2[DamageMod2["CRUSH"] = 20] = "CRUSH";
3136
+ DamageMod2[DamageMod2["TELEFRAG"] = 21] = "TELEFRAG";
3137
+ DamageMod2[DamageMod2["TELEFRAG_SPAWN"] = 22] = "TELEFRAG_SPAWN";
3138
+ DamageMod2[DamageMod2["FALLING"] = 23] = "FALLING";
3139
+ DamageMod2[DamageMod2["SUICIDE"] = 24] = "SUICIDE";
3140
+ DamageMod2[DamageMod2["HELD_GRENADE"] = 25] = "HELD_GRENADE";
3141
+ DamageMod2[DamageMod2["EXPLOSIVE"] = 26] = "EXPLOSIVE";
3142
+ DamageMod2[DamageMod2["BARREL"] = 27] = "BARREL";
3143
+ DamageMod2[DamageMod2["BOMB"] = 28] = "BOMB";
3144
+ DamageMod2[DamageMod2["EXIT"] = 29] = "EXIT";
3145
+ DamageMod2[DamageMod2["SPLASH"] = 30] = "SPLASH";
3146
+ DamageMod2[DamageMod2["TARGET_LASER"] = 31] = "TARGET_LASER";
3147
+ DamageMod2[DamageMod2["TRIGGER_HURT"] = 32] = "TRIGGER_HURT";
3148
+ DamageMod2[DamageMod2["HIT"] = 33] = "HIT";
3149
+ DamageMod2[DamageMod2["TARGET_BLASTER"] = 34] = "TARGET_BLASTER";
3150
+ DamageMod2[DamageMod2["RIPPER"] = 35] = "RIPPER";
3151
+ DamageMod2[DamageMod2["PHALANX"] = 36] = "PHALANX";
3152
+ DamageMod2[DamageMod2["BRAINTENTACLE"] = 37] = "BRAINTENTACLE";
3153
+ DamageMod2[DamageMod2["BLASTOFF"] = 38] = "BLASTOFF";
3154
+ DamageMod2[DamageMod2["GEKK"] = 39] = "GEKK";
3155
+ DamageMod2[DamageMod2["TRAP"] = 40] = "TRAP";
3156
+ DamageMod2[DamageMod2["CHAINFIST"] = 41] = "CHAINFIST";
3157
+ DamageMod2[DamageMod2["DISINTEGRATOR"] = 42] = "DISINTEGRATOR";
3158
+ DamageMod2[DamageMod2["ETF_RIFLE"] = 43] = "ETF_RIFLE";
3159
+ DamageMod2[DamageMod2["BLASTER2"] = 44] = "BLASTER2";
3160
+ DamageMod2[DamageMod2["HEATBEAM"] = 45] = "HEATBEAM";
3161
+ DamageMod2[DamageMod2["TESLA"] = 46] = "TESLA";
3162
+ DamageMod2[DamageMod2["PROX"] = 47] = "PROX";
3163
+ DamageMod2[DamageMod2["NUKE"] = 48] = "NUKE";
3164
+ DamageMod2[DamageMod2["VENGEANCE_SPHERE"] = 49] = "VENGEANCE_SPHERE";
3165
+ DamageMod2[DamageMod2["HUNTER_SPHERE"] = 50] = "HUNTER_SPHERE";
3166
+ DamageMod2[DamageMod2["DEFENDER_SPHERE"] = 51] = "DEFENDER_SPHERE";
3167
+ DamageMod2[DamageMod2["TRACKER"] = 52] = "TRACKER";
3168
+ DamageMod2[DamageMod2["DBALL_CRUSH"] = 53] = "DBALL_CRUSH";
3169
+ DamageMod2[DamageMod2["DOPPLE_EXPLODE"] = 54] = "DOPPLE_EXPLODE";
3170
+ DamageMod2[DamageMod2["DOPPLE_VENGEANCE"] = 55] = "DOPPLE_VENGEANCE";
3171
+ DamageMod2[DamageMod2["DOPPLE_HUNTER"] = 56] = "DOPPLE_HUNTER";
3172
+ DamageMod2[DamageMod2["GRAPPLE"] = 57] = "GRAPPLE";
3173
+ DamageMod2[DamageMod2["BLUEBLASTER"] = 58] = "BLUEBLASTER";
3174
+ return DamageMod2;
3175
+ })(DamageMod || {});
3176
+ var ORDERED_DAMAGE_MODS = [
3177
+ 0 /* UNKNOWN */,
3178
+ 1 /* BLASTER */,
3179
+ 2 /* SHOTGUN */,
3180
+ 3 /* SSHOTGUN */,
3181
+ 4 /* MACHINEGUN */,
3182
+ 5 /* CHAINGUN */,
3183
+ 6 /* GRENADE */,
3184
+ 7 /* G_SPLASH */,
3185
+ 8 /* ROCKET */,
3186
+ 9 /* R_SPLASH */,
3187
+ 10 /* HYPERBLASTER */,
3188
+ 11 /* RAILGUN */,
3189
+ 12 /* BFG_LASER */,
3190
+ 13 /* BFG_BLAST */,
3191
+ 14 /* BFG_EFFECT */,
3192
+ 15 /* HANDGRENADE */,
3193
+ 16 /* HG_SPLASH */,
3194
+ 17 /* WATER */,
3195
+ 18 /* SLIME */,
3196
+ 19 /* LAVA */,
3197
+ 20 /* CRUSH */,
3198
+ 21 /* TELEFRAG */,
3199
+ 22 /* TELEFRAG_SPAWN */,
3200
+ 23 /* FALLING */,
3201
+ 24 /* SUICIDE */,
3202
+ 25 /* HELD_GRENADE */,
3203
+ 26 /* EXPLOSIVE */,
3204
+ 27 /* BARREL */,
3205
+ 28 /* BOMB */,
3206
+ 29 /* EXIT */,
3207
+ 30 /* SPLASH */,
3208
+ 31 /* TARGET_LASER */,
3209
+ 32 /* TRIGGER_HURT */,
3210
+ 33 /* HIT */,
3211
+ 34 /* TARGET_BLASTER */,
3212
+ 35 /* RIPPER */,
3213
+ 36 /* PHALANX */,
3214
+ 37 /* BRAINTENTACLE */,
3215
+ 38 /* BLASTOFF */,
3216
+ 39 /* GEKK */,
3217
+ 40 /* TRAP */,
3218
+ 41 /* CHAINFIST */,
3219
+ 42 /* DISINTEGRATOR */,
3220
+ 43 /* ETF_RIFLE */,
3221
+ 44 /* BLASTER2 */,
3222
+ 45 /* HEATBEAM */,
3223
+ 46 /* TESLA */,
3224
+ 47 /* PROX */,
3225
+ 48 /* NUKE */,
3226
+ 49 /* VENGEANCE_SPHERE */,
3227
+ 50 /* HUNTER_SPHERE */,
3228
+ 51 /* DEFENDER_SPHERE */,
3229
+ 52 /* TRACKER */,
3230
+ 53 /* DBALL_CRUSH */,
3231
+ 54 /* DOPPLE_EXPLODE */,
3232
+ 55 /* DOPPLE_VENGEANCE */,
3233
+ 56 /* DOPPLE_HUNTER */,
3234
+ 57 /* GRAPPLE */,
3235
+ 58 /* BLUEBLASTER */
3236
+ ];
3237
+ function damageModName(mod) {
3238
+ return `MOD_${DamageMod[mod]}`;
3239
+ }
3240
+
3241
+ // src/combat/specialDamage.ts
3242
+ var ZERO2 = { x: 0, y: 0, z: 0 };
3243
+ var EnvironmentalFlags = /* @__PURE__ */ ((EnvironmentalFlags2) => {
3244
+ EnvironmentalFlags2[EnvironmentalFlags2["IN_WATER"] = 1] = "IN_WATER";
3245
+ EnvironmentalFlags2[EnvironmentalFlags2["IMMUNE_LAVA"] = 2] = "IMMUNE_LAVA";
3246
+ EnvironmentalFlags2[EnvironmentalFlags2["IMMUNE_SLIME"] = 4] = "IMMUNE_SLIME";
3247
+ return EnvironmentalFlags2;
3248
+ })(EnvironmentalFlags || {});
3249
+ function applyDamageEvent(target, amount, mod) {
3250
+ return T_Damage(target, null, null, ZERO2, target.origin, ZERO2, amount, 0, 2 /* NO_ARMOR */, mod);
3251
+ }
3252
+ function applyEnvironmentalDamage(target, nowMs) {
3253
+ const events = [];
3254
+ let flags = target.environmentFlags ?? 0;
3255
+ let enteredWater = false;
3256
+ let leftWater = false;
3257
+ if (target.waterlevel === WaterLevel.None) {
3258
+ if ((flags & 1 /* IN_WATER */) !== 0) {
3259
+ flags &= ~1 /* IN_WATER */;
3260
+ leftWater = true;
3261
+ }
3262
+ if (target.airFinished < nowMs && target.painDebounceTime <= nowMs) {
3263
+ const elapsedSeconds = Math.floor((nowMs - target.airFinished) / 1e3);
3264
+ const amount = Math.min(15, 2 + 2 * elapsedSeconds);
3265
+ const result = applyDamageEvent(target, amount, 17 /* WATER */);
3266
+ target.painDebounceTime = nowMs + 1e3;
3267
+ events.push({ mod: 17 /* WATER */, amount, result });
3268
+ }
3269
+ } else {
3270
+ target.airFinished = nowMs + 9e3;
3271
+ if ((flags & 1 /* IN_WATER */) === 0) {
3272
+ flags |= 1 /* IN_WATER */;
3273
+ enteredWater = true;
3274
+ target.damageDebounceTime = 0;
3275
+ }
3276
+ if (target.damageDebounceTime <= nowMs) {
3277
+ if ((target.watertype & CONTENTS_LAVA) !== 0 && (flags & 2 /* IMMUNE_LAVA */) === 0) {
3278
+ const amount = 10 * target.waterlevel;
3279
+ const result = applyDamageEvent(target, amount, 19 /* LAVA */);
3280
+ target.damageDebounceTime = nowMs + 100;
3281
+ events.push({ mod: 19 /* LAVA */, amount, result });
3282
+ } else if ((target.watertype & CONTENTS_SLIME) !== 0 && (flags & 4 /* IMMUNE_SLIME */) === 0) {
3283
+ const amount = 4 * target.waterlevel;
3284
+ const result = applyDamageEvent(target, amount, 18 /* SLIME */);
3285
+ target.damageDebounceTime = nowMs + 100;
3286
+ events.push({ mod: 18 /* SLIME */, amount, result });
3287
+ }
3288
+ }
3289
+ }
3290
+ target.environmentFlags = flags;
3291
+ return { events, enteredWater, leftWater };
3292
+ }
3293
+ function calculateFallingDamage(context) {
3294
+ const {
3295
+ impactDelta,
3296
+ waterLevel,
3297
+ onLadder = false,
3298
+ isDead = false,
3299
+ isPlayerModel = true,
3300
+ isNoClip = false,
3301
+ grappleBlockingFallDamage = false,
3302
+ clampFreeFall = false,
3303
+ skipDamage = false
3304
+ } = context;
3305
+ if (isDead || !isPlayerModel || isNoClip || grappleBlockingFallDamage || waterLevel === WaterLevel.Under) {
3306
+ return { damage: 0, event: null, fallValue: 0, adjustedDelta: 0 };
3307
+ }
3308
+ let delta = impactDelta * impactDelta * 1e-4;
3309
+ if (waterLevel === WaterLevel.Waist) {
3310
+ delta *= 0.25;
3311
+ } else if (waterLevel === WaterLevel.Feet) {
3312
+ delta *= 0.5;
3313
+ }
3314
+ if (clampFreeFall) {
3315
+ delta = Math.min(30, delta);
3316
+ }
3317
+ if (delta < 1) {
3318
+ return { damage: 0, event: null, fallValue: 0, adjustedDelta: delta };
3319
+ }
3320
+ let event = null;
3321
+ let damage = 0;
3322
+ let fallValue = 0;
3323
+ if (delta < 15) {
3324
+ event = onLadder ? null : "footstep";
3325
+ } else {
3326
+ fallValue = Math.min(delta * 0.5, 40);
3327
+ if (delta > 30) {
3328
+ event = delta >= 55 ? "fallfar" : "fall";
3329
+ damage = Math.max(1, (delta - 30) * 0.5);
3330
+ } else {
3331
+ event = "fallshort";
3332
+ }
3333
+ }
3334
+ if (skipDamage) {
3335
+ damage = 0;
3336
+ }
3337
+ return { damage, event, fallValue, adjustedDelta: delta };
3338
+ }
3339
+ function applyFallingDamage(target, context) {
3340
+ const result = calculateFallingDamage(context);
3341
+ if (result.damage > 0 && !context.skipDamage) {
3342
+ T_Damage(
3343
+ target,
3344
+ null,
3345
+ null,
3346
+ { x: 0, y: 0, z: 1 },
3347
+ target.origin,
3348
+ ZERO2,
3349
+ result.damage,
3350
+ 0,
3351
+ 2 /* NO_ARMOR */,
3352
+ 23 /* FALLING */
3353
+ );
3354
+ }
3355
+ return result;
3356
+ }
3357
+ function applyCrushDamage(crusher, target, options = {}) {
3358
+ const nonLivingDamage = options.nonLivingDamage ?? 1e5;
3359
+ const gibDamage = options.gibDamage ?? 100;
3360
+ const baseDamage = options.baseDamage ?? crusher.dmg ?? 10;
3361
+ const amount = !target.isMonster && !target.isClient ? nonLivingDamage : target.health < 1 ? gibDamage : baseDamage;
3362
+ const result = T_Damage(target, crusher, crusher, ZERO2, target.origin, ZERO2, amount, 1, 0 /* NONE */, 20 /* CRUSH */);
3363
+ return { amount, result };
3364
+ }
3365
+ function absoluteBounds(ent) {
3366
+ const mins = ent.mins ?? ZERO2;
3367
+ const maxs = ent.maxs ?? ZERO2;
3368
+ return {
3369
+ mins: addVec3(ent.origin, mins),
3370
+ maxs: addVec3(ent.origin, maxs)
3371
+ };
3372
+ }
3373
+ function killBox(teleporter, targets, options = {}) {
3374
+ if (teleporter.movetype === 1 /* Noclip */) {
3375
+ return { events: [], cleared: true };
3376
+ }
3377
+ const mod = options.mod ?? 21 /* TELEFRAG */;
3378
+ const teleBounds = absoluteBounds(teleporter);
3379
+ const events = [];
3380
+ let cleared = true;
3381
+ for (const target of targets) {
3382
+ if (target === teleporter || target.inUse === false) {
3383
+ continue;
3384
+ }
3385
+ const solidity = target.solid ?? 0 /* Not */;
3386
+ if (!target.takedamage || solidity === 0 /* Not */ || solidity === 1 /* Trigger */ || solidity === 3 /* Bsp */) {
3387
+ continue;
3388
+ }
3389
+ if (!boxesIntersect(teleBounds, absoluteBounds(target))) {
3390
+ continue;
3391
+ }
3392
+ const result = T_Damage(target, teleporter, teleporter, ZERO2, target.origin, ZERO2, 1e5, 0, 32 /* NO_PROTECTION */, mod);
3393
+ events.push({ target, result });
3394
+ if (!result || !result.killed || target.health > 0) {
3395
+ cleared = false;
3396
+ }
3397
+ }
3398
+ return { events, cleared };
3399
+ }
3400
+
3401
+ // src/inventory/ammo.ts
3402
+ var AmmoType = /* @__PURE__ */ ((AmmoType2) => {
3403
+ AmmoType2[AmmoType2["Bullets"] = 0] = "Bullets";
3404
+ AmmoType2[AmmoType2["Shells"] = 1] = "Shells";
3405
+ AmmoType2[AmmoType2["Rockets"] = 2] = "Rockets";
3406
+ AmmoType2[AmmoType2["Grenades"] = 3] = "Grenades";
3407
+ AmmoType2[AmmoType2["Cells"] = 4] = "Cells";
3408
+ AmmoType2[AmmoType2["Slugs"] = 5] = "Slugs";
3409
+ return AmmoType2;
3410
+ })(AmmoType || {});
3411
+ var AMMO_TYPE_COUNT = Object.keys(AmmoType).length / 2;
3412
+ function createBaseAmmoCaps() {
3413
+ const caps = Array(AMMO_TYPE_COUNT).fill(50);
3414
+ caps[0 /* Bullets */] = 200;
3415
+ caps[1 /* Shells */] = 100;
3416
+ caps[4 /* Cells */] = 200;
3417
+ return caps;
3418
+ }
3419
+ function clampAmmoCounts(counts, caps) {
3420
+ const limit = Math.min(counts.length, caps.length);
3421
+ const clamped = counts.slice(0, limit);
3422
+ for (let i = 0; i < limit; i++) {
3423
+ const cap = caps[i];
3424
+ if (cap !== void 0) {
3425
+ clamped[i] = Math.min(counts[i], cap);
3426
+ }
3427
+ }
3428
+ return clamped;
3429
+ }
682
3430
 
683
3431
  // src/index.ts
684
3432
  var ZERO_VEC32 = { x: 0, y: 0, z: 0 };
@@ -747,12 +3495,74 @@ function createGame(engine, options) {
747
3495
  }
748
3496
  // Annotate the CommonJS export names for ESM import in node:
749
3497
  0 && (module.exports = {
3498
+ AIFlags,
3499
+ AMMO_TYPE_COUNT,
3500
+ ARMOR_INFO,
3501
+ AmmoType,
3502
+ ArmorType,
3503
+ DamageFlags,
3504
+ DamageMod,
750
3505
  DeadFlag,
751
3506
  ENTITY_FIELD_METADATA,
752
3507
  Entity,
3508
+ EntityDamageFlags,
753
3509
  EntitySystem,
3510
+ EnvironmentalFlags,
3511
+ FL_NOVISIBLE,
754
3512
  MoveType,
3513
+ ORDERED_DAMAGE_MODS,
3514
+ RANGE_MELEE,
3515
+ RANGE_MID,
3516
+ RANGE_NEAR,
3517
+ RangeCategory,
3518
+ SAVE_FORMAT_VERSION,
3519
+ SPAWNFLAG_MONSTER_AMBUSH,
3520
+ SaveStorage,
3521
+ ServerFlags,
755
3522
  Solid,
756
- createGame
3523
+ SpawnRegistry,
3524
+ T_Damage,
3525
+ T_RadiusDamage,
3526
+ TraceMask,
3527
+ ai_charge,
3528
+ ai_face,
3529
+ ai_move,
3530
+ ai_run,
3531
+ ai_stand,
3532
+ ai_turn,
3533
+ ai_walk,
3534
+ applyCrushDamage,
3535
+ applyEntityKeyValues,
3536
+ applyEnvironmentalDamage,
3537
+ applyFallingDamage,
3538
+ applyPowerArmor,
3539
+ applyRegularArmor,
3540
+ applySaveFile,
3541
+ calculateFallingDamage,
3542
+ changeYaw,
3543
+ clampAmmoCounts,
3544
+ classifyRange,
3545
+ createBaseAmmoCaps,
3546
+ createDefaultSpawnRegistry,
3547
+ createGame,
3548
+ createSaveFile,
3549
+ damageModName,
3550
+ facingIdeal,
3551
+ hasAnyDamageFlag,
3552
+ hashGameState,
3553
+ infront,
3554
+ isZeroVector,
3555
+ killBox,
3556
+ parseEntityLump,
3557
+ parseRereleaseSave,
3558
+ parseSaveFile,
3559
+ rangeTo,
3560
+ registerDefaultSpawns,
3561
+ setMovedir,
3562
+ spawnEntitiesFromText,
3563
+ spawnEntityFromDictionary,
3564
+ summarizeRereleaseSave,
3565
+ visible,
3566
+ walkMove
757
3567
  });
758
3568
  //# sourceMappingURL=index.cjs.map