quake2ts 0.0.74 → 0.0.75
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/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/tsconfig.tsbuildinfo +1 -1
- 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 +751 -602
- package/packages/game/dist/cjs/index.cjs.map +1 -1
- package/packages/game/dist/esm/index.js +749 -602
- package/packages/game/dist/esm/index.js.map +1 -1
- package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/game/dist/types/ai/index.d.ts +1 -0
- package/packages/game/dist/types/ai/index.d.ts.map +1 -1
- package/packages/game/dist/types/ai/monster.d.ts +4 -0
- package/packages/game/dist/types/ai/monster.d.ts.map +1 -0
- package/packages/game/dist/types/entities/entity.d.ts +21 -0
- package/packages/game/dist/types/entities/entity.d.ts.map +1 -1
- package/packages/game/dist/types/entities/monsters/soldier.d.ts +3 -0
- package/packages/game/dist/types/entities/monsters/soldier.d.ts.map +1 -0
- package/packages/game/dist/types/entities/spawn.d.ts.map +1 -1
|
@@ -3333,704 +3333,849 @@ function registerLightSpawns(registry) {
|
|
|
3333
3333
|
});
|
|
3334
3334
|
}
|
|
3335
3335
|
|
|
3336
|
-
// src/
|
|
3337
|
-
var
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3336
|
+
// src/ai/constants.ts
|
|
3337
|
+
var RANGE_MELEE = 20;
|
|
3338
|
+
var RANGE_NEAR = 440;
|
|
3339
|
+
var RANGE_MID = 940;
|
|
3340
|
+
var FL_NOTARGET = 1 << 5;
|
|
3341
|
+
var FL_NOVISIBLE = 1 << 24;
|
|
3342
|
+
var SPAWNFLAG_MONSTER_AMBUSH = 1 << 0;
|
|
3343
|
+
var AIFlags = /* @__PURE__ */ ((AIFlags2) => {
|
|
3344
|
+
AIFlags2[AIFlags2["StandGround"] = 1] = "StandGround";
|
|
3345
|
+
AIFlags2[AIFlags2["TempStandGround"] = 2] = "TempStandGround";
|
|
3346
|
+
AIFlags2[AIFlags2["SoundTarget"] = 4] = "SoundTarget";
|
|
3347
|
+
AIFlags2[AIFlags2["LostSight"] = 8] = "LostSight";
|
|
3348
|
+
AIFlags2[AIFlags2["PursuitLastSeen"] = 16] = "PursuitLastSeen";
|
|
3349
|
+
AIFlags2[AIFlags2["PursueNext"] = 32] = "PursueNext";
|
|
3350
|
+
AIFlags2[AIFlags2["PursueTemp"] = 64] = "PursueTemp";
|
|
3351
|
+
AIFlags2[AIFlags2["HoldFrame"] = 128] = "HoldFrame";
|
|
3352
|
+
AIFlags2[AIFlags2["GoodGuy"] = 256] = "GoodGuy";
|
|
3353
|
+
AIFlags2[AIFlags2["Brutal"] = 512] = "Brutal";
|
|
3354
|
+
AIFlags2[AIFlags2["NoStep"] = 1024] = "NoStep";
|
|
3355
|
+
AIFlags2[AIFlags2["Ducked"] = 2048] = "Ducked";
|
|
3356
|
+
AIFlags2[AIFlags2["CombatPoint"] = 4096] = "CombatPoint";
|
|
3357
|
+
AIFlags2[AIFlags2["Medic"] = 8192] = "Medic";
|
|
3358
|
+
AIFlags2[AIFlags2["Resurrecting"] = 16384] = "Resurrecting";
|
|
3359
|
+
AIFlags2[AIFlags2["Pathing"] = 1073741824] = "Pathing";
|
|
3360
|
+
return AIFlags2;
|
|
3361
|
+
})(AIFlags || {});
|
|
3362
|
+
var TraceMask = /* @__PURE__ */ ((TraceMask2) => {
|
|
3363
|
+
TraceMask2[TraceMask2["Opaque"] = 1] = "Opaque";
|
|
3364
|
+
TraceMask2[TraceMask2["Window"] = 2] = "Window";
|
|
3365
|
+
return TraceMask2;
|
|
3366
|
+
})(TraceMask || {});
|
|
3367
|
+
|
|
3368
|
+
// src/ai/movement.ts
|
|
3369
|
+
function yawVector(yawDegrees, distance2) {
|
|
3370
|
+
if (distance2 === 0) {
|
|
3371
|
+
return { x: 0, y: 0, z: 0 };
|
|
3372
|
+
}
|
|
3373
|
+
const radians = degToRad(yawDegrees);
|
|
3374
|
+
return {
|
|
3375
|
+
x: Math.cos(radians) * distance2,
|
|
3376
|
+
y: Math.sin(radians) * distance2,
|
|
3377
|
+
z: 0
|
|
3378
|
+
};
|
|
3344
3379
|
}
|
|
3345
|
-
function
|
|
3346
|
-
const
|
|
3347
|
-
|
|
3380
|
+
function walkMove(self, yawDegrees, distance2) {
|
|
3381
|
+
const delta = yawVector(yawDegrees, distance2);
|
|
3382
|
+
const origin = self.origin;
|
|
3383
|
+
origin.x += delta.x;
|
|
3384
|
+
origin.y += delta.y;
|
|
3385
|
+
origin.z += delta.z;
|
|
3386
|
+
return true;
|
|
3348
3387
|
}
|
|
3349
|
-
function
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
case "boolean":
|
|
3356
|
-
return parseBoolean(value);
|
|
3357
|
-
case "vec3":
|
|
3358
|
-
return parseVec3(value);
|
|
3359
|
-
case "string":
|
|
3360
|
-
return value;
|
|
3361
|
-
case "entity":
|
|
3362
|
-
case "callback":
|
|
3363
|
-
return void 0;
|
|
3364
|
-
default:
|
|
3365
|
-
return value;
|
|
3388
|
+
function changeYaw(self, deltaSeconds) {
|
|
3389
|
+
const current = angleMod(self.angles.y);
|
|
3390
|
+
const ideal = self.ideal_yaw;
|
|
3391
|
+
if (current === ideal) {
|
|
3392
|
+
self.angles.y = current;
|
|
3393
|
+
return;
|
|
3366
3394
|
}
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
if (
|
|
3370
|
-
|
|
3395
|
+
const speed = self.yaw_speed * deltaSeconds * 10;
|
|
3396
|
+
let move = ideal - current;
|
|
3397
|
+
if (ideal > current) {
|
|
3398
|
+
if (move >= 180) move -= 360;
|
|
3399
|
+
} else if (move <= -180) {
|
|
3400
|
+
move += 360;
|
|
3371
3401
|
}
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
if (parsed !== void 0) {
|
|
3382
|
-
entity[descriptor.name] = parsed;
|
|
3383
|
-
}
|
|
3402
|
+
if (move > speed) move = speed;
|
|
3403
|
+
else if (move < -speed) move = -speed;
|
|
3404
|
+
self.angles.y = angleMod(current + move);
|
|
3405
|
+
}
|
|
3406
|
+
function facingIdeal(self) {
|
|
3407
|
+
const delta = angleMod(self.angles.y - self.ideal_yaw);
|
|
3408
|
+
const hasPathing = (self.monsterinfo.aiflags & 1073741824 /* Pathing */) !== 0;
|
|
3409
|
+
if (hasPathing) {
|
|
3410
|
+
return !(delta > 5 && delta < 355);
|
|
3384
3411
|
}
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3412
|
+
return !(delta > 45 && delta < 315);
|
|
3413
|
+
}
|
|
3414
|
+
function ai_move(self, distance2) {
|
|
3415
|
+
walkMove(self, self.angles.y, distance2);
|
|
3416
|
+
}
|
|
3417
|
+
function setIdealYawTowards(self, target) {
|
|
3418
|
+
if (!target) return;
|
|
3419
|
+
const toTarget = {
|
|
3420
|
+
x: target.origin.x - self.origin.x,
|
|
3421
|
+
y: target.origin.y - self.origin.y,
|
|
3422
|
+
z: target.origin.z - self.origin.z
|
|
3389
3423
|
};
|
|
3424
|
+
self.ideal_yaw = vectorToYaw(toTarget);
|
|
3390
3425
|
}
|
|
3391
|
-
function
|
|
3392
|
-
|
|
3393
|
-
let result = "";
|
|
3394
|
-
while (index < text.length) {
|
|
3395
|
-
const char = text[index];
|
|
3396
|
-
if (char === '"') {
|
|
3397
|
-
return { value: result, nextIndex: index + 1 };
|
|
3398
|
-
}
|
|
3399
|
-
result += char;
|
|
3400
|
-
index += 1;
|
|
3401
|
-
}
|
|
3402
|
-
throw new Error("Unterminated quoted string in entity lump");
|
|
3426
|
+
function ai_stand(self, deltaSeconds) {
|
|
3427
|
+
changeYaw(self, deltaSeconds);
|
|
3403
3428
|
}
|
|
3404
|
-
function
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3429
|
+
function ai_walk(self, distance2, deltaSeconds) {
|
|
3430
|
+
setIdealYawTowards(self, self.goalentity);
|
|
3431
|
+
changeYaw(self, deltaSeconds);
|
|
3432
|
+
if (distance2 !== 0) {
|
|
3433
|
+
walkMove(self, self.angles.y, distance2);
|
|
3408
3434
|
}
|
|
3409
|
-
return index;
|
|
3410
3435
|
}
|
|
3411
|
-
function
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
return { token: null, nextIndex: index };
|
|
3415
|
-
}
|
|
3416
|
-
const current = text[index];
|
|
3417
|
-
if (current === "{" || current === "}") {
|
|
3418
|
-
return { token: current, nextIndex: index + 1 };
|
|
3419
|
-
}
|
|
3420
|
-
if (current !== '"') {
|
|
3421
|
-
throw new Error(`Unexpected token in entity lump: ${current}`);
|
|
3436
|
+
function ai_turn(self, distance2, deltaSeconds) {
|
|
3437
|
+
if (distance2 !== 0) {
|
|
3438
|
+
walkMove(self, self.angles.y, distance2);
|
|
3422
3439
|
}
|
|
3423
|
-
|
|
3424
|
-
return { token: quoted.value, nextIndex: quoted.nextIndex };
|
|
3440
|
+
changeYaw(self, deltaSeconds);
|
|
3425
3441
|
}
|
|
3426
|
-
function
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
index = open.nextIndex;
|
|
3432
|
-
if (open.token === null) {
|
|
3433
|
-
break;
|
|
3434
|
-
}
|
|
3435
|
-
if (open.token !== "{") {
|
|
3436
|
-
throw new Error("Expected { at start of entity definition");
|
|
3437
|
-
}
|
|
3438
|
-
const entity = {};
|
|
3439
|
-
while (true) {
|
|
3440
|
-
const keyToken = parseToken(text, index);
|
|
3441
|
-
index = keyToken.nextIndex;
|
|
3442
|
-
if (keyToken.token === null) {
|
|
3443
|
-
throw new Error("EOF reached while parsing entity");
|
|
3444
|
-
}
|
|
3445
|
-
if (keyToken.token === "}") {
|
|
3446
|
-
break;
|
|
3447
|
-
}
|
|
3448
|
-
const valueToken = parseToken(text, index);
|
|
3449
|
-
index = valueToken.nextIndex;
|
|
3450
|
-
if (valueToken.token === null || valueToken.token === "{" || valueToken.token === "}") {
|
|
3451
|
-
throw new Error("Malformed entity key/value pair");
|
|
3452
|
-
}
|
|
3453
|
-
if (!keyToken.token.startsWith("_")) {
|
|
3454
|
-
entity[keyToken.token] = valueToken.token;
|
|
3455
|
-
}
|
|
3456
|
-
}
|
|
3457
|
-
entities.push(entity);
|
|
3442
|
+
function ai_run(self, distance2, deltaSeconds) {
|
|
3443
|
+
setIdealYawTowards(self, self.enemy ?? self.goalentity);
|
|
3444
|
+
changeYaw(self, deltaSeconds);
|
|
3445
|
+
if (distance2 !== 0) {
|
|
3446
|
+
walkMove(self, self.angles.y, distance2);
|
|
3458
3447
|
}
|
|
3459
|
-
return entities;
|
|
3460
3448
|
}
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
}
|
|
3465
|
-
register(classname, spawn) {
|
|
3466
|
-
this.registry.set(classname, spawn);
|
|
3449
|
+
function ai_face(self, enemy, distance2, deltaSeconds) {
|
|
3450
|
+
if (enemy) {
|
|
3451
|
+
setIdealYawTowards(self, enemy);
|
|
3467
3452
|
}
|
|
3468
|
-
|
|
3469
|
-
|
|
3453
|
+
changeYaw(self, deltaSeconds);
|
|
3454
|
+
if (distance2 !== 0) {
|
|
3455
|
+
walkMove(self, self.angles.y, distance2);
|
|
3470
3456
|
}
|
|
3471
|
-
};
|
|
3472
|
-
function defaultWarn(message) {
|
|
3473
|
-
void message;
|
|
3474
3457
|
}
|
|
3475
|
-
function
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
if (
|
|
3479
|
-
|
|
3480
|
-
return null;
|
|
3458
|
+
function ai_charge(self, distance2, deltaSeconds) {
|
|
3459
|
+
setIdealYawTowards(self, self.enemy);
|
|
3460
|
+
changeYaw(self, deltaSeconds);
|
|
3461
|
+
if (distance2 !== 0) {
|
|
3462
|
+
walkMove(self, self.angles.y, distance2);
|
|
3481
3463
|
}
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
// src/ai/perception.ts
|
|
3467
|
+
var RangeCategory = /* @__PURE__ */ ((RangeCategory2) => {
|
|
3468
|
+
RangeCategory2["Melee"] = "melee";
|
|
3469
|
+
RangeCategory2["Near"] = "near";
|
|
3470
|
+
RangeCategory2["Mid"] = "mid";
|
|
3471
|
+
RangeCategory2["Far"] = "far";
|
|
3472
|
+
return RangeCategory2;
|
|
3473
|
+
})(RangeCategory || {});
|
|
3474
|
+
function absBounds(entity) {
|
|
3475
|
+
return {
|
|
3476
|
+
mins: {
|
|
3477
|
+
x: entity.origin.x + entity.mins.x,
|
|
3478
|
+
y: entity.origin.y + entity.mins.y,
|
|
3479
|
+
z: entity.origin.z + entity.mins.z
|
|
3480
|
+
},
|
|
3481
|
+
maxs: {
|
|
3482
|
+
x: entity.origin.x + entity.maxs.x,
|
|
3483
|
+
y: entity.origin.y + entity.maxs.y,
|
|
3484
|
+
z: entity.origin.z + entity.maxs.z
|
|
3491
3485
|
}
|
|
3492
3486
|
};
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3487
|
+
}
|
|
3488
|
+
function rangeTo(self, other) {
|
|
3489
|
+
const a = absBounds(self);
|
|
3490
|
+
const b = absBounds(other);
|
|
3491
|
+
const distanceSquared = distanceBetweenBoxesSquared(a.mins, a.maxs, b.mins, b.maxs);
|
|
3492
|
+
return Math.sqrt(distanceSquared);
|
|
3493
|
+
}
|
|
3494
|
+
function classifyRange(distance2) {
|
|
3495
|
+
if (distance2 <= RANGE_MELEE) {
|
|
3496
|
+
return "melee" /* Melee */;
|
|
3500
3497
|
}
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
return null;
|
|
3498
|
+
if (distance2 <= RANGE_NEAR) {
|
|
3499
|
+
return "near" /* Near */;
|
|
3504
3500
|
}
|
|
3505
|
-
|
|
3506
|
-
|
|
3501
|
+
if (distance2 <= RANGE_MID) {
|
|
3502
|
+
return "mid" /* Mid */;
|
|
3503
|
+
}
|
|
3504
|
+
return "far" /* Far */;
|
|
3507
3505
|
}
|
|
3508
|
-
function
|
|
3509
|
-
const
|
|
3510
|
-
const
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
spawned.push(entity);
|
|
3515
|
-
}
|
|
3506
|
+
function infront(self, other) {
|
|
3507
|
+
const { forward } = angleVectors(self.angles);
|
|
3508
|
+
const direction = normalizeVec3(subtractVec3(other.origin, self.origin));
|
|
3509
|
+
const dot = dotVec3(direction, forward);
|
|
3510
|
+
if ((self.spawnflags & SPAWNFLAG_MONSTER_AMBUSH) !== 0 && self.trail_time === 0 && self.enemy === null) {
|
|
3511
|
+
return dot > 0.15;
|
|
3516
3512
|
}
|
|
3517
|
-
return
|
|
3513
|
+
return dot > -0.3;
|
|
3518
3514
|
}
|
|
3519
|
-
function
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3515
|
+
function visible(self, other, trace, options) {
|
|
3516
|
+
if ((other.flags & FL_NOVISIBLE) !== 0) {
|
|
3517
|
+
return false;
|
|
3518
|
+
}
|
|
3519
|
+
const start = { x: self.origin.x, y: self.origin.y, z: self.origin.z + self.viewheight };
|
|
3520
|
+
const end = { x: other.origin.x, y: other.origin.y, z: other.origin.z + other.viewheight };
|
|
3521
|
+
const mask = options?.throughGlass ? 1 /* Opaque */ : 1 /* Opaque */ | 2 /* Window */;
|
|
3522
|
+
const result = trace(start, end, self, mask);
|
|
3523
|
+
return result.fraction === 1 || result.entity === other;
|
|
3523
3524
|
}
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
}
|
|
3532
|
-
|
|
3533
|
-
});
|
|
3534
|
-
registry.register("info_player_coop", () => {
|
|
3535
|
-
});
|
|
3536
|
-
registry.register("info_null", (entity, context) => {
|
|
3537
|
-
context.free(entity);
|
|
3538
|
-
});
|
|
3539
|
-
registry.register("info_notnull", () => {
|
|
3540
|
-
});
|
|
3541
|
-
registry.register("info_teleport_destination", () => {
|
|
3542
|
-
});
|
|
3543
|
-
registerTriggerSpawns(registry);
|
|
3544
|
-
registerTargetSpawns(registry);
|
|
3545
|
-
registerMiscSpawns(registry);
|
|
3546
|
-
registerItemSpawns(game, registry);
|
|
3547
|
-
registerFuncSpawns(registry);
|
|
3548
|
-
registerPathSpawns(registry);
|
|
3549
|
-
registerLightSpawns(registry);
|
|
3525
|
+
|
|
3526
|
+
// src/ai/targeting.ts
|
|
3527
|
+
function setIdealYawTowards2(self, other) {
|
|
3528
|
+
const delta = {
|
|
3529
|
+
x: other.origin.x - self.origin.x,
|
|
3530
|
+
y: other.origin.y - self.origin.y,
|
|
3531
|
+
z: other.origin.z - self.origin.z
|
|
3532
|
+
};
|
|
3533
|
+
self.ideal_yaw = vectorToYaw(delta);
|
|
3550
3534
|
}
|
|
3551
|
-
function
|
|
3552
|
-
|
|
3553
|
-
registerDefaultSpawns(game, registry);
|
|
3554
|
-
return registry;
|
|
3535
|
+
function faceYawInstantly(self) {
|
|
3536
|
+
self.angles.y = angleMod(self.ideal_yaw);
|
|
3555
3537
|
}
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3538
|
+
function huntTarget(self, level) {
|
|
3539
|
+
if (!self.enemy) return;
|
|
3540
|
+
self.goalentity = self.enemy;
|
|
3541
|
+
setIdealYawTowards2(self, self.enemy);
|
|
3542
|
+
faceYawInstantly(self);
|
|
3543
|
+
if ((self.monsterinfo.aiflags & 1 /* StandGround */) !== 0) {
|
|
3544
|
+
self.monsterinfo.stand?.(self);
|
|
3545
|
+
} else {
|
|
3546
|
+
self.monsterinfo.run?.(self);
|
|
3547
|
+
self.attack_finished_time = level.timeSeconds + 1;
|
|
3548
|
+
}
|
|
3560
3549
|
}
|
|
3561
|
-
function
|
|
3562
|
-
if (
|
|
3550
|
+
function foundTarget(self, level, options) {
|
|
3551
|
+
if (!self.enemy) return;
|
|
3552
|
+
if ((self.enemy.svflags & 8 /* Player */) !== 0) {
|
|
3553
|
+
level.sightEntity = self;
|
|
3554
|
+
level.sightEntityFrame = level.frameNumber;
|
|
3555
|
+
self.light_level = 128;
|
|
3556
|
+
}
|
|
3557
|
+
self.show_hostile = level.timeSeconds + 1;
|
|
3558
|
+
const lastSighting = self.monsterinfo.last_sighting;
|
|
3559
|
+
lastSighting.x = self.enemy.origin.x;
|
|
3560
|
+
lastSighting.y = self.enemy.origin.y;
|
|
3561
|
+
lastSighting.z = self.enemy.origin.z;
|
|
3562
|
+
self.trail_time = level.timeSeconds;
|
|
3563
|
+
self.monsterinfo.trail_time = level.timeSeconds;
|
|
3564
|
+
if (!self.combattarget) {
|
|
3565
|
+
huntTarget(self, level);
|
|
3563
3566
|
return;
|
|
3564
3567
|
}
|
|
3565
|
-
|
|
3568
|
+
const pickTarget = options?.pickTarget;
|
|
3569
|
+
const movetarget = pickTarget?.(self.combattarget) ?? self.enemy;
|
|
3570
|
+
self.goalentity = movetarget;
|
|
3571
|
+
self.movetarget = movetarget;
|
|
3572
|
+
self.combattarget = void 0;
|
|
3573
|
+
self.monsterinfo.aiflags |= 4096 /* CombatPoint */;
|
|
3574
|
+
if (self.movetarget) {
|
|
3575
|
+
self.movetarget.targetname = void 0;
|
|
3576
|
+
}
|
|
3577
|
+
self.monsterinfo.pausetime = 0;
|
|
3578
|
+
self.monsterinfo.run?.(self);
|
|
3566
3579
|
}
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
"
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
}
|
|
3580
|
+
function classifyClientVisibility(self, other, level, trace) {
|
|
3581
|
+
const distance2 = rangeTo(self, other);
|
|
3582
|
+
const range = classifyRange(distance2);
|
|
3583
|
+
if (range === "far" /* Far */) return false;
|
|
3584
|
+
if (other.light_level <= 5) return false;
|
|
3585
|
+
if (!visible(self, other, trace, { throughGlass: false })) return false;
|
|
3586
|
+
if (range === "near" /* Near */) {
|
|
3587
|
+
return level.timeSeconds <= other.show_hostile || infront(self, other);
|
|
3588
|
+
}
|
|
3589
|
+
if (range === "mid" /* Mid */) {
|
|
3590
|
+
return infront(self, other);
|
|
3591
|
+
}
|
|
3592
|
+
return true;
|
|
3593
|
+
}
|
|
3594
|
+
function updateSoundChase(self, client, level, hearability, trace) {
|
|
3595
|
+
if ((self.spawnflags & SPAWNFLAG_MONSTER_AMBUSH) !== 0) {
|
|
3596
|
+
if (!visible(self, client, trace)) return false;
|
|
3597
|
+
} else if (hearability.canHear && !hearability.canHear(self, client)) {
|
|
3598
|
+
return false;
|
|
3599
|
+
}
|
|
3600
|
+
const delta = subtractVec3(client.origin, self.origin);
|
|
3601
|
+
if (lengthVec3(delta) > 1e3) return false;
|
|
3602
|
+
if (hearability.areasConnected && !hearability.areasConnected(self, client)) return false;
|
|
3603
|
+
self.ideal_yaw = vectorToYaw(delta);
|
|
3604
|
+
faceYawInstantly(self);
|
|
3605
|
+
self.monsterinfo.aiflags |= 4 /* SoundTarget */;
|
|
3606
|
+
self.enemy = client;
|
|
3607
|
+
return true;
|
|
3608
|
+
}
|
|
3609
|
+
function chooseCandidate(self, level) {
|
|
3610
|
+
if (level.sightEntity && level.sightEntityFrame >= level.frameNumber - 1 && (self.spawnflags & SPAWNFLAG_MONSTER_AMBUSH) === 0) {
|
|
3611
|
+
if (level.sightEntity.enemy !== self.enemy) {
|
|
3612
|
+
return { candidate: level.sightEntity, heardit: false };
|
|
3600
3613
|
}
|
|
3614
|
+
return { candidate: null, heardit: false };
|
|
3601
3615
|
}
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
handlers.push(handler);
|
|
3605
|
-
this.stageCounts[stage] += 1;
|
|
3606
|
-
return () => {
|
|
3607
|
-
const index = handlers.indexOf(handler);
|
|
3608
|
-
if (index >= 0 && handlers[index]) {
|
|
3609
|
-
handlers[index] = void 0;
|
|
3610
|
-
this.stageCounts[stage] -= 1;
|
|
3611
|
-
this.stageCompactionNeeded[stage] = true;
|
|
3612
|
-
}
|
|
3613
|
-
};
|
|
3616
|
+
if (level.soundEntity && level.soundEntityFrame >= level.frameNumber - 1) {
|
|
3617
|
+
return { candidate: level.soundEntity, heardit: true };
|
|
3614
3618
|
}
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
this.frame = 0;
|
|
3619
|
+
if (!self.enemy && level.sound2Entity && level.sound2EntityFrame >= level.frameNumber - 1 && (self.spawnflags & SPAWNFLAG_MONSTER_AMBUSH) === 0) {
|
|
3620
|
+
return { candidate: level.sound2Entity, heardit: true };
|
|
3618
3621
|
}
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
this.timeMs = previousTimeMs + step.deltaMs;
|
|
3622
|
-
this.frame = step.frame;
|
|
3623
|
-
const context = {
|
|
3624
|
-
...step,
|
|
3625
|
-
timeMs: this.timeMs,
|
|
3626
|
-
previousTimeMs,
|
|
3627
|
-
deltaSeconds: step.deltaMs / 1e3
|
|
3628
|
-
};
|
|
3629
|
-
this.runStage("prep", context);
|
|
3630
|
-
if (this.stageCounts.simulate === 0) {
|
|
3631
|
-
throw new Error("GameFrameLoop requires at least one simulate stage");
|
|
3632
|
-
}
|
|
3633
|
-
this.runStage("simulate", context);
|
|
3634
|
-
this.runStage("finish", context);
|
|
3635
|
-
return context;
|
|
3622
|
+
if (level.sightClient) {
|
|
3623
|
+
return { candidate: level.sightClient, heardit: false };
|
|
3636
3624
|
}
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
}
|
|
3644
|
-
handler(context);
|
|
3645
|
-
}
|
|
3646
|
-
if (this.stageCompactionNeeded[stage]) {
|
|
3647
|
-
this.compactStageHandlers(stage);
|
|
3648
|
-
}
|
|
3625
|
+
return { candidate: null, heardit: false };
|
|
3626
|
+
}
|
|
3627
|
+
function rejectNotargetEntity(client) {
|
|
3628
|
+
if ((client.flags & FL_NOTARGET) !== 0) return true;
|
|
3629
|
+
if ((client.svflags & 4 /* Monster */) !== 0 && client.enemy) {
|
|
3630
|
+
return (client.enemy.flags & FL_NOTARGET) !== 0;
|
|
3649
3631
|
}
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
writeIndex += 1;
|
|
3658
|
-
}
|
|
3632
|
+
if (client.enemy && (client.enemy.flags & FL_NOTARGET) !== 0) return true;
|
|
3633
|
+
return false;
|
|
3634
|
+
}
|
|
3635
|
+
function findTarget(self, level, trace, hearability = {}) {
|
|
3636
|
+
if ((self.monsterinfo.aiflags & 256 /* GoodGuy */) !== 0) {
|
|
3637
|
+
if (self.goalentity?.classname === "target_actor") {
|
|
3638
|
+
return false;
|
|
3659
3639
|
}
|
|
3660
|
-
|
|
3661
|
-
this.stageCompactionNeeded[stage] = false;
|
|
3640
|
+
return false;
|
|
3662
3641
|
}
|
|
3663
|
-
|
|
3664
|
-
return
|
|
3642
|
+
if ((self.monsterinfo.aiflags & 4096 /* CombatPoint */) !== 0) {
|
|
3643
|
+
return false;
|
|
3665
3644
|
}
|
|
3666
|
-
|
|
3667
|
-
|
|
3645
|
+
const { candidate, heardit } = chooseCandidate(self, level);
|
|
3646
|
+
if (!candidate || !candidate.inUse) return false;
|
|
3647
|
+
if (candidate === self.enemy) return true;
|
|
3648
|
+
if (rejectNotargetEntity(candidate)) return false;
|
|
3649
|
+
if (!heardit) {
|
|
3650
|
+
if (!classifyClientVisibility(self, candidate, level, trace)) return false;
|
|
3651
|
+
self.monsterinfo.aiflags &= ~4 /* SoundTarget */;
|
|
3652
|
+
self.enemy = candidate;
|
|
3653
|
+
} else if (!updateSoundChase(self, candidate, level, hearability, trace)) {
|
|
3654
|
+
return false;
|
|
3668
3655
|
}
|
|
3669
|
-
|
|
3656
|
+
foundTarget(self, level);
|
|
3657
|
+
if ((self.monsterinfo.aiflags & 4 /* SoundTarget */) === 0) {
|
|
3658
|
+
self.monsterinfo.sight?.(self, self.enemy);
|
|
3659
|
+
}
|
|
3660
|
+
return true;
|
|
3661
|
+
}
|
|
3670
3662
|
|
|
3671
|
-
// src/
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
deltaSeconds: 0
|
|
3677
|
-
};
|
|
3678
|
-
var LevelClock = class {
|
|
3679
|
-
constructor() {
|
|
3680
|
-
this.state = ZERO_STATE;
|
|
3663
|
+
// src/ai/monster.ts
|
|
3664
|
+
function M_MoveFrame(self) {
|
|
3665
|
+
const move = self.monsterinfo.current_move;
|
|
3666
|
+
if (!move) {
|
|
3667
|
+
return;
|
|
3681
3668
|
}
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
frameNumber: 0,
|
|
3686
|
-
timeSeconds: startSeconds,
|
|
3687
|
-
previousTimeSeconds: startSeconds,
|
|
3688
|
-
deltaSeconds: 0
|
|
3689
|
-
};
|
|
3669
|
+
if (self.frame < move.firstframe || self.frame > move.lastframe) {
|
|
3670
|
+
self.monsterinfo.aiflags &= ~128 /* HoldFrame */;
|
|
3671
|
+
self.frame = move.firstframe;
|
|
3690
3672
|
}
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
frameNumber: context.frame,
|
|
3694
|
-
timeSeconds: context.timeMs / 1e3,
|
|
3695
|
-
previousTimeSeconds: context.previousTimeMs / 1e3,
|
|
3696
|
-
deltaSeconds: context.deltaSeconds
|
|
3697
|
-
};
|
|
3698
|
-
return this.state;
|
|
3673
|
+
if ((self.monsterinfo.aiflags & 128 /* HoldFrame */) !== 0) {
|
|
3674
|
+
return;
|
|
3699
3675
|
}
|
|
3700
|
-
|
|
3701
|
-
|
|
3676
|
+
const index = self.frame - move.firstframe;
|
|
3677
|
+
const frame = move.frames[index];
|
|
3678
|
+
if (frame.ai) {
|
|
3679
|
+
frame.ai(self, frame.dist);
|
|
3702
3680
|
}
|
|
3703
|
-
|
|
3704
|
-
|
|
3681
|
+
if (frame.think) {
|
|
3682
|
+
frame.think(self);
|
|
3705
3683
|
}
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
AIFlags2[AIFlags2["GoodGuy"] = 256] = "GoodGuy";
|
|
3725
|
-
AIFlags2[AIFlags2["Brutal"] = 512] = "Brutal";
|
|
3726
|
-
AIFlags2[AIFlags2["NoStep"] = 1024] = "NoStep";
|
|
3727
|
-
AIFlags2[AIFlags2["Ducked"] = 2048] = "Ducked";
|
|
3728
|
-
AIFlags2[AIFlags2["CombatPoint"] = 4096] = "CombatPoint";
|
|
3729
|
-
AIFlags2[AIFlags2["Medic"] = 8192] = "Medic";
|
|
3730
|
-
AIFlags2[AIFlags2["Resurrecting"] = 16384] = "Resurrecting";
|
|
3731
|
-
AIFlags2[AIFlags2["Pathing"] = 1073741824] = "Pathing";
|
|
3732
|
-
return AIFlags2;
|
|
3733
|
-
})(AIFlags || {});
|
|
3734
|
-
var TraceMask = /* @__PURE__ */ ((TraceMask2) => {
|
|
3735
|
-
TraceMask2[TraceMask2["Opaque"] = 1] = "Opaque";
|
|
3736
|
-
TraceMask2[TraceMask2["Window"] = 2] = "Window";
|
|
3737
|
-
return TraceMask2;
|
|
3738
|
-
})(TraceMask || {});
|
|
3684
|
+
if (!self.inUse) {
|
|
3685
|
+
return;
|
|
3686
|
+
}
|
|
3687
|
+
self.frame++;
|
|
3688
|
+
if (self.frame > move.lastframe) {
|
|
3689
|
+
if (move.endfunc) {
|
|
3690
|
+
move.endfunc(self);
|
|
3691
|
+
if (self.monsterinfo.current_move !== move) {
|
|
3692
|
+
return;
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
function monster_think(self, context) {
|
|
3698
|
+
M_MoveFrame(self);
|
|
3699
|
+
const time = context && typeof context.timeSeconds === "number" ? context.timeSeconds : self.nextthink;
|
|
3700
|
+
self.nextthink = time + 0.1;
|
|
3701
|
+
}
|
|
3739
3702
|
|
|
3740
|
-
// src/
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3703
|
+
// src/entities/monsters/soldier.ts
|
|
3704
|
+
var MONSTER_TICK = 0.1;
|
|
3705
|
+
function monster_ai_stand(self, dist) {
|
|
3706
|
+
ai_stand(self, MONSTER_TICK);
|
|
3707
|
+
}
|
|
3708
|
+
function monster_ai_walk(self, dist) {
|
|
3709
|
+
ai_walk(self, dist, MONSTER_TICK);
|
|
3710
|
+
}
|
|
3711
|
+
function monster_ai_run(self, dist) {
|
|
3712
|
+
ai_run(self, dist, MONSTER_TICK);
|
|
3713
|
+
}
|
|
3714
|
+
function monster_ai_charge(self, dist) {
|
|
3715
|
+
ai_charge(self, dist, MONSTER_TICK);
|
|
3716
|
+
}
|
|
3717
|
+
var stand_move;
|
|
3718
|
+
var walk_move;
|
|
3719
|
+
var run_move;
|
|
3720
|
+
var attack_move;
|
|
3721
|
+
function soldier_stand(self) {
|
|
3722
|
+
self.monsterinfo.current_move = stand_move;
|
|
3723
|
+
}
|
|
3724
|
+
function soldier_walk(self) {
|
|
3725
|
+
self.monsterinfo.current_move = walk_move;
|
|
3726
|
+
}
|
|
3727
|
+
function soldier_run(self) {
|
|
3728
|
+
if (self.enemy && self.enemy.health > 0) {
|
|
3729
|
+
self.monsterinfo.current_move = run_move;
|
|
3730
|
+
} else {
|
|
3731
|
+
self.monsterinfo.current_move = stand_move;
|
|
3744
3732
|
}
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3733
|
+
}
|
|
3734
|
+
function soldier_attack(self) {
|
|
3735
|
+
self.monsterinfo.current_move = attack_move;
|
|
3736
|
+
}
|
|
3737
|
+
function soldier_fire(self) {
|
|
3738
|
+
}
|
|
3739
|
+
var stand_frames = Array.from({ length: 30 }, () => ({
|
|
3740
|
+
ai: monster_ai_stand,
|
|
3741
|
+
dist: 0
|
|
3742
|
+
}));
|
|
3743
|
+
stand_move = {
|
|
3744
|
+
firstframe: 0,
|
|
3745
|
+
lastframe: 29,
|
|
3746
|
+
frames: stand_frames,
|
|
3747
|
+
endfunc: soldier_stand
|
|
3748
|
+
};
|
|
3749
|
+
var walk_frames = Array.from({ length: 40 }, () => ({
|
|
3750
|
+
ai: monster_ai_walk,
|
|
3751
|
+
dist: 2
|
|
3752
|
+
}));
|
|
3753
|
+
walk_move = {
|
|
3754
|
+
firstframe: 30,
|
|
3755
|
+
lastframe: 69,
|
|
3756
|
+
frames: walk_frames,
|
|
3757
|
+
endfunc: soldier_walk
|
|
3758
|
+
};
|
|
3759
|
+
var run_frames = Array.from({ length: 20 }, () => ({
|
|
3760
|
+
ai: monster_ai_run,
|
|
3761
|
+
dist: 10
|
|
3762
|
+
}));
|
|
3763
|
+
run_move = {
|
|
3764
|
+
firstframe: 70,
|
|
3765
|
+
lastframe: 89,
|
|
3766
|
+
frames: run_frames,
|
|
3767
|
+
endfunc: soldier_run
|
|
3768
|
+
};
|
|
3769
|
+
var attack_frames = Array.from({ length: 10 }, (_, i) => ({
|
|
3770
|
+
ai: monster_ai_charge,
|
|
3771
|
+
dist: 0,
|
|
3772
|
+
think: i === 5 ? soldier_fire : null
|
|
3773
|
+
}));
|
|
3774
|
+
attack_move = {
|
|
3775
|
+
firstframe: 90,
|
|
3776
|
+
lastframe: 99,
|
|
3777
|
+
frames: attack_frames,
|
|
3778
|
+
endfunc: soldier_run
|
|
3779
|
+
};
|
|
3780
|
+
function SP_monster_soldier(self, context) {
|
|
3781
|
+
self.model = "models/monsters/soldier/tris.md2";
|
|
3782
|
+
self.mins = { x: -16, y: -16, z: -24 };
|
|
3783
|
+
self.maxs = { x: 16, y: 16, z: 32 };
|
|
3784
|
+
self.movetype = 5 /* Step */;
|
|
3785
|
+
self.solid = 2 /* BoundingBox */;
|
|
3786
|
+
self.health = 20;
|
|
3787
|
+
self.max_health = 20;
|
|
3788
|
+
self.mass = 100;
|
|
3789
|
+
self.pain = (self2, other, kick, damage) => {
|
|
3750
3790
|
};
|
|
3791
|
+
self.die = (self2, inflictor, attacker, damage, point) => {
|
|
3792
|
+
self2.deadflag = 2 /* Dead */;
|
|
3793
|
+
self2.solid = 0 /* Not */;
|
|
3794
|
+
};
|
|
3795
|
+
self.monsterinfo.stand = soldier_stand;
|
|
3796
|
+
self.monsterinfo.walk = soldier_walk;
|
|
3797
|
+
self.monsterinfo.run = soldier_run;
|
|
3798
|
+
self.monsterinfo.attack = soldier_attack;
|
|
3799
|
+
self.think = monster_think;
|
|
3800
|
+
soldier_stand(self);
|
|
3801
|
+
self.nextthink = self.timestamp + MONSTER_TICK;
|
|
3751
3802
|
}
|
|
3752
|
-
function
|
|
3753
|
-
|
|
3754
|
-
const origin = self.origin;
|
|
3755
|
-
origin.x += delta.x;
|
|
3756
|
-
origin.y += delta.y;
|
|
3757
|
-
origin.z += delta.z;
|
|
3758
|
-
return true;
|
|
3803
|
+
function registerMonsterSpawns(registry) {
|
|
3804
|
+
registry.register("monster_soldier", SP_monster_soldier);
|
|
3759
3805
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
const
|
|
3768
|
-
|
|
3769
|
-
if (ideal > current) {
|
|
3770
|
-
if (move >= 180) move -= 360;
|
|
3771
|
-
} else if (move <= -180) {
|
|
3772
|
-
move += 360;
|
|
3773
|
-
}
|
|
3774
|
-
if (move > speed) move = speed;
|
|
3775
|
-
else if (move < -speed) move = -speed;
|
|
3776
|
-
self.angles.y = angleMod(current + move);
|
|
3806
|
+
|
|
3807
|
+
// src/entities/spawn.ts
|
|
3808
|
+
var FIELD_LOOKUP = new Map(
|
|
3809
|
+
ENTITY_FIELD_METADATA.map((field) => [field.name, field])
|
|
3810
|
+
);
|
|
3811
|
+
function parseVec3(text) {
|
|
3812
|
+
const parts = text.trim().split(/\s+/);
|
|
3813
|
+
const [x = 0, y = 0, z = 0] = parts.map((part) => Number.parseFloat(part)).map((value) => Number.isNaN(value) ? 0 : value);
|
|
3814
|
+
return { x, y, z };
|
|
3777
3815
|
}
|
|
3778
|
-
function
|
|
3779
|
-
const
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3816
|
+
function parseBoolean(text) {
|
|
3817
|
+
const normalized = text.trim().toLowerCase();
|
|
3818
|
+
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
3819
|
+
}
|
|
3820
|
+
function parseValue(type, value) {
|
|
3821
|
+
switch (type) {
|
|
3822
|
+
case "int":
|
|
3823
|
+
return Number.parseInt(value, 10) || 0;
|
|
3824
|
+
case "float":
|
|
3825
|
+
return Number.parseFloat(value) || 0;
|
|
3826
|
+
case "boolean":
|
|
3827
|
+
return parseBoolean(value);
|
|
3828
|
+
case "vec3":
|
|
3829
|
+
return parseVec3(value);
|
|
3830
|
+
case "string":
|
|
3831
|
+
return value;
|
|
3832
|
+
case "entity":
|
|
3833
|
+
case "callback":
|
|
3834
|
+
return void 0;
|
|
3835
|
+
default:
|
|
3836
|
+
return value;
|
|
3783
3837
|
}
|
|
3784
|
-
return !(delta > 45 && delta < 315);
|
|
3785
|
-
}
|
|
3786
|
-
function ai_move(self, distance2) {
|
|
3787
|
-
walkMove(self, self.angles.y, distance2);
|
|
3788
3838
|
}
|
|
3789
|
-
function
|
|
3790
|
-
if (!
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3839
|
+
function applyEntityKeyValues(entity, values) {
|
|
3840
|
+
if ("angle" in values && !("angles" in values)) {
|
|
3841
|
+
entity.angles = { x: 0, y: Number.parseFloat(values.angle) || 0, z: 0 };
|
|
3842
|
+
}
|
|
3843
|
+
for (const [key, rawValue] of Object.entries(values)) {
|
|
3844
|
+
if (key.startsWith("_")) {
|
|
3845
|
+
continue;
|
|
3846
|
+
}
|
|
3847
|
+
const descriptor = FIELD_LOOKUP.get(key);
|
|
3848
|
+
if (!descriptor) {
|
|
3849
|
+
continue;
|
|
3850
|
+
}
|
|
3851
|
+
const parsed = parseValue(descriptor.type, rawValue);
|
|
3852
|
+
if (parsed !== void 0) {
|
|
3853
|
+
entity[descriptor.name] = parsed;
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
entity.size = {
|
|
3857
|
+
x: entity.maxs.x - entity.mins.x,
|
|
3858
|
+
y: entity.maxs.y - entity.mins.y,
|
|
3859
|
+
z: entity.maxs.z - entity.mins.z
|
|
3795
3860
|
};
|
|
3796
|
-
self.ideal_yaw = vectorToYaw(toTarget);
|
|
3797
3861
|
}
|
|
3798
|
-
function
|
|
3799
|
-
|
|
3862
|
+
function parseQuoted(text, start) {
|
|
3863
|
+
let index = start;
|
|
3864
|
+
let result = "";
|
|
3865
|
+
while (index < text.length) {
|
|
3866
|
+
const char = text[index];
|
|
3867
|
+
if (char === '"') {
|
|
3868
|
+
return { value: result, nextIndex: index + 1 };
|
|
3869
|
+
}
|
|
3870
|
+
result += char;
|
|
3871
|
+
index += 1;
|
|
3872
|
+
}
|
|
3873
|
+
throw new Error("Unterminated quoted string in entity lump");
|
|
3800
3874
|
}
|
|
3801
|
-
function
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
walkMove(self, self.angles.y, distance2);
|
|
3875
|
+
function consumeWhitespace(text, start) {
|
|
3876
|
+
let index = start;
|
|
3877
|
+
while (index < text.length && /\s/.test(text[index] ?? "")) {
|
|
3878
|
+
index += 1;
|
|
3806
3879
|
}
|
|
3880
|
+
return index;
|
|
3807
3881
|
}
|
|
3808
|
-
function
|
|
3809
|
-
|
|
3810
|
-
|
|
3882
|
+
function parseToken(text, start) {
|
|
3883
|
+
const index = consumeWhitespace(text, start);
|
|
3884
|
+
if (index >= text.length) {
|
|
3885
|
+
return { token: null, nextIndex: index };
|
|
3811
3886
|
}
|
|
3812
|
-
|
|
3887
|
+
const current = text[index];
|
|
3888
|
+
if (current === "{" || current === "}") {
|
|
3889
|
+
return { token: current, nextIndex: index + 1 };
|
|
3890
|
+
}
|
|
3891
|
+
if (current !== '"') {
|
|
3892
|
+
throw new Error(`Unexpected token in entity lump: ${current}`);
|
|
3893
|
+
}
|
|
3894
|
+
const quoted = parseQuoted(text, index + 1);
|
|
3895
|
+
return { token: quoted.value, nextIndex: quoted.nextIndex };
|
|
3813
3896
|
}
|
|
3814
|
-
function
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3897
|
+
function parseEntityLump(text) {
|
|
3898
|
+
const entities = [];
|
|
3899
|
+
let index = 0;
|
|
3900
|
+
while (index < text.length) {
|
|
3901
|
+
const open = parseToken(text, index);
|
|
3902
|
+
index = open.nextIndex;
|
|
3903
|
+
if (open.token === null) {
|
|
3904
|
+
break;
|
|
3905
|
+
}
|
|
3906
|
+
if (open.token !== "{") {
|
|
3907
|
+
throw new Error("Expected { at start of entity definition");
|
|
3908
|
+
}
|
|
3909
|
+
const entity = {};
|
|
3910
|
+
while (true) {
|
|
3911
|
+
const keyToken = parseToken(text, index);
|
|
3912
|
+
index = keyToken.nextIndex;
|
|
3913
|
+
if (keyToken.token === null) {
|
|
3914
|
+
throw new Error("EOF reached while parsing entity");
|
|
3915
|
+
}
|
|
3916
|
+
if (keyToken.token === "}") {
|
|
3917
|
+
break;
|
|
3918
|
+
}
|
|
3919
|
+
const valueToken = parseToken(text, index);
|
|
3920
|
+
index = valueToken.nextIndex;
|
|
3921
|
+
if (valueToken.token === null || valueToken.token === "{" || valueToken.token === "}") {
|
|
3922
|
+
throw new Error("Malformed entity key/value pair");
|
|
3923
|
+
}
|
|
3924
|
+
if (!keyToken.token.startsWith("_")) {
|
|
3925
|
+
entity[keyToken.token] = valueToken.token;
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
entities.push(entity);
|
|
3819
3929
|
}
|
|
3930
|
+
return entities;
|
|
3820
3931
|
}
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3932
|
+
var SpawnRegistry = class {
|
|
3933
|
+
constructor() {
|
|
3934
|
+
this.registry = /* @__PURE__ */ new Map();
|
|
3824
3935
|
}
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
walkMove(self, self.angles.y, distance2);
|
|
3936
|
+
register(classname, spawn) {
|
|
3937
|
+
this.registry.set(classname, spawn);
|
|
3828
3938
|
}
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
setIdealYawTowards(self, self.enemy);
|
|
3832
|
-
changeYaw(self, deltaSeconds);
|
|
3833
|
-
if (distance2 !== 0) {
|
|
3834
|
-
walkMove(self, self.angles.y, distance2);
|
|
3939
|
+
get(classname) {
|
|
3940
|
+
return this.registry.get(classname);
|
|
3835
3941
|
}
|
|
3942
|
+
};
|
|
3943
|
+
function defaultWarn(message) {
|
|
3944
|
+
void message;
|
|
3836
3945
|
}
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
maxs: {
|
|
3854
|
-
x: entity.origin.x + entity.maxs.x,
|
|
3855
|
-
y: entity.origin.y + entity.maxs.y,
|
|
3856
|
-
z: entity.origin.z + entity.maxs.z
|
|
3946
|
+
function spawnEntityFromDictionary(dictionary, options) {
|
|
3947
|
+
const warn = options.onWarning ?? defaultWarn;
|
|
3948
|
+
const classname = dictionary.classname;
|
|
3949
|
+
if (!classname) {
|
|
3950
|
+
warn("Encountered entity with no classname");
|
|
3951
|
+
return null;
|
|
3952
|
+
}
|
|
3953
|
+
const isWorld = classname === "worldspawn";
|
|
3954
|
+
const entity = isWorld ? options.entities.world : options.entities.spawn();
|
|
3955
|
+
applyEntityKeyValues(entity, dictionary);
|
|
3956
|
+
const context = {
|
|
3957
|
+
keyValues: dictionary,
|
|
3958
|
+
entities: options.entities,
|
|
3959
|
+
warn,
|
|
3960
|
+
free(target) {
|
|
3961
|
+
options.entities.freeImmediate(target);
|
|
3857
3962
|
}
|
|
3858
3963
|
};
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
function classifyRange(distance2) {
|
|
3867
|
-
if (distance2 <= RANGE_MELEE) {
|
|
3868
|
-
return "melee" /* Melee */;
|
|
3869
|
-
}
|
|
3870
|
-
if (distance2 <= RANGE_NEAR) {
|
|
3871
|
-
return "near" /* Near */;
|
|
3964
|
+
const spawnFunc = options.registry.get(classname);
|
|
3965
|
+
if (!spawnFunc) {
|
|
3966
|
+
warn(`${classname} does not have a spawn function`);
|
|
3967
|
+
if (!isWorld) {
|
|
3968
|
+
options.entities.freeImmediate(entity);
|
|
3969
|
+
}
|
|
3970
|
+
return null;
|
|
3872
3971
|
}
|
|
3873
|
-
|
|
3874
|
-
|
|
3972
|
+
spawnFunc(entity, context);
|
|
3973
|
+
if (!entity.inUse) {
|
|
3974
|
+
return null;
|
|
3875
3975
|
}
|
|
3876
|
-
|
|
3976
|
+
options.entities.finalizeSpawn(entity);
|
|
3977
|
+
return entity;
|
|
3877
3978
|
}
|
|
3878
|
-
function
|
|
3879
|
-
const
|
|
3880
|
-
const
|
|
3881
|
-
const
|
|
3882
|
-
|
|
3883
|
-
|
|
3979
|
+
function spawnEntitiesFromText(text, options) {
|
|
3980
|
+
const parsed = parseEntityLump(text);
|
|
3981
|
+
const spawned = [];
|
|
3982
|
+
for (const dictionary of parsed) {
|
|
3983
|
+
const entity = spawnEntityFromDictionary(dictionary, options);
|
|
3984
|
+
if (entity) {
|
|
3985
|
+
spawned.push(entity);
|
|
3986
|
+
}
|
|
3884
3987
|
}
|
|
3885
|
-
return
|
|
3988
|
+
return spawned;
|
|
3886
3989
|
}
|
|
3887
|
-
function
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
const start = { x: self.origin.x, y: self.origin.y, z: self.origin.z + self.viewheight };
|
|
3892
|
-
const end = { x: other.origin.x, y: other.origin.y, z: other.origin.z + other.viewheight };
|
|
3893
|
-
const mask = options?.throughGlass ? 1 /* Opaque */ : 1 /* Opaque */ | 2 /* Window */;
|
|
3894
|
-
const result = trace(start, end, self, mask);
|
|
3895
|
-
return result.fraction === 1 || result.entity === other;
|
|
3990
|
+
function findPlayerStart(entities) {
|
|
3991
|
+
return entities.find(
|
|
3992
|
+
(entity) => entity.classname === "info_player_start"
|
|
3993
|
+
);
|
|
3896
3994
|
}
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
};
|
|
3905
|
-
|
|
3995
|
+
function registerDefaultSpawns(game, registry) {
|
|
3996
|
+
registry.register("worldspawn", (entity) => {
|
|
3997
|
+
entity.movetype = 2 /* Push */;
|
|
3998
|
+
entity.solid = 3 /* Bsp */;
|
|
3999
|
+
entity.modelindex = entity.modelindex || 1;
|
|
4000
|
+
});
|
|
4001
|
+
registry.register("info_player_start", () => {
|
|
4002
|
+
});
|
|
4003
|
+
registry.register("info_player_deathmatch", () => {
|
|
4004
|
+
});
|
|
4005
|
+
registry.register("info_player_coop", () => {
|
|
4006
|
+
});
|
|
4007
|
+
registry.register("info_null", (entity, context) => {
|
|
4008
|
+
context.free(entity);
|
|
4009
|
+
});
|
|
4010
|
+
registry.register("info_notnull", () => {
|
|
4011
|
+
});
|
|
4012
|
+
registry.register("info_teleport_destination", () => {
|
|
4013
|
+
});
|
|
4014
|
+
registerTriggerSpawns(registry);
|
|
4015
|
+
registerTargetSpawns(registry);
|
|
4016
|
+
registerMiscSpawns(registry);
|
|
4017
|
+
registerItemSpawns(game, registry);
|
|
4018
|
+
registerFuncSpawns(registry);
|
|
4019
|
+
registerPathSpawns(registry);
|
|
4020
|
+
registerLightSpawns(registry);
|
|
4021
|
+
registerMonsterSpawns(registry);
|
|
3906
4022
|
}
|
|
3907
|
-
function
|
|
3908
|
-
|
|
4023
|
+
function createDefaultSpawnRegistry(game) {
|
|
4024
|
+
const registry = new SpawnRegistry();
|
|
4025
|
+
registerDefaultSpawns(game, registry);
|
|
4026
|
+
return registry;
|
|
3909
4027
|
}
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
faceYawInstantly(self);
|
|
3915
|
-
if ((self.monsterinfo.aiflags & 1 /* StandGround */) !== 0) {
|
|
3916
|
-
self.monsterinfo.stand?.(self);
|
|
3917
|
-
} else {
|
|
3918
|
-
self.monsterinfo.run?.(self);
|
|
3919
|
-
self.attack_finished_time = level.timeSeconds + 1;
|
|
3920
|
-
}
|
|
4028
|
+
|
|
4029
|
+
// src/entities/callbacks.ts
|
|
4030
|
+
function createCallbackRegistry() {
|
|
4031
|
+
return /* @__PURE__ */ new Map();
|
|
3921
4032
|
}
|
|
3922
|
-
function
|
|
3923
|
-
if (
|
|
3924
|
-
if ((self.enemy.svflags & 8 /* Player */) !== 0) {
|
|
3925
|
-
level.sightEntity = self;
|
|
3926
|
-
level.sightEntityFrame = level.frameNumber;
|
|
3927
|
-
self.light_level = 128;
|
|
3928
|
-
}
|
|
3929
|
-
self.show_hostile = level.timeSeconds + 1;
|
|
3930
|
-
const lastSighting = self.monsterinfo.last_sighting;
|
|
3931
|
-
lastSighting.x = self.enemy.origin.x;
|
|
3932
|
-
lastSighting.y = self.enemy.origin.y;
|
|
3933
|
-
lastSighting.z = self.enemy.origin.z;
|
|
3934
|
-
self.trail_time = level.timeSeconds;
|
|
3935
|
-
self.monsterinfo.trail_time = level.timeSeconds;
|
|
3936
|
-
if (!self.combattarget) {
|
|
3937
|
-
huntTarget(self, level);
|
|
4033
|
+
function registerCallback(registry, name, fn) {
|
|
4034
|
+
if (registry.has(name)) {
|
|
3938
4035
|
return;
|
|
3939
4036
|
}
|
|
3940
|
-
|
|
3941
|
-
const movetarget = pickTarget?.(self.combattarget) ?? self.enemy;
|
|
3942
|
-
self.goalentity = movetarget;
|
|
3943
|
-
self.movetarget = movetarget;
|
|
3944
|
-
self.combattarget = void 0;
|
|
3945
|
-
self.monsterinfo.aiflags |= 4096 /* CombatPoint */;
|
|
3946
|
-
if (self.movetarget) {
|
|
3947
|
-
self.movetarget.targetname = void 0;
|
|
3948
|
-
}
|
|
3949
|
-
self.monsterinfo.pausetime = 0;
|
|
3950
|
-
self.monsterinfo.run?.(self);
|
|
4037
|
+
registry.set(name, fn);
|
|
3951
4038
|
}
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
4039
|
+
|
|
4040
|
+
// src/loop.ts
|
|
4041
|
+
var orderedStageNames = [
|
|
4042
|
+
"prep",
|
|
4043
|
+
"simulate",
|
|
4044
|
+
"finish"
|
|
4045
|
+
];
|
|
4046
|
+
var GameFrameLoop = class {
|
|
4047
|
+
constructor(initialStages) {
|
|
4048
|
+
this.timeMs = 0;
|
|
4049
|
+
this.frame = 0;
|
|
4050
|
+
this.stageHandlers = {
|
|
4051
|
+
prep: [],
|
|
4052
|
+
simulate: [],
|
|
4053
|
+
finish: []
|
|
4054
|
+
};
|
|
4055
|
+
this.stageCounts = {
|
|
4056
|
+
prep: 0,
|
|
4057
|
+
simulate: 0,
|
|
4058
|
+
finish: 0
|
|
4059
|
+
};
|
|
4060
|
+
this.stageCompactionNeeded = {
|
|
4061
|
+
prep: false,
|
|
4062
|
+
simulate: false,
|
|
4063
|
+
finish: false
|
|
4064
|
+
};
|
|
4065
|
+
if (initialStages) {
|
|
4066
|
+
for (const stageName of orderedStageNames) {
|
|
4067
|
+
const handler = initialStages[stageName];
|
|
4068
|
+
if (handler) {
|
|
4069
|
+
this.addStage(stageName, handler);
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
3960
4073
|
}
|
|
3961
|
-
|
|
3962
|
-
|
|
4074
|
+
addStage(stage, handler) {
|
|
4075
|
+
const handlers = this.stageHandlers[stage];
|
|
4076
|
+
handlers.push(handler);
|
|
4077
|
+
this.stageCounts[stage] += 1;
|
|
4078
|
+
return () => {
|
|
4079
|
+
const index = handlers.indexOf(handler);
|
|
4080
|
+
if (index >= 0 && handlers[index]) {
|
|
4081
|
+
handlers[index] = void 0;
|
|
4082
|
+
this.stageCounts[stage] -= 1;
|
|
4083
|
+
this.stageCompactionNeeded[stage] = true;
|
|
4084
|
+
}
|
|
4085
|
+
};
|
|
3963
4086
|
}
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
if ((self.spawnflags & SPAWNFLAG_MONSTER_AMBUSH) !== 0) {
|
|
3968
|
-
if (!visible(self, client, trace)) return false;
|
|
3969
|
-
} else if (hearability.canHear && !hearability.canHear(self, client)) {
|
|
3970
|
-
return false;
|
|
4087
|
+
reset(startTimeMs) {
|
|
4088
|
+
this.timeMs = startTimeMs;
|
|
4089
|
+
this.frame = 0;
|
|
3971
4090
|
}
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
if (
|
|
3984
|
-
|
|
4091
|
+
advance(step) {
|
|
4092
|
+
const previousTimeMs = this.timeMs;
|
|
4093
|
+
this.timeMs = previousTimeMs + step.deltaMs;
|
|
4094
|
+
this.frame = step.frame;
|
|
4095
|
+
const context = {
|
|
4096
|
+
...step,
|
|
4097
|
+
timeMs: this.timeMs,
|
|
4098
|
+
previousTimeMs,
|
|
4099
|
+
deltaSeconds: step.deltaMs / 1e3
|
|
4100
|
+
};
|
|
4101
|
+
this.runStage("prep", context);
|
|
4102
|
+
if (this.stageCounts.simulate === 0) {
|
|
4103
|
+
throw new Error("GameFrameLoop requires at least one simulate stage");
|
|
3985
4104
|
}
|
|
3986
|
-
|
|
4105
|
+
this.runStage("simulate", context);
|
|
4106
|
+
this.runStage("finish", context);
|
|
4107
|
+
return context;
|
|
3987
4108
|
}
|
|
3988
|
-
|
|
3989
|
-
|
|
4109
|
+
runStage(stage, context) {
|
|
4110
|
+
const handlers = this.stageHandlers[stage];
|
|
4111
|
+
for (let i = 0; i < handlers.length; i += 1) {
|
|
4112
|
+
const handler = handlers[i];
|
|
4113
|
+
if (!handler) {
|
|
4114
|
+
continue;
|
|
4115
|
+
}
|
|
4116
|
+
handler(context);
|
|
4117
|
+
}
|
|
4118
|
+
if (this.stageCompactionNeeded[stage]) {
|
|
4119
|
+
this.compactStageHandlers(stage);
|
|
4120
|
+
}
|
|
3990
4121
|
}
|
|
3991
|
-
|
|
3992
|
-
|
|
4122
|
+
compactStageHandlers(stage) {
|
|
4123
|
+
const handlers = this.stageHandlers[stage];
|
|
4124
|
+
let writeIndex = 0;
|
|
4125
|
+
for (let readIndex = 0; readIndex < handlers.length; readIndex += 1) {
|
|
4126
|
+
const handler = handlers[readIndex];
|
|
4127
|
+
if (handler) {
|
|
4128
|
+
handlers[writeIndex] = handler;
|
|
4129
|
+
writeIndex += 1;
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
handlers.length = writeIndex;
|
|
4133
|
+
this.stageCompactionNeeded[stage] = false;
|
|
3993
4134
|
}
|
|
3994
|
-
|
|
3995
|
-
return
|
|
4135
|
+
get time() {
|
|
4136
|
+
return this.timeMs;
|
|
3996
4137
|
}
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
function rejectNotargetEntity(client) {
|
|
4000
|
-
if ((client.flags & FL_NOTARGET) !== 0) return true;
|
|
4001
|
-
if ((client.svflags & 4 /* Monster */) !== 0 && client.enemy) {
|
|
4002
|
-
return (client.enemy.flags & FL_NOTARGET) !== 0;
|
|
4138
|
+
get frameNumber() {
|
|
4139
|
+
return this.frame;
|
|
4003
4140
|
}
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4141
|
+
};
|
|
4142
|
+
|
|
4143
|
+
// src/level.ts
|
|
4144
|
+
var ZERO_STATE = {
|
|
4145
|
+
frameNumber: 0,
|
|
4146
|
+
timeSeconds: 0,
|
|
4147
|
+
previousTimeSeconds: 0,
|
|
4148
|
+
deltaSeconds: 0
|
|
4149
|
+
};
|
|
4150
|
+
var LevelClock = class {
|
|
4151
|
+
constructor() {
|
|
4152
|
+
this.state = ZERO_STATE;
|
|
4013
4153
|
}
|
|
4014
|
-
|
|
4015
|
-
|
|
4154
|
+
start(startTimeMs) {
|
|
4155
|
+
const startSeconds = startTimeMs / 1e3;
|
|
4156
|
+
this.state = {
|
|
4157
|
+
frameNumber: 0,
|
|
4158
|
+
timeSeconds: startSeconds,
|
|
4159
|
+
previousTimeSeconds: startSeconds,
|
|
4160
|
+
deltaSeconds: 0
|
|
4161
|
+
};
|
|
4016
4162
|
}
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
} else if (!updateSoundChase(self, candidate, level, hearability, trace)) {
|
|
4026
|
-
return false;
|
|
4163
|
+
tick(context) {
|
|
4164
|
+
this.state = {
|
|
4165
|
+
frameNumber: context.frame,
|
|
4166
|
+
timeSeconds: context.timeMs / 1e3,
|
|
4167
|
+
previousTimeSeconds: context.previousTimeMs / 1e3,
|
|
4168
|
+
deltaSeconds: context.deltaSeconds
|
|
4169
|
+
};
|
|
4170
|
+
return this.state;
|
|
4027
4171
|
}
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
self.monsterinfo.sight?.(self, self.enemy);
|
|
4172
|
+
get current() {
|
|
4173
|
+
return this.state;
|
|
4031
4174
|
}
|
|
4032
|
-
|
|
4033
|
-
}
|
|
4175
|
+
restore(state) {
|
|
4176
|
+
this.state = { ...state };
|
|
4177
|
+
}
|
|
4178
|
+
};
|
|
4034
4179
|
|
|
4035
4180
|
// src/checksum.ts
|
|
4036
4181
|
var FNV_OFFSET_BASIS = 2166136261;
|
|
@@ -5410,6 +5555,7 @@ export {
|
|
|
5410
5555
|
HEALTH_ITEMS,
|
|
5411
5556
|
KEY_ITEMS,
|
|
5412
5557
|
KeyId,
|
|
5558
|
+
M_MoveFrame,
|
|
5413
5559
|
MoveType,
|
|
5414
5560
|
ORDERED_DAMAGE_MODS,
|
|
5415
5561
|
POWERUP_ITEMS,
|
|
@@ -5486,6 +5632,7 @@ export {
|
|
|
5486
5632
|
infront,
|
|
5487
5633
|
isZeroVector,
|
|
5488
5634
|
killBox,
|
|
5635
|
+
monster_think,
|
|
5489
5636
|
parseEntityLump,
|
|
5490
5637
|
parseRereleaseSave,
|
|
5491
5638
|
parseSaveFile,
|