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