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