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.
- package/README.md +425 -0
- package/apps/viewer/dist/browser/index.global.js +1 -1
- package/apps/viewer/dist/browser/index.global.js.map +1 -1
- package/apps/viewer/dist/cjs/index.cjs +2097 -295
- package/apps/viewer/dist/cjs/index.cjs.map +1 -1
- package/apps/viewer/dist/esm/index.js +2097 -295
- package/apps/viewer/dist/esm/index.js.map +1 -1
- package/apps/viewer/dist/tsconfig.tsbuildinfo +1 -1
- package/apps/viewer/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/packages/client/dist/browser/index.global.js +1 -1
- package/packages/client/dist/browser/index.global.js.map +1 -1
- package/packages/client/dist/cjs/index.cjs +1200 -13
- package/packages/client/dist/cjs/index.cjs.map +1 -1
- package/packages/client/dist/esm/index.js +1186 -12
- package/packages/client/dist/esm/index.js.map +1 -1
- package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/client/dist/types/index.d.ts +14 -6
- package/packages/client/dist/types/index.d.ts.map +1 -1
- package/packages/client/dist/types/input/bindings.d.ts +18 -0
- package/packages/client/dist/types/input/bindings.d.ts.map +1 -0
- package/packages/client/dist/types/input/command-buffer.d.ts +15 -0
- package/packages/client/dist/types/input/command-buffer.d.ts.map +1 -0
- package/packages/client/dist/types/input/controller.d.ts +125 -0
- package/packages/client/dist/types/input/controller.d.ts.map +1 -0
- package/packages/client/dist/types/prediction.d.ts +38 -0
- package/packages/client/dist/types/prediction.d.ts.map +1 -0
- package/packages/client/dist/types/view-effects.d.ts +41 -0
- package/packages/client/dist/types/view-effects.d.ts.map +1 -0
- package/packages/engine/dist/browser/index.global.js +257 -1
- package/packages/engine/dist/browser/index.global.js.map +1 -1
- package/packages/engine/dist/cjs/index.cjs +2408 -2
- package/packages/engine/dist/cjs/index.cjs.map +1 -1
- package/packages/engine/dist/esm/index.js +2340 -2
- package/packages/engine/dist/esm/index.js.map +1 -1
- package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/engine/dist/types/assets/animation.d.ts +33 -0
- package/packages/engine/dist/types/assets/animation.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/audio.d.ts +21 -0
- package/packages/engine/dist/types/assets/audio.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/bsp.d.ts +1 -1
- package/packages/engine/dist/types/assets/bsp.d.ts.map +1 -1
- package/packages/engine/dist/types/assets/ingestion.d.ts +31 -0
- package/packages/engine/dist/types/assets/ingestion.d.ts.map +1 -1
- package/packages/engine/dist/types/assets/manager.d.ts +43 -0
- package/packages/engine/dist/types/assets/manager.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/md3.d.ts +69 -0
- package/packages/engine/dist/types/assets/md3.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/ogg.d.ts +12 -0
- package/packages/engine/dist/types/assets/ogg.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/pakIndexStore.d.ts +19 -0
- package/packages/engine/dist/types/assets/pakIndexStore.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/pakValidation.d.ts +28 -0
- package/packages/engine/dist/types/assets/pakValidation.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/pcx.d.ts +13 -0
- package/packages/engine/dist/types/assets/pcx.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/texture.d.ts +29 -0
- package/packages/engine/dist/types/assets/texture.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/wal.d.ts +21 -0
- package/packages/engine/dist/types/assets/wal.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/wav.d.ts +11 -0
- package/packages/engine/dist/types/assets/wav.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/api.d.ts +29 -0
- package/packages/engine/dist/types/audio/api.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/channels.d.ts +15 -0
- package/packages/engine/dist/types/audio/channels.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/constants.d.ts +24 -0
- package/packages/engine/dist/types/audio/constants.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/context.d.ts +67 -0
- package/packages/engine/dist/types/audio/context.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/music.d.ts +42 -0
- package/packages/engine/dist/types/audio/music.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/precache.d.ts +28 -0
- package/packages/engine/dist/types/audio/precache.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/registry.d.ts +13 -0
- package/packages/engine/dist/types/audio/registry.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/spatialization.d.ts +14 -0
- package/packages/engine/dist/types/audio/spatialization.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/system.d.ts +101 -0
- package/packages/engine/dist/types/audio/system.d.ts.map +1 -0
- package/packages/engine/dist/types/configstrings.d.ts +1 -0
- package/packages/engine/dist/types/configstrings.d.ts.map +1 -1
- package/packages/engine/dist/types/index.d.ts +26 -1
- package/packages/engine/dist/types/index.d.ts.map +1 -1
- package/packages/engine/dist/types/render/bspPipeline.d.ts +42 -0
- package/packages/engine/dist/types/render/bspPipeline.d.ts.map +1 -0
- package/packages/engine/dist/types/render/bspTraversal.d.ts +11 -0
- package/packages/engine/dist/types/render/bspTraversal.d.ts.map +1 -0
- package/packages/engine/dist/types/render/culling.d.ts +8 -0
- package/packages/engine/dist/types/render/culling.d.ts.map +1 -0
- package/packages/engine/dist/types/render/md2Pipeline.d.ts +51 -0
- package/packages/engine/dist/types/render/md2Pipeline.d.ts.map +1 -0
- package/packages/engine/dist/types/render/resources.d.ts +10 -0
- package/packages/engine/dist/types/render/resources.d.ts.map +1 -1
- package/packages/engine/dist/types/render/skybox.d.ts +26 -0
- package/packages/engine/dist/types/render/skybox.d.ts.map +1 -0
- package/packages/game/dist/browser/index.global.js +1 -1
- package/packages/game/dist/browser/index.global.js.map +1 -1
- package/packages/game/dist/cjs/index.cjs +2926 -116
- package/packages/game/dist/cjs/index.cjs.map +1 -1
- package/packages/game/dist/esm/index.js +2863 -115
- package/packages/game/dist/esm/index.js.map +1 -1
- package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/game/dist/types/ai/constants.d.ts +13 -0
- package/packages/game/dist/types/ai/constants.d.ts.map +1 -0
- package/packages/game/dist/types/ai/index.d.ts +4 -0
- package/packages/game/dist/types/ai/index.d.ts.map +1 -0
- package/packages/game/dist/types/ai/movement.d.ts +20 -0
- package/packages/game/dist/types/ai/movement.d.ts.map +1 -0
- package/packages/game/dist/types/ai/perception.d.ts +21 -0
- package/packages/game/dist/types/ai/perception.d.ts.map +1 -0
- package/packages/game/dist/types/checksum.d.ts +3 -0
- package/packages/game/dist/types/checksum.d.ts.map +1 -0
- package/packages/game/dist/types/combat/armor.d.ts +39 -0
- package/packages/game/dist/types/combat/armor.d.ts.map +1 -0
- package/packages/game/dist/types/combat/damage.d.ts +52 -0
- package/packages/game/dist/types/combat/damage.d.ts.map +1 -0
- package/packages/game/dist/types/combat/damageFlags.d.ts +15 -0
- package/packages/game/dist/types/combat/damageFlags.d.ts.map +1 -0
- package/packages/game/dist/types/combat/damageMods.d.ts +79 -0
- package/packages/game/dist/types/combat/damageMods.d.ts.map +1 -0
- package/packages/game/dist/types/combat/index.d.ts +6 -0
- package/packages/game/dist/types/combat/index.d.ts.map +1 -0
- package/packages/game/dist/types/combat/specialDamage.d.ts +88 -0
- package/packages/game/dist/types/combat/specialDamage.d.ts.map +1 -0
- package/packages/game/dist/types/entities/entity.d.ts +46 -2
- package/packages/game/dist/types/entities/entity.d.ts.map +1 -1
- package/packages/game/dist/types/entities/index.d.ts +6 -2
- package/packages/game/dist/types/entities/index.d.ts.map +1 -1
- package/packages/game/dist/types/entities/pool.d.ts +9 -0
- package/packages/game/dist/types/entities/pool.d.ts.map +1 -1
- package/packages/game/dist/types/entities/spawn.d.ts +27 -0
- package/packages/game/dist/types/entities/spawn.d.ts.map +1 -0
- package/packages/game/dist/types/entities/system.d.ts +32 -1
- package/packages/game/dist/types/entities/system.d.ts.map +1 -1
- package/packages/game/dist/types/entities/thinkScheduler.d.ts +6 -0
- package/packages/game/dist/types/entities/thinkScheduler.d.ts.map +1 -1
- package/packages/game/dist/types/entities/triggers.d.ts +3 -0
- package/packages/game/dist/types/entities/triggers.d.ts.map +1 -0
- package/packages/game/dist/types/entities/utils.d.ts +4 -0
- package/packages/game/dist/types/entities/utils.d.ts.map +1 -0
- package/packages/game/dist/types/index.d.ts +5 -0
- package/packages/game/dist/types/index.d.ts.map +1 -1
- package/packages/game/dist/types/inventory/ammo.d.ts +17 -0
- package/packages/game/dist/types/inventory/ammo.d.ts.map +1 -0
- package/packages/game/dist/types/inventory/index.d.ts +2 -0
- package/packages/game/dist/types/inventory/index.d.ts.map +1 -0
- package/packages/game/dist/types/level.d.ts +1 -0
- package/packages/game/dist/types/level.d.ts.map +1 -1
- package/packages/game/dist/types/save/index.d.ts +4 -0
- package/packages/game/dist/types/save/index.d.ts.map +1 -0
- package/packages/game/dist/types/save/rerelease.d.ts +25 -0
- package/packages/game/dist/types/save/rerelease.d.ts.map +1 -0
- package/packages/game/dist/types/save/save.d.ts +49 -0
- package/packages/game/dist/types/save/save.d.ts.map +1 -0
- package/packages/game/dist/types/save/storage.d.ts +37 -0
- package/packages/game/dist/types/save/storage.d.ts.map +1 -0
- package/packages/shared/dist/browser/index.global.js +1 -1
- package/packages/shared/dist/browser/index.global.js.map +1 -1
- package/packages/shared/dist/cjs/index.cjs +638 -9
- package/packages/shared/dist/cjs/index.cjs.map +1 -1
- package/packages/shared/dist/esm/index.js +616 -9
- package/packages/shared/dist/esm/index.js.map +1 -1
- package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/shared/dist/types/bsp/collision.d.ts +56 -0
- package/packages/shared/dist/types/bsp/collision.d.ts.map +1 -1
- package/packages/shared/dist/types/bsp/contents.d.ts +1 -0
- package/packages/shared/dist/types/bsp/contents.d.ts.map +1 -1
- package/packages/shared/dist/types/index.d.ts +2 -0
- package/packages/shared/dist/types/index.d.ts.map +1 -1
- package/packages/shared/dist/types/math/random.d.ts +11 -0
- package/packages/shared/dist/types/math/random.d.ts.map +1 -1
- package/packages/shared/dist/types/protocol/contracts.d.ts +17 -0
- package/packages/shared/dist/types/protocol/contracts.d.ts.map +1 -0
- package/packages/shared/dist/types/protocol/usercmd.d.ts +30 -0
- package/packages/shared/dist/types/protocol/usercmd.d.ts.map +1 -0
- 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
|
-
|
|
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
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
580
|
-
this.runStage("finish", context);
|
|
581
|
-
return context;
|
|
1140
|
+
bucket.add(entity);
|
|
582
1141
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
593
|
-
|
|
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
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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/
|
|
618
|
-
var
|
|
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
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
|
|
647
|
-
return this.
|
|
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
|
-
|
|
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
|