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
|
@@ -40,6 +40,7 @@ __export(index_exports, {
|
|
|
40
40
|
HEALTH_ITEMS: () => HEALTH_ITEMS,
|
|
41
41
|
KEY_ITEMS: () => KEY_ITEMS,
|
|
42
42
|
KeyId: () => KeyId,
|
|
43
|
+
M_MoveFrame: () => M_MoveFrame,
|
|
43
44
|
MoveType: () => MoveType,
|
|
44
45
|
ORDERED_DAMAGE_MODS: () => ORDERED_DAMAGE_MODS,
|
|
45
46
|
POWERUP_ITEMS: () => POWERUP_ITEMS,
|
|
@@ -116,6 +117,7 @@ __export(index_exports, {
|
|
|
116
117
|
infront: () => infront,
|
|
117
118
|
isZeroVector: () => isZeroVector,
|
|
118
119
|
killBox: () => killBox,
|
|
120
|
+
monster_think: () => monster_think,
|
|
119
121
|
parseEntityLump: () => parseEntityLump,
|
|
120
122
|
parseRereleaseSave: () => parseRereleaseSave,
|
|
121
123
|
parseSaveFile: () => parseSaveFile,
|
|
@@ -3474,704 +3476,849 @@ function registerLightSpawns(registry) {
|
|
|
3474
3476
|
});
|
|
3475
3477
|
}
|
|
3476
3478
|
|
|
3477
|
-
// src/
|
|
3478
|
-
var
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3479
|
+
// src/ai/constants.ts
|
|
3480
|
+
var RANGE_MELEE = 20;
|
|
3481
|
+
var RANGE_NEAR = 440;
|
|
3482
|
+
var RANGE_MID = 940;
|
|
3483
|
+
var FL_NOTARGET = 1 << 5;
|
|
3484
|
+
var FL_NOVISIBLE = 1 << 24;
|
|
3485
|
+
var SPAWNFLAG_MONSTER_AMBUSH = 1 << 0;
|
|
3486
|
+
var AIFlags = /* @__PURE__ */ ((AIFlags2) => {
|
|
3487
|
+
AIFlags2[AIFlags2["StandGround"] = 1] = "StandGround";
|
|
3488
|
+
AIFlags2[AIFlags2["TempStandGround"] = 2] = "TempStandGround";
|
|
3489
|
+
AIFlags2[AIFlags2["SoundTarget"] = 4] = "SoundTarget";
|
|
3490
|
+
AIFlags2[AIFlags2["LostSight"] = 8] = "LostSight";
|
|
3491
|
+
AIFlags2[AIFlags2["PursuitLastSeen"] = 16] = "PursuitLastSeen";
|
|
3492
|
+
AIFlags2[AIFlags2["PursueNext"] = 32] = "PursueNext";
|
|
3493
|
+
AIFlags2[AIFlags2["PursueTemp"] = 64] = "PursueTemp";
|
|
3494
|
+
AIFlags2[AIFlags2["HoldFrame"] = 128] = "HoldFrame";
|
|
3495
|
+
AIFlags2[AIFlags2["GoodGuy"] = 256] = "GoodGuy";
|
|
3496
|
+
AIFlags2[AIFlags2["Brutal"] = 512] = "Brutal";
|
|
3497
|
+
AIFlags2[AIFlags2["NoStep"] = 1024] = "NoStep";
|
|
3498
|
+
AIFlags2[AIFlags2["Ducked"] = 2048] = "Ducked";
|
|
3499
|
+
AIFlags2[AIFlags2["CombatPoint"] = 4096] = "CombatPoint";
|
|
3500
|
+
AIFlags2[AIFlags2["Medic"] = 8192] = "Medic";
|
|
3501
|
+
AIFlags2[AIFlags2["Resurrecting"] = 16384] = "Resurrecting";
|
|
3502
|
+
AIFlags2[AIFlags2["Pathing"] = 1073741824] = "Pathing";
|
|
3503
|
+
return AIFlags2;
|
|
3504
|
+
})(AIFlags || {});
|
|
3505
|
+
var TraceMask = /* @__PURE__ */ ((TraceMask2) => {
|
|
3506
|
+
TraceMask2[TraceMask2["Opaque"] = 1] = "Opaque";
|
|
3507
|
+
TraceMask2[TraceMask2["Window"] = 2] = "Window";
|
|
3508
|
+
return TraceMask2;
|
|
3509
|
+
})(TraceMask || {});
|
|
3510
|
+
|
|
3511
|
+
// src/ai/movement.ts
|
|
3512
|
+
function yawVector(yawDegrees, distance2) {
|
|
3513
|
+
if (distance2 === 0) {
|
|
3514
|
+
return { x: 0, y: 0, z: 0 };
|
|
3515
|
+
}
|
|
3516
|
+
const radians = degToRad(yawDegrees);
|
|
3517
|
+
return {
|
|
3518
|
+
x: Math.cos(radians) * distance2,
|
|
3519
|
+
y: Math.sin(radians) * distance2,
|
|
3520
|
+
z: 0
|
|
3521
|
+
};
|
|
3485
3522
|
}
|
|
3486
|
-
function
|
|
3487
|
-
const
|
|
3488
|
-
|
|
3523
|
+
function walkMove(self, yawDegrees, distance2) {
|
|
3524
|
+
const delta = yawVector(yawDegrees, distance2);
|
|
3525
|
+
const origin = self.origin;
|
|
3526
|
+
origin.x += delta.x;
|
|
3527
|
+
origin.y += delta.y;
|
|
3528
|
+
origin.z += delta.z;
|
|
3529
|
+
return true;
|
|
3489
3530
|
}
|
|
3490
|
-
function
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
case "boolean":
|
|
3497
|
-
return parseBoolean(value);
|
|
3498
|
-
case "vec3":
|
|
3499
|
-
return parseVec3(value);
|
|
3500
|
-
case "string":
|
|
3501
|
-
return value;
|
|
3502
|
-
case "entity":
|
|
3503
|
-
case "callback":
|
|
3504
|
-
return void 0;
|
|
3505
|
-
default:
|
|
3506
|
-
return value;
|
|
3531
|
+
function changeYaw(self, deltaSeconds) {
|
|
3532
|
+
const current = angleMod(self.angles.y);
|
|
3533
|
+
const ideal = self.ideal_yaw;
|
|
3534
|
+
if (current === ideal) {
|
|
3535
|
+
self.angles.y = current;
|
|
3536
|
+
return;
|
|
3507
3537
|
}
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
if (
|
|
3511
|
-
|
|
3538
|
+
const speed = self.yaw_speed * deltaSeconds * 10;
|
|
3539
|
+
let move = ideal - current;
|
|
3540
|
+
if (ideal > current) {
|
|
3541
|
+
if (move >= 180) move -= 360;
|
|
3542
|
+
} else if (move <= -180) {
|
|
3543
|
+
move += 360;
|
|
3512
3544
|
}
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
if (parsed !== void 0) {
|
|
3523
|
-
entity[descriptor.name] = parsed;
|
|
3524
|
-
}
|
|
3545
|
+
if (move > speed) move = speed;
|
|
3546
|
+
else if (move < -speed) move = -speed;
|
|
3547
|
+
self.angles.y = angleMod(current + move);
|
|
3548
|
+
}
|
|
3549
|
+
function facingIdeal(self) {
|
|
3550
|
+
const delta = angleMod(self.angles.y - self.ideal_yaw);
|
|
3551
|
+
const hasPathing = (self.monsterinfo.aiflags & 1073741824 /* Pathing */) !== 0;
|
|
3552
|
+
if (hasPathing) {
|
|
3553
|
+
return !(delta > 5 && delta < 355);
|
|
3525
3554
|
}
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3555
|
+
return !(delta > 45 && delta < 315);
|
|
3556
|
+
}
|
|
3557
|
+
function ai_move(self, distance2) {
|
|
3558
|
+
walkMove(self, self.angles.y, distance2);
|
|
3559
|
+
}
|
|
3560
|
+
function setIdealYawTowards(self, target) {
|
|
3561
|
+
if (!target) return;
|
|
3562
|
+
const toTarget = {
|
|
3563
|
+
x: target.origin.x - self.origin.x,
|
|
3564
|
+
y: target.origin.y - self.origin.y,
|
|
3565
|
+
z: target.origin.z - self.origin.z
|
|
3530
3566
|
};
|
|
3567
|
+
self.ideal_yaw = vectorToYaw(toTarget);
|
|
3531
3568
|
}
|
|
3532
|
-
function
|
|
3533
|
-
|
|
3534
|
-
let result = "";
|
|
3535
|
-
while (index < text.length) {
|
|
3536
|
-
const char = text[index];
|
|
3537
|
-
if (char === '"') {
|
|
3538
|
-
return { value: result, nextIndex: index + 1 };
|
|
3539
|
-
}
|
|
3540
|
-
result += char;
|
|
3541
|
-
index += 1;
|
|
3542
|
-
}
|
|
3543
|
-
throw new Error("Unterminated quoted string in entity lump");
|
|
3569
|
+
function ai_stand(self, deltaSeconds) {
|
|
3570
|
+
changeYaw(self, deltaSeconds);
|
|
3544
3571
|
}
|
|
3545
|
-
function
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3572
|
+
function ai_walk(self, distance2, deltaSeconds) {
|
|
3573
|
+
setIdealYawTowards(self, self.goalentity);
|
|
3574
|
+
changeYaw(self, deltaSeconds);
|
|
3575
|
+
if (distance2 !== 0) {
|
|
3576
|
+
walkMove(self, self.angles.y, distance2);
|
|
3549
3577
|
}
|
|
3550
|
-
return index;
|
|
3551
3578
|
}
|
|
3552
|
-
function
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
return { token: null, nextIndex: index };
|
|
3556
|
-
}
|
|
3557
|
-
const current = text[index];
|
|
3558
|
-
if (current === "{" || current === "}") {
|
|
3559
|
-
return { token: current, nextIndex: index + 1 };
|
|
3560
|
-
}
|
|
3561
|
-
if (current !== '"') {
|
|
3562
|
-
throw new Error(`Unexpected token in entity lump: ${current}`);
|
|
3579
|
+
function ai_turn(self, distance2, deltaSeconds) {
|
|
3580
|
+
if (distance2 !== 0) {
|
|
3581
|
+
walkMove(self, self.angles.y, distance2);
|
|
3563
3582
|
}
|
|
3564
|
-
|
|
3565
|
-
return { token: quoted.value, nextIndex: quoted.nextIndex };
|
|
3583
|
+
changeYaw(self, deltaSeconds);
|
|
3566
3584
|
}
|
|
3567
|
-
function
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
index = open.nextIndex;
|
|
3573
|
-
if (open.token === null) {
|
|
3574
|
-
break;
|
|
3575
|
-
}
|
|
3576
|
-
if (open.token !== "{") {
|
|
3577
|
-
throw new Error("Expected { at start of entity definition");
|
|
3578
|
-
}
|
|
3579
|
-
const entity = {};
|
|
3580
|
-
while (true) {
|
|
3581
|
-
const keyToken = parseToken(text, index);
|
|
3582
|
-
index = keyToken.nextIndex;
|
|
3583
|
-
if (keyToken.token === null) {
|
|
3584
|
-
throw new Error("EOF reached while parsing entity");
|
|
3585
|
-
}
|
|
3586
|
-
if (keyToken.token === "}") {
|
|
3587
|
-
break;
|
|
3588
|
-
}
|
|
3589
|
-
const valueToken = parseToken(text, index);
|
|
3590
|
-
index = valueToken.nextIndex;
|
|
3591
|
-
if (valueToken.token === null || valueToken.token === "{" || valueToken.token === "}") {
|
|
3592
|
-
throw new Error("Malformed entity key/value pair");
|
|
3593
|
-
}
|
|
3594
|
-
if (!keyToken.token.startsWith("_")) {
|
|
3595
|
-
entity[keyToken.token] = valueToken.token;
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
entities.push(entity);
|
|
3585
|
+
function ai_run(self, distance2, deltaSeconds) {
|
|
3586
|
+
setIdealYawTowards(self, self.enemy ?? self.goalentity);
|
|
3587
|
+
changeYaw(self, deltaSeconds);
|
|
3588
|
+
if (distance2 !== 0) {
|
|
3589
|
+
walkMove(self, self.angles.y, distance2);
|
|
3599
3590
|
}
|
|
3600
|
-
return entities;
|
|
3601
3591
|
}
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
}
|
|
3606
|
-
register(classname, spawn) {
|
|
3607
|
-
this.registry.set(classname, spawn);
|
|
3592
|
+
function ai_face(self, enemy, distance2, deltaSeconds) {
|
|
3593
|
+
if (enemy) {
|
|
3594
|
+
setIdealYawTowards(self, enemy);
|
|
3608
3595
|
}
|
|
3609
|
-
|
|
3610
|
-
|
|
3596
|
+
changeYaw(self, deltaSeconds);
|
|
3597
|
+
if (distance2 !== 0) {
|
|
3598
|
+
walkMove(self, self.angles.y, distance2);
|
|
3611
3599
|
}
|
|
3612
|
-
};
|
|
3613
|
-
function defaultWarn(message) {
|
|
3614
|
-
void message;
|
|
3615
3600
|
}
|
|
3616
|
-
function
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
if (
|
|
3620
|
-
|
|
3621
|
-
return null;
|
|
3601
|
+
function ai_charge(self, distance2, deltaSeconds) {
|
|
3602
|
+
setIdealYawTowards(self, self.enemy);
|
|
3603
|
+
changeYaw(self, deltaSeconds);
|
|
3604
|
+
if (distance2 !== 0) {
|
|
3605
|
+
walkMove(self, self.angles.y, distance2);
|
|
3622
3606
|
}
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
// src/ai/perception.ts
|
|
3610
|
+
var RangeCategory = /* @__PURE__ */ ((RangeCategory2) => {
|
|
3611
|
+
RangeCategory2["Melee"] = "melee";
|
|
3612
|
+
RangeCategory2["Near"] = "near";
|
|
3613
|
+
RangeCategory2["Mid"] = "mid";
|
|
3614
|
+
RangeCategory2["Far"] = "far";
|
|
3615
|
+
return RangeCategory2;
|
|
3616
|
+
})(RangeCategory || {});
|
|
3617
|
+
function absBounds(entity) {
|
|
3618
|
+
return {
|
|
3619
|
+
mins: {
|
|
3620
|
+
x: entity.origin.x + entity.mins.x,
|
|
3621
|
+
y: entity.origin.y + entity.mins.y,
|
|
3622
|
+
z: entity.origin.z + entity.mins.z
|
|
3623
|
+
},
|
|
3624
|
+
maxs: {
|
|
3625
|
+
x: entity.origin.x + entity.maxs.x,
|
|
3626
|
+
y: entity.origin.y + entity.maxs.y,
|
|
3627
|
+
z: entity.origin.z + entity.maxs.z
|
|
3632
3628
|
}
|
|
3633
3629
|
};
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3630
|
+
}
|
|
3631
|
+
function rangeTo(self, other) {
|
|
3632
|
+
const a = absBounds(self);
|
|
3633
|
+
const b = absBounds(other);
|
|
3634
|
+
const distanceSquared = distanceBetweenBoxesSquared(a.mins, a.maxs, b.mins, b.maxs);
|
|
3635
|
+
return Math.sqrt(distanceSquared);
|
|
3636
|
+
}
|
|
3637
|
+
function classifyRange(distance2) {
|
|
3638
|
+
if (distance2 <= RANGE_MELEE) {
|
|
3639
|
+
return "melee" /* Melee */;
|
|
3641
3640
|
}
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
return null;
|
|
3641
|
+
if (distance2 <= RANGE_NEAR) {
|
|
3642
|
+
return "near" /* Near */;
|
|
3645
3643
|
}
|
|
3646
|
-
|
|
3647
|
-
|
|
3644
|
+
if (distance2 <= RANGE_MID) {
|
|
3645
|
+
return "mid" /* Mid */;
|
|
3646
|
+
}
|
|
3647
|
+
return "far" /* Far */;
|
|
3648
3648
|
}
|
|
3649
|
-
function
|
|
3650
|
-
const
|
|
3651
|
-
const
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
spawned.push(entity);
|
|
3656
|
-
}
|
|
3649
|
+
function infront(self, other) {
|
|
3650
|
+
const { forward } = angleVectors(self.angles);
|
|
3651
|
+
const direction = normalizeVec3(subtractVec3(other.origin, self.origin));
|
|
3652
|
+
const dot = dotVec3(direction, forward);
|
|
3653
|
+
if ((self.spawnflags & SPAWNFLAG_MONSTER_AMBUSH) !== 0 && self.trail_time === 0 && self.enemy === null) {
|
|
3654
|
+
return dot > 0.15;
|
|
3657
3655
|
}
|
|
3658
|
-
return
|
|
3656
|
+
return dot > -0.3;
|
|
3659
3657
|
}
|
|
3660
|
-
function
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3658
|
+
function visible(self, other, trace, options) {
|
|
3659
|
+
if ((other.flags & FL_NOVISIBLE) !== 0) {
|
|
3660
|
+
return false;
|
|
3661
|
+
}
|
|
3662
|
+
const start = { x: self.origin.x, y: self.origin.y, z: self.origin.z + self.viewheight };
|
|
3663
|
+
const end = { x: other.origin.x, y: other.origin.y, z: other.origin.z + other.viewheight };
|
|
3664
|
+
const mask = options?.throughGlass ? 1 /* Opaque */ : 1 /* Opaque */ | 2 /* Window */;
|
|
3665
|
+
const result = trace(start, end, self, mask);
|
|
3666
|
+
return result.fraction === 1 || result.entity === other;
|
|
3664
3667
|
}
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
}
|
|
3673
|
-
|
|
3674
|
-
});
|
|
3675
|
-
registry.register("info_player_coop", () => {
|
|
3676
|
-
});
|
|
3677
|
-
registry.register("info_null", (entity, context) => {
|
|
3678
|
-
context.free(entity);
|
|
3679
|
-
});
|
|
3680
|
-
registry.register("info_notnull", () => {
|
|
3681
|
-
});
|
|
3682
|
-
registry.register("info_teleport_destination", () => {
|
|
3683
|
-
});
|
|
3684
|
-
registerTriggerSpawns(registry);
|
|
3685
|
-
registerTargetSpawns(registry);
|
|
3686
|
-
registerMiscSpawns(registry);
|
|
3687
|
-
registerItemSpawns(game, registry);
|
|
3688
|
-
registerFuncSpawns(registry);
|
|
3689
|
-
registerPathSpawns(registry);
|
|
3690
|
-
registerLightSpawns(registry);
|
|
3668
|
+
|
|
3669
|
+
// src/ai/targeting.ts
|
|
3670
|
+
function setIdealYawTowards2(self, other) {
|
|
3671
|
+
const delta = {
|
|
3672
|
+
x: other.origin.x - self.origin.x,
|
|
3673
|
+
y: other.origin.y - self.origin.y,
|
|
3674
|
+
z: other.origin.z - self.origin.z
|
|
3675
|
+
};
|
|
3676
|
+
self.ideal_yaw = vectorToYaw(delta);
|
|
3691
3677
|
}
|
|
3692
|
-
function
|
|
3693
|
-
|
|
3694
|
-
registerDefaultSpawns(game, registry);
|
|
3695
|
-
return registry;
|
|
3678
|
+
function faceYawInstantly(self) {
|
|
3679
|
+
self.angles.y = angleMod(self.ideal_yaw);
|
|
3696
3680
|
}
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3681
|
+
function huntTarget(self, level) {
|
|
3682
|
+
if (!self.enemy) return;
|
|
3683
|
+
self.goalentity = self.enemy;
|
|
3684
|
+
setIdealYawTowards2(self, self.enemy);
|
|
3685
|
+
faceYawInstantly(self);
|
|
3686
|
+
if ((self.monsterinfo.aiflags & 1 /* StandGround */) !== 0) {
|
|
3687
|
+
self.monsterinfo.stand?.(self);
|
|
3688
|
+
} else {
|
|
3689
|
+
self.monsterinfo.run?.(self);
|
|
3690
|
+
self.attack_finished_time = level.timeSeconds + 1;
|
|
3691
|
+
}
|
|
3701
3692
|
}
|
|
3702
|
-
function
|
|
3703
|
-
if (
|
|
3693
|
+
function foundTarget(self, level, options) {
|
|
3694
|
+
if (!self.enemy) return;
|
|
3695
|
+
if ((self.enemy.svflags & 8 /* Player */) !== 0) {
|
|
3696
|
+
level.sightEntity = self;
|
|
3697
|
+
level.sightEntityFrame = level.frameNumber;
|
|
3698
|
+
self.light_level = 128;
|
|
3699
|
+
}
|
|
3700
|
+
self.show_hostile = level.timeSeconds + 1;
|
|
3701
|
+
const lastSighting = self.monsterinfo.last_sighting;
|
|
3702
|
+
lastSighting.x = self.enemy.origin.x;
|
|
3703
|
+
lastSighting.y = self.enemy.origin.y;
|
|
3704
|
+
lastSighting.z = self.enemy.origin.z;
|
|
3705
|
+
self.trail_time = level.timeSeconds;
|
|
3706
|
+
self.monsterinfo.trail_time = level.timeSeconds;
|
|
3707
|
+
if (!self.combattarget) {
|
|
3708
|
+
huntTarget(self, level);
|
|
3704
3709
|
return;
|
|
3705
3710
|
}
|
|
3706
|
-
|
|
3711
|
+
const pickTarget = options?.pickTarget;
|
|
3712
|
+
const movetarget = pickTarget?.(self.combattarget) ?? self.enemy;
|
|
3713
|
+
self.goalentity = movetarget;
|
|
3714
|
+
self.movetarget = movetarget;
|
|
3715
|
+
self.combattarget = void 0;
|
|
3716
|
+
self.monsterinfo.aiflags |= 4096 /* CombatPoint */;
|
|
3717
|
+
if (self.movetarget) {
|
|
3718
|
+
self.movetarget.targetname = void 0;
|
|
3719
|
+
}
|
|
3720
|
+
self.monsterinfo.pausetime = 0;
|
|
3721
|
+
self.monsterinfo.run?.(self);
|
|
3707
3722
|
}
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
"
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
}
|
|
3723
|
+
function classifyClientVisibility(self, other, level, trace) {
|
|
3724
|
+
const distance2 = rangeTo(self, other);
|
|
3725
|
+
const range = classifyRange(distance2);
|
|
3726
|
+
if (range === "far" /* Far */) return false;
|
|
3727
|
+
if (other.light_level <= 5) return false;
|
|
3728
|
+
if (!visible(self, other, trace, { throughGlass: false })) return false;
|
|
3729
|
+
if (range === "near" /* Near */) {
|
|
3730
|
+
return level.timeSeconds <= other.show_hostile || infront(self, other);
|
|
3731
|
+
}
|
|
3732
|
+
if (range === "mid" /* Mid */) {
|
|
3733
|
+
return infront(self, other);
|
|
3734
|
+
}
|
|
3735
|
+
return true;
|
|
3736
|
+
}
|
|
3737
|
+
function updateSoundChase(self, client, level, hearability, trace) {
|
|
3738
|
+
if ((self.spawnflags & SPAWNFLAG_MONSTER_AMBUSH) !== 0) {
|
|
3739
|
+
if (!visible(self, client, trace)) return false;
|
|
3740
|
+
} else if (hearability.canHear && !hearability.canHear(self, client)) {
|
|
3741
|
+
return false;
|
|
3742
|
+
}
|
|
3743
|
+
const delta = subtractVec3(client.origin, self.origin);
|
|
3744
|
+
if (lengthVec3(delta) > 1e3) return false;
|
|
3745
|
+
if (hearability.areasConnected && !hearability.areasConnected(self, client)) return false;
|
|
3746
|
+
self.ideal_yaw = vectorToYaw(delta);
|
|
3747
|
+
faceYawInstantly(self);
|
|
3748
|
+
self.monsterinfo.aiflags |= 4 /* SoundTarget */;
|
|
3749
|
+
self.enemy = client;
|
|
3750
|
+
return true;
|
|
3751
|
+
}
|
|
3752
|
+
function chooseCandidate(self, level) {
|
|
3753
|
+
if (level.sightEntity && level.sightEntityFrame >= level.frameNumber - 1 && (self.spawnflags & SPAWNFLAG_MONSTER_AMBUSH) === 0) {
|
|
3754
|
+
if (level.sightEntity.enemy !== self.enemy) {
|
|
3755
|
+
return { candidate: level.sightEntity, heardit: false };
|
|
3741
3756
|
}
|
|
3757
|
+
return { candidate: null, heardit: false };
|
|
3742
3758
|
}
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
handlers.push(handler);
|
|
3746
|
-
this.stageCounts[stage] += 1;
|
|
3747
|
-
return () => {
|
|
3748
|
-
const index = handlers.indexOf(handler);
|
|
3749
|
-
if (index >= 0 && handlers[index]) {
|
|
3750
|
-
handlers[index] = void 0;
|
|
3751
|
-
this.stageCounts[stage] -= 1;
|
|
3752
|
-
this.stageCompactionNeeded[stage] = true;
|
|
3753
|
-
}
|
|
3754
|
-
};
|
|
3759
|
+
if (level.soundEntity && level.soundEntityFrame >= level.frameNumber - 1) {
|
|
3760
|
+
return { candidate: level.soundEntity, heardit: true };
|
|
3755
3761
|
}
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
this.frame = 0;
|
|
3762
|
+
if (!self.enemy && level.sound2Entity && level.sound2EntityFrame >= level.frameNumber - 1 && (self.spawnflags & SPAWNFLAG_MONSTER_AMBUSH) === 0) {
|
|
3763
|
+
return { candidate: level.sound2Entity, heardit: true };
|
|
3759
3764
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
this.timeMs = previousTimeMs + step.deltaMs;
|
|
3763
|
-
this.frame = step.frame;
|
|
3764
|
-
const context = {
|
|
3765
|
-
...step,
|
|
3766
|
-
timeMs: this.timeMs,
|
|
3767
|
-
previousTimeMs,
|
|
3768
|
-
deltaSeconds: step.deltaMs / 1e3
|
|
3769
|
-
};
|
|
3770
|
-
this.runStage("prep", context);
|
|
3771
|
-
if (this.stageCounts.simulate === 0) {
|
|
3772
|
-
throw new Error("GameFrameLoop requires at least one simulate stage");
|
|
3773
|
-
}
|
|
3774
|
-
this.runStage("simulate", context);
|
|
3775
|
-
this.runStage("finish", context);
|
|
3776
|
-
return context;
|
|
3765
|
+
if (level.sightClient) {
|
|
3766
|
+
return { candidate: level.sightClient, heardit: false };
|
|
3777
3767
|
}
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
}
|
|
3785
|
-
handler(context);
|
|
3786
|
-
}
|
|
3787
|
-
if (this.stageCompactionNeeded[stage]) {
|
|
3788
|
-
this.compactStageHandlers(stage);
|
|
3789
|
-
}
|
|
3768
|
+
return { candidate: null, heardit: false };
|
|
3769
|
+
}
|
|
3770
|
+
function rejectNotargetEntity(client) {
|
|
3771
|
+
if ((client.flags & FL_NOTARGET) !== 0) return true;
|
|
3772
|
+
if ((client.svflags & 4 /* Monster */) !== 0 && client.enemy) {
|
|
3773
|
+
return (client.enemy.flags & FL_NOTARGET) !== 0;
|
|
3790
3774
|
}
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
writeIndex += 1;
|
|
3799
|
-
}
|
|
3775
|
+
if (client.enemy && (client.enemy.flags & FL_NOTARGET) !== 0) return true;
|
|
3776
|
+
return false;
|
|
3777
|
+
}
|
|
3778
|
+
function findTarget(self, level, trace, hearability = {}) {
|
|
3779
|
+
if ((self.monsterinfo.aiflags & 256 /* GoodGuy */) !== 0) {
|
|
3780
|
+
if (self.goalentity?.classname === "target_actor") {
|
|
3781
|
+
return false;
|
|
3800
3782
|
}
|
|
3801
|
-
|
|
3802
|
-
this.stageCompactionNeeded[stage] = false;
|
|
3783
|
+
return false;
|
|
3803
3784
|
}
|
|
3804
|
-
|
|
3805
|
-
return
|
|
3785
|
+
if ((self.monsterinfo.aiflags & 4096 /* CombatPoint */) !== 0) {
|
|
3786
|
+
return false;
|
|
3806
3787
|
}
|
|
3807
|
-
|
|
3808
|
-
|
|
3788
|
+
const { candidate, heardit } = chooseCandidate(self, level);
|
|
3789
|
+
if (!candidate || !candidate.inUse) return false;
|
|
3790
|
+
if (candidate === self.enemy) return true;
|
|
3791
|
+
if (rejectNotargetEntity(candidate)) return false;
|
|
3792
|
+
if (!heardit) {
|
|
3793
|
+
if (!classifyClientVisibility(self, candidate, level, trace)) return false;
|
|
3794
|
+
self.monsterinfo.aiflags &= ~4 /* SoundTarget */;
|
|
3795
|
+
self.enemy = candidate;
|
|
3796
|
+
} else if (!updateSoundChase(self, candidate, level, hearability, trace)) {
|
|
3797
|
+
return false;
|
|
3809
3798
|
}
|
|
3810
|
-
|
|
3799
|
+
foundTarget(self, level);
|
|
3800
|
+
if ((self.monsterinfo.aiflags & 4 /* SoundTarget */) === 0) {
|
|
3801
|
+
self.monsterinfo.sight?.(self, self.enemy);
|
|
3802
|
+
}
|
|
3803
|
+
return true;
|
|
3804
|
+
}
|
|
3811
3805
|
|
|
3812
|
-
// src/
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
deltaSeconds: 0
|
|
3818
|
-
};
|
|
3819
|
-
var LevelClock = class {
|
|
3820
|
-
constructor() {
|
|
3821
|
-
this.state = ZERO_STATE;
|
|
3806
|
+
// src/ai/monster.ts
|
|
3807
|
+
function M_MoveFrame(self) {
|
|
3808
|
+
const move = self.monsterinfo.current_move;
|
|
3809
|
+
if (!move) {
|
|
3810
|
+
return;
|
|
3822
3811
|
}
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
frameNumber: 0,
|
|
3827
|
-
timeSeconds: startSeconds,
|
|
3828
|
-
previousTimeSeconds: startSeconds,
|
|
3829
|
-
deltaSeconds: 0
|
|
3830
|
-
};
|
|
3812
|
+
if (self.frame < move.firstframe || self.frame > move.lastframe) {
|
|
3813
|
+
self.monsterinfo.aiflags &= ~128 /* HoldFrame */;
|
|
3814
|
+
self.frame = move.firstframe;
|
|
3831
3815
|
}
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
frameNumber: context.frame,
|
|
3835
|
-
timeSeconds: context.timeMs / 1e3,
|
|
3836
|
-
previousTimeSeconds: context.previousTimeMs / 1e3,
|
|
3837
|
-
deltaSeconds: context.deltaSeconds
|
|
3838
|
-
};
|
|
3839
|
-
return this.state;
|
|
3816
|
+
if ((self.monsterinfo.aiflags & 128 /* HoldFrame */) !== 0) {
|
|
3817
|
+
return;
|
|
3840
3818
|
}
|
|
3841
|
-
|
|
3842
|
-
|
|
3819
|
+
const index = self.frame - move.firstframe;
|
|
3820
|
+
const frame = move.frames[index];
|
|
3821
|
+
if (frame.ai) {
|
|
3822
|
+
frame.ai(self, frame.dist);
|
|
3843
3823
|
}
|
|
3844
|
-
|
|
3845
|
-
|
|
3824
|
+
if (frame.think) {
|
|
3825
|
+
frame.think(self);
|
|
3846
3826
|
}
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
AIFlags2[AIFlags2["GoodGuy"] = 256] = "GoodGuy";
|
|
3866
|
-
AIFlags2[AIFlags2["Brutal"] = 512] = "Brutal";
|
|
3867
|
-
AIFlags2[AIFlags2["NoStep"] = 1024] = "NoStep";
|
|
3868
|
-
AIFlags2[AIFlags2["Ducked"] = 2048] = "Ducked";
|
|
3869
|
-
AIFlags2[AIFlags2["CombatPoint"] = 4096] = "CombatPoint";
|
|
3870
|
-
AIFlags2[AIFlags2["Medic"] = 8192] = "Medic";
|
|
3871
|
-
AIFlags2[AIFlags2["Resurrecting"] = 16384] = "Resurrecting";
|
|
3872
|
-
AIFlags2[AIFlags2["Pathing"] = 1073741824] = "Pathing";
|
|
3873
|
-
return AIFlags2;
|
|
3874
|
-
})(AIFlags || {});
|
|
3875
|
-
var TraceMask = /* @__PURE__ */ ((TraceMask2) => {
|
|
3876
|
-
TraceMask2[TraceMask2["Opaque"] = 1] = "Opaque";
|
|
3877
|
-
TraceMask2[TraceMask2["Window"] = 2] = "Window";
|
|
3878
|
-
return TraceMask2;
|
|
3879
|
-
})(TraceMask || {});
|
|
3827
|
+
if (!self.inUse) {
|
|
3828
|
+
return;
|
|
3829
|
+
}
|
|
3830
|
+
self.frame++;
|
|
3831
|
+
if (self.frame > move.lastframe) {
|
|
3832
|
+
if (move.endfunc) {
|
|
3833
|
+
move.endfunc(self);
|
|
3834
|
+
if (self.monsterinfo.current_move !== move) {
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
function monster_think(self, context) {
|
|
3841
|
+
M_MoveFrame(self);
|
|
3842
|
+
const time = context && typeof context.timeSeconds === "number" ? context.timeSeconds : self.nextthink;
|
|
3843
|
+
self.nextthink = time + 0.1;
|
|
3844
|
+
}
|
|
3880
3845
|
|
|
3881
|
-
// src/
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3846
|
+
// src/entities/monsters/soldier.ts
|
|
3847
|
+
var MONSTER_TICK = 0.1;
|
|
3848
|
+
function monster_ai_stand(self, dist) {
|
|
3849
|
+
ai_stand(self, MONSTER_TICK);
|
|
3850
|
+
}
|
|
3851
|
+
function monster_ai_walk(self, dist) {
|
|
3852
|
+
ai_walk(self, dist, MONSTER_TICK);
|
|
3853
|
+
}
|
|
3854
|
+
function monster_ai_run(self, dist) {
|
|
3855
|
+
ai_run(self, dist, MONSTER_TICK);
|
|
3856
|
+
}
|
|
3857
|
+
function monster_ai_charge(self, dist) {
|
|
3858
|
+
ai_charge(self, dist, MONSTER_TICK);
|
|
3859
|
+
}
|
|
3860
|
+
var stand_move;
|
|
3861
|
+
var walk_move;
|
|
3862
|
+
var run_move;
|
|
3863
|
+
var attack_move;
|
|
3864
|
+
function soldier_stand(self) {
|
|
3865
|
+
self.monsterinfo.current_move = stand_move;
|
|
3866
|
+
}
|
|
3867
|
+
function soldier_walk(self) {
|
|
3868
|
+
self.monsterinfo.current_move = walk_move;
|
|
3869
|
+
}
|
|
3870
|
+
function soldier_run(self) {
|
|
3871
|
+
if (self.enemy && self.enemy.health > 0) {
|
|
3872
|
+
self.monsterinfo.current_move = run_move;
|
|
3873
|
+
} else {
|
|
3874
|
+
self.monsterinfo.current_move = stand_move;
|
|
3885
3875
|
}
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3876
|
+
}
|
|
3877
|
+
function soldier_attack(self) {
|
|
3878
|
+
self.monsterinfo.current_move = attack_move;
|
|
3879
|
+
}
|
|
3880
|
+
function soldier_fire(self) {
|
|
3881
|
+
}
|
|
3882
|
+
var stand_frames = Array.from({ length: 30 }, () => ({
|
|
3883
|
+
ai: monster_ai_stand,
|
|
3884
|
+
dist: 0
|
|
3885
|
+
}));
|
|
3886
|
+
stand_move = {
|
|
3887
|
+
firstframe: 0,
|
|
3888
|
+
lastframe: 29,
|
|
3889
|
+
frames: stand_frames,
|
|
3890
|
+
endfunc: soldier_stand
|
|
3891
|
+
};
|
|
3892
|
+
var walk_frames = Array.from({ length: 40 }, () => ({
|
|
3893
|
+
ai: monster_ai_walk,
|
|
3894
|
+
dist: 2
|
|
3895
|
+
}));
|
|
3896
|
+
walk_move = {
|
|
3897
|
+
firstframe: 30,
|
|
3898
|
+
lastframe: 69,
|
|
3899
|
+
frames: walk_frames,
|
|
3900
|
+
endfunc: soldier_walk
|
|
3901
|
+
};
|
|
3902
|
+
var run_frames = Array.from({ length: 20 }, () => ({
|
|
3903
|
+
ai: monster_ai_run,
|
|
3904
|
+
dist: 10
|
|
3905
|
+
}));
|
|
3906
|
+
run_move = {
|
|
3907
|
+
firstframe: 70,
|
|
3908
|
+
lastframe: 89,
|
|
3909
|
+
frames: run_frames,
|
|
3910
|
+
endfunc: soldier_run
|
|
3911
|
+
};
|
|
3912
|
+
var attack_frames = Array.from({ length: 10 }, (_, i) => ({
|
|
3913
|
+
ai: monster_ai_charge,
|
|
3914
|
+
dist: 0,
|
|
3915
|
+
think: i === 5 ? soldier_fire : null
|
|
3916
|
+
}));
|
|
3917
|
+
attack_move = {
|
|
3918
|
+
firstframe: 90,
|
|
3919
|
+
lastframe: 99,
|
|
3920
|
+
frames: attack_frames,
|
|
3921
|
+
endfunc: soldier_run
|
|
3922
|
+
};
|
|
3923
|
+
function SP_monster_soldier(self, context) {
|
|
3924
|
+
self.model = "models/monsters/soldier/tris.md2";
|
|
3925
|
+
self.mins = { x: -16, y: -16, z: -24 };
|
|
3926
|
+
self.maxs = { x: 16, y: 16, z: 32 };
|
|
3927
|
+
self.movetype = 5 /* Step */;
|
|
3928
|
+
self.solid = 2 /* BoundingBox */;
|
|
3929
|
+
self.health = 20;
|
|
3930
|
+
self.max_health = 20;
|
|
3931
|
+
self.mass = 100;
|
|
3932
|
+
self.pain = (self2, other, kick, damage) => {
|
|
3891
3933
|
};
|
|
3934
|
+
self.die = (self2, inflictor, attacker, damage, point) => {
|
|
3935
|
+
self2.deadflag = 2 /* Dead */;
|
|
3936
|
+
self2.solid = 0 /* Not */;
|
|
3937
|
+
};
|
|
3938
|
+
self.monsterinfo.stand = soldier_stand;
|
|
3939
|
+
self.monsterinfo.walk = soldier_walk;
|
|
3940
|
+
self.monsterinfo.run = soldier_run;
|
|
3941
|
+
self.monsterinfo.attack = soldier_attack;
|
|
3942
|
+
self.think = monster_think;
|
|
3943
|
+
soldier_stand(self);
|
|
3944
|
+
self.nextthink = self.timestamp + MONSTER_TICK;
|
|
3892
3945
|
}
|
|
3893
|
-
function
|
|
3894
|
-
|
|
3895
|
-
const origin = self.origin;
|
|
3896
|
-
origin.x += delta.x;
|
|
3897
|
-
origin.y += delta.y;
|
|
3898
|
-
origin.z += delta.z;
|
|
3899
|
-
return true;
|
|
3946
|
+
function registerMonsterSpawns(registry) {
|
|
3947
|
+
registry.register("monster_soldier", SP_monster_soldier);
|
|
3900
3948
|
}
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
const
|
|
3909
|
-
|
|
3910
|
-
if (ideal > current) {
|
|
3911
|
-
if (move >= 180) move -= 360;
|
|
3912
|
-
} else if (move <= -180) {
|
|
3913
|
-
move += 360;
|
|
3914
|
-
}
|
|
3915
|
-
if (move > speed) move = speed;
|
|
3916
|
-
else if (move < -speed) move = -speed;
|
|
3917
|
-
self.angles.y = angleMod(current + move);
|
|
3949
|
+
|
|
3950
|
+
// src/entities/spawn.ts
|
|
3951
|
+
var FIELD_LOOKUP = new Map(
|
|
3952
|
+
ENTITY_FIELD_METADATA.map((field) => [field.name, field])
|
|
3953
|
+
);
|
|
3954
|
+
function parseVec3(text) {
|
|
3955
|
+
const parts = text.trim().split(/\s+/);
|
|
3956
|
+
const [x = 0, y = 0, z = 0] = parts.map((part) => Number.parseFloat(part)).map((value) => Number.isNaN(value) ? 0 : value);
|
|
3957
|
+
return { x, y, z };
|
|
3918
3958
|
}
|
|
3919
|
-
function
|
|
3920
|
-
const
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3959
|
+
function parseBoolean(text) {
|
|
3960
|
+
const normalized = text.trim().toLowerCase();
|
|
3961
|
+
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
3962
|
+
}
|
|
3963
|
+
function parseValue(type, value) {
|
|
3964
|
+
switch (type) {
|
|
3965
|
+
case "int":
|
|
3966
|
+
return Number.parseInt(value, 10) || 0;
|
|
3967
|
+
case "float":
|
|
3968
|
+
return Number.parseFloat(value) || 0;
|
|
3969
|
+
case "boolean":
|
|
3970
|
+
return parseBoolean(value);
|
|
3971
|
+
case "vec3":
|
|
3972
|
+
return parseVec3(value);
|
|
3973
|
+
case "string":
|
|
3974
|
+
return value;
|
|
3975
|
+
case "entity":
|
|
3976
|
+
case "callback":
|
|
3977
|
+
return void 0;
|
|
3978
|
+
default:
|
|
3979
|
+
return value;
|
|
3924
3980
|
}
|
|
3925
|
-
return !(delta > 45 && delta < 315);
|
|
3926
|
-
}
|
|
3927
|
-
function ai_move(self, distance2) {
|
|
3928
|
-
walkMove(self, self.angles.y, distance2);
|
|
3929
3981
|
}
|
|
3930
|
-
function
|
|
3931
|
-
if (!
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3982
|
+
function applyEntityKeyValues(entity, values) {
|
|
3983
|
+
if ("angle" in values && !("angles" in values)) {
|
|
3984
|
+
entity.angles = { x: 0, y: Number.parseFloat(values.angle) || 0, z: 0 };
|
|
3985
|
+
}
|
|
3986
|
+
for (const [key, rawValue] of Object.entries(values)) {
|
|
3987
|
+
if (key.startsWith("_")) {
|
|
3988
|
+
continue;
|
|
3989
|
+
}
|
|
3990
|
+
const descriptor = FIELD_LOOKUP.get(key);
|
|
3991
|
+
if (!descriptor) {
|
|
3992
|
+
continue;
|
|
3993
|
+
}
|
|
3994
|
+
const parsed = parseValue(descriptor.type, rawValue);
|
|
3995
|
+
if (parsed !== void 0) {
|
|
3996
|
+
entity[descriptor.name] = parsed;
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
entity.size = {
|
|
4000
|
+
x: entity.maxs.x - entity.mins.x,
|
|
4001
|
+
y: entity.maxs.y - entity.mins.y,
|
|
4002
|
+
z: entity.maxs.z - entity.mins.z
|
|
3936
4003
|
};
|
|
3937
|
-
self.ideal_yaw = vectorToYaw(toTarget);
|
|
3938
4004
|
}
|
|
3939
|
-
function
|
|
3940
|
-
|
|
4005
|
+
function parseQuoted(text, start) {
|
|
4006
|
+
let index = start;
|
|
4007
|
+
let result = "";
|
|
4008
|
+
while (index < text.length) {
|
|
4009
|
+
const char = text[index];
|
|
4010
|
+
if (char === '"') {
|
|
4011
|
+
return { value: result, nextIndex: index + 1 };
|
|
4012
|
+
}
|
|
4013
|
+
result += char;
|
|
4014
|
+
index += 1;
|
|
4015
|
+
}
|
|
4016
|
+
throw new Error("Unterminated quoted string in entity lump");
|
|
3941
4017
|
}
|
|
3942
|
-
function
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
walkMove(self, self.angles.y, distance2);
|
|
4018
|
+
function consumeWhitespace(text, start) {
|
|
4019
|
+
let index = start;
|
|
4020
|
+
while (index < text.length && /\s/.test(text[index] ?? "")) {
|
|
4021
|
+
index += 1;
|
|
3947
4022
|
}
|
|
4023
|
+
return index;
|
|
3948
4024
|
}
|
|
3949
|
-
function
|
|
3950
|
-
|
|
3951
|
-
|
|
4025
|
+
function parseToken(text, start) {
|
|
4026
|
+
const index = consumeWhitespace(text, start);
|
|
4027
|
+
if (index >= text.length) {
|
|
4028
|
+
return { token: null, nextIndex: index };
|
|
3952
4029
|
}
|
|
3953
|
-
|
|
4030
|
+
const current = text[index];
|
|
4031
|
+
if (current === "{" || current === "}") {
|
|
4032
|
+
return { token: current, nextIndex: index + 1 };
|
|
4033
|
+
}
|
|
4034
|
+
if (current !== '"') {
|
|
4035
|
+
throw new Error(`Unexpected token in entity lump: ${current}`);
|
|
4036
|
+
}
|
|
4037
|
+
const quoted = parseQuoted(text, index + 1);
|
|
4038
|
+
return { token: quoted.value, nextIndex: quoted.nextIndex };
|
|
3954
4039
|
}
|
|
3955
|
-
function
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
4040
|
+
function parseEntityLump(text) {
|
|
4041
|
+
const entities = [];
|
|
4042
|
+
let index = 0;
|
|
4043
|
+
while (index < text.length) {
|
|
4044
|
+
const open = parseToken(text, index);
|
|
4045
|
+
index = open.nextIndex;
|
|
4046
|
+
if (open.token === null) {
|
|
4047
|
+
break;
|
|
4048
|
+
}
|
|
4049
|
+
if (open.token !== "{") {
|
|
4050
|
+
throw new Error("Expected { at start of entity definition");
|
|
4051
|
+
}
|
|
4052
|
+
const entity = {};
|
|
4053
|
+
while (true) {
|
|
4054
|
+
const keyToken = parseToken(text, index);
|
|
4055
|
+
index = keyToken.nextIndex;
|
|
4056
|
+
if (keyToken.token === null) {
|
|
4057
|
+
throw new Error("EOF reached while parsing entity");
|
|
4058
|
+
}
|
|
4059
|
+
if (keyToken.token === "}") {
|
|
4060
|
+
break;
|
|
4061
|
+
}
|
|
4062
|
+
const valueToken = parseToken(text, index);
|
|
4063
|
+
index = valueToken.nextIndex;
|
|
4064
|
+
if (valueToken.token === null || valueToken.token === "{" || valueToken.token === "}") {
|
|
4065
|
+
throw new Error("Malformed entity key/value pair");
|
|
4066
|
+
}
|
|
4067
|
+
if (!keyToken.token.startsWith("_")) {
|
|
4068
|
+
entity[keyToken.token] = valueToken.token;
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
entities.push(entity);
|
|
3960
4072
|
}
|
|
4073
|
+
return entities;
|
|
3961
4074
|
}
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
4075
|
+
var SpawnRegistry = class {
|
|
4076
|
+
constructor() {
|
|
4077
|
+
this.registry = /* @__PURE__ */ new Map();
|
|
3965
4078
|
}
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
walkMove(self, self.angles.y, distance2);
|
|
4079
|
+
register(classname, spawn) {
|
|
4080
|
+
this.registry.set(classname, spawn);
|
|
3969
4081
|
}
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
setIdealYawTowards(self, self.enemy);
|
|
3973
|
-
changeYaw(self, deltaSeconds);
|
|
3974
|
-
if (distance2 !== 0) {
|
|
3975
|
-
walkMove(self, self.angles.y, distance2);
|
|
4082
|
+
get(classname) {
|
|
4083
|
+
return this.registry.get(classname);
|
|
3976
4084
|
}
|
|
4085
|
+
};
|
|
4086
|
+
function defaultWarn(message) {
|
|
4087
|
+
void message;
|
|
3977
4088
|
}
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
maxs: {
|
|
3995
|
-
x: entity.origin.x + entity.maxs.x,
|
|
3996
|
-
y: entity.origin.y + entity.maxs.y,
|
|
3997
|
-
z: entity.origin.z + entity.maxs.z
|
|
4089
|
+
function spawnEntityFromDictionary(dictionary, options) {
|
|
4090
|
+
const warn = options.onWarning ?? defaultWarn;
|
|
4091
|
+
const classname = dictionary.classname;
|
|
4092
|
+
if (!classname) {
|
|
4093
|
+
warn("Encountered entity with no classname");
|
|
4094
|
+
return null;
|
|
4095
|
+
}
|
|
4096
|
+
const isWorld = classname === "worldspawn";
|
|
4097
|
+
const entity = isWorld ? options.entities.world : options.entities.spawn();
|
|
4098
|
+
applyEntityKeyValues(entity, dictionary);
|
|
4099
|
+
const context = {
|
|
4100
|
+
keyValues: dictionary,
|
|
4101
|
+
entities: options.entities,
|
|
4102
|
+
warn,
|
|
4103
|
+
free(target) {
|
|
4104
|
+
options.entities.freeImmediate(target);
|
|
3998
4105
|
}
|
|
3999
4106
|
};
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
function classifyRange(distance2) {
|
|
4008
|
-
if (distance2 <= RANGE_MELEE) {
|
|
4009
|
-
return "melee" /* Melee */;
|
|
4010
|
-
}
|
|
4011
|
-
if (distance2 <= RANGE_NEAR) {
|
|
4012
|
-
return "near" /* Near */;
|
|
4107
|
+
const spawnFunc = options.registry.get(classname);
|
|
4108
|
+
if (!spawnFunc) {
|
|
4109
|
+
warn(`${classname} does not have a spawn function`);
|
|
4110
|
+
if (!isWorld) {
|
|
4111
|
+
options.entities.freeImmediate(entity);
|
|
4112
|
+
}
|
|
4113
|
+
return null;
|
|
4013
4114
|
}
|
|
4014
|
-
|
|
4015
|
-
|
|
4115
|
+
spawnFunc(entity, context);
|
|
4116
|
+
if (!entity.inUse) {
|
|
4117
|
+
return null;
|
|
4016
4118
|
}
|
|
4017
|
-
|
|
4119
|
+
options.entities.finalizeSpawn(entity);
|
|
4120
|
+
return entity;
|
|
4018
4121
|
}
|
|
4019
|
-
function
|
|
4020
|
-
const
|
|
4021
|
-
const
|
|
4022
|
-
const
|
|
4023
|
-
|
|
4024
|
-
|
|
4122
|
+
function spawnEntitiesFromText(text, options) {
|
|
4123
|
+
const parsed = parseEntityLump(text);
|
|
4124
|
+
const spawned = [];
|
|
4125
|
+
for (const dictionary of parsed) {
|
|
4126
|
+
const entity = spawnEntityFromDictionary(dictionary, options);
|
|
4127
|
+
if (entity) {
|
|
4128
|
+
spawned.push(entity);
|
|
4129
|
+
}
|
|
4025
4130
|
}
|
|
4026
|
-
return
|
|
4131
|
+
return spawned;
|
|
4027
4132
|
}
|
|
4028
|
-
function
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
const start = { x: self.origin.x, y: self.origin.y, z: self.origin.z + self.viewheight };
|
|
4033
|
-
const end = { x: other.origin.x, y: other.origin.y, z: other.origin.z + other.viewheight };
|
|
4034
|
-
const mask = options?.throughGlass ? 1 /* Opaque */ : 1 /* Opaque */ | 2 /* Window */;
|
|
4035
|
-
const result = trace(start, end, self, mask);
|
|
4036
|
-
return result.fraction === 1 || result.entity === other;
|
|
4133
|
+
function findPlayerStart(entities) {
|
|
4134
|
+
return entities.find(
|
|
4135
|
+
(entity) => entity.classname === "info_player_start"
|
|
4136
|
+
);
|
|
4037
4137
|
}
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
};
|
|
4046
|
-
|
|
4138
|
+
function registerDefaultSpawns(game, registry) {
|
|
4139
|
+
registry.register("worldspawn", (entity) => {
|
|
4140
|
+
entity.movetype = 2 /* Push */;
|
|
4141
|
+
entity.solid = 3 /* Bsp */;
|
|
4142
|
+
entity.modelindex = entity.modelindex || 1;
|
|
4143
|
+
});
|
|
4144
|
+
registry.register("info_player_start", () => {
|
|
4145
|
+
});
|
|
4146
|
+
registry.register("info_player_deathmatch", () => {
|
|
4147
|
+
});
|
|
4148
|
+
registry.register("info_player_coop", () => {
|
|
4149
|
+
});
|
|
4150
|
+
registry.register("info_null", (entity, context) => {
|
|
4151
|
+
context.free(entity);
|
|
4152
|
+
});
|
|
4153
|
+
registry.register("info_notnull", () => {
|
|
4154
|
+
});
|
|
4155
|
+
registry.register("info_teleport_destination", () => {
|
|
4156
|
+
});
|
|
4157
|
+
registerTriggerSpawns(registry);
|
|
4158
|
+
registerTargetSpawns(registry);
|
|
4159
|
+
registerMiscSpawns(registry);
|
|
4160
|
+
registerItemSpawns(game, registry);
|
|
4161
|
+
registerFuncSpawns(registry);
|
|
4162
|
+
registerPathSpawns(registry);
|
|
4163
|
+
registerLightSpawns(registry);
|
|
4164
|
+
registerMonsterSpawns(registry);
|
|
4047
4165
|
}
|
|
4048
|
-
function
|
|
4049
|
-
|
|
4166
|
+
function createDefaultSpawnRegistry(game) {
|
|
4167
|
+
const registry = new SpawnRegistry();
|
|
4168
|
+
registerDefaultSpawns(game, registry);
|
|
4169
|
+
return registry;
|
|
4050
4170
|
}
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
faceYawInstantly(self);
|
|
4056
|
-
if ((self.monsterinfo.aiflags & 1 /* StandGround */) !== 0) {
|
|
4057
|
-
self.monsterinfo.stand?.(self);
|
|
4058
|
-
} else {
|
|
4059
|
-
self.monsterinfo.run?.(self);
|
|
4060
|
-
self.attack_finished_time = level.timeSeconds + 1;
|
|
4061
|
-
}
|
|
4171
|
+
|
|
4172
|
+
// src/entities/callbacks.ts
|
|
4173
|
+
function createCallbackRegistry() {
|
|
4174
|
+
return /* @__PURE__ */ new Map();
|
|
4062
4175
|
}
|
|
4063
|
-
function
|
|
4064
|
-
if (
|
|
4065
|
-
if ((self.enemy.svflags & 8 /* Player */) !== 0) {
|
|
4066
|
-
level.sightEntity = self;
|
|
4067
|
-
level.sightEntityFrame = level.frameNumber;
|
|
4068
|
-
self.light_level = 128;
|
|
4069
|
-
}
|
|
4070
|
-
self.show_hostile = level.timeSeconds + 1;
|
|
4071
|
-
const lastSighting = self.monsterinfo.last_sighting;
|
|
4072
|
-
lastSighting.x = self.enemy.origin.x;
|
|
4073
|
-
lastSighting.y = self.enemy.origin.y;
|
|
4074
|
-
lastSighting.z = self.enemy.origin.z;
|
|
4075
|
-
self.trail_time = level.timeSeconds;
|
|
4076
|
-
self.monsterinfo.trail_time = level.timeSeconds;
|
|
4077
|
-
if (!self.combattarget) {
|
|
4078
|
-
huntTarget(self, level);
|
|
4176
|
+
function registerCallback(registry, name, fn) {
|
|
4177
|
+
if (registry.has(name)) {
|
|
4079
4178
|
return;
|
|
4080
4179
|
}
|
|
4081
|
-
|
|
4082
|
-
const movetarget = pickTarget?.(self.combattarget) ?? self.enemy;
|
|
4083
|
-
self.goalentity = movetarget;
|
|
4084
|
-
self.movetarget = movetarget;
|
|
4085
|
-
self.combattarget = void 0;
|
|
4086
|
-
self.monsterinfo.aiflags |= 4096 /* CombatPoint */;
|
|
4087
|
-
if (self.movetarget) {
|
|
4088
|
-
self.movetarget.targetname = void 0;
|
|
4089
|
-
}
|
|
4090
|
-
self.monsterinfo.pausetime = 0;
|
|
4091
|
-
self.monsterinfo.run?.(self);
|
|
4180
|
+
registry.set(name, fn);
|
|
4092
4181
|
}
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4182
|
+
|
|
4183
|
+
// src/loop.ts
|
|
4184
|
+
var orderedStageNames = [
|
|
4185
|
+
"prep",
|
|
4186
|
+
"simulate",
|
|
4187
|
+
"finish"
|
|
4188
|
+
];
|
|
4189
|
+
var GameFrameLoop = class {
|
|
4190
|
+
constructor(initialStages) {
|
|
4191
|
+
this.timeMs = 0;
|
|
4192
|
+
this.frame = 0;
|
|
4193
|
+
this.stageHandlers = {
|
|
4194
|
+
prep: [],
|
|
4195
|
+
simulate: [],
|
|
4196
|
+
finish: []
|
|
4197
|
+
};
|
|
4198
|
+
this.stageCounts = {
|
|
4199
|
+
prep: 0,
|
|
4200
|
+
simulate: 0,
|
|
4201
|
+
finish: 0
|
|
4202
|
+
};
|
|
4203
|
+
this.stageCompactionNeeded = {
|
|
4204
|
+
prep: false,
|
|
4205
|
+
simulate: false,
|
|
4206
|
+
finish: false
|
|
4207
|
+
};
|
|
4208
|
+
if (initialStages) {
|
|
4209
|
+
for (const stageName of orderedStageNames) {
|
|
4210
|
+
const handler = initialStages[stageName];
|
|
4211
|
+
if (handler) {
|
|
4212
|
+
this.addStage(stageName, handler);
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4101
4216
|
}
|
|
4102
|
-
|
|
4103
|
-
|
|
4217
|
+
addStage(stage, handler) {
|
|
4218
|
+
const handlers = this.stageHandlers[stage];
|
|
4219
|
+
handlers.push(handler);
|
|
4220
|
+
this.stageCounts[stage] += 1;
|
|
4221
|
+
return () => {
|
|
4222
|
+
const index = handlers.indexOf(handler);
|
|
4223
|
+
if (index >= 0 && handlers[index]) {
|
|
4224
|
+
handlers[index] = void 0;
|
|
4225
|
+
this.stageCounts[stage] -= 1;
|
|
4226
|
+
this.stageCompactionNeeded[stage] = true;
|
|
4227
|
+
}
|
|
4228
|
+
};
|
|
4104
4229
|
}
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
if ((self.spawnflags & SPAWNFLAG_MONSTER_AMBUSH) !== 0) {
|
|
4109
|
-
if (!visible(self, client, trace)) return false;
|
|
4110
|
-
} else if (hearability.canHear && !hearability.canHear(self, client)) {
|
|
4111
|
-
return false;
|
|
4230
|
+
reset(startTimeMs) {
|
|
4231
|
+
this.timeMs = startTimeMs;
|
|
4232
|
+
this.frame = 0;
|
|
4112
4233
|
}
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
if (
|
|
4125
|
-
|
|
4234
|
+
advance(step) {
|
|
4235
|
+
const previousTimeMs = this.timeMs;
|
|
4236
|
+
this.timeMs = previousTimeMs + step.deltaMs;
|
|
4237
|
+
this.frame = step.frame;
|
|
4238
|
+
const context = {
|
|
4239
|
+
...step,
|
|
4240
|
+
timeMs: this.timeMs,
|
|
4241
|
+
previousTimeMs,
|
|
4242
|
+
deltaSeconds: step.deltaMs / 1e3
|
|
4243
|
+
};
|
|
4244
|
+
this.runStage("prep", context);
|
|
4245
|
+
if (this.stageCounts.simulate === 0) {
|
|
4246
|
+
throw new Error("GameFrameLoop requires at least one simulate stage");
|
|
4126
4247
|
}
|
|
4127
|
-
|
|
4248
|
+
this.runStage("simulate", context);
|
|
4249
|
+
this.runStage("finish", context);
|
|
4250
|
+
return context;
|
|
4128
4251
|
}
|
|
4129
|
-
|
|
4130
|
-
|
|
4252
|
+
runStage(stage, context) {
|
|
4253
|
+
const handlers = this.stageHandlers[stage];
|
|
4254
|
+
for (let i = 0; i < handlers.length; i += 1) {
|
|
4255
|
+
const handler = handlers[i];
|
|
4256
|
+
if (!handler) {
|
|
4257
|
+
continue;
|
|
4258
|
+
}
|
|
4259
|
+
handler(context);
|
|
4260
|
+
}
|
|
4261
|
+
if (this.stageCompactionNeeded[stage]) {
|
|
4262
|
+
this.compactStageHandlers(stage);
|
|
4263
|
+
}
|
|
4131
4264
|
}
|
|
4132
|
-
|
|
4133
|
-
|
|
4265
|
+
compactStageHandlers(stage) {
|
|
4266
|
+
const handlers = this.stageHandlers[stage];
|
|
4267
|
+
let writeIndex = 0;
|
|
4268
|
+
for (let readIndex = 0; readIndex < handlers.length; readIndex += 1) {
|
|
4269
|
+
const handler = handlers[readIndex];
|
|
4270
|
+
if (handler) {
|
|
4271
|
+
handlers[writeIndex] = handler;
|
|
4272
|
+
writeIndex += 1;
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
handlers.length = writeIndex;
|
|
4276
|
+
this.stageCompactionNeeded[stage] = false;
|
|
4134
4277
|
}
|
|
4135
|
-
|
|
4136
|
-
return
|
|
4278
|
+
get time() {
|
|
4279
|
+
return this.timeMs;
|
|
4137
4280
|
}
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
function rejectNotargetEntity(client) {
|
|
4141
|
-
if ((client.flags & FL_NOTARGET) !== 0) return true;
|
|
4142
|
-
if ((client.svflags & 4 /* Monster */) !== 0 && client.enemy) {
|
|
4143
|
-
return (client.enemy.flags & FL_NOTARGET) !== 0;
|
|
4281
|
+
get frameNumber() {
|
|
4282
|
+
return this.frame;
|
|
4144
4283
|
}
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4284
|
+
};
|
|
4285
|
+
|
|
4286
|
+
// src/level.ts
|
|
4287
|
+
var ZERO_STATE = {
|
|
4288
|
+
frameNumber: 0,
|
|
4289
|
+
timeSeconds: 0,
|
|
4290
|
+
previousTimeSeconds: 0,
|
|
4291
|
+
deltaSeconds: 0
|
|
4292
|
+
};
|
|
4293
|
+
var LevelClock = class {
|
|
4294
|
+
constructor() {
|
|
4295
|
+
this.state = ZERO_STATE;
|
|
4154
4296
|
}
|
|
4155
|
-
|
|
4156
|
-
|
|
4297
|
+
start(startTimeMs) {
|
|
4298
|
+
const startSeconds = startTimeMs / 1e3;
|
|
4299
|
+
this.state = {
|
|
4300
|
+
frameNumber: 0,
|
|
4301
|
+
timeSeconds: startSeconds,
|
|
4302
|
+
previousTimeSeconds: startSeconds,
|
|
4303
|
+
deltaSeconds: 0
|
|
4304
|
+
};
|
|
4157
4305
|
}
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
} else if (!updateSoundChase(self, candidate, level, hearability, trace)) {
|
|
4167
|
-
return false;
|
|
4306
|
+
tick(context) {
|
|
4307
|
+
this.state = {
|
|
4308
|
+
frameNumber: context.frame,
|
|
4309
|
+
timeSeconds: context.timeMs / 1e3,
|
|
4310
|
+
previousTimeSeconds: context.previousTimeMs / 1e3,
|
|
4311
|
+
deltaSeconds: context.deltaSeconds
|
|
4312
|
+
};
|
|
4313
|
+
return this.state;
|
|
4168
4314
|
}
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
self.monsterinfo.sight?.(self, self.enemy);
|
|
4315
|
+
get current() {
|
|
4316
|
+
return this.state;
|
|
4172
4317
|
}
|
|
4173
|
-
|
|
4174
|
-
}
|
|
4318
|
+
restore(state) {
|
|
4319
|
+
this.state = { ...state };
|
|
4320
|
+
}
|
|
4321
|
+
};
|
|
4175
4322
|
|
|
4176
4323
|
// src/checksum.ts
|
|
4177
4324
|
var FNV_OFFSET_BASIS = 2166136261;
|
|
@@ -5552,6 +5699,7 @@ function createGame({ trace, pointcontents }, engine, options) {
|
|
|
5552
5699
|
HEALTH_ITEMS,
|
|
5553
5700
|
KEY_ITEMS,
|
|
5554
5701
|
KeyId,
|
|
5702
|
+
M_MoveFrame,
|
|
5555
5703
|
MoveType,
|
|
5556
5704
|
ORDERED_DAMAGE_MODS,
|
|
5557
5705
|
POWERUP_ITEMS,
|
|
@@ -5628,6 +5776,7 @@ function createGame({ trace, pointcontents }, engine, options) {
|
|
|
5628
5776
|
infront,
|
|
5629
5777
|
isZeroVector,
|
|
5630
5778
|
killBox,
|
|
5779
|
+
monster_think,
|
|
5631
5780
|
parseEntityLump,
|
|
5632
5781
|
parseRereleaseSave,
|
|
5633
5782
|
parseSaveFile,
|