@jgengine/core 0.8.0 → 0.10.0
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/CHANGELOG.md +36 -0
- package/dist/ai/flock.d.ts +36 -0
- package/dist/ai/flock.js +182 -0
- package/dist/ai/groupAssist.d.ts +19 -0
- package/dist/ai/groupAssist.js +59 -0
- package/dist/ai/laneSelect.d.ts +21 -0
- package/dist/ai/laneSelect.js +37 -0
- package/dist/ai/mobBrain.d.ts +68 -0
- package/dist/ai/mobBrain.js +115 -0
- package/dist/ai/spawnDirector.d.ts +13 -0
- package/dist/ai/spawnDirector.js +41 -17
- package/dist/ai/spawnPoint.d.ts +5 -0
- package/dist/ai/spawnPoint.js +5 -0
- package/dist/ai/threat.d.ts +2 -0
- package/dist/ai/threat.js +34 -0
- package/dist/audio/audioFalloff.d.ts +5 -2
- package/dist/audio/music.d.ts +53 -0
- package/dist/audio/music.js +34 -0
- package/dist/audio/synth.d.ts +60 -0
- package/dist/audio/synth.js +8 -0
- package/dist/authoring.d.ts +14 -0
- package/dist/authoring.js +14 -0
- package/dist/cartridge/runtime.d.ts +15 -0
- package/dist/cartridge/runtime.js +556 -0
- package/dist/cartridge/spec.d.ts +188 -0
- package/dist/cartridge/spec.js +19 -0
- package/dist/cartridge/validate.d.ts +2 -0
- package/dist/cartridge/validate.js +78 -0
- package/dist/combat/abilityKit.d.ts +17 -2
- package/dist/combat/abilityKit.js +62 -13
- package/dist/combat/castRunner.d.ts +40 -0
- package/dist/combat/castRunner.js +56 -0
- package/dist/combat/comboPoints.d.ts +14 -0
- package/dist/combat/comboPoints.js +52 -0
- package/dist/combat/defensiveWindow.d.ts +1 -1
- package/dist/combat/defensiveWindow.js +5 -3
- package/dist/combat/dotField.d.ts +43 -0
- package/dist/combat/dotField.js +48 -0
- package/dist/combat/projectiles.d.ts +33 -12
- package/dist/combat/projectiles.js +214 -129
- package/dist/combat/regenShield.d.ts +35 -0
- package/dist/combat/regenShield.js +45 -0
- package/dist/combat/resistance.d.ts +16 -4
- package/dist/combat/resistance.js +43 -7
- package/dist/combat/resourcePool.d.ts +20 -0
- package/dist/combat/resourcePool.js +55 -0
- package/dist/combat/shotOrigin.d.ts +52 -0
- package/dist/combat/shotOrigin.js +86 -0
- package/dist/combat/telegraph.d.ts +25 -0
- package/dist/combat/telegraph.js +47 -0
- package/dist/crafting/recipe.js +4 -1
- package/dist/devtools/devtools.d.ts +72 -24
- package/dist/devtools/devtools.js +449 -101
- package/dist/devtools/transformTunables.js +3 -3
- package/dist/devtools/tunableSchema.d.ts +140 -0
- package/dist/devtools/tunableSchema.js +476 -0
- package/dist/editor/commands.d.ts +65 -0
- package/dist/editor/commands.js +193 -0
- package/dist/editor/document.d.ts +40 -0
- package/dist/editor/document.js +153 -0
- package/dist/editor/index.d.ts +30 -0
- package/dist/editor/index.js +6 -0
- package/dist/editor/types.d.ts +69 -0
- package/dist/editor/types.js +29 -0
- package/dist/faction/factions.d.ts +33 -0
- package/dist/faction/factions.js +76 -0
- package/dist/faction/reputation.d.ts +32 -0
- package/dist/faction/reputation.js +82 -0
- package/dist/game/chat.d.ts +2 -0
- package/dist/game/chat.js +9 -1
- package/dist/game/chatFilter.d.ts +15 -0
- package/dist/game/chatFilter.js +55 -0
- package/dist/game/connectedPlayers.d.ts +28 -0
- package/dist/game/connectedPlayers.js +21 -0
- package/dist/game/controlGate.d.ts +4 -0
- package/dist/game/controlGate.js +7 -0
- package/dist/game/defineGame.d.ts +44 -1
- package/dist/game/defineGame.js +1 -0
- package/dist/game/events.d.ts +25 -0
- package/dist/game/gamePhase.d.ts +20 -0
- package/dist/game/gamePhase.js +21 -0
- package/dist/game/keyValueStore.d.ts +25 -0
- package/dist/game/keyValueStore.js +61 -0
- package/dist/game/levelSequence.d.ts +24 -2
- package/dist/game/levelSequence.js +51 -2
- package/dist/game/lootTable.d.ts +14 -1
- package/dist/game/lootTable.js +28 -7
- package/dist/game/modelAnimation.d.ts +8 -0
- package/dist/game/modelAnimation.js +18 -0
- package/dist/game/playableGame.d.ts +116 -4
- package/dist/game/playableGame.js +1 -1
- package/dist/game/quest.d.ts +7 -3
- package/dist/game/quest.js +17 -8
- package/dist/game/race.d.ts +39 -1
- package/dist/game/race.js +96 -11
- package/dist/game/recordBook.d.ts +27 -0
- package/dist/game/recordBook.js +86 -0
- package/dist/game/talents.d.ts +47 -0
- package/dist/game/talents.js +140 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/input/actionBindings.d.ts +2 -0
- package/dist/input/actionBindings.js +1 -0
- package/dist/input/axisInput.d.ts +33 -3
- package/dist/input/axisInput.js +60 -4
- package/dist/input/bindingOverrides.d.ts +25 -0
- package/dist/input/bindingOverrides.js +95 -0
- package/dist/input/pointerAxis.d.ts +30 -0
- package/dist/input/pointerAxis.js +31 -0
- package/dist/input/touchScheme.d.ts +6 -0
- package/dist/input/touchScheme.js +45 -7
- package/dist/interaction/contextMenu.d.ts +1 -0
- package/dist/interaction/contextMenu.js +1 -0
- package/dist/interaction/proximityPrompt.d.ts +3 -1
- package/dist/interaction/proximityPrompt.js +3 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +44 -1
- package/dist/movement/dash.d.ts +3 -0
- package/dist/movement/dash.js +19 -5
- package/dist/movement/glideModel.d.ts +44 -0
- package/dist/movement/glideModel.js +54 -0
- package/dist/movement/grappleSwing.d.ts +34 -0
- package/dist/movement/grappleSwing.js +56 -0
- package/dist/movement/leaderTrail.d.ts +29 -0
- package/dist/movement/leaderTrail.js +67 -0
- package/dist/movement/movementModel.d.ts +4 -0
- package/dist/movement/movementModel.js +13 -0
- package/dist/movement/playerMovement.d.ts +41 -0
- package/dist/movement/playerMovement.js +181 -0
- package/dist/movement/steering.d.ts +27 -0
- package/dist/movement/steering.js +40 -0
- package/dist/multiplayer/feedWriteGate.d.ts +10 -0
- package/dist/multiplayer/feedWriteGate.js +12 -0
- package/dist/multiplayer/presenceModel.d.ts +14 -2
- package/dist/multiplayer/presenceModel.js +15 -3
- package/dist/nav/corridors.d.ts +30 -0
- package/dist/nav/corridors.js +69 -0
- package/dist/nav/navGrid.js +30 -4
- package/dist/nav/railGraph.d.ts +66 -0
- package/dist/nav/railGraph.js +129 -0
- package/dist/nav/timetable.d.ts +41 -0
- package/dist/nav/timetable.js +94 -0
- package/dist/physics/buoyancy.d.ts +6 -0
- package/dist/physics/buoyancy.js +14 -0
- package/dist/physics/flowTube.d.ts +32 -0
- package/dist/physics/flowTube.js +64 -0
- package/dist/physics/forceVolume.d.ts +27 -0
- package/dist/physics/forceVolume.js +38 -0
- package/dist/physics/kinematicVehicle.d.ts +52 -0
- package/dist/physics/kinematicVehicle.js +81 -0
- package/dist/physics/physicsWorld.d.ts +13 -6
- package/dist/physics/physicsWorld.js +169 -40
- package/dist/physics/ragdoll.js +1 -1
- package/dist/physics/vehicleBody.js +2 -1
- package/dist/random/rng.d.ts +2 -0
- package/dist/random/rng.js +2 -0
- package/dist/random/seedLink.d.ts +5 -0
- package/dist/random/seedLink.js +35 -0
- package/dist/render/postProcessing.d.ts +56 -0
- package/dist/render/postProcessing.js +1 -0
- package/dist/runtime/adapter.d.ts +19 -0
- package/dist/runtime/adapter.js +11 -5
- package/dist/runtime/cameraDirector.d.ts +6 -1
- package/dist/runtime/cameraDirector.js +6 -0
- package/dist/runtime/commandRunner.d.ts +2 -2
- package/dist/runtime/commandRunner.js +2 -2
- package/dist/runtime/gameContext.d.ts +72 -8
- package/dist/runtime/gameContext.js +374 -61
- package/dist/runtime/gameRuntime.d.ts +5 -1
- package/dist/runtime/gameRuntime.js +13 -12
- package/dist/runtime/hostPersistence.d.ts +9 -1
- package/dist/runtime/hostPersistence.js +23 -8
- package/dist/runtime/hostedGameRunner.d.ts +49 -0
- package/dist/runtime/hostedGameRunner.js +62 -0
- package/dist/runtime/hostedWorldSession.d.ts +65 -0
- package/dist/runtime/hostedWorldSession.js +57 -0
- package/dist/runtime/inputSnapshot.d.ts +18 -0
- package/dist/runtime/inputSnapshot.js +7 -0
- package/dist/runtime/motionIntents.d.ts +16 -6
- package/dist/runtime/motionIntents.js +31 -2
- package/dist/runtime/snapshot.d.ts +1 -0
- package/dist/runtime/snapshot.js +1 -1
- package/dist/runtime/visibility.d.ts +74 -0
- package/dist/runtime/visibility.js +127 -0
- package/dist/runtime/worldChannel.d.ts +58 -0
- package/dist/runtime/worldChannel.js +69 -0
- package/dist/runtime/worldMirror.d.ts +24 -0
- package/dist/runtime/worldMirror.js +31 -0
- package/dist/runtime/worldReplication.d.ts +46 -0
- package/dist/runtime/worldReplication.js +185 -0
- package/dist/runtime/worldSnapshot.d.ts +17 -0
- package/dist/runtime/worldSnapshot.js +15 -0
- package/dist/scene/assetCatalog.d.ts +1 -0
- package/dist/scene/colliders.d.ts +50 -0
- package/dist/scene/colliders.js +101 -0
- package/dist/scene/entityStats.d.ts +4 -0
- package/dist/scene/entityStats.js +21 -0
- package/dist/scene/entityStore.d.ts +51 -5
- package/dist/scene/entityStore.js +119 -12
- package/dist/scene/objectQuery.d.ts +13 -2
- package/dist/scene/objectQuery.js +34 -2
- package/dist/scene/objectStore.d.ts +2 -0
- package/dist/scene/objectStore.js +31 -12
- package/dist/scene/sceneRaycast.d.ts +80 -0
- package/dist/scene/sceneRaycast.js +284 -0
- package/dist/scene/spatial.d.ts +11 -16
- package/dist/scene/spatial.js +99 -38
- package/dist/sensor/recordingBuffer.d.ts +6 -0
- package/dist/sensor/recordingBuffer.js +7 -0
- package/dist/sensor/replayLoop.d.ts +39 -0
- package/dist/sensor/replayLoop.js +64 -0
- package/dist/sensor/visionCone.d.ts +31 -0
- package/dist/sensor/visionCone.js +72 -0
- package/dist/session/extraction.d.ts +2 -2
- package/dist/session/extraction.js +13 -2
- package/dist/settings/settingsModel.d.ts +97 -0
- package/dist/settings/settingsModel.js +100 -0
- package/dist/stats/statModifiers.d.ts +5 -2
- package/dist/stats/statModifiers.js +32 -14
- package/dist/store/observableKeyedStore.d.ts +4 -0
- package/dist/store/observableKeyedStore.js +17 -2
- package/dist/survival/decayMeter.d.ts +1 -1
- package/dist/survival/decayMeter.js +0 -1
- package/dist/tactics/fallingGrid.d.ts +2 -2
- package/dist/tactics/fallingGrid.js +2 -2
- package/dist/time/beatClock.d.ts +18 -0
- package/dist/time/beatClock.js +24 -0
- package/dist/time/idleProgress.d.ts +29 -0
- package/dist/time/idleProgress.js +31 -0
- package/dist/time/serverTick.d.ts +7 -1
- package/dist/time/serverTick.js +27 -4
- package/dist/time/stateSchedule.d.ts +57 -0
- package/dist/time/stateSchedule.js +121 -0
- package/dist/turn/commit.d.ts +2 -2
- package/dist/turn/commit.js +2 -2
- package/dist/ui/gameLayout.d.ts +125 -0
- package/dist/ui/gameLayout.js +129 -0
- package/dist/ui/hudLayout.d.ts +58 -0
- package/dist/ui/hudLayout.js +207 -0
- package/dist/ui/hudScale.d.ts +44 -0
- package/dist/ui/hudScale.js +55 -0
- package/dist/ui/orientation.d.ts +39 -0
- package/dist/ui/orientation.js +49 -0
- package/dist/ui/swingTimer.d.ts +42 -0
- package/dist/ui/swingTimer.js +38 -0
- package/dist/visibility/assetStreaming.d.ts +66 -0
- package/dist/visibility/assetStreaming.js +201 -0
- package/dist/visibility/bounds.d.ts +66 -0
- package/dist/visibility/bounds.js +113 -0
- package/dist/visibility/camera.d.ts +21 -0
- package/dist/visibility/camera.js +3 -0
- package/dist/visibility/config.d.ts +21 -0
- package/dist/visibility/config.js +1 -0
- package/dist/visibility/diagnostics.d.ts +26 -0
- package/dist/visibility/diagnostics.js +36 -0
- package/dist/visibility/distance.d.ts +11 -0
- package/dist/visibility/distance.js +30 -0
- package/dist/visibility/frustum.d.ts +47 -0
- package/dist/visibility/frustum.js +186 -0
- package/dist/visibility/occlusion.d.ts +36 -0
- package/dist/visibility/occlusion.js +45 -0
- package/dist/visibility/settings.d.ts +75 -0
- package/dist/visibility/settings.js +49 -0
- package/dist/visibility/simulationCulling.d.ts +34 -0
- package/dist/visibility/simulationCulling.js +36 -0
- package/dist/visibility/spatialIndex.d.ts +45 -0
- package/dist/visibility/spatialIndex.js +212 -0
- package/dist/visibility/visibilitySystem.d.ts +90 -0
- package/dist/visibility/visibilitySystem.js +257 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/buildings.js +152 -0
- package/dist/world/carve.d.ts +7 -2
- package/dist/world/carve.js +17 -7
- package/dist/world/cellStates.d.ts +45 -0
- package/dist/world/cellStates.js +103 -0
- package/dist/world/environmentSummary.d.ts +21 -4
- package/dist/world/environmentSummary.js +22 -3
- package/dist/world/features.d.ts +185 -8
- package/dist/world/features.js +64 -6
- package/dist/world/gridInstances.d.ts +7 -8
- package/dist/world/gridInstances.js +145 -7
- package/dist/world/lod.d.ts +17 -0
- package/dist/world/lod.js +66 -0
- package/dist/world/mapLayers.d.ts +60 -0
- package/dist/world/mapLayers.js +44 -0
- package/dist/world/massing.d.ts +45 -0
- package/dist/world/massing.js +581 -0
- package/dist/world/minimap.d.ts +13 -1
- package/dist/world/minimap.js +21 -2
- package/dist/world/roads.d.ts +47 -0
- package/dist/world/roads.js +146 -0
- package/dist/world/streets.d.ts +80 -0
- package/dist/world/streets.js +144 -0
- package/dist/world/terrain.d.ts +69 -3
- package/dist/world/terrain.js +309 -16
- package/dist/world/water.d.ts +7 -0
- package/dist/world/water.js +19 -4
- package/dist/world/weather.d.ts +3 -18
- package/dist/world/weather.js +4 -17
- package/dist/world/windZones.d.ts +47 -0
- package/dist/world/windZones.js +59 -0
- package/llms.txt +3013 -1726
- package/package.json +12 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createKeyValueStore } from "./keyValueStore.js";
|
|
1
2
|
/**
|
|
2
3
|
* A pure, deterministic level campaign: an ordered list of levels, each with its own opaque config, played
|
|
3
4
|
* through a `start` → (`clear` → `advance`)* → `complete` happy path, with `fail`/`retry` handling per-level
|
|
@@ -7,10 +8,32 @@
|
|
|
7
8
|
export function createLevelSequence(config) {
|
|
8
9
|
const levels = config.levels;
|
|
9
10
|
const maxAttempts = 1 + Math.max(0, Math.floor(config.retriesPerLevel ?? 0));
|
|
11
|
+
const store = config.key === undefined
|
|
12
|
+
? null
|
|
13
|
+
: createKeyValueStore({
|
|
14
|
+
key: config.key,
|
|
15
|
+
initial: {},
|
|
16
|
+
storage: config.storage,
|
|
17
|
+
});
|
|
10
18
|
let index = 0;
|
|
11
19
|
let attempt = 1;
|
|
12
20
|
let status = "idle";
|
|
13
21
|
const clearedIds = [];
|
|
22
|
+
const levelRecords = store === null ? {} : { ...store.get() };
|
|
23
|
+
function persistRecords() {
|
|
24
|
+
store?.set({ ...levelRecords });
|
|
25
|
+
}
|
|
26
|
+
function indexOfLevel(id) {
|
|
27
|
+
return levels.findIndex((level) => level.id === id);
|
|
28
|
+
}
|
|
29
|
+
function isUnlocked(id) {
|
|
30
|
+
const idx = indexOfLevel(id);
|
|
31
|
+
if (idx < 0)
|
|
32
|
+
return false;
|
|
33
|
+
if (idx === 0)
|
|
34
|
+
return true;
|
|
35
|
+
return levelRecords[levels[idx - 1].id]?.cleared === true;
|
|
36
|
+
}
|
|
14
37
|
function current() {
|
|
15
38
|
if (status === "idle" || status === "complete")
|
|
16
39
|
return null;
|
|
@@ -30,12 +53,38 @@ export function createLevelSequence(config) {
|
|
|
30
53
|
clearedIds.length = 0;
|
|
31
54
|
status = "playing";
|
|
32
55
|
},
|
|
33
|
-
clear() {
|
|
56
|
+
clear(stars) {
|
|
34
57
|
if (status !== "playing" || current() === null)
|
|
35
58
|
return;
|
|
36
|
-
|
|
59
|
+
const id = levels[index].id;
|
|
60
|
+
clearedIds.push(id);
|
|
61
|
+
const previousStars = levelRecords[id]?.stars ?? 0;
|
|
62
|
+
const bestStars = Math.max(previousStars, stars ?? 0);
|
|
63
|
+
levelRecords[id] = { cleared: true, stars: bestStars };
|
|
64
|
+
persistRecords();
|
|
37
65
|
status = "cleared";
|
|
38
66
|
},
|
|
67
|
+
select(level) {
|
|
68
|
+
const idx = indexOfLevel(level);
|
|
69
|
+
if (idx < 0 || !isUnlocked(level))
|
|
70
|
+
return false;
|
|
71
|
+
index = idx;
|
|
72
|
+
attempt = 1;
|
|
73
|
+
status = "playing";
|
|
74
|
+
return true;
|
|
75
|
+
},
|
|
76
|
+
isUnlocked,
|
|
77
|
+
record: (level) => levelRecords[level] ?? null,
|
|
78
|
+
records: () => ({ ...levelRecords }),
|
|
79
|
+
clearRecords() {
|
|
80
|
+
for (const id of Object.keys(levelRecords))
|
|
81
|
+
delete levelRecords[id];
|
|
82
|
+
store?.clear();
|
|
83
|
+
index = 0;
|
|
84
|
+
attempt = 1;
|
|
85
|
+
clearedIds.length = 0;
|
|
86
|
+
status = "idle";
|
|
87
|
+
},
|
|
39
88
|
fail() {
|
|
40
89
|
if (status !== "playing" || current() === null)
|
|
41
90
|
return "failed";
|
package/dist/game/lootTable.d.ts
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
|
+
/** One possible drop in a {@link LootTableDef} — an item or currency, its count range, and its odds. */
|
|
1
2
|
export interface LootEntry {
|
|
2
3
|
item?: string;
|
|
3
4
|
currency?: string;
|
|
4
5
|
count: number | [number, number];
|
|
5
|
-
weight
|
|
6
|
+
/** Relative pick weight — required in `"weighted"` mode, forbidden in `"independent"` mode. */
|
|
7
|
+
weight?: number;
|
|
8
|
+
/** Per-entry drop probability in `(0, 1]` — required in `"independent"` mode, forbidden in `"weighted"` mode. */
|
|
9
|
+
chance?: number;
|
|
6
10
|
}
|
|
11
|
+
/** A named, validated loot table — its roll count, weighted-vs-independent mode, and candidate entries. */
|
|
7
12
|
export interface LootTableDef {
|
|
8
13
|
id: string;
|
|
9
14
|
rolls?: number;
|
|
15
|
+
/**
|
|
16
|
+
* `"weighted"` (default): each roll picks exactly one entry by relative `weight`.
|
|
17
|
+
* `"independent"`: each roll gives every entry its own `chance` — classic drop lists
|
|
18
|
+
* where a kill can yield several items (or nothing) without filler entries.
|
|
19
|
+
*/
|
|
20
|
+
mode?: "weighted" | "independent";
|
|
10
21
|
entries: LootEntry[];
|
|
11
22
|
}
|
|
23
|
+
/** A resolved loot outcome — one item or currency grant with its rolled count. */
|
|
12
24
|
export interface Drop {
|
|
13
25
|
item?: string;
|
|
14
26
|
currency?: string;
|
|
@@ -20,6 +32,7 @@ export interface LootRegistry {
|
|
|
20
32
|
roll(id: string, rng?: () => number): Drop[];
|
|
21
33
|
}
|
|
22
34
|
export declare function createLootRegistry(): LootRegistry;
|
|
35
|
+
/** Validates a loot table definition and returns it unchanged, for use with {@link createLootRegistry}. */
|
|
23
36
|
export declare function lootTable(def: LootTableDef): LootTableDef;
|
|
24
37
|
export declare function grantDrops(drops: Drop[], appliers: {
|
|
25
38
|
putItem: (itemId: string, count: number) => unknown;
|
package/dist/game/lootTable.js
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
function assertValidEntry(entry) {
|
|
1
|
+
function assertValidEntry(entry, mode) {
|
|
2
2
|
const hasItem = entry.item !== undefined;
|
|
3
3
|
const hasCurrency = entry.currency !== undefined;
|
|
4
4
|
if (hasItem === hasCurrency) {
|
|
5
5
|
throw new Error("loot entry must have exactly one of item or currency");
|
|
6
6
|
}
|
|
7
|
-
if (
|
|
8
|
-
|
|
7
|
+
if (mode === "weighted") {
|
|
8
|
+
if (entry.chance !== undefined) {
|
|
9
|
+
throw new Error("loot entry chance is only valid in independent mode");
|
|
10
|
+
}
|
|
11
|
+
if (entry.weight === undefined || !(entry.weight > 0)) {
|
|
12
|
+
throw new Error(`loot entry weight must be positive, got ${entry.weight}`);
|
|
13
|
+
}
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (entry.weight !== undefined) {
|
|
17
|
+
throw new Error("loot entry weight is only valid in weighted mode");
|
|
18
|
+
}
|
|
19
|
+
if (entry.chance === undefined || !(entry.chance > 0) || entry.chance > 1) {
|
|
20
|
+
throw new Error(`loot entry chance must be in (0, 1], got ${entry.chance}`);
|
|
9
21
|
}
|
|
10
22
|
}
|
|
11
23
|
function assertValidDef(def) {
|
|
@@ -13,7 +25,7 @@ function assertValidDef(def) {
|
|
|
13
25
|
throw new Error(`loot table "${def.id}" must have at least one entry`);
|
|
14
26
|
}
|
|
15
27
|
for (const entry of def.entries)
|
|
16
|
-
assertValidEntry(entry);
|
|
28
|
+
assertValidEntry(entry, def.mode ?? "weighted");
|
|
17
29
|
}
|
|
18
30
|
function resolveCount(count, rng) {
|
|
19
31
|
if (typeof count === "number")
|
|
@@ -22,10 +34,10 @@ function resolveCount(count, rng) {
|
|
|
22
34
|
return min + Math.floor(rng() * (max - min + 1));
|
|
23
35
|
}
|
|
24
36
|
function pickEntry(entries, rng) {
|
|
25
|
-
const total = entries.reduce((sum, entry) => sum + entry.weight, 0);
|
|
37
|
+
const total = entries.reduce((sum, entry) => sum + (entry.weight ?? 0), 0);
|
|
26
38
|
let roll = rng() * total;
|
|
27
39
|
for (const entry of entries) {
|
|
28
|
-
roll -= entry.weight;
|
|
40
|
+
roll -= entry.weight ?? 0;
|
|
29
41
|
if (roll < 0)
|
|
30
42
|
return entry;
|
|
31
43
|
}
|
|
@@ -56,12 +68,21 @@ export function createLootRegistry() {
|
|
|
56
68
|
const rolls = table.rolls ?? 1;
|
|
57
69
|
const drops = [];
|
|
58
70
|
for (let i = 0; i < rolls; i++) {
|
|
59
|
-
|
|
71
|
+
if (table.mode === "independent") {
|
|
72
|
+
for (const entry of table.entries) {
|
|
73
|
+
if (rng() < (entry.chance ?? 0))
|
|
74
|
+
drops.push(rollEntry(entry, rng));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
drops.push(rollEntry(pickEntry(table.entries, rng), rng));
|
|
79
|
+
}
|
|
60
80
|
}
|
|
61
81
|
return drops;
|
|
62
82
|
},
|
|
63
83
|
};
|
|
64
84
|
}
|
|
85
|
+
/** Validates a loot table definition and returns it unchanged, for use with {@link createLootRegistry}. */
|
|
65
86
|
export function lootTable(def) {
|
|
66
87
|
assertValidDef(def);
|
|
67
88
|
return def;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The two one-shot events the shell fires automatically for an entity, from `combat.hitReaction` and `entity.died`. */
|
|
2
|
+
export declare const AUTO_ONE_SHOT_EVENTS: readonly ["hit", "death"];
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the clip name a one-shot `event` should play from a model's `animation.oneShots` map, or `null`
|
|
5
|
+
* if the event isn't bound. A `string[]` binding picks a variant by `roll` (a value in `[0, 1)`), so combat
|
|
6
|
+
* can vary attack swings. Pure and deterministic given `roll` — the shell supplies the randomness.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveOneShotClip(oneShots: Record<string, string | readonly string[]> | undefined, event: string, roll: number): string | null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The two one-shot events the shell fires automatically for an entity, from `combat.hitReaction` and `entity.died`. */
|
|
2
|
+
export const AUTO_ONE_SHOT_EVENTS = ["hit", "death"];
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the clip name a one-shot `event` should play from a model's `animation.oneShots` map, or `null`
|
|
5
|
+
* if the event isn't bound. A `string[]` binding picks a variant by `roll` (a value in `[0, 1)`), so combat
|
|
6
|
+
* can vary attack swings. Pure and deterministic given `roll` — the shell supplies the randomness.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveOneShotClip(oneShots, event, roll) {
|
|
9
|
+
const spec = oneShots?.[event];
|
|
10
|
+
if (spec === undefined)
|
|
11
|
+
return null;
|
|
12
|
+
if (typeof spec === "string")
|
|
13
|
+
return spec;
|
|
14
|
+
if (spec.length === 0)
|
|
15
|
+
return null;
|
|
16
|
+
const index = Math.min(spec.length - 1, Math.max(0, Math.floor(roll * spec.length)));
|
|
17
|
+
return spec[index] ?? null;
|
|
18
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { AudioBusDef, SoundDef } from "../audio/audioFalloff.js";
|
|
2
|
+
import type { MusicTheme } from "../audio/music.js";
|
|
3
|
+
import type { PostProcessingConfig } from "../render/postProcessing.js";
|
|
2
4
|
import type { TouchControlsConfig } from "../input/touchScheme.js";
|
|
5
|
+
import type { GameSettingsConfig } from "../settings/settingsModel.js";
|
|
6
|
+
import type { GameOrientation } from "../ui/orientation.js";
|
|
7
|
+
import type { HudPlatform, HudViewportConfig } from "../ui/hudScale.js";
|
|
3
8
|
import type { PositionedPrompt } from "../interaction/proximityPrompt.js";
|
|
4
9
|
import type { CatalogEntityRole, GameContext, GameContextContent } from "../runtime/gameContext.js";
|
|
5
10
|
import type { ModelDims } from "../scene/assetCatalog.js";
|
|
6
11
|
import type { SkyEnvironmentConfig } from "../world/features.js";
|
|
12
|
+
import type { VisibilityConfig } from "../visibility/config.js";
|
|
7
13
|
import type { GameDefinition, GameLoop } from "./defineGame.js";
|
|
8
14
|
import type { LootFilterRule } from "./lootFilter.js";
|
|
9
15
|
import type { RarityStyle } from "./worldItem.js";
|
|
@@ -75,6 +81,8 @@ export interface FirstPersonCameraConfig {
|
|
|
75
81
|
* - `none` — no camera rig is mounted; use for HUD-only presentations or a game that manages its own camera.
|
|
76
82
|
*/
|
|
77
83
|
export type CameraRigKind = "orbit" | "first" | "topDown" | "rts" | "shoulder" | "lockOn" | "chase" | "observer" | "turntable" | "sideScroll" | "inspection" | "none";
|
|
84
|
+
/** Canvas camera projection. "orthographic" renders a flat 2D-style view (side-scrollers, falling-block puzzles) — pair with `rig: "sideScroll"`; default "perspective". */
|
|
85
|
+
export type CameraProjection = "perspective" | "orthographic";
|
|
78
86
|
/** Fixed lateral 2.5D follow (side-on platformer cam): the camera sits perpendicular to the travel axis, tracks the followed entity, and never reads player look input. */
|
|
79
87
|
export interface SideScrollCameraConfig {
|
|
80
88
|
/** World axis the action travels along; the camera watches from the perpendicular side. Default "x". */
|
|
@@ -202,6 +210,17 @@ export interface ChaseCameraConfig {
|
|
|
202
210
|
};
|
|
203
211
|
/** Procedural shake amplitude per unit of speed (adds to the trauma channel). Default 0.0. */
|
|
204
212
|
shakePerSpeed?: number;
|
|
213
|
+
/** Velocity-lead / predictive follow (#286.9): the rig aims `time` seconds ahead of the target along its velocity, clamped to `max` world units (default 4). */
|
|
214
|
+
lead?: {
|
|
215
|
+
time: number;
|
|
216
|
+
max?: number;
|
|
217
|
+
};
|
|
218
|
+
/** Roll into turns (#286.10): radians of camera roll per rad/s of the target's yaw rate, clamped to `max` (default 0.35), exponentially smoothed by `damping` (default 8). */
|
|
219
|
+
bank?: {
|
|
220
|
+
perYawRate: number;
|
|
221
|
+
max?: number;
|
|
222
|
+
damping?: number;
|
|
223
|
+
};
|
|
205
224
|
/** Which view to mount. Default "chase". */
|
|
206
225
|
view?: ChaseView;
|
|
207
226
|
/** Local offset for cockpit/hood/rear seats (relative to the vehicle, +z forward). */
|
|
@@ -354,16 +373,35 @@ export declare const CAMERA_FRUSTUM_DEFAULTS: {
|
|
|
354
373
|
readonly fov: 55;
|
|
355
374
|
readonly near: 0.1;
|
|
356
375
|
readonly far: 300;
|
|
376
|
+
readonly zoom: 50;
|
|
357
377
|
};
|
|
378
|
+
/** Player-facing FOV preference applied across every perspective camera rig. Orthographic projections ignore it. */
|
|
379
|
+
export interface PlayerFovConfig {
|
|
380
|
+
/** Inclusive lower bound for the slider and persisted values. Default 40. */
|
|
381
|
+
min?: number;
|
|
382
|
+
/** Inclusive upper bound for the slider and persisted values. Default 120. */
|
|
383
|
+
max?: number;
|
|
384
|
+
/** Initial FOV when nothing is persisted. Defaults to `frustum.fov` or 55. */
|
|
385
|
+
default?: number;
|
|
386
|
+
/** Persist the preference in localStorage across reloads. Default true. */
|
|
387
|
+
persist?: boolean;
|
|
388
|
+
/** Show the shell FOV slider. Default true for perspective cameras; forced off for orthographic. */
|
|
389
|
+
control?: boolean;
|
|
390
|
+
}
|
|
358
391
|
export interface GameCameraConfig {
|
|
359
392
|
/** Selects the rig. Overrides `perspective`; leave unset to fall back to `perspective`. */
|
|
360
393
|
rig?: CameraRigKind;
|
|
361
|
-
/**
|
|
394
|
+
/** Canvas camera projection. "orthographic" renders a flat 2D-style view (side-scrollers, falling-block puzzles) — pair with `rig: "sideScroll"`; default perspective. */
|
|
395
|
+
projection?: CameraProjection;
|
|
396
|
+
/** Render frustum overrides applied to the canvas camera. `far` defaults to 300 — raise it for worlds whose content spans more than a few hundred units, or distant scenery silently clips. `zoom` is the orthographic zoom in canvas pixels per world unit, read only when `projection` is "orthographic"; default 50. */
|
|
362
397
|
frustum?: {
|
|
363
398
|
fov?: number;
|
|
364
399
|
near?: number;
|
|
365
400
|
far?: number;
|
|
401
|
+
zoom?: number;
|
|
366
402
|
};
|
|
403
|
+
/** Universal player FOV preference (slider + persistence) for perspective rigs. Ignored when `projection` is `"orthographic"`. */
|
|
404
|
+
playerFov?: PlayerFovConfig;
|
|
367
405
|
/** "third" mounts the orbit camera (default); "first" mounts pointer-lock mouse-look. Shorthand for `rig: "orbit" | "first"`. */
|
|
368
406
|
perspective?: "third" | "first";
|
|
369
407
|
/** First-person tuning; only read when the rig resolves to "first". */
|
|
@@ -420,6 +458,12 @@ export interface GameCameraConfig {
|
|
|
420
458
|
/** Vertical look clamp (three.js polar angle, radians): 0 = top-down over the head, PI/2 = level, >PI/2 = look up from below. Widen for top-down or vertical aim; unset keeps the standard chase feel. */
|
|
421
459
|
minPolarAngle?: number;
|
|
422
460
|
maxPolarAngle?: number;
|
|
461
|
+
/** Spring-arm occlusion for the orbit rig: pull the camera in past walls/terrain so it never clips inside geometry. Off unless `enabled`. */
|
|
462
|
+
collision?: {
|
|
463
|
+
enabled?: boolean;
|
|
464
|
+
padding?: number;
|
|
465
|
+
minTargetDistance?: number;
|
|
466
|
+
};
|
|
423
467
|
}
|
|
424
468
|
export interface EntitySpriteConfig {
|
|
425
469
|
url: string;
|
|
@@ -457,6 +501,21 @@ export interface VoxelCollisionConfig {
|
|
|
457
501
|
/** Tallest ledge walked up without jumping. Default 0.6. */
|
|
458
502
|
stepHeight?: number;
|
|
459
503
|
}
|
|
504
|
+
/** Movement-state clip set for `ModelAnimationConfig.states`: the shell reads the entity's live speed each frame and crossfades between these clips, so a walking mob animates without any game-side driver. */
|
|
505
|
+
export interface ModelAnimationStates {
|
|
506
|
+
/** Clip name while the entity is stationary. */
|
|
507
|
+
idle: string;
|
|
508
|
+
/** Clip name while the entity is moving. */
|
|
509
|
+
walk: string;
|
|
510
|
+
/** Clip name above `runSpeed`; omit to keep `walk` at any speed. */
|
|
511
|
+
run?: string;
|
|
512
|
+
/** Speed (world units/sec) above which the entity counts as moving. Default 0.5. */
|
|
513
|
+
walkSpeed?: number;
|
|
514
|
+
/** Speed above which `run` plays when provided. Default 6. */
|
|
515
|
+
runSpeed?: number;
|
|
516
|
+
/** Crossfade duration in seconds when the state changes. Default 0.2. */
|
|
517
|
+
fadeSec?: number;
|
|
518
|
+
}
|
|
460
519
|
/** Rig playback for a `ModelConfig`'s GLTF animation clips — looping idles, one-shots, and held poses. */
|
|
461
520
|
export interface ModelAnimationConfig {
|
|
462
521
|
/** Clip name to play; defaults to the GLB's first clip. */
|
|
@@ -469,6 +528,15 @@ export interface ModelAnimationConfig {
|
|
|
469
528
|
paused?: boolean;
|
|
470
529
|
/** Seek the clip to this time in seconds; combine with `paused: true` to hold a specific pose ("pose library" usage). */
|
|
471
530
|
time?: number;
|
|
531
|
+
/** Speed-driven idle/walk/run clip switching, crossfaded by the shell from the entity's live movement; overrides `clip` while set. */
|
|
532
|
+
states?: ModelAnimationStates;
|
|
533
|
+
/**
|
|
534
|
+
* One-shot clips keyed by event name, each played once over the locomotion state then released back to it.
|
|
535
|
+
* Reserved keys `hit` and `death` auto-fire on this entity's `combat.hitReaction` / `entity.died`; any other
|
|
536
|
+
* key fires when the game emits `entity.animation` (`ctx.game.playEntityAnimation(instanceId, event)`). A
|
|
537
|
+
* `string[]` picks a random variant per trigger. `death` clamps on its final frame instead of returning.
|
|
538
|
+
*/
|
|
539
|
+
oneShots?: Record<string, string | readonly string[]>;
|
|
472
540
|
}
|
|
473
541
|
/** Per-entity PBR material override (#151.3) applied to every `MeshStandardMaterial` in the model's cloned scene graph. */
|
|
474
542
|
export interface ModelMaterialOverride {
|
|
@@ -478,9 +546,24 @@ export interface ModelMaterialOverride {
|
|
|
478
546
|
emissive?: string;
|
|
479
547
|
emissiveIntensity?: number;
|
|
480
548
|
}
|
|
549
|
+
/** Parents a prop/weapon model to a named bone or node on the host model's rig — a sword on `handslot.r`, a spellbook offhand — following the bone's animated transform each frame. */
|
|
550
|
+
export interface ModelAttachment {
|
|
551
|
+
/** Bone or node name in the rig to parent to (e.g. `"handslot.r"`). */
|
|
552
|
+
slot: string;
|
|
553
|
+
/** The attached model — a catalog asset id or an inline `ModelConfig`. */
|
|
554
|
+
model: string | ModelConfig;
|
|
555
|
+
/** Local position offset at the bone, in the attached model's own space. */
|
|
556
|
+
position?: [number, number, number];
|
|
557
|
+
/** Local Euler rotation (radians) applied at the bone. */
|
|
558
|
+
rotation?: [number, number, number];
|
|
559
|
+
/** Uniform scale of the attached model at the bone. Default 1. */
|
|
560
|
+
scale?: number;
|
|
561
|
+
}
|
|
481
562
|
export interface ModelConfig {
|
|
482
563
|
url: string;
|
|
483
564
|
scale?: number;
|
|
565
|
+
/** Normalize the rendered model to this world-unit height: the shell measures the loaded scene's bounding box, scales it so its height matches, and grounds its lowest point at the placement Y. Composes with `scale` as a multiplier. */
|
|
566
|
+
targetHeight?: number;
|
|
484
567
|
y?: number;
|
|
485
568
|
/** How the model registers on its placement point. `"center"` (default) horizontally centers the measured footprint on the point and ground-snaps its lowest vertex to the point's Y — the correct behavior for corner-pivot modular kits. `"origin"` renders at the raw GLB origin (legacy). */
|
|
486
569
|
anchor?: "center" | "origin";
|
|
@@ -490,6 +573,8 @@ export interface ModelConfig {
|
|
|
490
573
|
material?: ModelMaterialOverride;
|
|
491
574
|
/** Plays a GLTF animation clip on the model when the source has any (skinned or not); omit to render the rig's bind pose. */
|
|
492
575
|
animation?: ModelAnimationConfig;
|
|
576
|
+
/** Props/weapons parented to named bones on this model's rig; each follows its bone through animation. */
|
|
577
|
+
attachments?: readonly ModelAttachment[];
|
|
493
578
|
}
|
|
494
579
|
export interface ObjectStyle {
|
|
495
580
|
color?: string;
|
|
@@ -505,6 +590,14 @@ export interface DirectionalLightingConfig {
|
|
|
505
590
|
intensity?: number;
|
|
506
591
|
position: readonly [number, number, number];
|
|
507
592
|
castShadow?: boolean;
|
|
593
|
+
/** Shadow map resolution in px (square) when `castShadow`. Higher = crisper, costlier. Default 1024. */
|
|
594
|
+
shadowMapSize?: number;
|
|
595
|
+
/** Half-extent of the orthographic shadow camera in world units — sized to the shadowed area. Default 40. */
|
|
596
|
+
shadowCameraSize?: number;
|
|
597
|
+
/** Shadow depth bias to fight acne. Default -0.0004. */
|
|
598
|
+
shadowBias?: number;
|
|
599
|
+
/** Shadow normal bias to fight peter-panning. Default 0.02. */
|
|
600
|
+
shadowNormalBias?: number;
|
|
508
601
|
}
|
|
509
602
|
export interface HemisphereLightingConfig {
|
|
510
603
|
skyColor?: string;
|
|
@@ -542,6 +635,8 @@ export interface PlayerMovementConfig {
|
|
|
542
635
|
collideObjects?: boolean;
|
|
543
636
|
/** Intercepts each frame's resolved position before the pose commits (and before onTick): return a replacement [x,y,z] to constrain or redirect the step, or nothing to accept it. */
|
|
544
637
|
beforeCommit?: (frame: MovementCommitFrame) => readonly [number, number, number] | undefined | void;
|
|
638
|
+
/** Gates the built-in sprint (`runSpeedMultiplier`) behind live game state — a stamina stat, an encumbrance check (#282.3). Called each frame while sprint is held; `false` walks. */
|
|
639
|
+
canSprint?: (ctx: GameContext) => boolean;
|
|
545
640
|
}
|
|
546
641
|
/** One frame's movement resolution handed to `PlayerMovementConfig.beforeCommit`. */
|
|
547
642
|
export interface MovementCommitFrame {
|
|
@@ -549,11 +644,13 @@ export interface MovementCommitFrame {
|
|
|
549
644
|
current: readonly [number, number, number];
|
|
550
645
|
next: readonly [number, number, number];
|
|
551
646
|
dt: number;
|
|
647
|
+
/** The live game context (#282.2) — movement rules read stores/stats directly instead of module-level bridge objects. */
|
|
648
|
+
ctx: GameContext;
|
|
552
649
|
}
|
|
553
650
|
export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEntity = never, TRenderObject = never> {
|
|
554
651
|
game: GameDefinition;
|
|
555
652
|
content: GameContextContent;
|
|
556
|
-
loop: Required<GameLoop<GameContext
|
|
653
|
+
loop: Required<Omit<GameLoop<GameContext>, "onPlayerLeave">> & Pick<GameLoop<GameContext>, "onPlayerLeave">;
|
|
557
654
|
GameUI: TUi;
|
|
558
655
|
/** Which shell mount to use. Default `"3d"` (canvas, camera rig, pointer, world rendering). `"hud"` mounts no 3D canvas, camera rig, or pointer — the game is `GameUI` plus the command/input loop, for board/card/menu games. */
|
|
559
656
|
presentation?: "3d" | "hud";
|
|
@@ -585,15 +682,24 @@ export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEnt
|
|
|
585
682
|
pointer?: PointerConfig;
|
|
586
683
|
/** Touch controls on coarse-pointer devices. Unset derives a scheme from `input` (virtual joystick for movement actions, on-screen buttons for the rest); a config refines it with gestures and curated buttons; `false` opts out. */
|
|
587
684
|
touch?: TouchControlsConfig | false;
|
|
588
|
-
/**
|
|
685
|
+
/** Phone orientation contract. Legacy `"landscape"`/`"portrait"` stays advisory (a dismissible rotate hint). The object form `{ mobile: "landscape-required" }` is strict — the shell shows an engine-owned rotate screen and blocks gameplay until the device is turned. See `GameOrientation`. */
|
|
686
|
+
orientation?: GameOrientation;
|
|
687
|
+
/** Where the game is meant to be played. Default `["web", "mobile"]` — design-resolution HUD fit is on for every game: `HudCanvas` auto-scales from `hudFit.designSize` down to the live viewport, so the desktop layout shrinks to fit a phone instead of overflowing it. Declare `["web"]` to opt a desktop-only game out (compact displays fall back to the legacy fixed 0.85 zoom). */
|
|
688
|
+
platforms?: readonly HudPlatform[];
|
|
689
|
+
/** HUD design resolution + scale clamps (default 1600×900, scale 0.4–1). `mobile` overrides tune the phone fit separately — the same resolution system drives desktop UI-scale and phone shrink. */
|
|
690
|
+
hudFit?: HudViewportConfig;
|
|
691
|
+
/** Opt in to world-space health bars floating over non-local entities that carry the stat. `roles` restricts bars to entities whose catalog entry declares one of the given roles; `maxDistance` hides bars beyond this many world units from the player (default 60). */
|
|
589
692
|
worldHealthBars?: boolean | {
|
|
590
693
|
statId?: string;
|
|
591
694
|
roles?: readonly CatalogEntityRole[];
|
|
695
|
+
maxDistance?: number;
|
|
592
696
|
};
|
|
593
|
-
/** Sound catalog + mix buses (music/sfx/ambient/…) the shell's Web Audio glue plays from. Catalog-first — no per-game audio wiring. */
|
|
697
|
+
/** Sound catalog + mix buses (music/sfx/ambient/…) the shell's Web Audio glue plays from. Catalog-first — no per-game audio wiring. `sounds` may be sample (`url`) or procedural (`synth`); `music` holds procedural themes crossfaded via `ctx.game.audio.music(id)`. */
|
|
594
698
|
audio?: {
|
|
595
699
|
sounds: Record<string, SoundDef>;
|
|
596
700
|
buses?: Record<string, AudioBusDef>;
|
|
701
|
+
music?: Record<string, MusicTheme>;
|
|
702
|
+
musicBus?: string;
|
|
597
703
|
};
|
|
598
704
|
/** Continuous positional emitter keyed by entity kind name: while a matching entity exists, the shell plays and repositions the mapped `audio.sounds` id (looping engine hum, campfire crackle, footstep loop) with listener-distance falloff. */
|
|
599
705
|
entitySounds?: Record<string, string>;
|
|
@@ -609,7 +715,13 @@ export interface PlayableGame<TUi = unknown, TWorldOverlay = unknown, TRenderEnt
|
|
|
609
715
|
lighting?: LightingConfig;
|
|
610
716
|
/** Generic background/sky/fog (#207.6), applied for any world kind including a custom `environment` component. */
|
|
611
717
|
backdrop?: BackdropConfig;
|
|
718
|
+
/** Declarative post-processing chain (AO/bloom/tone-map/grade). When set, the shell mounts an EffectComposer and owns the render; absent leaves the renderer drawing directly (unchanged). */
|
|
719
|
+
postProcessing?: PostProcessingConfig;
|
|
612
720
|
/** F2 debug overlay (frame/sim timing, logs, backend latency, keybinds, live tunables). On for every game by default; `false` disables the toggle. */
|
|
613
721
|
devtools?: boolean;
|
|
722
|
+
/** Player settings menu. Auto-mounted for every game (Sound / Graphics / Gameplay / Controls); unset uses the defaults, `false` opts out. Add game-specific rows via `extra`, switch overlay/full-page via `mode`, or swap the gear for compact on-screen buttons via `surface: "quick"`. */
|
|
723
|
+
settings?: GameSettingsConfig | false;
|
|
724
|
+
/** Automatic camera frustum + distance culling and asset streaming. On by default with conservative margins; unset uses engine defaults, `{ enabled: false }` opts out. Override bounds/distance/pins per entity kind or object catalog id. */
|
|
725
|
+
visibility?: VisibilityConfig;
|
|
614
726
|
}
|
|
615
727
|
export declare function worldHealthBarAllowsRole(roles: readonly CatalogEntityRole[] | undefined, role: CatalogEntityRole | undefined): boolean;
|
package/dist/game/quest.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export interface QuestJournalDeps {
|
|
|
65
65
|
partyMembersNear?(userId: string, radius: number): string[];
|
|
66
66
|
}
|
|
67
67
|
export interface QuestJournal {
|
|
68
|
-
register(catalog: QuestDef[] | Record<string, QuestDef>): void;
|
|
68
|
+
register(catalog: readonly QuestDef[] | Record<string, QuestDef>): void;
|
|
69
69
|
has(questId: string): boolean;
|
|
70
70
|
canAccept(userId: string, questId: string): {
|
|
71
71
|
reason: string;
|
|
@@ -126,7 +126,11 @@ export interface QuestEvaluator {
|
|
|
126
126
|
export declare function applyQuestRewards(rewards: QuestRewards, appliers: {
|
|
127
127
|
grantXp?(amount: number): void;
|
|
128
128
|
grantEconomy?(currencyId: string, amount: number): void;
|
|
129
|
-
grantItem?(inventoryId: string, itemId: string, count: number):
|
|
129
|
+
grantItem?(inventoryId: string, itemId: string, count: number): {
|
|
130
|
+
reason: string;
|
|
131
|
+
} | null | void;
|
|
130
132
|
grantUnlock?(unlockId: string): void;
|
|
131
|
-
}):
|
|
133
|
+
}): {
|
|
134
|
+
reason: string;
|
|
135
|
+
} | null;
|
|
132
136
|
export declare function createQuestEvaluator(defs: QuestDef[] | Record<string, QuestDef>): QuestEvaluator;
|
package/dist/game/quest.js
CHANGED
|
@@ -72,17 +72,20 @@ export function createQuestJournal(deps) {
|
|
|
72
72
|
return null;
|
|
73
73
|
}
|
|
74
74
|
function applyRewards(userId, rewards) {
|
|
75
|
+
for (const entry of rewards.items ?? []) {
|
|
76
|
+
const fail = deps.rewards.grantItem(userId, entry.inventory, entry.item, entry.count);
|
|
77
|
+
if (fail !== null)
|
|
78
|
+
return fail;
|
|
79
|
+
}
|
|
75
80
|
if (rewards.xp)
|
|
76
81
|
deps.rewards.grantXp(userId, rewards.xp.amount);
|
|
77
82
|
for (const [currencyId, amount] of Object.entries(rewards.economy ?? {})) {
|
|
78
83
|
deps.rewards.grantEconomy(userId, currencyId, amount);
|
|
79
84
|
}
|
|
80
|
-
for (const entry of rewards.items ?? []) {
|
|
81
|
-
deps.rewards.grantItem(userId, entry.inventory, entry.item, entry.count);
|
|
82
|
-
}
|
|
83
85
|
for (const unlockId of rewards.unlocks ?? []) {
|
|
84
86
|
deps.rewards.grantUnlock(userId, unlockId);
|
|
85
87
|
}
|
|
88
|
+
return null;
|
|
86
89
|
}
|
|
87
90
|
function turnIn(userId, questId) {
|
|
88
91
|
const denied = canTurnIn(userId, questId);
|
|
@@ -90,9 +93,12 @@ export function createQuestJournal(deps) {
|
|
|
90
93
|
return denied;
|
|
91
94
|
const def = catalog.get(questId);
|
|
92
95
|
const state = users.get(userId).get(questId);
|
|
96
|
+
if (def.rewards) {
|
|
97
|
+
const fail = applyRewards(userId, def.rewards);
|
|
98
|
+
if (fail !== null)
|
|
99
|
+
return fail;
|
|
100
|
+
}
|
|
93
101
|
state.status = "completed";
|
|
94
|
-
if (def.rewards)
|
|
95
|
-
applyRewards(userId, def.rewards);
|
|
96
102
|
deps.events.emit("quest.completed", { userId, questId });
|
|
97
103
|
for (const nextQuestId of def.rewards?.quests ?? []) {
|
|
98
104
|
if (canAccept(userId, nextQuestId) === null)
|
|
@@ -227,17 +233,20 @@ export function createQuestJournal(deps) {
|
|
|
227
233
|
};
|
|
228
234
|
}
|
|
229
235
|
export function applyQuestRewards(rewards, appliers) {
|
|
236
|
+
for (const entry of rewards.items ?? []) {
|
|
237
|
+
const fail = appliers.grantItem?.(entry.inventory, entry.item, entry.count);
|
|
238
|
+
if (fail != null && typeof fail === "object" && "reason" in fail)
|
|
239
|
+
return fail;
|
|
240
|
+
}
|
|
230
241
|
if (rewards.xp)
|
|
231
242
|
appliers.grantXp?.(rewards.xp.amount);
|
|
232
243
|
for (const [currencyId, amount] of Object.entries(rewards.economy ?? {})) {
|
|
233
244
|
appliers.grantEconomy?.(currencyId, amount);
|
|
234
245
|
}
|
|
235
|
-
for (const entry of rewards.items ?? []) {
|
|
236
|
-
appliers.grantItem?.(entry.inventory, entry.item, entry.count);
|
|
237
|
-
}
|
|
238
246
|
for (const unlockId of rewards.unlocks ?? []) {
|
|
239
247
|
appliers.grantUnlock?.(unlockId);
|
|
240
248
|
}
|
|
249
|
+
return null;
|
|
241
250
|
}
|
|
242
251
|
function toWorking(state) {
|
|
243
252
|
const working = new Map();
|
package/dist/game/race.d.ts
CHANGED
|
@@ -3,18 +3,36 @@ export interface Checkpoint {
|
|
|
3
3
|
center: readonly [number, number, number];
|
|
4
4
|
half: readonly [number, number, number];
|
|
5
5
|
}
|
|
6
|
+
export interface RaceForkRoute {
|
|
7
|
+
id: string;
|
|
8
|
+
checkpoints: readonly Checkpoint[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* An alternate-route section (#286.3): after passing mainline checkpoint `afterIndex`, a racer commits
|
|
12
|
+
* to whichever route's first checkpoint they hit, runs its checkpoints in order, and rejoins at
|
|
13
|
+
* mainline `afterIndex + 1`. Every completed route contributes exactly one checkpoint of `progress`
|
|
14
|
+
* regardless of length, and each hit records a split — so route time accounting comes for free.
|
|
15
|
+
*/
|
|
16
|
+
export interface RaceFork {
|
|
17
|
+
id: string;
|
|
18
|
+
/** Mainline checkpoint index the fork opens after; must be before the finish line. */
|
|
19
|
+
afterIndex: number;
|
|
20
|
+
routes: readonly RaceForkRoute[];
|
|
21
|
+
}
|
|
6
22
|
export interface RaceTrackConfig {
|
|
7
23
|
checkpoints: readonly Checkpoint[];
|
|
8
24
|
laps?: number;
|
|
25
|
+
forks?: readonly RaceFork[];
|
|
9
26
|
}
|
|
10
27
|
export interface RaceTrack {
|
|
11
28
|
readonly checkpoints: readonly Checkpoint[];
|
|
12
29
|
readonly laps: number;
|
|
30
|
+
readonly forks: readonly RaceFork[];
|
|
13
31
|
}
|
|
14
32
|
/**
|
|
15
33
|
* A race track is an ordered ring of checkpoint trigger volumes plus a lap count. The final checkpoint
|
|
16
34
|
* is the lap/finish line: a racer completes a lap by passing all checkpoints in order and hitting the
|
|
17
|
-
* last one.
|
|
35
|
+
* last one. `forks` splice alternate route segments between mainline checkpoints.
|
|
18
36
|
*/
|
|
19
37
|
export declare function raceTrack(config: RaceTrackConfig): RaceTrack;
|
|
20
38
|
export interface RacerProgress {
|
|
@@ -28,6 +46,13 @@ export interface RacerProgress {
|
|
|
28
46
|
finishTime: number | null;
|
|
29
47
|
eliminated: boolean;
|
|
30
48
|
splits: readonly number[];
|
|
49
|
+
/** Fork id → route id last taken through it. */
|
|
50
|
+
routesTaken: Readonly<Record<string, string>>;
|
|
51
|
+
/** Route the racer is currently inside, or `null` on the mainline. */
|
|
52
|
+
activeRoute: {
|
|
53
|
+
forkId: string;
|
|
54
|
+
routeId: string;
|
|
55
|
+
} | null;
|
|
31
56
|
}
|
|
32
57
|
export type RaceEvent = {
|
|
33
58
|
type: "checkpoint.hit";
|
|
@@ -35,6 +60,18 @@ export type RaceEvent = {
|
|
|
35
60
|
checkpoint: number;
|
|
36
61
|
lap: number;
|
|
37
62
|
time: number;
|
|
63
|
+
/** Set when the hit is a fork-route checkpoint (`checkpoint` is then the fork's `afterIndex`). */
|
|
64
|
+
fork?: {
|
|
65
|
+
forkId: string;
|
|
66
|
+
routeId: string;
|
|
67
|
+
index: number;
|
|
68
|
+
};
|
|
69
|
+
} | {
|
|
70
|
+
type: "fork.taken";
|
|
71
|
+
racerId: string;
|
|
72
|
+
forkId: string;
|
|
73
|
+
routeId: string;
|
|
74
|
+
time: number;
|
|
38
75
|
} | {
|
|
39
76
|
type: "lap.completed";
|
|
40
77
|
racerId: string;
|
|
@@ -90,6 +127,7 @@ export declare class RaceState {
|
|
|
90
127
|
progressOf(racerId: string): RacerProgress | null;
|
|
91
128
|
private snapshot;
|
|
92
129
|
standings(): readonly RacerProgress[];
|
|
130
|
+
private expectedCheckpoint;
|
|
93
131
|
resetToCheckpoint(racerId: string): {
|
|
94
132
|
position: [number, number, number];
|
|
95
133
|
heading: number;
|