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
|
@@ -877,7 +877,7 @@ function boundsIntersect(a, b) {
|
|
|
877
877
|
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);
|
|
878
878
|
}
|
|
879
879
|
var SERIALIZABLE_FIELDS = ENTITY_FIELD_METADATA.filter(
|
|
880
|
-
(field) => field.save
|
|
880
|
+
(field) => field.save || field.type === "callback"
|
|
881
881
|
);
|
|
882
882
|
var DESCRIPTORS = new Map(SERIALIZABLE_FIELDS.map((descriptor) => [descriptor.name, descriptor]));
|
|
883
883
|
function serializeVec3(vec) {
|
|
@@ -908,12 +908,18 @@ function deserializeInventory(value) {
|
|
|
908
908
|
return parsed;
|
|
909
909
|
}
|
|
910
910
|
var EntitySystem = class {
|
|
911
|
-
constructor(maxEntities) {
|
|
911
|
+
constructor(maxEntities, callbackRegistry) {
|
|
912
912
|
this.targetNameIndex = /* @__PURE__ */ new Map();
|
|
913
913
|
this.random = createRandomGenerator();
|
|
914
914
|
this.currentTimeSeconds = 0;
|
|
915
915
|
this.pool = new EntityPool(maxEntities);
|
|
916
916
|
this.thinkScheduler = new ThinkScheduler();
|
|
917
|
+
this.callbackToName = /* @__PURE__ */ new Map();
|
|
918
|
+
if (callbackRegistry) {
|
|
919
|
+
for (const [name, fn] of callbackRegistry.entries()) {
|
|
920
|
+
this.callbackToName.set(fn, name);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
917
923
|
}
|
|
918
924
|
get world() {
|
|
919
925
|
return this.pool.world;
|
|
@@ -1038,6 +1044,9 @@ var EntitySystem = class {
|
|
|
1038
1044
|
case "inventory":
|
|
1039
1045
|
fields[descriptor.name] = serializeInventory(value);
|
|
1040
1046
|
break;
|
|
1047
|
+
case "callback":
|
|
1048
|
+
fields[descriptor.name] = value ? this.callbackToName.get(value) ?? null : null;
|
|
1049
|
+
break;
|
|
1041
1050
|
default:
|
|
1042
1051
|
fields[descriptor.name] = value ?? null;
|
|
1043
1052
|
break;
|
|
@@ -1055,7 +1064,7 @@ var EntitySystem = class {
|
|
|
1055
1064
|
thinks: this.thinkScheduler.snapshot()
|
|
1056
1065
|
};
|
|
1057
1066
|
}
|
|
1058
|
-
restore(snapshot) {
|
|
1067
|
+
restore(snapshot, callbackRegistry) {
|
|
1059
1068
|
this.currentTimeSeconds = snapshot.timeSeconds;
|
|
1060
1069
|
this.pool.restore(snapshot.pool);
|
|
1061
1070
|
const indexToEntity = /* @__PURE__ */ new Map();
|
|
@@ -1090,6 +1099,14 @@ var EntitySystem = class {
|
|
|
1090
1099
|
case "boolean":
|
|
1091
1100
|
assignField(entity, name, Boolean(value));
|
|
1092
1101
|
break;
|
|
1102
|
+
case "callback":
|
|
1103
|
+
if (value) {
|
|
1104
|
+
const callback = callbackRegistry?.get(value);
|
|
1105
|
+
if (callback) {
|
|
1106
|
+
assignField(entity, name, callback);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
break;
|
|
1093
1110
|
default:
|
|
1094
1111
|
assignField(entity, name, value);
|
|
1095
1112
|
break;
|
|
@@ -1931,6 +1948,17 @@ function createDefaultSpawnRegistry() {
|
|
|
1931
1948
|
return registry;
|
|
1932
1949
|
}
|
|
1933
1950
|
|
|
1951
|
+
// src/entities/callbacks.ts
|
|
1952
|
+
function createCallbackRegistry() {
|
|
1953
|
+
return /* @__PURE__ */ new Map();
|
|
1954
|
+
}
|
|
1955
|
+
function registerCallback(registry, name, fn) {
|
|
1956
|
+
if (registry.has(name)) {
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
registry.set(name, fn);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1934
1962
|
// src/loop.ts
|
|
1935
1963
|
var orderedStageNames = [
|
|
1936
1964
|
"prep",
|
|
@@ -2443,126 +2471,430 @@ function hashGameState(state) {
|
|
|
2443
2471
|
return hash >>> 0;
|
|
2444
2472
|
}
|
|
2445
2473
|
|
|
2446
|
-
// src/
|
|
2447
|
-
var
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2474
|
+
// src/inventory/ammo.ts
|
|
2475
|
+
var AmmoType = /* @__PURE__ */ ((AmmoType3) => {
|
|
2476
|
+
AmmoType3[AmmoType3["Bullets"] = 0] = "Bullets";
|
|
2477
|
+
AmmoType3[AmmoType3["Shells"] = 1] = "Shells";
|
|
2478
|
+
AmmoType3[AmmoType3["Rockets"] = 2] = "Rockets";
|
|
2479
|
+
AmmoType3[AmmoType3["Grenades"] = 3] = "Grenades";
|
|
2480
|
+
AmmoType3[AmmoType3["Cells"] = 4] = "Cells";
|
|
2481
|
+
AmmoType3[AmmoType3["Slugs"] = 5] = "Slugs";
|
|
2482
|
+
return AmmoType3;
|
|
2483
|
+
})(AmmoType || {});
|
|
2484
|
+
var AMMO_TYPE_COUNT = Object.keys(AmmoType).length / 2;
|
|
2485
|
+
var AmmoItemId = /* @__PURE__ */ ((AmmoItemId3) => {
|
|
2486
|
+
AmmoItemId3["Shells"] = "ammo_shells";
|
|
2487
|
+
AmmoItemId3["Bullets"] = "ammo_bullets";
|
|
2488
|
+
AmmoItemId3["Rockets"] = "ammo_rockets";
|
|
2489
|
+
AmmoItemId3["Grenades"] = "ammo_grenades";
|
|
2490
|
+
AmmoItemId3["Cells"] = "ammo_cells";
|
|
2491
|
+
AmmoItemId3["Slugs"] = "ammo_slugs";
|
|
2492
|
+
return AmmoItemId3;
|
|
2493
|
+
})(AmmoItemId || {});
|
|
2494
|
+
var AMMO_ITEM_DEFINITIONS = {
|
|
2495
|
+
["ammo_shells" /* Shells */]: { id: "ammo_shells" /* Shells */, ammoType: 1 /* Shells */, quantity: 10, weaponAmmo: false },
|
|
2496
|
+
["ammo_bullets" /* Bullets */]: { id: "ammo_bullets" /* Bullets */, ammoType: 0 /* Bullets */, quantity: 50, weaponAmmo: false },
|
|
2497
|
+
["ammo_rockets" /* Rockets */]: { id: "ammo_rockets" /* Rockets */, ammoType: 2 /* Rockets */, quantity: 5, weaponAmmo: false },
|
|
2498
|
+
["ammo_grenades" /* Grenades */]: { id: "ammo_grenades" /* Grenades */, ammoType: 3 /* Grenades */, quantity: 5, weaponAmmo: true },
|
|
2499
|
+
["ammo_cells" /* Cells */]: { id: "ammo_cells" /* Cells */, ammoType: 4 /* Cells */, quantity: 50, weaponAmmo: false },
|
|
2500
|
+
["ammo_slugs" /* Slugs */]: { id: "ammo_slugs" /* Slugs */, ammoType: 5 /* Slugs */, quantity: 10, weaponAmmo: false }
|
|
2501
|
+
};
|
|
2502
|
+
function getAmmoItemDefinition(id) {
|
|
2503
|
+
return AMMO_ITEM_DEFINITIONS[id];
|
|
2454
2504
|
}
|
|
2455
|
-
function
|
|
2456
|
-
|
|
2457
|
-
throw new Error(`${label} must be a finite number`);
|
|
2458
|
-
}
|
|
2459
|
-
return value;
|
|
2505
|
+
function createAmmoInventory(caps = createBaseAmmoCaps()) {
|
|
2506
|
+
return { caps: caps.slice(), counts: Array(AMMO_TYPE_COUNT).fill(0) };
|
|
2460
2507
|
}
|
|
2461
|
-
function
|
|
2462
|
-
|
|
2463
|
-
|
|
2508
|
+
function createBaseAmmoCaps() {
|
|
2509
|
+
const caps = Array(AMMO_TYPE_COUNT).fill(50);
|
|
2510
|
+
caps[0 /* Bullets */] = 200;
|
|
2511
|
+
caps[1 /* Shells */] = 100;
|
|
2512
|
+
caps[4 /* Cells */] = 200;
|
|
2513
|
+
return caps;
|
|
2514
|
+
}
|
|
2515
|
+
function clampAmmoCounts(counts, caps) {
|
|
2516
|
+
const limit = Math.min(counts.length, caps.length);
|
|
2517
|
+
const clamped = counts.slice(0, limit);
|
|
2518
|
+
for (let i = 0; i < limit; i++) {
|
|
2519
|
+
const cap = caps[i];
|
|
2520
|
+
if (cap !== void 0) {
|
|
2521
|
+
clamped[i] = Math.min(counts[i], cap);
|
|
2522
|
+
}
|
|
2464
2523
|
}
|
|
2465
|
-
return
|
|
2524
|
+
return clamped;
|
|
2466
2525
|
}
|
|
2467
|
-
function
|
|
2468
|
-
|
|
2469
|
-
|
|
2526
|
+
function addAmmo(inventory, ammoType, amount) {
|
|
2527
|
+
const cap = inventory.caps[ammoType];
|
|
2528
|
+
const current = inventory.counts[ammoType] ?? 0;
|
|
2529
|
+
if (cap !== void 0 && current >= cap) {
|
|
2530
|
+
return { ammoType, added: 0, newCount: current, capped: cap, pickedUp: false };
|
|
2470
2531
|
}
|
|
2471
|
-
|
|
2532
|
+
const uncapped = current + amount;
|
|
2533
|
+
const newCount = cap === void 0 ? uncapped : Math.min(uncapped, cap);
|
|
2534
|
+
const added = newCount - current;
|
|
2535
|
+
inventory.counts[ammoType] = newCount;
|
|
2536
|
+
return { ammoType, added, newCount, capped: cap ?? Number.POSITIVE_INFINITY, pickedUp: added > 0 };
|
|
2472
2537
|
}
|
|
2473
|
-
function
|
|
2474
|
-
|
|
2475
|
-
|
|
2538
|
+
function pickupAmmo(inventory, itemId, options = {}) {
|
|
2539
|
+
const def = getAmmoItemDefinition(itemId);
|
|
2540
|
+
const amount = options.countOverride ?? def.quantity;
|
|
2541
|
+
return addAmmo(inventory, def.ammoType, amount);
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
// src/combat/damageFlags.ts
|
|
2545
|
+
var DamageFlags = /* @__PURE__ */ ((DamageFlags2) => {
|
|
2546
|
+
DamageFlags2[DamageFlags2["NONE"] = 0] = "NONE";
|
|
2547
|
+
DamageFlags2[DamageFlags2["RADIUS"] = 1] = "RADIUS";
|
|
2548
|
+
DamageFlags2[DamageFlags2["NO_ARMOR"] = 2] = "NO_ARMOR";
|
|
2549
|
+
DamageFlags2[DamageFlags2["ENERGY"] = 4] = "ENERGY";
|
|
2550
|
+
DamageFlags2[DamageFlags2["NO_KNOCKBACK"] = 8] = "NO_KNOCKBACK";
|
|
2551
|
+
DamageFlags2[DamageFlags2["BULLET"] = 16] = "BULLET";
|
|
2552
|
+
DamageFlags2[DamageFlags2["NO_PROTECTION"] = 32] = "NO_PROTECTION";
|
|
2553
|
+
DamageFlags2[DamageFlags2["DESTROY_ARMOR"] = 64] = "DESTROY_ARMOR";
|
|
2554
|
+
DamageFlags2[DamageFlags2["NO_REG_ARMOR"] = 128] = "NO_REG_ARMOR";
|
|
2555
|
+
DamageFlags2[DamageFlags2["NO_POWER_ARMOR"] = 256] = "NO_POWER_ARMOR";
|
|
2556
|
+
DamageFlags2[DamageFlags2["NO_INDICATOR"] = 512] = "NO_INDICATOR";
|
|
2557
|
+
return DamageFlags2;
|
|
2558
|
+
})(DamageFlags || {});
|
|
2559
|
+
function hasAnyDamageFlag(flags, mask) {
|
|
2560
|
+
return (flags & mask) !== 0;
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
// src/combat/armor.ts
|
|
2564
|
+
var ArmorType = /* @__PURE__ */ ((ArmorType3) => {
|
|
2565
|
+
ArmorType3["BODY"] = "body";
|
|
2566
|
+
ArmorType3["COMBAT"] = "combat";
|
|
2567
|
+
ArmorType3["JACKET"] = "jacket";
|
|
2568
|
+
return ArmorType3;
|
|
2569
|
+
})(ArmorType || {});
|
|
2570
|
+
var ARMOR_INFO = {
|
|
2571
|
+
["jacket" /* JACKET */]: {
|
|
2572
|
+
baseCount: 25,
|
|
2573
|
+
maxCount: 50,
|
|
2574
|
+
normalProtection: 0.3,
|
|
2575
|
+
energyProtection: 0
|
|
2576
|
+
},
|
|
2577
|
+
["combat" /* COMBAT */]: {
|
|
2578
|
+
baseCount: 50,
|
|
2579
|
+
maxCount: 100,
|
|
2580
|
+
normalProtection: 0.6,
|
|
2581
|
+
energyProtection: 0.3
|
|
2582
|
+
},
|
|
2583
|
+
["body" /* BODY */]: {
|
|
2584
|
+
baseCount: 100,
|
|
2585
|
+
maxCount: 200,
|
|
2586
|
+
normalProtection: 0.8,
|
|
2587
|
+
energyProtection: 0.6
|
|
2476
2588
|
}
|
|
2477
|
-
|
|
2478
|
-
|
|
2589
|
+
};
|
|
2590
|
+
function applyRegularArmor(damage, flags, state) {
|
|
2591
|
+
if (damage <= 0 || hasAnyDamageFlag(flags, 2 /* NO_ARMOR */ | 128 /* NO_REG_ARMOR */) || !state.armorType || state.armorCount <= 0) {
|
|
2592
|
+
return { saved: 0, remainingArmor: state.armorCount };
|
|
2479
2593
|
}
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
if (
|
|
2484
|
-
|
|
2594
|
+
const info = ARMOR_INFO[state.armorType];
|
|
2595
|
+
const protection = hasAnyDamageFlag(flags, 4 /* ENERGY */) ? info.energyProtection : info.normalProtection;
|
|
2596
|
+
let saved = Math.ceil(protection * damage);
|
|
2597
|
+
if (saved >= state.armorCount) {
|
|
2598
|
+
saved = state.armorCount;
|
|
2485
2599
|
}
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
frameNumber: ensureNumberOrDefault(level.frameNumber, "level.frameNumber", 0),
|
|
2489
|
-
timeSeconds: ensureNumberOrDefault(level.timeSeconds, "level.timeSeconds", 0),
|
|
2490
|
-
previousTimeSeconds: ensureNumberOrDefault(level.previousTimeSeconds, "level.previousTimeSeconds", 0),
|
|
2491
|
-
deltaSeconds: ensureNumberOrDefault(level.deltaSeconds, "level.deltaSeconds", 0)
|
|
2492
|
-
};
|
|
2493
|
-
}
|
|
2494
|
-
function parseRngState(raw) {
|
|
2495
|
-
if (raw === void 0) {
|
|
2496
|
-
return new RandomGenerator().getState();
|
|
2600
|
+
if (saved <= 0) {
|
|
2601
|
+
return { saved: 0, remainingArmor: state.armorCount };
|
|
2497
2602
|
}
|
|
2498
|
-
|
|
2499
|
-
const mt = ensureObject(rng.mt, "rng.mt");
|
|
2500
|
-
const state = ensureNumberArray(mt.state, "rng.mt.state");
|
|
2501
|
-
return {
|
|
2502
|
-
mt: {
|
|
2503
|
-
index: ensureNumber(mt.index, "rng.mt.index"),
|
|
2504
|
-
state
|
|
2505
|
-
}
|
|
2506
|
-
};
|
|
2603
|
+
return { saved, remainingArmor: state.armorCount - saved };
|
|
2507
2604
|
}
|
|
2508
|
-
function
|
|
2509
|
-
if (
|
|
2510
|
-
return
|
|
2605
|
+
function applyPowerArmor(damage, flags, hitPoint, _hitNormal, state, options = {}) {
|
|
2606
|
+
if (state.health <= 0 || damage <= 0) {
|
|
2607
|
+
return { saved: 0, remainingCells: state.cellCount };
|
|
2511
2608
|
}
|
|
2512
|
-
if (
|
|
2513
|
-
|
|
2609
|
+
if (hasAnyDamageFlag(flags, 2 /* NO_ARMOR */ | 256 /* NO_POWER_ARMOR */)) {
|
|
2610
|
+
return { saved: 0, remainingCells: state.cellCount };
|
|
2514
2611
|
}
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
return {
|
|
2518
|
-
time: ensureNumber(think.time, `thinks[${idx}].time`),
|
|
2519
|
-
entityIndex: ensureNumber(think.entityIndex, `thinks[${idx}].entityIndex`)
|
|
2520
|
-
};
|
|
2521
|
-
});
|
|
2522
|
-
}
|
|
2523
|
-
function parseEntityFields(raw) {
|
|
2524
|
-
if (raw === void 0) {
|
|
2525
|
-
return {};
|
|
2612
|
+
if (!state.type || state.cellCount <= 0) {
|
|
2613
|
+
return { saved: 0, remainingCells: state.cellCount };
|
|
2526
2614
|
}
|
|
2527
|
-
const
|
|
2528
|
-
const
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
const inventory = {};
|
|
2544
|
-
for (const [entryName, entryValue] of Object.entries(object)) {
|
|
2545
|
-
inventory[entryName] = ensureNumber(entryValue, `${name}.${entryName}`);
|
|
2546
|
-
}
|
|
2547
|
-
parsed[name] = inventory;
|
|
2548
|
-
break;
|
|
2549
|
-
}
|
|
2550
|
-
if (Array.isArray(value) && value.length === 3) {
|
|
2551
|
-
const [x, y, z] = value;
|
|
2552
|
-
parsed[name] = [
|
|
2553
|
-
ensureNumber(x, `${name}[0]`),
|
|
2554
|
-
ensureNumber(y, `${name}[1]`),
|
|
2555
|
-
ensureNumber(z, `${name}[2]`)
|
|
2556
|
-
];
|
|
2557
|
-
break;
|
|
2558
|
-
}
|
|
2559
|
-
throw new Error(`Unsupported entity field value for ${name}`);
|
|
2560
|
-
}
|
|
2615
|
+
const { forward } = angleVectors(state.angles);
|
|
2616
|
+
const toImpact = {
|
|
2617
|
+
x: hitPoint.x - state.origin.x,
|
|
2618
|
+
y: hitPoint.y - state.origin.y,
|
|
2619
|
+
z: hitPoint.z - state.origin.z
|
|
2620
|
+
};
|
|
2621
|
+
const toImpactLength = Math.hypot(toImpact.x, toImpact.y, toImpact.z);
|
|
2622
|
+
if (state.type === "screen" && toImpactLength > 0) {
|
|
2623
|
+
const dir = {
|
|
2624
|
+
x: toImpact.x / toImpactLength,
|
|
2625
|
+
y: toImpact.y / toImpactLength,
|
|
2626
|
+
z: toImpact.z / toImpactLength
|
|
2627
|
+
};
|
|
2628
|
+
const dot = dir.x * forward.x + dir.y * forward.y + dir.z * forward.z;
|
|
2629
|
+
if (dot <= 0.3) {
|
|
2630
|
+
return { saved: 0, remainingCells: state.cellCount };
|
|
2561
2631
|
}
|
|
2562
2632
|
}
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2633
|
+
const ctfMode = options.ctfMode ?? false;
|
|
2634
|
+
const damagePerCell = state.type === "screen" ? 1 : ctfMode ? 1 : 2;
|
|
2635
|
+
let adjustedDamage = state.type === "screen" ? damage / 3 : 2 * damage / 3;
|
|
2636
|
+
adjustedDamage = Math.max(1, adjustedDamage);
|
|
2637
|
+
let saved = state.cellCount * damagePerCell;
|
|
2638
|
+
if (hasAnyDamageFlag(flags, 4 /* ENERGY */)) {
|
|
2639
|
+
saved = Math.max(1, Math.floor(saved / 2));
|
|
2640
|
+
}
|
|
2641
|
+
if (saved > adjustedDamage) {
|
|
2642
|
+
saved = Math.floor(adjustedDamage);
|
|
2643
|
+
}
|
|
2644
|
+
let powerUsed = saved / damagePerCell;
|
|
2645
|
+
if (hasAnyDamageFlag(flags, 4 /* ENERGY */)) {
|
|
2646
|
+
powerUsed *= 2;
|
|
2647
|
+
}
|
|
2648
|
+
powerUsed = Math.max(1, Math.floor(powerUsed));
|
|
2649
|
+
const cellsSpent = Math.max(damagePerCell, powerUsed);
|
|
2650
|
+
const remainingCells = Math.max(0, state.cellCount - cellsSpent);
|
|
2651
|
+
return { saved, remainingCells };
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
// src/inventory/playerInventory.ts
|
|
2655
|
+
var WeaponId = /* @__PURE__ */ ((WeaponId2) => {
|
|
2656
|
+
WeaponId2["Blaster"] = "blaster";
|
|
2657
|
+
WeaponId2["Shotgun"] = "shotgun";
|
|
2658
|
+
WeaponId2["SuperShotgun"] = "super_shotgun";
|
|
2659
|
+
WeaponId2["Machinegun"] = "machinegun";
|
|
2660
|
+
WeaponId2["Chaingun"] = "chaingun";
|
|
2661
|
+
WeaponId2["GrenadeLauncher"] = "grenade_launcher";
|
|
2662
|
+
WeaponId2["RocketLauncher"] = "rocket_launcher";
|
|
2663
|
+
WeaponId2["HyperBlaster"] = "hyperblaster";
|
|
2664
|
+
WeaponId2["Railgun"] = "railgun";
|
|
2665
|
+
WeaponId2["BFG10K"] = "bfg10k";
|
|
2666
|
+
return WeaponId2;
|
|
2667
|
+
})(WeaponId || {});
|
|
2668
|
+
var PowerupId = /* @__PURE__ */ ((PowerupId2) => {
|
|
2669
|
+
PowerupId2["QuadDamage"] = "quad";
|
|
2670
|
+
PowerupId2["Invulnerability"] = "invulnerability";
|
|
2671
|
+
PowerupId2["EnviroSuit"] = "enviro_suit";
|
|
2672
|
+
PowerupId2["Rebreather"] = "rebreather";
|
|
2673
|
+
PowerupId2["Silencer"] = "silencer";
|
|
2674
|
+
return PowerupId2;
|
|
2675
|
+
})(PowerupId || {});
|
|
2676
|
+
var KeyId = /* @__PURE__ */ ((KeyId2) => {
|
|
2677
|
+
KeyId2["Blue"] = "blue";
|
|
2678
|
+
KeyId2["Red"] = "red";
|
|
2679
|
+
KeyId2["Green"] = "green";
|
|
2680
|
+
KeyId2["Yellow"] = "yellow";
|
|
2681
|
+
return KeyId2;
|
|
2682
|
+
})(KeyId || {});
|
|
2683
|
+
function createPlayerInventory(options = {}) {
|
|
2684
|
+
const ammo = createAmmoInventory(options.ammoCaps);
|
|
2685
|
+
const ownedWeapons = new Set(options.weapons ?? []);
|
|
2686
|
+
const powerups = new Map(options.powerups ?? []);
|
|
2687
|
+
const keys = new Set(options.keys ?? []);
|
|
2688
|
+
return {
|
|
2689
|
+
ammo,
|
|
2690
|
+
ownedWeapons,
|
|
2691
|
+
currentWeapon: options.currentWeapon,
|
|
2692
|
+
armor: options.armor ?? null,
|
|
2693
|
+
powerups,
|
|
2694
|
+
keys
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2697
|
+
function giveAmmo(inventory, ammoType, amount) {
|
|
2698
|
+
return addAmmo(inventory.ammo, ammoType, amount);
|
|
2699
|
+
}
|
|
2700
|
+
function giveAmmoItem(inventory, itemId, options) {
|
|
2701
|
+
return pickupAmmo(inventory.ammo, itemId, options);
|
|
2702
|
+
}
|
|
2703
|
+
function giveWeapon(inventory, weapon, select = false) {
|
|
2704
|
+
const hadWeapon = inventory.ownedWeapons.has(weapon);
|
|
2705
|
+
inventory.ownedWeapons.add(weapon);
|
|
2706
|
+
if (select || !inventory.currentWeapon) {
|
|
2707
|
+
inventory.currentWeapon = weapon;
|
|
2708
|
+
}
|
|
2709
|
+
return !hadWeapon;
|
|
2710
|
+
}
|
|
2711
|
+
function hasWeapon(inventory, weapon) {
|
|
2712
|
+
return inventory.ownedWeapons.has(weapon);
|
|
2713
|
+
}
|
|
2714
|
+
function selectWeapon(inventory, weapon) {
|
|
2715
|
+
if (!inventory.ownedWeapons.has(weapon)) {
|
|
2716
|
+
return false;
|
|
2717
|
+
}
|
|
2718
|
+
inventory.currentWeapon = weapon;
|
|
2719
|
+
return true;
|
|
2720
|
+
}
|
|
2721
|
+
function equipArmor(inventory, armorType, amount) {
|
|
2722
|
+
if (!armorType || amount <= 0) {
|
|
2723
|
+
inventory.armor = null;
|
|
2724
|
+
return null;
|
|
2725
|
+
}
|
|
2726
|
+
const info = ARMOR_INFO[armorType];
|
|
2727
|
+
const armorCount = Math.min(amount, info.maxCount);
|
|
2728
|
+
inventory.armor = { armorType, armorCount };
|
|
2729
|
+
return inventory.armor;
|
|
2730
|
+
}
|
|
2731
|
+
function addPowerup(inventory, powerup, expiresAt) {
|
|
2732
|
+
inventory.powerups.set(powerup, expiresAt);
|
|
2733
|
+
}
|
|
2734
|
+
function hasPowerup(inventory, powerup) {
|
|
2735
|
+
return inventory.powerups.has(powerup);
|
|
2736
|
+
}
|
|
2737
|
+
function clearExpiredPowerups(inventory, nowMs) {
|
|
2738
|
+
for (const [id, expiresAt] of inventory.powerups.entries()) {
|
|
2739
|
+
if (expiresAt !== null && expiresAt <= nowMs) {
|
|
2740
|
+
inventory.powerups.delete(id);
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
function addKey(inventory, key) {
|
|
2745
|
+
const before = inventory.keys.size;
|
|
2746
|
+
inventory.keys.add(key);
|
|
2747
|
+
return inventory.keys.size > before;
|
|
2748
|
+
}
|
|
2749
|
+
function hasKey(inventory, key) {
|
|
2750
|
+
return inventory.keys.has(key);
|
|
2751
|
+
}
|
|
2752
|
+
function serializePlayerInventory(inventory) {
|
|
2753
|
+
return {
|
|
2754
|
+
ammo: inventory.ammo.counts,
|
|
2755
|
+
ownedWeapons: [...inventory.ownedWeapons],
|
|
2756
|
+
currentWeapon: inventory.currentWeapon,
|
|
2757
|
+
armor: inventory.armor ? { ...inventory.armor } : null,
|
|
2758
|
+
powerups: [...inventory.powerups.entries()],
|
|
2759
|
+
keys: [...inventory.keys]
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2762
|
+
function deserializePlayerInventory(serialized, options = {}) {
|
|
2763
|
+
const ammo = createAmmoInventory(options.ammoCaps);
|
|
2764
|
+
const limit = Math.min(ammo.counts.length, serialized.ammo.length);
|
|
2765
|
+
for (let i = 0; i < limit; i++) {
|
|
2766
|
+
ammo.counts[i] = serialized.ammo[i];
|
|
2767
|
+
}
|
|
2768
|
+
return {
|
|
2769
|
+
ammo,
|
|
2770
|
+
ownedWeapons: new Set(serialized.ownedWeapons),
|
|
2771
|
+
currentWeapon: serialized.currentWeapon,
|
|
2772
|
+
armor: serialized.armor ? { ...serialized.armor } : null,
|
|
2773
|
+
powerups: new Map(serialized.powerups),
|
|
2774
|
+
keys: new Set(serialized.keys)
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
// src/save/save.ts
|
|
2779
|
+
var SAVE_FORMAT_VERSION = 2;
|
|
2780
|
+
var MIN_SUPPORTED_VERSION = 1;
|
|
2781
|
+
function ensureObject(value, label) {
|
|
2782
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2783
|
+
throw new Error(`${label} must be an object`);
|
|
2784
|
+
}
|
|
2785
|
+
return value;
|
|
2786
|
+
}
|
|
2787
|
+
function ensureNumber(value, label) {
|
|
2788
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2789
|
+
throw new Error(`${label} must be a finite number`);
|
|
2790
|
+
}
|
|
2791
|
+
return value;
|
|
2792
|
+
}
|
|
2793
|
+
function ensureNumberOrDefault(value, label, defaultValue) {
|
|
2794
|
+
if (value === void 0) {
|
|
2795
|
+
return defaultValue;
|
|
2796
|
+
}
|
|
2797
|
+
return ensureNumber(value, label);
|
|
2798
|
+
}
|
|
2799
|
+
function ensureString(value, label) {
|
|
2800
|
+
if (typeof value !== "string") {
|
|
2801
|
+
throw new Error(`${label} must be a string`);
|
|
2802
|
+
}
|
|
2803
|
+
return value;
|
|
2804
|
+
}
|
|
2805
|
+
function ensureNumberArray(value, label) {
|
|
2806
|
+
if (!Array.isArray(value)) {
|
|
2807
|
+
throw new Error(`${label} must be an array`);
|
|
2808
|
+
}
|
|
2809
|
+
for (const element of value) {
|
|
2810
|
+
ensureNumber(element, `${label} element`);
|
|
2811
|
+
}
|
|
2812
|
+
return value;
|
|
2813
|
+
}
|
|
2814
|
+
function parseLevelState(raw) {
|
|
2815
|
+
if (raw === void 0) {
|
|
2816
|
+
return { frameNumber: 0, timeSeconds: 0, previousTimeSeconds: 0, deltaSeconds: 0 };
|
|
2817
|
+
}
|
|
2818
|
+
const level = ensureObject(raw, "level");
|
|
2819
|
+
return {
|
|
2820
|
+
frameNumber: ensureNumberOrDefault(level.frameNumber, "level.frameNumber", 0),
|
|
2821
|
+
timeSeconds: ensureNumberOrDefault(level.timeSeconds, "level.timeSeconds", 0),
|
|
2822
|
+
previousTimeSeconds: ensureNumberOrDefault(level.previousTimeSeconds, "level.previousTimeSeconds", 0),
|
|
2823
|
+
deltaSeconds: ensureNumberOrDefault(level.deltaSeconds, "level.deltaSeconds", 0)
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
2826
|
+
function parseRngState(raw) {
|
|
2827
|
+
if (raw === void 0) {
|
|
2828
|
+
return new RandomGenerator().getState();
|
|
2829
|
+
}
|
|
2830
|
+
const rng = ensureObject(raw, "rng");
|
|
2831
|
+
const mt = ensureObject(rng.mt, "rng.mt");
|
|
2832
|
+
const state = ensureNumberArray(mt.state, "rng.mt.state");
|
|
2833
|
+
return {
|
|
2834
|
+
mt: {
|
|
2835
|
+
index: ensureNumber(mt.index, "rng.mt.index"),
|
|
2836
|
+
state
|
|
2837
|
+
}
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
function parseThinkEntries(raw) {
|
|
2841
|
+
if (raw === void 0) {
|
|
2842
|
+
return [];
|
|
2843
|
+
}
|
|
2844
|
+
if (!Array.isArray(raw)) {
|
|
2845
|
+
throw new Error("thinks must be an array");
|
|
2846
|
+
}
|
|
2847
|
+
return raw.map((entry, idx) => {
|
|
2848
|
+
const think = ensureObject(entry, `thinks[${idx}]`);
|
|
2849
|
+
return {
|
|
2850
|
+
time: ensureNumber(think.time, `thinks[${idx}].time`),
|
|
2851
|
+
entityIndex: ensureNumber(think.entityIndex, `thinks[${idx}].entityIndex`)
|
|
2852
|
+
};
|
|
2853
|
+
});
|
|
2854
|
+
}
|
|
2855
|
+
function parseEntityFields(raw) {
|
|
2856
|
+
if (raw === void 0) {
|
|
2857
|
+
return {};
|
|
2858
|
+
}
|
|
2859
|
+
const fields = ensureObject(raw, "entity.fields");
|
|
2860
|
+
const parsed = {};
|
|
2861
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
2862
|
+
if (value === null) {
|
|
2863
|
+
parsed[name] = null;
|
|
2864
|
+
continue;
|
|
2865
|
+
}
|
|
2866
|
+
switch (typeof value) {
|
|
2867
|
+
case "number":
|
|
2868
|
+
case "string":
|
|
2869
|
+
case "boolean":
|
|
2870
|
+
parsed[name] = value;
|
|
2871
|
+
break;
|
|
2872
|
+
default: {
|
|
2873
|
+
if (!Array.isArray(value)) {
|
|
2874
|
+
const object = ensureObject(value, name);
|
|
2875
|
+
const inventory = {};
|
|
2876
|
+
for (const [entryName, entryValue] of Object.entries(object)) {
|
|
2877
|
+
inventory[entryName] = ensureNumber(entryValue, `${name}.${entryName}`);
|
|
2878
|
+
}
|
|
2879
|
+
parsed[name] = inventory;
|
|
2880
|
+
break;
|
|
2881
|
+
}
|
|
2882
|
+
if (Array.isArray(value) && value.length === 3) {
|
|
2883
|
+
const [x, y, z] = value;
|
|
2884
|
+
parsed[name] = [
|
|
2885
|
+
ensureNumber(x, `${name}[0]`),
|
|
2886
|
+
ensureNumber(y, `${name}[1]`),
|
|
2887
|
+
ensureNumber(z, `${name}[2]`)
|
|
2888
|
+
];
|
|
2889
|
+
break;
|
|
2890
|
+
}
|
|
2891
|
+
throw new Error(`Unsupported entity field value for ${name}`);
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
return parsed;
|
|
2896
|
+
}
|
|
2897
|
+
function parseEntities(raw) {
|
|
2566
2898
|
if (!Array.isArray(raw)) {
|
|
2567
2899
|
throw new Error("entities must be an array");
|
|
2568
2900
|
}
|
|
@@ -2661,7 +2993,8 @@ function createSaveFile(options) {
|
|
|
2661
2993
|
configstrings = [],
|
|
2662
2994
|
cvars,
|
|
2663
2995
|
gameState = {},
|
|
2664
|
-
timestamp = Date.now()
|
|
2996
|
+
timestamp = Date.now(),
|
|
2997
|
+
player
|
|
2665
2998
|
} = options;
|
|
2666
2999
|
return {
|
|
2667
3000
|
version: SAVE_FORMAT_VERSION,
|
|
@@ -2674,7 +3007,8 @@ function createSaveFile(options) {
|
|
|
2674
3007
|
rng: cloneRngState(rngState),
|
|
2675
3008
|
entities: entitySystem.createSnapshot(),
|
|
2676
3009
|
cvars: serializeCvars(cvars),
|
|
2677
|
-
configstrings: [...configstrings]
|
|
3010
|
+
configstrings: [...configstrings],
|
|
3011
|
+
player: player ? serializePlayerInventory(player) : void 0
|
|
2678
3012
|
};
|
|
2679
3013
|
}
|
|
2680
3014
|
function parseSaveFile(serialized, options = {}) {
|
|
@@ -2700,14 +3034,19 @@ function parseSaveFile(serialized, options = {}) {
|
|
|
2700
3034
|
rng: parseRngState(save.rng),
|
|
2701
3035
|
entities: parseEntitySnapshot(save.entities),
|
|
2702
3036
|
cvars: parseCvars(save.cvars),
|
|
2703
|
-
configstrings: parseConfigstrings(save.configstrings)
|
|
3037
|
+
configstrings: parseConfigstrings(save.configstrings),
|
|
3038
|
+
player: save.player ? save.player : void 0
|
|
2704
3039
|
};
|
|
2705
3040
|
}
|
|
2706
3041
|
function applySaveFile(save, targets) {
|
|
2707
3042
|
targets.levelClock.restore(save.level);
|
|
2708
|
-
targets.entitySystem.restore(save.entities);
|
|
3043
|
+
targets.entitySystem.restore(save.entities, targets.callbackRegistry);
|
|
2709
3044
|
targets.rng.setState(save.rng);
|
|
2710
3045
|
applyCvars(save.cvars, targets.cvars);
|
|
3046
|
+
if (save.player && targets.player) {
|
|
3047
|
+
const deserialized = deserializePlayerInventory(save.player);
|
|
3048
|
+
Object.assign(targets.player, deserialized);
|
|
3049
|
+
}
|
|
2711
3050
|
}
|
|
2712
3051
|
|
|
2713
3052
|
// src/save/rerelease.ts
|
|
@@ -3099,116 +3438,6 @@ _SaveStorage.DEFAULT_STORE = "saves";
|
|
|
3099
3438
|
_SaveStorage.QUICK_SLOT = "quicksave";
|
|
3100
3439
|
var SaveStorage = _SaveStorage;
|
|
3101
3440
|
|
|
3102
|
-
// src/combat/damageFlags.ts
|
|
3103
|
-
var DamageFlags = /* @__PURE__ */ ((DamageFlags2) => {
|
|
3104
|
-
DamageFlags2[DamageFlags2["NONE"] = 0] = "NONE";
|
|
3105
|
-
DamageFlags2[DamageFlags2["RADIUS"] = 1] = "RADIUS";
|
|
3106
|
-
DamageFlags2[DamageFlags2["NO_ARMOR"] = 2] = "NO_ARMOR";
|
|
3107
|
-
DamageFlags2[DamageFlags2["ENERGY"] = 4] = "ENERGY";
|
|
3108
|
-
DamageFlags2[DamageFlags2["NO_KNOCKBACK"] = 8] = "NO_KNOCKBACK";
|
|
3109
|
-
DamageFlags2[DamageFlags2["BULLET"] = 16] = "BULLET";
|
|
3110
|
-
DamageFlags2[DamageFlags2["NO_PROTECTION"] = 32] = "NO_PROTECTION";
|
|
3111
|
-
DamageFlags2[DamageFlags2["DESTROY_ARMOR"] = 64] = "DESTROY_ARMOR";
|
|
3112
|
-
DamageFlags2[DamageFlags2["NO_REG_ARMOR"] = 128] = "NO_REG_ARMOR";
|
|
3113
|
-
DamageFlags2[DamageFlags2["NO_POWER_ARMOR"] = 256] = "NO_POWER_ARMOR";
|
|
3114
|
-
DamageFlags2[DamageFlags2["NO_INDICATOR"] = 512] = "NO_INDICATOR";
|
|
3115
|
-
return DamageFlags2;
|
|
3116
|
-
})(DamageFlags || {});
|
|
3117
|
-
function hasAnyDamageFlag(flags, mask) {
|
|
3118
|
-
return (flags & mask) !== 0;
|
|
3119
|
-
}
|
|
3120
|
-
|
|
3121
|
-
// src/combat/armor.ts
|
|
3122
|
-
var ArmorType = /* @__PURE__ */ ((ArmorType3) => {
|
|
3123
|
-
ArmorType3["BODY"] = "body";
|
|
3124
|
-
ArmorType3["COMBAT"] = "combat";
|
|
3125
|
-
ArmorType3["JACKET"] = "jacket";
|
|
3126
|
-
return ArmorType3;
|
|
3127
|
-
})(ArmorType || {});
|
|
3128
|
-
var ARMOR_INFO = {
|
|
3129
|
-
["jacket" /* JACKET */]: {
|
|
3130
|
-
baseCount: 25,
|
|
3131
|
-
maxCount: 50,
|
|
3132
|
-
normalProtection: 0.3,
|
|
3133
|
-
energyProtection: 0
|
|
3134
|
-
},
|
|
3135
|
-
["combat" /* COMBAT */]: {
|
|
3136
|
-
baseCount: 50,
|
|
3137
|
-
maxCount: 100,
|
|
3138
|
-
normalProtection: 0.6,
|
|
3139
|
-
energyProtection: 0.3
|
|
3140
|
-
},
|
|
3141
|
-
["body" /* BODY */]: {
|
|
3142
|
-
baseCount: 100,
|
|
3143
|
-
maxCount: 200,
|
|
3144
|
-
normalProtection: 0.8,
|
|
3145
|
-
energyProtection: 0.6
|
|
3146
|
-
}
|
|
3147
|
-
};
|
|
3148
|
-
function applyRegularArmor(damage, flags, state) {
|
|
3149
|
-
if (damage <= 0 || hasAnyDamageFlag(flags, 2 /* NO_ARMOR */ | 128 /* NO_REG_ARMOR */) || !state.armorType || state.armorCount <= 0) {
|
|
3150
|
-
return { saved: 0, remainingArmor: state.armorCount };
|
|
3151
|
-
}
|
|
3152
|
-
const info = ARMOR_INFO[state.armorType];
|
|
3153
|
-
const protection = hasAnyDamageFlag(flags, 4 /* ENERGY */) ? info.energyProtection : info.normalProtection;
|
|
3154
|
-
let saved = Math.ceil(protection * damage);
|
|
3155
|
-
if (saved >= state.armorCount) {
|
|
3156
|
-
saved = state.armorCount;
|
|
3157
|
-
}
|
|
3158
|
-
if (saved <= 0) {
|
|
3159
|
-
return { saved: 0, remainingArmor: state.armorCount };
|
|
3160
|
-
}
|
|
3161
|
-
return { saved, remainingArmor: state.armorCount - saved };
|
|
3162
|
-
}
|
|
3163
|
-
function applyPowerArmor(damage, flags, hitPoint, _hitNormal, state, options = {}) {
|
|
3164
|
-
if (state.health <= 0 || damage <= 0) {
|
|
3165
|
-
return { saved: 0, remainingCells: state.cellCount };
|
|
3166
|
-
}
|
|
3167
|
-
if (hasAnyDamageFlag(flags, 2 /* NO_ARMOR */ | 256 /* NO_POWER_ARMOR */)) {
|
|
3168
|
-
return { saved: 0, remainingCells: state.cellCount };
|
|
3169
|
-
}
|
|
3170
|
-
if (!state.type || state.cellCount <= 0) {
|
|
3171
|
-
return { saved: 0, remainingCells: state.cellCount };
|
|
3172
|
-
}
|
|
3173
|
-
const { forward } = angleVectors(state.angles);
|
|
3174
|
-
const toImpact = {
|
|
3175
|
-
x: hitPoint.x - state.origin.x,
|
|
3176
|
-
y: hitPoint.y - state.origin.y,
|
|
3177
|
-
z: hitPoint.z - state.origin.z
|
|
3178
|
-
};
|
|
3179
|
-
const toImpactLength = Math.hypot(toImpact.x, toImpact.y, toImpact.z);
|
|
3180
|
-
if (state.type === "screen" && toImpactLength > 0) {
|
|
3181
|
-
const dir = {
|
|
3182
|
-
x: toImpact.x / toImpactLength,
|
|
3183
|
-
y: toImpact.y / toImpactLength,
|
|
3184
|
-
z: toImpact.z / toImpactLength
|
|
3185
|
-
};
|
|
3186
|
-
const dot = dir.x * forward.x + dir.y * forward.y + dir.z * forward.z;
|
|
3187
|
-
if (dot <= 0.3) {
|
|
3188
|
-
return { saved: 0, remainingCells: state.cellCount };
|
|
3189
|
-
}
|
|
3190
|
-
}
|
|
3191
|
-
const ctfMode = options.ctfMode ?? false;
|
|
3192
|
-
const damagePerCell = state.type === "screen" ? 1 : ctfMode ? 1 : 2;
|
|
3193
|
-
let adjustedDamage = state.type === "screen" ? damage / 3 : 2 * damage / 3;
|
|
3194
|
-
adjustedDamage = Math.max(1, adjustedDamage);
|
|
3195
|
-
let saved = state.cellCount * damagePerCell;
|
|
3196
|
-
if (hasAnyDamageFlag(flags, 4 /* ENERGY */)) {
|
|
3197
|
-
saved = Math.max(1, Math.floor(saved / 2));
|
|
3198
|
-
}
|
|
3199
|
-
if (saved > adjustedDamage) {
|
|
3200
|
-
saved = Math.floor(adjustedDamage);
|
|
3201
|
-
}
|
|
3202
|
-
let powerUsed = saved / damagePerCell;
|
|
3203
|
-
if (hasAnyDamageFlag(flags, 4 /* ENERGY */)) {
|
|
3204
|
-
powerUsed *= 2;
|
|
3205
|
-
}
|
|
3206
|
-
powerUsed = Math.max(1, Math.floor(powerUsed));
|
|
3207
|
-
const cellsSpent = Math.max(damagePerCell, powerUsed);
|
|
3208
|
-
const remainingCells = Math.max(0, state.cellCount - cellsSpent);
|
|
3209
|
-
return { saved, remainingCells };
|
|
3210
|
-
}
|
|
3211
|
-
|
|
3212
3441
|
// src/combat/damage.ts
|
|
3213
3442
|
var EntityDamageFlags = /* @__PURE__ */ ((EntityDamageFlags2) => {
|
|
3214
3443
|
EntityDamageFlags2[EntityDamageFlags2["GODMODE"] = 1] = "GODMODE";
|
|
@@ -3611,175 +3840,6 @@ function killBox(teleporter, targets, options = {}) {
|
|
|
3611
3840
|
return { events, cleared };
|
|
3612
3841
|
}
|
|
3613
3842
|
|
|
3614
|
-
// src/inventory/ammo.ts
|
|
3615
|
-
var AmmoType = /* @__PURE__ */ ((AmmoType3) => {
|
|
3616
|
-
AmmoType3[AmmoType3["Bullets"] = 0] = "Bullets";
|
|
3617
|
-
AmmoType3[AmmoType3["Shells"] = 1] = "Shells";
|
|
3618
|
-
AmmoType3[AmmoType3["Rockets"] = 2] = "Rockets";
|
|
3619
|
-
AmmoType3[AmmoType3["Grenades"] = 3] = "Grenades";
|
|
3620
|
-
AmmoType3[AmmoType3["Cells"] = 4] = "Cells";
|
|
3621
|
-
AmmoType3[AmmoType3["Slugs"] = 5] = "Slugs";
|
|
3622
|
-
return AmmoType3;
|
|
3623
|
-
})(AmmoType || {});
|
|
3624
|
-
var AMMO_TYPE_COUNT = Object.keys(AmmoType).length / 2;
|
|
3625
|
-
var AmmoItemId = /* @__PURE__ */ ((AmmoItemId3) => {
|
|
3626
|
-
AmmoItemId3["Shells"] = "ammo_shells";
|
|
3627
|
-
AmmoItemId3["Bullets"] = "ammo_bullets";
|
|
3628
|
-
AmmoItemId3["Rockets"] = "ammo_rockets";
|
|
3629
|
-
AmmoItemId3["Grenades"] = "ammo_grenades";
|
|
3630
|
-
AmmoItemId3["Cells"] = "ammo_cells";
|
|
3631
|
-
AmmoItemId3["Slugs"] = "ammo_slugs";
|
|
3632
|
-
return AmmoItemId3;
|
|
3633
|
-
})(AmmoItemId || {});
|
|
3634
|
-
var AMMO_ITEM_DEFINITIONS = {
|
|
3635
|
-
["ammo_shells" /* Shells */]: { id: "ammo_shells" /* Shells */, ammoType: 1 /* Shells */, quantity: 10, weaponAmmo: false },
|
|
3636
|
-
["ammo_bullets" /* Bullets */]: { id: "ammo_bullets" /* Bullets */, ammoType: 0 /* Bullets */, quantity: 50, weaponAmmo: false },
|
|
3637
|
-
["ammo_rockets" /* Rockets */]: { id: "ammo_rockets" /* Rockets */, ammoType: 2 /* Rockets */, quantity: 5, weaponAmmo: false },
|
|
3638
|
-
["ammo_grenades" /* Grenades */]: { id: "ammo_grenades" /* Grenades */, ammoType: 3 /* Grenades */, quantity: 5, weaponAmmo: true },
|
|
3639
|
-
["ammo_cells" /* Cells */]: { id: "ammo_cells" /* Cells */, ammoType: 4 /* Cells */, quantity: 50, weaponAmmo: false },
|
|
3640
|
-
["ammo_slugs" /* Slugs */]: { id: "ammo_slugs" /* Slugs */, ammoType: 5 /* Slugs */, quantity: 10, weaponAmmo: false }
|
|
3641
|
-
};
|
|
3642
|
-
function getAmmoItemDefinition(id) {
|
|
3643
|
-
return AMMO_ITEM_DEFINITIONS[id];
|
|
3644
|
-
}
|
|
3645
|
-
function createAmmoInventory(caps = createBaseAmmoCaps()) {
|
|
3646
|
-
return { caps: caps.slice(), counts: Array(AMMO_TYPE_COUNT).fill(0) };
|
|
3647
|
-
}
|
|
3648
|
-
function createBaseAmmoCaps() {
|
|
3649
|
-
const caps = Array(AMMO_TYPE_COUNT).fill(50);
|
|
3650
|
-
caps[0 /* Bullets */] = 200;
|
|
3651
|
-
caps[1 /* Shells */] = 100;
|
|
3652
|
-
caps[4 /* Cells */] = 200;
|
|
3653
|
-
return caps;
|
|
3654
|
-
}
|
|
3655
|
-
function clampAmmoCounts(counts, caps) {
|
|
3656
|
-
const limit = Math.min(counts.length, caps.length);
|
|
3657
|
-
const clamped = counts.slice(0, limit);
|
|
3658
|
-
for (let i = 0; i < limit; i++) {
|
|
3659
|
-
const cap = caps[i];
|
|
3660
|
-
if (cap !== void 0) {
|
|
3661
|
-
clamped[i] = Math.min(counts[i], cap);
|
|
3662
|
-
}
|
|
3663
|
-
}
|
|
3664
|
-
return clamped;
|
|
3665
|
-
}
|
|
3666
|
-
function addAmmo(inventory, ammoType, amount) {
|
|
3667
|
-
const cap = inventory.caps[ammoType];
|
|
3668
|
-
const current = inventory.counts[ammoType] ?? 0;
|
|
3669
|
-
if (cap !== void 0 && current >= cap) {
|
|
3670
|
-
return { ammoType, added: 0, newCount: current, capped: cap, pickedUp: false };
|
|
3671
|
-
}
|
|
3672
|
-
const uncapped = current + amount;
|
|
3673
|
-
const newCount = cap === void 0 ? uncapped : Math.min(uncapped, cap);
|
|
3674
|
-
const added = newCount - current;
|
|
3675
|
-
inventory.counts[ammoType] = newCount;
|
|
3676
|
-
return { ammoType, added, newCount, capped: cap ?? Number.POSITIVE_INFINITY, pickedUp: added > 0 };
|
|
3677
|
-
}
|
|
3678
|
-
function pickupAmmo(inventory, itemId, options = {}) {
|
|
3679
|
-
const def = getAmmoItemDefinition(itemId);
|
|
3680
|
-
const amount = options.countOverride ?? def.quantity;
|
|
3681
|
-
return addAmmo(inventory, def.ammoType, amount);
|
|
3682
|
-
}
|
|
3683
|
-
|
|
3684
|
-
// src/inventory/playerInventory.ts
|
|
3685
|
-
var WeaponId = /* @__PURE__ */ ((WeaponId2) => {
|
|
3686
|
-
WeaponId2["Blaster"] = "blaster";
|
|
3687
|
-
WeaponId2["Shotgun"] = "shotgun";
|
|
3688
|
-
WeaponId2["SuperShotgun"] = "super_shotgun";
|
|
3689
|
-
WeaponId2["Machinegun"] = "machinegun";
|
|
3690
|
-
WeaponId2["Chaingun"] = "chaingun";
|
|
3691
|
-
WeaponId2["GrenadeLauncher"] = "grenade_launcher";
|
|
3692
|
-
WeaponId2["RocketLauncher"] = "rocket_launcher";
|
|
3693
|
-
WeaponId2["HyperBlaster"] = "hyperblaster";
|
|
3694
|
-
WeaponId2["Railgun"] = "railgun";
|
|
3695
|
-
WeaponId2["BFG10K"] = "bfg10k";
|
|
3696
|
-
return WeaponId2;
|
|
3697
|
-
})(WeaponId || {});
|
|
3698
|
-
var PowerupId = /* @__PURE__ */ ((PowerupId2) => {
|
|
3699
|
-
PowerupId2["QuadDamage"] = "quad";
|
|
3700
|
-
PowerupId2["Invulnerability"] = "invulnerability";
|
|
3701
|
-
PowerupId2["EnviroSuit"] = "enviro_suit";
|
|
3702
|
-
PowerupId2["Rebreather"] = "rebreather";
|
|
3703
|
-
PowerupId2["Silencer"] = "silencer";
|
|
3704
|
-
return PowerupId2;
|
|
3705
|
-
})(PowerupId || {});
|
|
3706
|
-
var KeyId = /* @__PURE__ */ ((KeyId2) => {
|
|
3707
|
-
KeyId2["Blue"] = "blue";
|
|
3708
|
-
KeyId2["Red"] = "red";
|
|
3709
|
-
KeyId2["Green"] = "green";
|
|
3710
|
-
KeyId2["Yellow"] = "yellow";
|
|
3711
|
-
return KeyId2;
|
|
3712
|
-
})(KeyId || {});
|
|
3713
|
-
function createPlayerInventory(options = {}) {
|
|
3714
|
-
const ammo = createAmmoInventory(options.ammoCaps);
|
|
3715
|
-
const ownedWeapons = new Set(options.weapons ?? []);
|
|
3716
|
-
const powerups = new Map(options.powerups ?? []);
|
|
3717
|
-
const keys = new Set(options.keys ?? []);
|
|
3718
|
-
return {
|
|
3719
|
-
ammo,
|
|
3720
|
-
ownedWeapons,
|
|
3721
|
-
currentWeapon: options.currentWeapon,
|
|
3722
|
-
armor: options.armor ?? null,
|
|
3723
|
-
powerups,
|
|
3724
|
-
keys
|
|
3725
|
-
};
|
|
3726
|
-
}
|
|
3727
|
-
function giveAmmo(inventory, ammoType, amount) {
|
|
3728
|
-
return addAmmo(inventory.ammo, ammoType, amount);
|
|
3729
|
-
}
|
|
3730
|
-
function giveAmmoItem(inventory, itemId, options) {
|
|
3731
|
-
return pickupAmmo(inventory.ammo, itemId, options);
|
|
3732
|
-
}
|
|
3733
|
-
function giveWeapon(inventory, weapon, select = false) {
|
|
3734
|
-
const hadWeapon = inventory.ownedWeapons.has(weapon);
|
|
3735
|
-
inventory.ownedWeapons.add(weapon);
|
|
3736
|
-
if (select || !inventory.currentWeapon) {
|
|
3737
|
-
inventory.currentWeapon = weapon;
|
|
3738
|
-
}
|
|
3739
|
-
return !hadWeapon;
|
|
3740
|
-
}
|
|
3741
|
-
function hasWeapon(inventory, weapon) {
|
|
3742
|
-
return inventory.ownedWeapons.has(weapon);
|
|
3743
|
-
}
|
|
3744
|
-
function selectWeapon(inventory, weapon) {
|
|
3745
|
-
if (!inventory.ownedWeapons.has(weapon)) {
|
|
3746
|
-
return false;
|
|
3747
|
-
}
|
|
3748
|
-
inventory.currentWeapon = weapon;
|
|
3749
|
-
return true;
|
|
3750
|
-
}
|
|
3751
|
-
function equipArmor(inventory, armorType, amount) {
|
|
3752
|
-
if (!armorType || amount <= 0) {
|
|
3753
|
-
inventory.armor = null;
|
|
3754
|
-
return null;
|
|
3755
|
-
}
|
|
3756
|
-
const info = ARMOR_INFO[armorType];
|
|
3757
|
-
const armorCount = Math.min(amount, info.maxCount);
|
|
3758
|
-
inventory.armor = { armorType, armorCount };
|
|
3759
|
-
return inventory.armor;
|
|
3760
|
-
}
|
|
3761
|
-
function addPowerup(inventory, powerup, expiresAt) {
|
|
3762
|
-
inventory.powerups.set(powerup, expiresAt);
|
|
3763
|
-
}
|
|
3764
|
-
function hasPowerup(inventory, powerup) {
|
|
3765
|
-
return inventory.powerups.has(powerup);
|
|
3766
|
-
}
|
|
3767
|
-
function clearExpiredPowerups(inventory, nowMs) {
|
|
3768
|
-
for (const [id, expiresAt] of inventory.powerups.entries()) {
|
|
3769
|
-
if (expiresAt !== null && expiresAt <= nowMs) {
|
|
3770
|
-
inventory.powerups.delete(id);
|
|
3771
|
-
}
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
function addKey(inventory, key) {
|
|
3775
|
-
const before = inventory.keys.size;
|
|
3776
|
-
inventory.keys.add(key);
|
|
3777
|
-
return inventory.keys.size > before;
|
|
3778
|
-
}
|
|
3779
|
-
function hasKey(inventory, key) {
|
|
3780
|
-
return inventory.keys.has(key);
|
|
3781
|
-
}
|
|
3782
|
-
|
|
3783
3843
|
// src/index.ts
|
|
3784
3844
|
var ZERO_VEC32 = { x: 0, y: 0, z: 0 };
|
|
3785
3845
|
function createGame(engine, options) {
|
|
@@ -3907,11 +3967,13 @@ export {
|
|
|
3907
3967
|
convertRereleaseSaveToGameSave,
|
|
3908
3968
|
createAmmoInventory,
|
|
3909
3969
|
createBaseAmmoCaps,
|
|
3970
|
+
createCallbackRegistry,
|
|
3910
3971
|
createDefaultSpawnRegistry,
|
|
3911
3972
|
createGame,
|
|
3912
3973
|
createPlayerInventory,
|
|
3913
3974
|
createSaveFile,
|
|
3914
3975
|
damageModName,
|
|
3976
|
+
deserializePlayerInventory,
|
|
3915
3977
|
equipArmor,
|
|
3916
3978
|
facingIdeal,
|
|
3917
3979
|
findTarget,
|
|
@@ -3934,8 +3996,10 @@ export {
|
|
|
3934
3996
|
parseSaveFile,
|
|
3935
3997
|
pickupAmmo,
|
|
3936
3998
|
rangeTo,
|
|
3999
|
+
registerCallback,
|
|
3937
4000
|
registerDefaultSpawns,
|
|
3938
4001
|
selectWeapon,
|
|
4002
|
+
serializePlayerInventory,
|
|
3939
4003
|
serializeRereleaseSave,
|
|
3940
4004
|
setMovedir,
|
|
3941
4005
|
spawnEntitiesFromText,
|