quake2ts 0.0.49 → 0.0.50
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/apps/viewer/dist/browser/index.global.js +1 -1
- package/apps/viewer/dist/browser/index.global.js.map +1 -1
- package/apps/viewer/dist/cjs/index.cjs +33 -15
- package/apps/viewer/dist/cjs/index.cjs.map +1 -1
- package/apps/viewer/dist/esm/index.js +33 -15
- package/apps/viewer/dist/esm/index.js.map +1 -1
- package/apps/viewer/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +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 +458 -390
- package/packages/game/dist/cjs/index.cjs.map +1 -1
- package/packages/game/dist/esm/index.js +454 -390
- package/packages/game/dist/esm/index.js.map +1 -1
- package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/game/dist/types/entities/callbacks.d.ts +6 -0
- package/packages/game/dist/types/entities/callbacks.d.ts.map +1 -0
- package/packages/game/dist/types/entities/index.d.ts +1 -0
- package/packages/game/dist/types/entities/index.d.ts.map +1 -1
- package/packages/game/dist/types/entities/system.d.ts +5 -3
- package/packages/game/dist/types/entities/system.d.ts.map +1 -1
- package/packages/game/dist/types/inventory/playerInventory.d.ts +10 -0
- package/packages/game/dist/types/inventory/playerInventory.d.ts.map +1 -1
- package/packages/game/dist/types/save/save.d.ts +7 -2
- package/packages/game/dist/types/save/save.d.ts.map +1 -1
- package/packages/game/dist/types/save/tests/callbacks.test.d.ts +2 -0
- package/packages/game/dist/types/save/tests/callbacks.test.d.ts.map +1 -0
- package/packages/game/dist/types/save/tests/playerInventory.test.d.ts +2 -0
- package/packages/game/dist/types/save/tests/playerInventory.test.d.ts.map +1 -0
|
@@ -81,11 +81,13 @@ __export(index_exports, {
|
|
|
81
81
|
convertRereleaseSaveToGameSave: () => convertRereleaseSaveToGameSave,
|
|
82
82
|
createAmmoInventory: () => createAmmoInventory,
|
|
83
83
|
createBaseAmmoCaps: () => createBaseAmmoCaps,
|
|
84
|
+
createCallbackRegistry: () => createCallbackRegistry,
|
|
84
85
|
createDefaultSpawnRegistry: () => createDefaultSpawnRegistry,
|
|
85
86
|
createGame: () => createGame,
|
|
86
87
|
createPlayerInventory: () => createPlayerInventory,
|
|
87
88
|
createSaveFile: () => createSaveFile,
|
|
88
89
|
damageModName: () => damageModName,
|
|
90
|
+
deserializePlayerInventory: () => deserializePlayerInventory,
|
|
89
91
|
equipArmor: () => equipArmor,
|
|
90
92
|
facingIdeal: () => facingIdeal,
|
|
91
93
|
findTarget: () => findTarget,
|
|
@@ -108,8 +110,10 @@ __export(index_exports, {
|
|
|
108
110
|
parseSaveFile: () => parseSaveFile,
|
|
109
111
|
pickupAmmo: () => pickupAmmo,
|
|
110
112
|
rangeTo: () => rangeTo,
|
|
113
|
+
registerCallback: () => registerCallback,
|
|
111
114
|
registerDefaultSpawns: () => registerDefaultSpawns,
|
|
112
115
|
selectWeapon: () => selectWeapon,
|
|
116
|
+
serializePlayerInventory: () => serializePlayerInventory,
|
|
113
117
|
serializeRereleaseSave: () => serializeRereleaseSave,
|
|
114
118
|
setMovedir: () => setMovedir,
|
|
115
119
|
spawnEntitiesFromText: () => spawnEntitiesFromText,
|
|
@@ -999,7 +1003,7 @@ function boundsIntersect(a, b) {
|
|
|
999
1003
|
return !(a.min.x > b.max.x || a.max.x < b.min.x || a.min.y > b.max.y || a.max.y < b.min.y || a.min.z > b.max.z || a.max.z < b.min.z);
|
|
1000
1004
|
}
|
|
1001
1005
|
var SERIALIZABLE_FIELDS = ENTITY_FIELD_METADATA.filter(
|
|
1002
|
-
(field) => field.save
|
|
1006
|
+
(field) => field.save || field.type === "callback"
|
|
1003
1007
|
);
|
|
1004
1008
|
var DESCRIPTORS = new Map(SERIALIZABLE_FIELDS.map((descriptor) => [descriptor.name, descriptor]));
|
|
1005
1009
|
function serializeVec3(vec) {
|
|
@@ -1030,12 +1034,18 @@ function deserializeInventory(value) {
|
|
|
1030
1034
|
return parsed;
|
|
1031
1035
|
}
|
|
1032
1036
|
var EntitySystem = class {
|
|
1033
|
-
constructor(maxEntities) {
|
|
1037
|
+
constructor(maxEntities, callbackRegistry) {
|
|
1034
1038
|
this.targetNameIndex = /* @__PURE__ */ new Map();
|
|
1035
1039
|
this.random = createRandomGenerator();
|
|
1036
1040
|
this.currentTimeSeconds = 0;
|
|
1037
1041
|
this.pool = new EntityPool(maxEntities);
|
|
1038
1042
|
this.thinkScheduler = new ThinkScheduler();
|
|
1043
|
+
this.callbackToName = /* @__PURE__ */ new Map();
|
|
1044
|
+
if (callbackRegistry) {
|
|
1045
|
+
for (const [name, fn] of callbackRegistry.entries()) {
|
|
1046
|
+
this.callbackToName.set(fn, name);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1039
1049
|
}
|
|
1040
1050
|
get world() {
|
|
1041
1051
|
return this.pool.world;
|
|
@@ -1160,6 +1170,9 @@ var EntitySystem = class {
|
|
|
1160
1170
|
case "inventory":
|
|
1161
1171
|
fields[descriptor.name] = serializeInventory(value);
|
|
1162
1172
|
break;
|
|
1173
|
+
case "callback":
|
|
1174
|
+
fields[descriptor.name] = value ? this.callbackToName.get(value) ?? null : null;
|
|
1175
|
+
break;
|
|
1163
1176
|
default:
|
|
1164
1177
|
fields[descriptor.name] = value ?? null;
|
|
1165
1178
|
break;
|
|
@@ -1177,7 +1190,7 @@ var EntitySystem = class {
|
|
|
1177
1190
|
thinks: this.thinkScheduler.snapshot()
|
|
1178
1191
|
};
|
|
1179
1192
|
}
|
|
1180
|
-
restore(snapshot) {
|
|
1193
|
+
restore(snapshot, callbackRegistry) {
|
|
1181
1194
|
this.currentTimeSeconds = snapshot.timeSeconds;
|
|
1182
1195
|
this.pool.restore(snapshot.pool);
|
|
1183
1196
|
const indexToEntity = /* @__PURE__ */ new Map();
|
|
@@ -1212,6 +1225,14 @@ var EntitySystem = class {
|
|
|
1212
1225
|
case "boolean":
|
|
1213
1226
|
assignField(entity, name, Boolean(value));
|
|
1214
1227
|
break;
|
|
1228
|
+
case "callback":
|
|
1229
|
+
if (value) {
|
|
1230
|
+
const callback = callbackRegistry?.get(value);
|
|
1231
|
+
if (callback) {
|
|
1232
|
+
assignField(entity, name, callback);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
break;
|
|
1215
1236
|
default:
|
|
1216
1237
|
assignField(entity, name, value);
|
|
1217
1238
|
break;
|
|
@@ -2053,6 +2074,17 @@ function createDefaultSpawnRegistry() {
|
|
|
2053
2074
|
return registry;
|
|
2054
2075
|
}
|
|
2055
2076
|
|
|
2077
|
+
// src/entities/callbacks.ts
|
|
2078
|
+
function createCallbackRegistry() {
|
|
2079
|
+
return /* @__PURE__ */ new Map();
|
|
2080
|
+
}
|
|
2081
|
+
function registerCallback(registry, name, fn) {
|
|
2082
|
+
if (registry.has(name)) {
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
registry.set(name, fn);
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2056
2088
|
// src/loop.ts
|
|
2057
2089
|
var orderedStageNames = [
|
|
2058
2090
|
"prep",
|
|
@@ -2565,126 +2597,430 @@ function hashGameState(state) {
|
|
|
2565
2597
|
return hash >>> 0;
|
|
2566
2598
|
}
|
|
2567
2599
|
|
|
2568
|
-
// src/
|
|
2569
|
-
var
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2600
|
+
// src/inventory/ammo.ts
|
|
2601
|
+
var AmmoType = /* @__PURE__ */ ((AmmoType3) => {
|
|
2602
|
+
AmmoType3[AmmoType3["Bullets"] = 0] = "Bullets";
|
|
2603
|
+
AmmoType3[AmmoType3["Shells"] = 1] = "Shells";
|
|
2604
|
+
AmmoType3[AmmoType3["Rockets"] = 2] = "Rockets";
|
|
2605
|
+
AmmoType3[AmmoType3["Grenades"] = 3] = "Grenades";
|
|
2606
|
+
AmmoType3[AmmoType3["Cells"] = 4] = "Cells";
|
|
2607
|
+
AmmoType3[AmmoType3["Slugs"] = 5] = "Slugs";
|
|
2608
|
+
return AmmoType3;
|
|
2609
|
+
})(AmmoType || {});
|
|
2610
|
+
var AMMO_TYPE_COUNT = Object.keys(AmmoType).length / 2;
|
|
2611
|
+
var AmmoItemId = /* @__PURE__ */ ((AmmoItemId3) => {
|
|
2612
|
+
AmmoItemId3["Shells"] = "ammo_shells";
|
|
2613
|
+
AmmoItemId3["Bullets"] = "ammo_bullets";
|
|
2614
|
+
AmmoItemId3["Rockets"] = "ammo_rockets";
|
|
2615
|
+
AmmoItemId3["Grenades"] = "ammo_grenades";
|
|
2616
|
+
AmmoItemId3["Cells"] = "ammo_cells";
|
|
2617
|
+
AmmoItemId3["Slugs"] = "ammo_slugs";
|
|
2618
|
+
return AmmoItemId3;
|
|
2619
|
+
})(AmmoItemId || {});
|
|
2620
|
+
var AMMO_ITEM_DEFINITIONS = {
|
|
2621
|
+
["ammo_shells" /* Shells */]: { id: "ammo_shells" /* Shells */, ammoType: 1 /* Shells */, quantity: 10, weaponAmmo: false },
|
|
2622
|
+
["ammo_bullets" /* Bullets */]: { id: "ammo_bullets" /* Bullets */, ammoType: 0 /* Bullets */, quantity: 50, weaponAmmo: false },
|
|
2623
|
+
["ammo_rockets" /* Rockets */]: { id: "ammo_rockets" /* Rockets */, ammoType: 2 /* Rockets */, quantity: 5, weaponAmmo: false },
|
|
2624
|
+
["ammo_grenades" /* Grenades */]: { id: "ammo_grenades" /* Grenades */, ammoType: 3 /* Grenades */, quantity: 5, weaponAmmo: true },
|
|
2625
|
+
["ammo_cells" /* Cells */]: { id: "ammo_cells" /* Cells */, ammoType: 4 /* Cells */, quantity: 50, weaponAmmo: false },
|
|
2626
|
+
["ammo_slugs" /* Slugs */]: { id: "ammo_slugs" /* Slugs */, ammoType: 5 /* Slugs */, quantity: 10, weaponAmmo: false }
|
|
2627
|
+
};
|
|
2628
|
+
function getAmmoItemDefinition(id) {
|
|
2629
|
+
return AMMO_ITEM_DEFINITIONS[id];
|
|
2576
2630
|
}
|
|
2577
|
-
function
|
|
2578
|
-
|
|
2579
|
-
throw new Error(`${label} must be a finite number`);
|
|
2580
|
-
}
|
|
2581
|
-
return value;
|
|
2631
|
+
function createAmmoInventory(caps = createBaseAmmoCaps()) {
|
|
2632
|
+
return { caps: caps.slice(), counts: Array(AMMO_TYPE_COUNT).fill(0) };
|
|
2582
2633
|
}
|
|
2583
|
-
function
|
|
2584
|
-
|
|
2585
|
-
|
|
2634
|
+
function createBaseAmmoCaps() {
|
|
2635
|
+
const caps = Array(AMMO_TYPE_COUNT).fill(50);
|
|
2636
|
+
caps[0 /* Bullets */] = 200;
|
|
2637
|
+
caps[1 /* Shells */] = 100;
|
|
2638
|
+
caps[4 /* Cells */] = 200;
|
|
2639
|
+
return caps;
|
|
2640
|
+
}
|
|
2641
|
+
function clampAmmoCounts(counts, caps) {
|
|
2642
|
+
const limit = Math.min(counts.length, caps.length);
|
|
2643
|
+
const clamped = counts.slice(0, limit);
|
|
2644
|
+
for (let i = 0; i < limit; i++) {
|
|
2645
|
+
const cap = caps[i];
|
|
2646
|
+
if (cap !== void 0) {
|
|
2647
|
+
clamped[i] = Math.min(counts[i], cap);
|
|
2648
|
+
}
|
|
2586
2649
|
}
|
|
2587
|
-
return
|
|
2650
|
+
return clamped;
|
|
2588
2651
|
}
|
|
2589
|
-
function
|
|
2590
|
-
|
|
2591
|
-
|
|
2652
|
+
function addAmmo(inventory, ammoType, amount) {
|
|
2653
|
+
const cap = inventory.caps[ammoType];
|
|
2654
|
+
const current = inventory.counts[ammoType] ?? 0;
|
|
2655
|
+
if (cap !== void 0 && current >= cap) {
|
|
2656
|
+
return { ammoType, added: 0, newCount: current, capped: cap, pickedUp: false };
|
|
2592
2657
|
}
|
|
2593
|
-
|
|
2658
|
+
const uncapped = current + amount;
|
|
2659
|
+
const newCount = cap === void 0 ? uncapped : Math.min(uncapped, cap);
|
|
2660
|
+
const added = newCount - current;
|
|
2661
|
+
inventory.counts[ammoType] = newCount;
|
|
2662
|
+
return { ammoType, added, newCount, capped: cap ?? Number.POSITIVE_INFINITY, pickedUp: added > 0 };
|
|
2594
2663
|
}
|
|
2595
|
-
function
|
|
2596
|
-
|
|
2597
|
-
|
|
2664
|
+
function pickupAmmo(inventory, itemId, options = {}) {
|
|
2665
|
+
const def = getAmmoItemDefinition(itemId);
|
|
2666
|
+
const amount = options.countOverride ?? def.quantity;
|
|
2667
|
+
return addAmmo(inventory, def.ammoType, amount);
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
// src/combat/damageFlags.ts
|
|
2671
|
+
var DamageFlags = /* @__PURE__ */ ((DamageFlags2) => {
|
|
2672
|
+
DamageFlags2[DamageFlags2["NONE"] = 0] = "NONE";
|
|
2673
|
+
DamageFlags2[DamageFlags2["RADIUS"] = 1] = "RADIUS";
|
|
2674
|
+
DamageFlags2[DamageFlags2["NO_ARMOR"] = 2] = "NO_ARMOR";
|
|
2675
|
+
DamageFlags2[DamageFlags2["ENERGY"] = 4] = "ENERGY";
|
|
2676
|
+
DamageFlags2[DamageFlags2["NO_KNOCKBACK"] = 8] = "NO_KNOCKBACK";
|
|
2677
|
+
DamageFlags2[DamageFlags2["BULLET"] = 16] = "BULLET";
|
|
2678
|
+
DamageFlags2[DamageFlags2["NO_PROTECTION"] = 32] = "NO_PROTECTION";
|
|
2679
|
+
DamageFlags2[DamageFlags2["DESTROY_ARMOR"] = 64] = "DESTROY_ARMOR";
|
|
2680
|
+
DamageFlags2[DamageFlags2["NO_REG_ARMOR"] = 128] = "NO_REG_ARMOR";
|
|
2681
|
+
DamageFlags2[DamageFlags2["NO_POWER_ARMOR"] = 256] = "NO_POWER_ARMOR";
|
|
2682
|
+
DamageFlags2[DamageFlags2["NO_INDICATOR"] = 512] = "NO_INDICATOR";
|
|
2683
|
+
return DamageFlags2;
|
|
2684
|
+
})(DamageFlags || {});
|
|
2685
|
+
function hasAnyDamageFlag(flags, mask) {
|
|
2686
|
+
return (flags & mask) !== 0;
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
// src/combat/armor.ts
|
|
2690
|
+
var ArmorType = /* @__PURE__ */ ((ArmorType3) => {
|
|
2691
|
+
ArmorType3["BODY"] = "body";
|
|
2692
|
+
ArmorType3["COMBAT"] = "combat";
|
|
2693
|
+
ArmorType3["JACKET"] = "jacket";
|
|
2694
|
+
return ArmorType3;
|
|
2695
|
+
})(ArmorType || {});
|
|
2696
|
+
var ARMOR_INFO = {
|
|
2697
|
+
["jacket" /* JACKET */]: {
|
|
2698
|
+
baseCount: 25,
|
|
2699
|
+
maxCount: 50,
|
|
2700
|
+
normalProtection: 0.3,
|
|
2701
|
+
energyProtection: 0
|
|
2702
|
+
},
|
|
2703
|
+
["combat" /* COMBAT */]: {
|
|
2704
|
+
baseCount: 50,
|
|
2705
|
+
maxCount: 100,
|
|
2706
|
+
normalProtection: 0.6,
|
|
2707
|
+
energyProtection: 0.3
|
|
2708
|
+
},
|
|
2709
|
+
["body" /* BODY */]: {
|
|
2710
|
+
baseCount: 100,
|
|
2711
|
+
maxCount: 200,
|
|
2712
|
+
normalProtection: 0.8,
|
|
2713
|
+
energyProtection: 0.6
|
|
2598
2714
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2715
|
+
};
|
|
2716
|
+
function applyRegularArmor(damage, flags, state) {
|
|
2717
|
+
if (damage <= 0 || hasAnyDamageFlag(flags, 2 /* NO_ARMOR */ | 128 /* NO_REG_ARMOR */) || !state.armorType || state.armorCount <= 0) {
|
|
2718
|
+
return { saved: 0, remainingArmor: state.armorCount };
|
|
2601
2719
|
}
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
if (
|
|
2606
|
-
|
|
2720
|
+
const info = ARMOR_INFO[state.armorType];
|
|
2721
|
+
const protection = hasAnyDamageFlag(flags, 4 /* ENERGY */) ? info.energyProtection : info.normalProtection;
|
|
2722
|
+
let saved = Math.ceil(protection * damage);
|
|
2723
|
+
if (saved >= state.armorCount) {
|
|
2724
|
+
saved = state.armorCount;
|
|
2607
2725
|
}
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
frameNumber: ensureNumberOrDefault(level.frameNumber, "level.frameNumber", 0),
|
|
2611
|
-
timeSeconds: ensureNumberOrDefault(level.timeSeconds, "level.timeSeconds", 0),
|
|
2612
|
-
previousTimeSeconds: ensureNumberOrDefault(level.previousTimeSeconds, "level.previousTimeSeconds", 0),
|
|
2613
|
-
deltaSeconds: ensureNumberOrDefault(level.deltaSeconds, "level.deltaSeconds", 0)
|
|
2614
|
-
};
|
|
2615
|
-
}
|
|
2616
|
-
function parseRngState(raw) {
|
|
2617
|
-
if (raw === void 0) {
|
|
2618
|
-
return new RandomGenerator().getState();
|
|
2726
|
+
if (saved <= 0) {
|
|
2727
|
+
return { saved: 0, remainingArmor: state.armorCount };
|
|
2619
2728
|
}
|
|
2620
|
-
|
|
2621
|
-
const mt = ensureObject(rng.mt, "rng.mt");
|
|
2622
|
-
const state = ensureNumberArray(mt.state, "rng.mt.state");
|
|
2623
|
-
return {
|
|
2624
|
-
mt: {
|
|
2625
|
-
index: ensureNumber(mt.index, "rng.mt.index"),
|
|
2626
|
-
state
|
|
2627
|
-
}
|
|
2628
|
-
};
|
|
2729
|
+
return { saved, remainingArmor: state.armorCount - saved };
|
|
2629
2730
|
}
|
|
2630
|
-
function
|
|
2631
|
-
if (
|
|
2632
|
-
return
|
|
2731
|
+
function applyPowerArmor(damage, flags, hitPoint, _hitNormal, state, options = {}) {
|
|
2732
|
+
if (state.health <= 0 || damage <= 0) {
|
|
2733
|
+
return { saved: 0, remainingCells: state.cellCount };
|
|
2633
2734
|
}
|
|
2634
|
-
if (
|
|
2635
|
-
|
|
2735
|
+
if (hasAnyDamageFlag(flags, 2 /* NO_ARMOR */ | 256 /* NO_POWER_ARMOR */)) {
|
|
2736
|
+
return { saved: 0, remainingCells: state.cellCount };
|
|
2636
2737
|
}
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
return {
|
|
2640
|
-
time: ensureNumber(think.time, `thinks[${idx}].time`),
|
|
2641
|
-
entityIndex: ensureNumber(think.entityIndex, `thinks[${idx}].entityIndex`)
|
|
2642
|
-
};
|
|
2643
|
-
});
|
|
2644
|
-
}
|
|
2645
|
-
function parseEntityFields(raw) {
|
|
2646
|
-
if (raw === void 0) {
|
|
2647
|
-
return {};
|
|
2738
|
+
if (!state.type || state.cellCount <= 0) {
|
|
2739
|
+
return { saved: 0, remainingCells: state.cellCount };
|
|
2648
2740
|
}
|
|
2649
|
-
const
|
|
2650
|
-
const
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
const inventory = {};
|
|
2666
|
-
for (const [entryName, entryValue] of Object.entries(object)) {
|
|
2667
|
-
inventory[entryName] = ensureNumber(entryValue, `${name}.${entryName}`);
|
|
2668
|
-
}
|
|
2669
|
-
parsed[name] = inventory;
|
|
2670
|
-
break;
|
|
2671
|
-
}
|
|
2672
|
-
if (Array.isArray(value) && value.length === 3) {
|
|
2673
|
-
const [x, y, z] = value;
|
|
2674
|
-
parsed[name] = [
|
|
2675
|
-
ensureNumber(x, `${name}[0]`),
|
|
2676
|
-
ensureNumber(y, `${name}[1]`),
|
|
2677
|
-
ensureNumber(z, `${name}[2]`)
|
|
2678
|
-
];
|
|
2679
|
-
break;
|
|
2680
|
-
}
|
|
2681
|
-
throw new Error(`Unsupported entity field value for ${name}`);
|
|
2682
|
-
}
|
|
2741
|
+
const { forward } = angleVectors(state.angles);
|
|
2742
|
+
const toImpact = {
|
|
2743
|
+
x: hitPoint.x - state.origin.x,
|
|
2744
|
+
y: hitPoint.y - state.origin.y,
|
|
2745
|
+
z: hitPoint.z - state.origin.z
|
|
2746
|
+
};
|
|
2747
|
+
const toImpactLength = Math.hypot(toImpact.x, toImpact.y, toImpact.z);
|
|
2748
|
+
if (state.type === "screen" && toImpactLength > 0) {
|
|
2749
|
+
const dir = {
|
|
2750
|
+
x: toImpact.x / toImpactLength,
|
|
2751
|
+
y: toImpact.y / toImpactLength,
|
|
2752
|
+
z: toImpact.z / toImpactLength
|
|
2753
|
+
};
|
|
2754
|
+
const dot = dir.x * forward.x + dir.y * forward.y + dir.z * forward.z;
|
|
2755
|
+
if (dot <= 0.3) {
|
|
2756
|
+
return { saved: 0, remainingCells: state.cellCount };
|
|
2683
2757
|
}
|
|
2684
2758
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2759
|
+
const ctfMode = options.ctfMode ?? false;
|
|
2760
|
+
const damagePerCell = state.type === "screen" ? 1 : ctfMode ? 1 : 2;
|
|
2761
|
+
let adjustedDamage = state.type === "screen" ? damage / 3 : 2 * damage / 3;
|
|
2762
|
+
adjustedDamage = Math.max(1, adjustedDamage);
|
|
2763
|
+
let saved = state.cellCount * damagePerCell;
|
|
2764
|
+
if (hasAnyDamageFlag(flags, 4 /* ENERGY */)) {
|
|
2765
|
+
saved = Math.max(1, Math.floor(saved / 2));
|
|
2766
|
+
}
|
|
2767
|
+
if (saved > adjustedDamage) {
|
|
2768
|
+
saved = Math.floor(adjustedDamage);
|
|
2769
|
+
}
|
|
2770
|
+
let powerUsed = saved / damagePerCell;
|
|
2771
|
+
if (hasAnyDamageFlag(flags, 4 /* ENERGY */)) {
|
|
2772
|
+
powerUsed *= 2;
|
|
2773
|
+
}
|
|
2774
|
+
powerUsed = Math.max(1, Math.floor(powerUsed));
|
|
2775
|
+
const cellsSpent = Math.max(damagePerCell, powerUsed);
|
|
2776
|
+
const remainingCells = Math.max(0, state.cellCount - cellsSpent);
|
|
2777
|
+
return { saved, remainingCells };
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// src/inventory/playerInventory.ts
|
|
2781
|
+
var WeaponId = /* @__PURE__ */ ((WeaponId2) => {
|
|
2782
|
+
WeaponId2["Blaster"] = "blaster";
|
|
2783
|
+
WeaponId2["Shotgun"] = "shotgun";
|
|
2784
|
+
WeaponId2["SuperShotgun"] = "super_shotgun";
|
|
2785
|
+
WeaponId2["Machinegun"] = "machinegun";
|
|
2786
|
+
WeaponId2["Chaingun"] = "chaingun";
|
|
2787
|
+
WeaponId2["GrenadeLauncher"] = "grenade_launcher";
|
|
2788
|
+
WeaponId2["RocketLauncher"] = "rocket_launcher";
|
|
2789
|
+
WeaponId2["HyperBlaster"] = "hyperblaster";
|
|
2790
|
+
WeaponId2["Railgun"] = "railgun";
|
|
2791
|
+
WeaponId2["BFG10K"] = "bfg10k";
|
|
2792
|
+
return WeaponId2;
|
|
2793
|
+
})(WeaponId || {});
|
|
2794
|
+
var PowerupId = /* @__PURE__ */ ((PowerupId2) => {
|
|
2795
|
+
PowerupId2["QuadDamage"] = "quad";
|
|
2796
|
+
PowerupId2["Invulnerability"] = "invulnerability";
|
|
2797
|
+
PowerupId2["EnviroSuit"] = "enviro_suit";
|
|
2798
|
+
PowerupId2["Rebreather"] = "rebreather";
|
|
2799
|
+
PowerupId2["Silencer"] = "silencer";
|
|
2800
|
+
return PowerupId2;
|
|
2801
|
+
})(PowerupId || {});
|
|
2802
|
+
var KeyId = /* @__PURE__ */ ((KeyId2) => {
|
|
2803
|
+
KeyId2["Blue"] = "blue";
|
|
2804
|
+
KeyId2["Red"] = "red";
|
|
2805
|
+
KeyId2["Green"] = "green";
|
|
2806
|
+
KeyId2["Yellow"] = "yellow";
|
|
2807
|
+
return KeyId2;
|
|
2808
|
+
})(KeyId || {});
|
|
2809
|
+
function createPlayerInventory(options = {}) {
|
|
2810
|
+
const ammo = createAmmoInventory(options.ammoCaps);
|
|
2811
|
+
const ownedWeapons = new Set(options.weapons ?? []);
|
|
2812
|
+
const powerups = new Map(options.powerups ?? []);
|
|
2813
|
+
const keys = new Set(options.keys ?? []);
|
|
2814
|
+
return {
|
|
2815
|
+
ammo,
|
|
2816
|
+
ownedWeapons,
|
|
2817
|
+
currentWeapon: options.currentWeapon,
|
|
2818
|
+
armor: options.armor ?? null,
|
|
2819
|
+
powerups,
|
|
2820
|
+
keys
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
function giveAmmo(inventory, ammoType, amount) {
|
|
2824
|
+
return addAmmo(inventory.ammo, ammoType, amount);
|
|
2825
|
+
}
|
|
2826
|
+
function giveAmmoItem(inventory, itemId, options) {
|
|
2827
|
+
return pickupAmmo(inventory.ammo, itemId, options);
|
|
2828
|
+
}
|
|
2829
|
+
function giveWeapon(inventory, weapon, select = false) {
|
|
2830
|
+
const hadWeapon = inventory.ownedWeapons.has(weapon);
|
|
2831
|
+
inventory.ownedWeapons.add(weapon);
|
|
2832
|
+
if (select || !inventory.currentWeapon) {
|
|
2833
|
+
inventory.currentWeapon = weapon;
|
|
2834
|
+
}
|
|
2835
|
+
return !hadWeapon;
|
|
2836
|
+
}
|
|
2837
|
+
function hasWeapon(inventory, weapon) {
|
|
2838
|
+
return inventory.ownedWeapons.has(weapon);
|
|
2839
|
+
}
|
|
2840
|
+
function selectWeapon(inventory, weapon) {
|
|
2841
|
+
if (!inventory.ownedWeapons.has(weapon)) {
|
|
2842
|
+
return false;
|
|
2843
|
+
}
|
|
2844
|
+
inventory.currentWeapon = weapon;
|
|
2845
|
+
return true;
|
|
2846
|
+
}
|
|
2847
|
+
function equipArmor(inventory, armorType, amount) {
|
|
2848
|
+
if (!armorType || amount <= 0) {
|
|
2849
|
+
inventory.armor = null;
|
|
2850
|
+
return null;
|
|
2851
|
+
}
|
|
2852
|
+
const info = ARMOR_INFO[armorType];
|
|
2853
|
+
const armorCount = Math.min(amount, info.maxCount);
|
|
2854
|
+
inventory.armor = { armorType, armorCount };
|
|
2855
|
+
return inventory.armor;
|
|
2856
|
+
}
|
|
2857
|
+
function addPowerup(inventory, powerup, expiresAt) {
|
|
2858
|
+
inventory.powerups.set(powerup, expiresAt);
|
|
2859
|
+
}
|
|
2860
|
+
function hasPowerup(inventory, powerup) {
|
|
2861
|
+
return inventory.powerups.has(powerup);
|
|
2862
|
+
}
|
|
2863
|
+
function clearExpiredPowerups(inventory, nowMs) {
|
|
2864
|
+
for (const [id, expiresAt] of inventory.powerups.entries()) {
|
|
2865
|
+
if (expiresAt !== null && expiresAt <= nowMs) {
|
|
2866
|
+
inventory.powerups.delete(id);
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
function addKey(inventory, key) {
|
|
2871
|
+
const before = inventory.keys.size;
|
|
2872
|
+
inventory.keys.add(key);
|
|
2873
|
+
return inventory.keys.size > before;
|
|
2874
|
+
}
|
|
2875
|
+
function hasKey(inventory, key) {
|
|
2876
|
+
return inventory.keys.has(key);
|
|
2877
|
+
}
|
|
2878
|
+
function serializePlayerInventory(inventory) {
|
|
2879
|
+
return {
|
|
2880
|
+
ammo: inventory.ammo.counts,
|
|
2881
|
+
ownedWeapons: [...inventory.ownedWeapons],
|
|
2882
|
+
currentWeapon: inventory.currentWeapon,
|
|
2883
|
+
armor: inventory.armor ? { ...inventory.armor } : null,
|
|
2884
|
+
powerups: [...inventory.powerups.entries()],
|
|
2885
|
+
keys: [...inventory.keys]
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
function deserializePlayerInventory(serialized, options = {}) {
|
|
2889
|
+
const ammo = createAmmoInventory(options.ammoCaps);
|
|
2890
|
+
const limit = Math.min(ammo.counts.length, serialized.ammo.length);
|
|
2891
|
+
for (let i = 0; i < limit; i++) {
|
|
2892
|
+
ammo.counts[i] = serialized.ammo[i];
|
|
2893
|
+
}
|
|
2894
|
+
return {
|
|
2895
|
+
ammo,
|
|
2896
|
+
ownedWeapons: new Set(serialized.ownedWeapons),
|
|
2897
|
+
currentWeapon: serialized.currentWeapon,
|
|
2898
|
+
armor: serialized.armor ? { ...serialized.armor } : null,
|
|
2899
|
+
powerups: new Map(serialized.powerups),
|
|
2900
|
+
keys: new Set(serialized.keys)
|
|
2901
|
+
};
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
// src/save/save.ts
|
|
2905
|
+
var SAVE_FORMAT_VERSION = 2;
|
|
2906
|
+
var MIN_SUPPORTED_VERSION = 1;
|
|
2907
|
+
function ensureObject(value, label) {
|
|
2908
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2909
|
+
throw new Error(`${label} must be an object`);
|
|
2910
|
+
}
|
|
2911
|
+
return value;
|
|
2912
|
+
}
|
|
2913
|
+
function ensureNumber(value, label) {
|
|
2914
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2915
|
+
throw new Error(`${label} must be a finite number`);
|
|
2916
|
+
}
|
|
2917
|
+
return value;
|
|
2918
|
+
}
|
|
2919
|
+
function ensureNumberOrDefault(value, label, defaultValue) {
|
|
2920
|
+
if (value === void 0) {
|
|
2921
|
+
return defaultValue;
|
|
2922
|
+
}
|
|
2923
|
+
return ensureNumber(value, label);
|
|
2924
|
+
}
|
|
2925
|
+
function ensureString(value, label) {
|
|
2926
|
+
if (typeof value !== "string") {
|
|
2927
|
+
throw new Error(`${label} must be a string`);
|
|
2928
|
+
}
|
|
2929
|
+
return value;
|
|
2930
|
+
}
|
|
2931
|
+
function ensureNumberArray(value, label) {
|
|
2932
|
+
if (!Array.isArray(value)) {
|
|
2933
|
+
throw new Error(`${label} must be an array`);
|
|
2934
|
+
}
|
|
2935
|
+
for (const element of value) {
|
|
2936
|
+
ensureNumber(element, `${label} element`);
|
|
2937
|
+
}
|
|
2938
|
+
return value;
|
|
2939
|
+
}
|
|
2940
|
+
function parseLevelState(raw) {
|
|
2941
|
+
if (raw === void 0) {
|
|
2942
|
+
return { frameNumber: 0, timeSeconds: 0, previousTimeSeconds: 0, deltaSeconds: 0 };
|
|
2943
|
+
}
|
|
2944
|
+
const level = ensureObject(raw, "level");
|
|
2945
|
+
return {
|
|
2946
|
+
frameNumber: ensureNumberOrDefault(level.frameNumber, "level.frameNumber", 0),
|
|
2947
|
+
timeSeconds: ensureNumberOrDefault(level.timeSeconds, "level.timeSeconds", 0),
|
|
2948
|
+
previousTimeSeconds: ensureNumberOrDefault(level.previousTimeSeconds, "level.previousTimeSeconds", 0),
|
|
2949
|
+
deltaSeconds: ensureNumberOrDefault(level.deltaSeconds, "level.deltaSeconds", 0)
|
|
2950
|
+
};
|
|
2951
|
+
}
|
|
2952
|
+
function parseRngState(raw) {
|
|
2953
|
+
if (raw === void 0) {
|
|
2954
|
+
return new RandomGenerator().getState();
|
|
2955
|
+
}
|
|
2956
|
+
const rng = ensureObject(raw, "rng");
|
|
2957
|
+
const mt = ensureObject(rng.mt, "rng.mt");
|
|
2958
|
+
const state = ensureNumberArray(mt.state, "rng.mt.state");
|
|
2959
|
+
return {
|
|
2960
|
+
mt: {
|
|
2961
|
+
index: ensureNumber(mt.index, "rng.mt.index"),
|
|
2962
|
+
state
|
|
2963
|
+
}
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
function parseThinkEntries(raw) {
|
|
2967
|
+
if (raw === void 0) {
|
|
2968
|
+
return [];
|
|
2969
|
+
}
|
|
2970
|
+
if (!Array.isArray(raw)) {
|
|
2971
|
+
throw new Error("thinks must be an array");
|
|
2972
|
+
}
|
|
2973
|
+
return raw.map((entry, idx) => {
|
|
2974
|
+
const think = ensureObject(entry, `thinks[${idx}]`);
|
|
2975
|
+
return {
|
|
2976
|
+
time: ensureNumber(think.time, `thinks[${idx}].time`),
|
|
2977
|
+
entityIndex: ensureNumber(think.entityIndex, `thinks[${idx}].entityIndex`)
|
|
2978
|
+
};
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2981
|
+
function parseEntityFields(raw) {
|
|
2982
|
+
if (raw === void 0) {
|
|
2983
|
+
return {};
|
|
2984
|
+
}
|
|
2985
|
+
const fields = ensureObject(raw, "entity.fields");
|
|
2986
|
+
const parsed = {};
|
|
2987
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
2988
|
+
if (value === null) {
|
|
2989
|
+
parsed[name] = null;
|
|
2990
|
+
continue;
|
|
2991
|
+
}
|
|
2992
|
+
switch (typeof value) {
|
|
2993
|
+
case "number":
|
|
2994
|
+
case "string":
|
|
2995
|
+
case "boolean":
|
|
2996
|
+
parsed[name] = value;
|
|
2997
|
+
break;
|
|
2998
|
+
default: {
|
|
2999
|
+
if (!Array.isArray(value)) {
|
|
3000
|
+
const object = ensureObject(value, name);
|
|
3001
|
+
const inventory = {};
|
|
3002
|
+
for (const [entryName, entryValue] of Object.entries(object)) {
|
|
3003
|
+
inventory[entryName] = ensureNumber(entryValue, `${name}.${entryName}`);
|
|
3004
|
+
}
|
|
3005
|
+
parsed[name] = inventory;
|
|
3006
|
+
break;
|
|
3007
|
+
}
|
|
3008
|
+
if (Array.isArray(value) && value.length === 3) {
|
|
3009
|
+
const [x, y, z] = value;
|
|
3010
|
+
parsed[name] = [
|
|
3011
|
+
ensureNumber(x, `${name}[0]`),
|
|
3012
|
+
ensureNumber(y, `${name}[1]`),
|
|
3013
|
+
ensureNumber(z, `${name}[2]`)
|
|
3014
|
+
];
|
|
3015
|
+
break;
|
|
3016
|
+
}
|
|
3017
|
+
throw new Error(`Unsupported entity field value for ${name}`);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
return parsed;
|
|
3022
|
+
}
|
|
3023
|
+
function parseEntities(raw) {
|
|
2688
3024
|
if (!Array.isArray(raw)) {
|
|
2689
3025
|
throw new Error("entities must be an array");
|
|
2690
3026
|
}
|
|
@@ -2783,7 +3119,8 @@ function createSaveFile(options) {
|
|
|
2783
3119
|
configstrings = [],
|
|
2784
3120
|
cvars,
|
|
2785
3121
|
gameState = {},
|
|
2786
|
-
timestamp = Date.now()
|
|
3122
|
+
timestamp = Date.now(),
|
|
3123
|
+
player
|
|
2787
3124
|
} = options;
|
|
2788
3125
|
return {
|
|
2789
3126
|
version: SAVE_FORMAT_VERSION,
|
|
@@ -2796,7 +3133,8 @@ function createSaveFile(options) {
|
|
|
2796
3133
|
rng: cloneRngState(rngState),
|
|
2797
3134
|
entities: entitySystem.createSnapshot(),
|
|
2798
3135
|
cvars: serializeCvars(cvars),
|
|
2799
|
-
configstrings: [...configstrings]
|
|
3136
|
+
configstrings: [...configstrings],
|
|
3137
|
+
player: player ? serializePlayerInventory(player) : void 0
|
|
2800
3138
|
};
|
|
2801
3139
|
}
|
|
2802
3140
|
function parseSaveFile(serialized, options = {}) {
|
|
@@ -2822,14 +3160,19 @@ function parseSaveFile(serialized, options = {}) {
|
|
|
2822
3160
|
rng: parseRngState(save.rng),
|
|
2823
3161
|
entities: parseEntitySnapshot(save.entities),
|
|
2824
3162
|
cvars: parseCvars(save.cvars),
|
|
2825
|
-
configstrings: parseConfigstrings(save.configstrings)
|
|
3163
|
+
configstrings: parseConfigstrings(save.configstrings),
|
|
3164
|
+
player: save.player ? save.player : void 0
|
|
2826
3165
|
};
|
|
2827
3166
|
}
|
|
2828
3167
|
function applySaveFile(save, targets) {
|
|
2829
3168
|
targets.levelClock.restore(save.level);
|
|
2830
|
-
targets.entitySystem.restore(save.entities);
|
|
3169
|
+
targets.entitySystem.restore(save.entities, targets.callbackRegistry);
|
|
2831
3170
|
targets.rng.setState(save.rng);
|
|
2832
3171
|
applyCvars(save.cvars, targets.cvars);
|
|
3172
|
+
if (save.player && targets.player) {
|
|
3173
|
+
const deserialized = deserializePlayerInventory(save.player);
|
|
3174
|
+
Object.assign(targets.player, deserialized);
|
|
3175
|
+
}
|
|
2833
3176
|
}
|
|
2834
3177
|
|
|
2835
3178
|
// src/save/rerelease.ts
|
|
@@ -3221,116 +3564,6 @@ _SaveStorage.DEFAULT_STORE = "saves";
|
|
|
3221
3564
|
_SaveStorage.QUICK_SLOT = "quicksave";
|
|
3222
3565
|
var SaveStorage = _SaveStorage;
|
|
3223
3566
|
|
|
3224
|
-
// src/combat/damageFlags.ts
|
|
3225
|
-
var DamageFlags = /* @__PURE__ */ ((DamageFlags2) => {
|
|
3226
|
-
DamageFlags2[DamageFlags2["NONE"] = 0] = "NONE";
|
|
3227
|
-
DamageFlags2[DamageFlags2["RADIUS"] = 1] = "RADIUS";
|
|
3228
|
-
DamageFlags2[DamageFlags2["NO_ARMOR"] = 2] = "NO_ARMOR";
|
|
3229
|
-
DamageFlags2[DamageFlags2["ENERGY"] = 4] = "ENERGY";
|
|
3230
|
-
DamageFlags2[DamageFlags2["NO_KNOCKBACK"] = 8] = "NO_KNOCKBACK";
|
|
3231
|
-
DamageFlags2[DamageFlags2["BULLET"] = 16] = "BULLET";
|
|
3232
|
-
DamageFlags2[DamageFlags2["NO_PROTECTION"] = 32] = "NO_PROTECTION";
|
|
3233
|
-
DamageFlags2[DamageFlags2["DESTROY_ARMOR"] = 64] = "DESTROY_ARMOR";
|
|
3234
|
-
DamageFlags2[DamageFlags2["NO_REG_ARMOR"] = 128] = "NO_REG_ARMOR";
|
|
3235
|
-
DamageFlags2[DamageFlags2["NO_POWER_ARMOR"] = 256] = "NO_POWER_ARMOR";
|
|
3236
|
-
DamageFlags2[DamageFlags2["NO_INDICATOR"] = 512] = "NO_INDICATOR";
|
|
3237
|
-
return DamageFlags2;
|
|
3238
|
-
})(DamageFlags || {});
|
|
3239
|
-
function hasAnyDamageFlag(flags, mask) {
|
|
3240
|
-
return (flags & mask) !== 0;
|
|
3241
|
-
}
|
|
3242
|
-
|
|
3243
|
-
// src/combat/armor.ts
|
|
3244
|
-
var ArmorType = /* @__PURE__ */ ((ArmorType3) => {
|
|
3245
|
-
ArmorType3["BODY"] = "body";
|
|
3246
|
-
ArmorType3["COMBAT"] = "combat";
|
|
3247
|
-
ArmorType3["JACKET"] = "jacket";
|
|
3248
|
-
return ArmorType3;
|
|
3249
|
-
})(ArmorType || {});
|
|
3250
|
-
var ARMOR_INFO = {
|
|
3251
|
-
["jacket" /* JACKET */]: {
|
|
3252
|
-
baseCount: 25,
|
|
3253
|
-
maxCount: 50,
|
|
3254
|
-
normalProtection: 0.3,
|
|
3255
|
-
energyProtection: 0
|
|
3256
|
-
},
|
|
3257
|
-
["combat" /* COMBAT */]: {
|
|
3258
|
-
baseCount: 50,
|
|
3259
|
-
maxCount: 100,
|
|
3260
|
-
normalProtection: 0.6,
|
|
3261
|
-
energyProtection: 0.3
|
|
3262
|
-
},
|
|
3263
|
-
["body" /* BODY */]: {
|
|
3264
|
-
baseCount: 100,
|
|
3265
|
-
maxCount: 200,
|
|
3266
|
-
normalProtection: 0.8,
|
|
3267
|
-
energyProtection: 0.6
|
|
3268
|
-
}
|
|
3269
|
-
};
|
|
3270
|
-
function applyRegularArmor(damage, flags, state) {
|
|
3271
|
-
if (damage <= 0 || hasAnyDamageFlag(flags, 2 /* NO_ARMOR */ | 128 /* NO_REG_ARMOR */) || !state.armorType || state.armorCount <= 0) {
|
|
3272
|
-
return { saved: 0, remainingArmor: state.armorCount };
|
|
3273
|
-
}
|
|
3274
|
-
const info = ARMOR_INFO[state.armorType];
|
|
3275
|
-
const protection = hasAnyDamageFlag(flags, 4 /* ENERGY */) ? info.energyProtection : info.normalProtection;
|
|
3276
|
-
let saved = Math.ceil(protection * damage);
|
|
3277
|
-
if (saved >= state.armorCount) {
|
|
3278
|
-
saved = state.armorCount;
|
|
3279
|
-
}
|
|
3280
|
-
if (saved <= 0) {
|
|
3281
|
-
return { saved: 0, remainingArmor: state.armorCount };
|
|
3282
|
-
}
|
|
3283
|
-
return { saved, remainingArmor: state.armorCount - saved };
|
|
3284
|
-
}
|
|
3285
|
-
function applyPowerArmor(damage, flags, hitPoint, _hitNormal, state, options = {}) {
|
|
3286
|
-
if (state.health <= 0 || damage <= 0) {
|
|
3287
|
-
return { saved: 0, remainingCells: state.cellCount };
|
|
3288
|
-
}
|
|
3289
|
-
if (hasAnyDamageFlag(flags, 2 /* NO_ARMOR */ | 256 /* NO_POWER_ARMOR */)) {
|
|
3290
|
-
return { saved: 0, remainingCells: state.cellCount };
|
|
3291
|
-
}
|
|
3292
|
-
if (!state.type || state.cellCount <= 0) {
|
|
3293
|
-
return { saved: 0, remainingCells: state.cellCount };
|
|
3294
|
-
}
|
|
3295
|
-
const { forward } = angleVectors(state.angles);
|
|
3296
|
-
const toImpact = {
|
|
3297
|
-
x: hitPoint.x - state.origin.x,
|
|
3298
|
-
y: hitPoint.y - state.origin.y,
|
|
3299
|
-
z: hitPoint.z - state.origin.z
|
|
3300
|
-
};
|
|
3301
|
-
const toImpactLength = Math.hypot(toImpact.x, toImpact.y, toImpact.z);
|
|
3302
|
-
if (state.type === "screen" && toImpactLength > 0) {
|
|
3303
|
-
const dir = {
|
|
3304
|
-
x: toImpact.x / toImpactLength,
|
|
3305
|
-
y: toImpact.y / toImpactLength,
|
|
3306
|
-
z: toImpact.z / toImpactLength
|
|
3307
|
-
};
|
|
3308
|
-
const dot = dir.x * forward.x + dir.y * forward.y + dir.z * forward.z;
|
|
3309
|
-
if (dot <= 0.3) {
|
|
3310
|
-
return { saved: 0, remainingCells: state.cellCount };
|
|
3311
|
-
}
|
|
3312
|
-
}
|
|
3313
|
-
const ctfMode = options.ctfMode ?? false;
|
|
3314
|
-
const damagePerCell = state.type === "screen" ? 1 : ctfMode ? 1 : 2;
|
|
3315
|
-
let adjustedDamage = state.type === "screen" ? damage / 3 : 2 * damage / 3;
|
|
3316
|
-
adjustedDamage = Math.max(1, adjustedDamage);
|
|
3317
|
-
let saved = state.cellCount * damagePerCell;
|
|
3318
|
-
if (hasAnyDamageFlag(flags, 4 /* ENERGY */)) {
|
|
3319
|
-
saved = Math.max(1, Math.floor(saved / 2));
|
|
3320
|
-
}
|
|
3321
|
-
if (saved > adjustedDamage) {
|
|
3322
|
-
saved = Math.floor(adjustedDamage);
|
|
3323
|
-
}
|
|
3324
|
-
let powerUsed = saved / damagePerCell;
|
|
3325
|
-
if (hasAnyDamageFlag(flags, 4 /* ENERGY */)) {
|
|
3326
|
-
powerUsed *= 2;
|
|
3327
|
-
}
|
|
3328
|
-
powerUsed = Math.max(1, Math.floor(powerUsed));
|
|
3329
|
-
const cellsSpent = Math.max(damagePerCell, powerUsed);
|
|
3330
|
-
const remainingCells = Math.max(0, state.cellCount - cellsSpent);
|
|
3331
|
-
return { saved, remainingCells };
|
|
3332
|
-
}
|
|
3333
|
-
|
|
3334
3567
|
// src/combat/damage.ts
|
|
3335
3568
|
var EntityDamageFlags = /* @__PURE__ */ ((EntityDamageFlags2) => {
|
|
3336
3569
|
EntityDamageFlags2[EntityDamageFlags2["GODMODE"] = 1] = "GODMODE";
|
|
@@ -3733,175 +3966,6 @@ function killBox(teleporter, targets, options = {}) {
|
|
|
3733
3966
|
return { events, cleared };
|
|
3734
3967
|
}
|
|
3735
3968
|
|
|
3736
|
-
// src/inventory/ammo.ts
|
|
3737
|
-
var AmmoType = /* @__PURE__ */ ((AmmoType3) => {
|
|
3738
|
-
AmmoType3[AmmoType3["Bullets"] = 0] = "Bullets";
|
|
3739
|
-
AmmoType3[AmmoType3["Shells"] = 1] = "Shells";
|
|
3740
|
-
AmmoType3[AmmoType3["Rockets"] = 2] = "Rockets";
|
|
3741
|
-
AmmoType3[AmmoType3["Grenades"] = 3] = "Grenades";
|
|
3742
|
-
AmmoType3[AmmoType3["Cells"] = 4] = "Cells";
|
|
3743
|
-
AmmoType3[AmmoType3["Slugs"] = 5] = "Slugs";
|
|
3744
|
-
return AmmoType3;
|
|
3745
|
-
})(AmmoType || {});
|
|
3746
|
-
var AMMO_TYPE_COUNT = Object.keys(AmmoType).length / 2;
|
|
3747
|
-
var AmmoItemId = /* @__PURE__ */ ((AmmoItemId3) => {
|
|
3748
|
-
AmmoItemId3["Shells"] = "ammo_shells";
|
|
3749
|
-
AmmoItemId3["Bullets"] = "ammo_bullets";
|
|
3750
|
-
AmmoItemId3["Rockets"] = "ammo_rockets";
|
|
3751
|
-
AmmoItemId3["Grenades"] = "ammo_grenades";
|
|
3752
|
-
AmmoItemId3["Cells"] = "ammo_cells";
|
|
3753
|
-
AmmoItemId3["Slugs"] = "ammo_slugs";
|
|
3754
|
-
return AmmoItemId3;
|
|
3755
|
-
})(AmmoItemId || {});
|
|
3756
|
-
var AMMO_ITEM_DEFINITIONS = {
|
|
3757
|
-
["ammo_shells" /* Shells */]: { id: "ammo_shells" /* Shells */, ammoType: 1 /* Shells */, quantity: 10, weaponAmmo: false },
|
|
3758
|
-
["ammo_bullets" /* Bullets */]: { id: "ammo_bullets" /* Bullets */, ammoType: 0 /* Bullets */, quantity: 50, weaponAmmo: false },
|
|
3759
|
-
["ammo_rockets" /* Rockets */]: { id: "ammo_rockets" /* Rockets */, ammoType: 2 /* Rockets */, quantity: 5, weaponAmmo: false },
|
|
3760
|
-
["ammo_grenades" /* Grenades */]: { id: "ammo_grenades" /* Grenades */, ammoType: 3 /* Grenades */, quantity: 5, weaponAmmo: true },
|
|
3761
|
-
["ammo_cells" /* Cells */]: { id: "ammo_cells" /* Cells */, ammoType: 4 /* Cells */, quantity: 50, weaponAmmo: false },
|
|
3762
|
-
["ammo_slugs" /* Slugs */]: { id: "ammo_slugs" /* Slugs */, ammoType: 5 /* Slugs */, quantity: 10, weaponAmmo: false }
|
|
3763
|
-
};
|
|
3764
|
-
function getAmmoItemDefinition(id) {
|
|
3765
|
-
return AMMO_ITEM_DEFINITIONS[id];
|
|
3766
|
-
}
|
|
3767
|
-
function createAmmoInventory(caps = createBaseAmmoCaps()) {
|
|
3768
|
-
return { caps: caps.slice(), counts: Array(AMMO_TYPE_COUNT).fill(0) };
|
|
3769
|
-
}
|
|
3770
|
-
function createBaseAmmoCaps() {
|
|
3771
|
-
const caps = Array(AMMO_TYPE_COUNT).fill(50);
|
|
3772
|
-
caps[0 /* Bullets */] = 200;
|
|
3773
|
-
caps[1 /* Shells */] = 100;
|
|
3774
|
-
caps[4 /* Cells */] = 200;
|
|
3775
|
-
return caps;
|
|
3776
|
-
}
|
|
3777
|
-
function clampAmmoCounts(counts, caps) {
|
|
3778
|
-
const limit = Math.min(counts.length, caps.length);
|
|
3779
|
-
const clamped = counts.slice(0, limit);
|
|
3780
|
-
for (let i = 0; i < limit; i++) {
|
|
3781
|
-
const cap = caps[i];
|
|
3782
|
-
if (cap !== void 0) {
|
|
3783
|
-
clamped[i] = Math.min(counts[i], cap);
|
|
3784
|
-
}
|
|
3785
|
-
}
|
|
3786
|
-
return clamped;
|
|
3787
|
-
}
|
|
3788
|
-
function addAmmo(inventory, ammoType, amount) {
|
|
3789
|
-
const cap = inventory.caps[ammoType];
|
|
3790
|
-
const current = inventory.counts[ammoType] ?? 0;
|
|
3791
|
-
if (cap !== void 0 && current >= cap) {
|
|
3792
|
-
return { ammoType, added: 0, newCount: current, capped: cap, pickedUp: false };
|
|
3793
|
-
}
|
|
3794
|
-
const uncapped = current + amount;
|
|
3795
|
-
const newCount = cap === void 0 ? uncapped : Math.min(uncapped, cap);
|
|
3796
|
-
const added = newCount - current;
|
|
3797
|
-
inventory.counts[ammoType] = newCount;
|
|
3798
|
-
return { ammoType, added, newCount, capped: cap ?? Number.POSITIVE_INFINITY, pickedUp: added > 0 };
|
|
3799
|
-
}
|
|
3800
|
-
function pickupAmmo(inventory, itemId, options = {}) {
|
|
3801
|
-
const def = getAmmoItemDefinition(itemId);
|
|
3802
|
-
const amount = options.countOverride ?? def.quantity;
|
|
3803
|
-
return addAmmo(inventory, def.ammoType, amount);
|
|
3804
|
-
}
|
|
3805
|
-
|
|
3806
|
-
// src/inventory/playerInventory.ts
|
|
3807
|
-
var WeaponId = /* @__PURE__ */ ((WeaponId2) => {
|
|
3808
|
-
WeaponId2["Blaster"] = "blaster";
|
|
3809
|
-
WeaponId2["Shotgun"] = "shotgun";
|
|
3810
|
-
WeaponId2["SuperShotgun"] = "super_shotgun";
|
|
3811
|
-
WeaponId2["Machinegun"] = "machinegun";
|
|
3812
|
-
WeaponId2["Chaingun"] = "chaingun";
|
|
3813
|
-
WeaponId2["GrenadeLauncher"] = "grenade_launcher";
|
|
3814
|
-
WeaponId2["RocketLauncher"] = "rocket_launcher";
|
|
3815
|
-
WeaponId2["HyperBlaster"] = "hyperblaster";
|
|
3816
|
-
WeaponId2["Railgun"] = "railgun";
|
|
3817
|
-
WeaponId2["BFG10K"] = "bfg10k";
|
|
3818
|
-
return WeaponId2;
|
|
3819
|
-
})(WeaponId || {});
|
|
3820
|
-
var PowerupId = /* @__PURE__ */ ((PowerupId2) => {
|
|
3821
|
-
PowerupId2["QuadDamage"] = "quad";
|
|
3822
|
-
PowerupId2["Invulnerability"] = "invulnerability";
|
|
3823
|
-
PowerupId2["EnviroSuit"] = "enviro_suit";
|
|
3824
|
-
PowerupId2["Rebreather"] = "rebreather";
|
|
3825
|
-
PowerupId2["Silencer"] = "silencer";
|
|
3826
|
-
return PowerupId2;
|
|
3827
|
-
})(PowerupId || {});
|
|
3828
|
-
var KeyId = /* @__PURE__ */ ((KeyId2) => {
|
|
3829
|
-
KeyId2["Blue"] = "blue";
|
|
3830
|
-
KeyId2["Red"] = "red";
|
|
3831
|
-
KeyId2["Green"] = "green";
|
|
3832
|
-
KeyId2["Yellow"] = "yellow";
|
|
3833
|
-
return KeyId2;
|
|
3834
|
-
})(KeyId || {});
|
|
3835
|
-
function createPlayerInventory(options = {}) {
|
|
3836
|
-
const ammo = createAmmoInventory(options.ammoCaps);
|
|
3837
|
-
const ownedWeapons = new Set(options.weapons ?? []);
|
|
3838
|
-
const powerups = new Map(options.powerups ?? []);
|
|
3839
|
-
const keys = new Set(options.keys ?? []);
|
|
3840
|
-
return {
|
|
3841
|
-
ammo,
|
|
3842
|
-
ownedWeapons,
|
|
3843
|
-
currentWeapon: options.currentWeapon,
|
|
3844
|
-
armor: options.armor ?? null,
|
|
3845
|
-
powerups,
|
|
3846
|
-
keys
|
|
3847
|
-
};
|
|
3848
|
-
}
|
|
3849
|
-
function giveAmmo(inventory, ammoType, amount) {
|
|
3850
|
-
return addAmmo(inventory.ammo, ammoType, amount);
|
|
3851
|
-
}
|
|
3852
|
-
function giveAmmoItem(inventory, itemId, options) {
|
|
3853
|
-
return pickupAmmo(inventory.ammo, itemId, options);
|
|
3854
|
-
}
|
|
3855
|
-
function giveWeapon(inventory, weapon, select = false) {
|
|
3856
|
-
const hadWeapon = inventory.ownedWeapons.has(weapon);
|
|
3857
|
-
inventory.ownedWeapons.add(weapon);
|
|
3858
|
-
if (select || !inventory.currentWeapon) {
|
|
3859
|
-
inventory.currentWeapon = weapon;
|
|
3860
|
-
}
|
|
3861
|
-
return !hadWeapon;
|
|
3862
|
-
}
|
|
3863
|
-
function hasWeapon(inventory, weapon) {
|
|
3864
|
-
return inventory.ownedWeapons.has(weapon);
|
|
3865
|
-
}
|
|
3866
|
-
function selectWeapon(inventory, weapon) {
|
|
3867
|
-
if (!inventory.ownedWeapons.has(weapon)) {
|
|
3868
|
-
return false;
|
|
3869
|
-
}
|
|
3870
|
-
inventory.currentWeapon = weapon;
|
|
3871
|
-
return true;
|
|
3872
|
-
}
|
|
3873
|
-
function equipArmor(inventory, armorType, amount) {
|
|
3874
|
-
if (!armorType || amount <= 0) {
|
|
3875
|
-
inventory.armor = null;
|
|
3876
|
-
return null;
|
|
3877
|
-
}
|
|
3878
|
-
const info = ARMOR_INFO[armorType];
|
|
3879
|
-
const armorCount = Math.min(amount, info.maxCount);
|
|
3880
|
-
inventory.armor = { armorType, armorCount };
|
|
3881
|
-
return inventory.armor;
|
|
3882
|
-
}
|
|
3883
|
-
function addPowerup(inventory, powerup, expiresAt) {
|
|
3884
|
-
inventory.powerups.set(powerup, expiresAt);
|
|
3885
|
-
}
|
|
3886
|
-
function hasPowerup(inventory, powerup) {
|
|
3887
|
-
return inventory.powerups.has(powerup);
|
|
3888
|
-
}
|
|
3889
|
-
function clearExpiredPowerups(inventory, nowMs) {
|
|
3890
|
-
for (const [id, expiresAt] of inventory.powerups.entries()) {
|
|
3891
|
-
if (expiresAt !== null && expiresAt <= nowMs) {
|
|
3892
|
-
inventory.powerups.delete(id);
|
|
3893
|
-
}
|
|
3894
|
-
}
|
|
3895
|
-
}
|
|
3896
|
-
function addKey(inventory, key) {
|
|
3897
|
-
const before = inventory.keys.size;
|
|
3898
|
-
inventory.keys.add(key);
|
|
3899
|
-
return inventory.keys.size > before;
|
|
3900
|
-
}
|
|
3901
|
-
function hasKey(inventory, key) {
|
|
3902
|
-
return inventory.keys.has(key);
|
|
3903
|
-
}
|
|
3904
|
-
|
|
3905
3969
|
// src/index.ts
|
|
3906
3970
|
var ZERO_VEC32 = { x: 0, y: 0, z: 0 };
|
|
3907
3971
|
function createGame(engine, options) {
|
|
@@ -4030,11 +4094,13 @@ function createGame(engine, options) {
|
|
|
4030
4094
|
convertRereleaseSaveToGameSave,
|
|
4031
4095
|
createAmmoInventory,
|
|
4032
4096
|
createBaseAmmoCaps,
|
|
4097
|
+
createCallbackRegistry,
|
|
4033
4098
|
createDefaultSpawnRegistry,
|
|
4034
4099
|
createGame,
|
|
4035
4100
|
createPlayerInventory,
|
|
4036
4101
|
createSaveFile,
|
|
4037
4102
|
damageModName,
|
|
4103
|
+
deserializePlayerInventory,
|
|
4038
4104
|
equipArmor,
|
|
4039
4105
|
facingIdeal,
|
|
4040
4106
|
findTarget,
|
|
@@ -4057,8 +4123,10 @@ function createGame(engine, options) {
|
|
|
4057
4123
|
parseSaveFile,
|
|
4058
4124
|
pickupAmmo,
|
|
4059
4125
|
rangeTo,
|
|
4126
|
+
registerCallback,
|
|
4060
4127
|
registerDefaultSpawns,
|
|
4061
4128
|
selectWeapon,
|
|
4129
|
+
serializePlayerInventory,
|
|
4062
4130
|
serializeRereleaseSave,
|
|
4063
4131
|
setMovedir,
|
|
4064
4132
|
spawnEntitiesFromText,
|