@jgengine/core 0.11.0 → 0.13.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 +52 -0
- package/dist/ai/populationDirector.d.ts +103 -0
- package/dist/ai/populationDirector.js +190 -0
- package/dist/ai/pursuit.d.ts +56 -0
- package/dist/ai/pursuit.js +53 -0
- package/dist/authoring.d.ts +1 -1
- package/dist/authoring.js +1 -1
- package/dist/combat/effects.d.ts +11 -4
- package/dist/combat/effects.js +24 -17
- package/dist/combat/magazine.d.ts +13 -0
- package/dist/combat/magazine.js +27 -0
- package/dist/combat/vfxPresets.d.ts +176 -0
- package/dist/combat/vfxPresets.js +74 -0
- package/dist/combat/weaponFire.d.ts +114 -0
- package/dist/combat/weaponFire.js +96 -0
- package/dist/combat.d.ts +5 -2
- package/dist/combat.js +4 -1
- package/dist/devtools/controls.d.ts +38 -0
- package/dist/devtools/controls.js +181 -0
- package/dist/devtools/devtools.d.ts +5 -197
- package/dist/devtools/devtools.js +38 -774
- package/dist/devtools/discover.d.ts +32 -0
- package/dist/devtools/discover.js +240 -0
- package/dist/devtools/fallbackSeams.d.ts +50 -0
- package/dist/devtools/fallbackSeams.js +111 -0
- package/dist/devtools/frame.d.ts +15 -0
- package/dist/devtools/frame.js +247 -0
- package/dist/devtools/logs.d.ts +15 -0
- package/dist/devtools/logs.js +86 -0
- package/dist/devtools/overrides.d.ts +16 -0
- package/dist/devtools/overrides.js +66 -0
- package/dist/devtools/types.d.ts +212 -0
- package/dist/devtools/types.js +1 -0
- package/dist/devtools/urlFlags.d.ts +22 -0
- package/dist/devtools/urlFlags.js +62 -0
- package/dist/economy/auctionBook.d.ts +122 -0
- package/dist/economy/auctionBook.js +213 -0
- package/dist/economy/priceHistory.d.ts +44 -0
- package/dist/economy/priceHistory.js +89 -0
- package/dist/economy/unlockPoints.d.ts +92 -0
- package/dist/economy/unlockPoints.js +103 -0
- package/dist/editor/commandApply.d.ts +5 -0
- package/dist/editor/commandApply.js +755 -0
- package/dist/editor/commands.d.ts +48 -3
- package/dist/editor/commands.js +40 -754
- package/dist/editor/directives.d.ts +94 -0
- package/dist/editor/directives.js +206 -0
- package/dist/editor/document.d.ts +37 -2
- package/dist/editor/document.js +422 -6
- package/dist/editor/entityCatalog.d.ts +42 -0
- package/dist/editor/entityCatalog.js +101 -0
- package/dist/editor/environment.d.ts +74 -0
- package/dist/editor/environment.js +169 -0
- package/dist/editor/index.d.ts +26 -1
- package/dist/editor/index.js +8 -1
- package/dist/editor/minimap.d.ts +31 -0
- package/dist/editor/minimap.js +73 -0
- package/dist/editor/streamer.d.ts +66 -0
- package/dist/editor/streamer.js +68 -0
- package/dist/editor/types.d.ts +143 -0
- package/dist/editor/world.d.ts +136 -0
- package/dist/editor/world.js +303 -0
- package/dist/game/breeding.d.ts +161 -0
- package/dist/game/breeding.js +181 -0
- package/dist/game/cameraConfig.d.ts +451 -0
- package/dist/game/cameraConfig.js +1 -0
- package/dist/game/clipRoles.d.ts +51 -0
- package/dist/game/clipRoles.js +142 -0
- package/dist/game/defineGame.d.ts +16 -9
- package/dist/game/defineGame.js +14 -6
- package/dist/game/defineSystem.d.ts +2 -0
- package/dist/game/defineSystem.js +2 -0
- package/dist/game/events.d.ts +28 -0
- package/dist/game/partAnimation.d.ts +78 -0
- package/dist/game/partAnimation.js +147 -0
- package/dist/game/playableGame.d.ts +72 -440
- package/dist/game/playableGame.js +1 -1
- package/dist/game/progression.d.ts +35 -0
- package/dist/game/progression.js +22 -1
- package/dist/game/quest.js +1 -3
- package/dist/game/tribe.d.ts +184 -0
- package/dist/game/tribe.js +432 -0
- package/dist/gameplay.d.ts +3 -1
- package/dist/gameplay.js +3 -1
- package/dist/inventory/encumbrance.d.ts +69 -0
- package/dist/inventory/encumbrance.js +68 -0
- package/dist/inventory/inventoryModel.d.ts +24 -8
- package/dist/inventory/inventoryModel.js +47 -8
- package/dist/item/durability.js +1 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +48 -1
- package/dist/movement/movementModel.d.ts +15 -1
- package/dist/movement/movementModel.js +33 -15
- package/dist/movement/playerMovement.js +102 -2
- package/dist/multiplayer/combatSnapshot.js +13 -8
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +8 -0
- package/dist/physics/drivableVehicle.d.ts +2 -0
- package/dist/physics/drivableVehicle.js +1 -1
- package/dist/physics/flightDynamics.d.ts +82 -0
- package/dist/physics/flightDynamics.js +165 -0
- package/dist/physics/gravityField.d.ts +25 -0
- package/dist/physics/gravityField.js +41 -0
- package/dist/physics/kinematicVehicle.d.ts +87 -0
- package/dist/physics/kinematicVehicle.js +193 -11
- package/dist/physics/physicsWorld.d.ts +26 -0
- package/dist/physics/physicsWorld.js +109 -117
- package/dist/physics/vehicleObstacles.d.ts +44 -0
- package/dist/physics/vehicleObstacles.js +115 -0
- package/dist/random/rng.d.ts +4 -1
- package/dist/random/rng.js +4 -1
- package/dist/runtime/adapter.d.ts +13 -0
- package/dist/runtime/adapter.js +13 -0
- package/dist/runtime/context/combat.d.ts +51 -0
- package/dist/runtime/context/combat.js +131 -0
- package/dist/runtime/context/combatFx.js +15 -5
- package/dist/runtime/context/deathLoot.d.ts +30 -0
- package/dist/runtime/context/deathLoot.js +39 -0
- package/dist/runtime/context/player.d.ts +48 -0
- package/dist/runtime/context/player.js +125 -0
- package/dist/runtime/context/scene.d.ts +60 -0
- package/dist/runtime/context/scene.js +329 -0
- package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
- package/dist/runtime/context/snapshotCodecs.js +26 -0
- package/dist/runtime/descriptors/baseline.js +26 -3
- package/dist/runtime/descriptors/features.d.ts +5 -0
- package/dist/runtime/descriptors/features.js +20 -2
- package/dist/runtime/gameContext.d.ts +2 -428
- package/dist/runtime/gameContext.js +133 -500
- package/dist/runtime/gameContextTypes.d.ts +500 -0
- package/dist/runtime/gameContextTypes.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +17 -2
- package/dist/runtime/headlessRunner.js +15 -0
- package/dist/runtime/hostPolicy.d.ts +51 -0
- package/dist/runtime/hostPolicy.js +68 -0
- package/dist/runtime/hostedGameRunner.d.ts +3 -1
- package/dist/runtime/hostedGameRunner.js +1 -0
- package/dist/runtime/hostedWorldSession.d.ts +3 -1
- package/dist/runtime/hostedWorldSession.js +1 -0
- package/dist/runtime/worldSnapshot.d.ts +13 -1
- package/dist/runtime/worldSnapshot.js +15 -3
- package/dist/scene/assetCatalog.d.ts +7 -0
- package/dist/scene/assetSpace.js +1 -3
- package/dist/scene/authoredTriggers.d.ts +50 -0
- package/dist/scene/authoredTriggers.js +114 -0
- package/dist/scene/builtinSceneKinds.js +8 -3
- package/dist/scene/colliders.d.ts +79 -0
- package/dist/scene/colliders.js +170 -0
- package/dist/scene/collisionMesh.d.ts +94 -0
- package/dist/scene/collisionMesh.js +699 -0
- package/dist/scene/companion.d.ts +147 -0
- package/dist/scene/companion.js +193 -0
- package/dist/scene/entityStats.d.ts +7 -9
- package/dist/scene/entityStats.js +19 -15
- package/dist/scene/sceneKinds.d.ts +16 -0
- package/dist/scene/sceneKinds.js +4 -0
- package/dist/scene/sceneRaycast.js +15 -0
- package/dist/stats/spawnLevelStats.d.ts +110 -0
- package/dist/stats/spawnLevelStats.js +140 -0
- package/dist/stats/statModifiers.d.ts +16 -0
- package/dist/stats/statModifiers.js +30 -1
- package/dist/stats/statPool.d.ts +70 -0
- package/dist/stats/statPool.js +62 -0
- package/dist/store/changeSignal.d.ts +3 -0
- package/dist/store/changeSignal.js +2 -0
- package/dist/store/observableKeyedStore.d.ts +2 -0
- package/dist/store/observableKeyedStore.js +1 -0
- package/dist/survival/decayMeter.js +1 -3
- package/dist/survival/regionHealth.js +1 -3
- package/dist/ui/hudLayout.d.ts +5 -0
- package/dist/ui/hudLayout.js +17 -1
- package/dist/ui/panelModel.d.ts +120 -0
- package/dist/ui/panelModel.js +168 -0
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +1 -0
- package/dist/world/authoredEntities.d.ts +29 -0
- package/dist/world/authoredEntities.js +29 -0
- package/dist/world/authoredObjects.d.ts +15 -1
- package/dist/world/authoredObjects.js +13 -1
- package/dist/world/authoredSpawn.d.ts +39 -0
- package/dist/world/authoredSpawn.js +37 -0
- package/dist/world/buildSockets.d.ts +152 -0
- package/dist/world/buildSockets.js +188 -0
- package/dist/world/buildingLots.d.ts +84 -0
- package/dist/world/buildingLots.js +119 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/cityBlocks.d.ts +152 -0
- package/dist/world/cityBlocks.js +670 -0
- package/dist/world/cityContent.d.ts +77 -0
- package/dist/world/cityContent.js +261 -0
- package/dist/world/cityGenerator.d.ts +41 -0
- package/dist/world/cityGenerator.js +58 -0
- package/dist/world/cityGeometry.d.ts +114 -0
- package/dist/world/cityGeometry.js +573 -0
- package/dist/world/cityKind.d.ts +314 -0
- package/dist/world/cityKind.js +1578 -0
- package/dist/world/environmentSummary.js +57 -1
- package/dist/world/features.d.ts +53 -5
- package/dist/world/features.js +15 -3
- package/dist/world/grassKind.js +2 -2
- package/dist/world/markers.d.ts +50 -4
- package/dist/world/markers.js +49 -0
- package/dist/world/massing.js +1 -1
- package/dist/world/minimapBake.d.ts +78 -0
- package/dist/world/minimapBake.js +208 -0
- package/dist/world/minimapTrack.d.ts +17 -0
- package/dist/world/minimapTrack.js +22 -0
- package/dist/world/place.d.ts +166 -0
- package/dist/world/place.js +96 -0
- package/dist/world/placeAsset.js +3 -1
- package/dist/world/poleLineKind.d.ts +40 -0
- package/dist/world/poleLineKind.js +97 -0
- package/dist/world/polyline.js +9 -2
- package/dist/world/resourceNode.d.ts +109 -0
- package/dist/world/resourceNode.js +140 -0
- package/dist/world/roads.d.ts +19 -2
- package/dist/world/roads.js +60 -3
- package/dist/world/scatter.js +2 -20
- package/dist/world/streetGenerator.d.ts +159 -0
- package/dist/world/streetGenerator.js +684 -0
- package/dist/world/streets.d.ts +103 -1
- package/dist/world/streets.js +273 -0
- package/dist/world/terraform.js +58 -88
- package/dist/world/terrain.d.ts +65 -1
- package/dist/world/terrain.js +119 -2
- package/dist/world.d.ts +14 -5
- package/dist/world.js +12 -3
- package/package.json +1 -1
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +8 -4
- package/skills/jgengine/api.md +182 -81
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-combat/SKILL.md +19 -1
- package/skills/jgengine-combat/api.md +75 -2
- package/skills/jgengine-combat/capabilities.md +61 -1
- package/skills/jgengine-combat/recipes/portable-damage-effects.md +197 -0
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +154 -0
- package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +156 -0
- package/skills/jgengine-combat/recipes/visible-attacks.md +81 -0
- package/skills/jgengine-combat/reference.md +33 -4
- package/skills/jgengine-gameplay/SKILL.md +8 -0
- package/skills/jgengine-gameplay/api.md +182 -26
- package/skills/jgengine-gameplay/capabilities.md +96 -2
- package/skills/jgengine-gameplay/recipes/portable-xp-leveling.md +168 -0
- package/skills/jgengine-gameplay/reference-systems.md +10 -5
- package/skills/jgengine-world/SKILL.md +13 -0
- package/skills/jgengine-world/api.md +453 -21
- package/skills/jgengine-world/capabilities.md +112 -2
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +43 -4
- package/skills/jgengine/reference-cartridge.md +0 -72
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
function clamp01(value) {
|
|
2
|
+
return value < 0 ? 0 : value > 1 ? 1 : value;
|
|
3
|
+
}
|
|
4
|
+
function normalizeLevel(level) {
|
|
5
|
+
const floored = Math.floor(level);
|
|
6
|
+
return floored < 1 ? 1 : floored;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Scatter `count` points one at a time across `statIds`, choosing each target uniformly from the
|
|
10
|
+
* injected rng. Mutates `into` in place; bounded by `count`. The rng draws the sole source of
|
|
11
|
+
* randomness, so an identical rng sequence reproduces an identical distribution.
|
|
12
|
+
*/
|
|
13
|
+
function distributePoints(statIds, count, rng, into) {
|
|
14
|
+
const n = statIds.length;
|
|
15
|
+
if (n === 0 || count <= 0)
|
|
16
|
+
return;
|
|
17
|
+
for (let i = 0; i < count; i++) {
|
|
18
|
+
let index = Math.floor(rng() * n);
|
|
19
|
+
if (index >= n)
|
|
20
|
+
index = n - 1;
|
|
21
|
+
if (index < 0)
|
|
22
|
+
index = 0;
|
|
23
|
+
const stat = statIds[index];
|
|
24
|
+
into[stat] = (into[stat] ?? 0) + 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function zeroedPoints(statIds) {
|
|
28
|
+
const points = {};
|
|
29
|
+
for (const stat of statIds)
|
|
30
|
+
points[stat] = 0;
|
|
31
|
+
return points;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Roll a fresh wild creature at `level`: distribute `(level - 1)` points randomly across the
|
|
35
|
+
* species' stats using the injected rng and return a serializable {@link CreatureStatInstance}.
|
|
36
|
+
* Deterministic — the same rng sequence yields the same point spread. Levels below 1 clamp to 1
|
|
37
|
+
* (no points). Use `seededRng` from `../random/rng` for a persisted, reproducible stream.
|
|
38
|
+
*
|
|
39
|
+
* @capability spawn-level-roll distribute a wild creature's spawn level into random per-stat points
|
|
40
|
+
*/
|
|
41
|
+
export function rollSpawnInstance(speciesId, species, level, rng) {
|
|
42
|
+
const statIds = Object.keys(species);
|
|
43
|
+
const points = zeroedPoints(statIds);
|
|
44
|
+
const normalized = normalizeLevel(level);
|
|
45
|
+
distributePoints(statIds, normalized - 1, rng, points);
|
|
46
|
+
return { speciesId, level: normalized, points, domesticLevels: 0 };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve an instance's live stat values from its distributed points:
|
|
50
|
+
* `value = base * (1 + points * growth)` per stat. Stats with zero points return their base.
|
|
51
|
+
*
|
|
52
|
+
* @capability spawn-stat-values resolve a creature's per-stat values from its distributed points
|
|
53
|
+
*/
|
|
54
|
+
export function resolveSpawnStats(species, instance) {
|
|
55
|
+
const out = {};
|
|
56
|
+
for (const stat of Object.keys(species)) {
|
|
57
|
+
const def = species[stat];
|
|
58
|
+
const pts = instance.points[stat] ?? 0;
|
|
59
|
+
out[stat] = def.base * (1 + pts * def.growth);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Bonus levels granted by a capture at the given effectiveness:
|
|
65
|
+
* `floor(level * clamp01(effectiveness) / 2)`. A perfect (1.0) capture grants half the wild
|
|
66
|
+
* level; a botched (0) capture grants none. Effectiveness outside `[0,1]` clamps.
|
|
67
|
+
*
|
|
68
|
+
* @capability capture-bonus-levels convert wild level and capture effectiveness into bonus levels
|
|
69
|
+
*/
|
|
70
|
+
export function captureBonusLevels(level, effectiveness) {
|
|
71
|
+
return Math.floor((normalizeLevel(level) * clamp01(effectiveness)) / 2);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Apply a capture at the given effectiveness: compute bonus levels via {@link captureBonusLevels},
|
|
75
|
+
* distribute those extra points randomly with the injected rng, and return the post-capture
|
|
76
|
+
* instance (level raised to the tamed total) alongside the granted bonus levels and tamed level.
|
|
77
|
+
* The input instance is not mutated.
|
|
78
|
+
*
|
|
79
|
+
* @capability capture-effectiveness-tame apply capture effectiveness to grant and distribute bonus levels
|
|
80
|
+
*/
|
|
81
|
+
export function applyCaptureEffectiveness(species, instance, effectiveness, rng) {
|
|
82
|
+
const bonusLevels = captureBonusLevels(instance.level, effectiveness);
|
|
83
|
+
const statIds = Object.keys(species);
|
|
84
|
+
const points = { ...instance.points };
|
|
85
|
+
distributePoints(statIds, bonusLevels, rng, points);
|
|
86
|
+
const tamedLevel = instance.level + bonusLevels;
|
|
87
|
+
return {
|
|
88
|
+
bonusLevels,
|
|
89
|
+
tamedLevel,
|
|
90
|
+
instance: { ...instance, level: tamedLevel, points },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Spend one earned post-capture level into `stat`, adding a single growth increment (one point)
|
|
95
|
+
* and raising both `level` and `domesticLevels`. When `maxDomesticLevels` is set and already
|
|
96
|
+
* reached, the spend is refused (`spent: false`) and the instance is returned untouched. Pure:
|
|
97
|
+
* returns a new instance rather than mutating the input.
|
|
98
|
+
*
|
|
99
|
+
* @capability domestic-level-spend spend an earned post-capture level into a chosen stat under a cap
|
|
100
|
+
*/
|
|
101
|
+
export function spendDomesticLevel(instance, stat, options) {
|
|
102
|
+
const cap = options?.maxDomesticLevels;
|
|
103
|
+
if (cap !== undefined && instance.domesticLevels >= cap) {
|
|
104
|
+
return { instance, spent: false };
|
|
105
|
+
}
|
|
106
|
+
const points = { ...instance.points };
|
|
107
|
+
points[stat] = (points[stat] ?? 0) + 1;
|
|
108
|
+
return {
|
|
109
|
+
instance: {
|
|
110
|
+
...instance,
|
|
111
|
+
points,
|
|
112
|
+
level: instance.level + 1,
|
|
113
|
+
domesticLevels: instance.domesticLevels + 1,
|
|
114
|
+
},
|
|
115
|
+
spent: true,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Deep-copy an instance into a detached, serializable record — the transport/save counterpart of
|
|
120
|
+
* {@link restoreSpawnInstance}. Copies the points map so later edits don't leak into the snapshot.
|
|
121
|
+
*
|
|
122
|
+
* @capability spawn-instance-snapshot deep-copy a creature stat instance into a serializable record
|
|
123
|
+
*/
|
|
124
|
+
export function snapshotSpawnInstance(instance) {
|
|
125
|
+
return {
|
|
126
|
+
speciesId: instance.speciesId,
|
|
127
|
+
level: instance.level,
|
|
128
|
+
domesticLevels: instance.domesticLevels,
|
|
129
|
+
points: { ...instance.points },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Rehydrate a creature stat instance from decoded data (e.g. `JSON.parse` output), returning a
|
|
134
|
+
* fresh deep copy the caller owns. Inverse of {@link snapshotSpawnInstance}.
|
|
135
|
+
*
|
|
136
|
+
* @capability spawn-instance-restore rehydrate a creature stat instance from decoded snapshot data
|
|
137
|
+
*/
|
|
138
|
+
export function restoreSpawnInstance(data) {
|
|
139
|
+
return snapshotSpawnInstance(data);
|
|
140
|
+
}
|
|
@@ -6,6 +6,17 @@ export type StatModifierSet<TStat extends string> = Partial<Record<TStat, StatMo
|
|
|
6
6
|
export interface CreateStatsOptions {
|
|
7
7
|
now?: () => number;
|
|
8
8
|
}
|
|
9
|
+
/** One serializable modifier source in a {@link StatsSnapshot}. */
|
|
10
|
+
interface StatModifierSourceSnapshot<TStat extends string> {
|
|
11
|
+
id: string;
|
|
12
|
+
modifiers: StatModifierSet<TStat>;
|
|
13
|
+
expiresAtMs?: number;
|
|
14
|
+
}
|
|
15
|
+
/** Complete plain-data state for a {@link Stats} runtime. */
|
|
16
|
+
export interface StatsSnapshot<TStat extends string> {
|
|
17
|
+
base: Record<TStat, number>;
|
|
18
|
+
sources: StatModifierSourceSnapshot<TStat>[];
|
|
19
|
+
}
|
|
9
20
|
export interface Stats<TStat extends string> {
|
|
10
21
|
setBase(stat: TStat, value: number): void;
|
|
11
22
|
getBase(stat: TStat): number;
|
|
@@ -17,6 +28,10 @@ export interface Stats<TStat extends string> {
|
|
|
17
28
|
get(stat: TStat, nowMs?: number): number;
|
|
18
29
|
pruneExpired(nowMs: number): string[];
|
|
19
30
|
sources(): string[];
|
|
31
|
+
/** Return a detached JSON-serializable copy of base values and modifier sources. */
|
|
32
|
+
snapshot(): StatsSnapshot<TStat>;
|
|
33
|
+
/** Replace all runtime state from caller-owned decoded snapshot data. */
|
|
34
|
+
restore(snapshot: StatsSnapshot<TStat>): void;
|
|
20
35
|
}
|
|
21
36
|
/**
|
|
22
37
|
* A stat block whose base values take stacking, timed buffs and debuffs, resolving the modified value on read.
|
|
@@ -24,3 +39,4 @@ export interface Stats<TStat extends string> {
|
|
|
24
39
|
* @capability stat-block base stats with stacking, expiring buffs and debuffs applied on read
|
|
25
40
|
*/
|
|
26
41
|
export declare function createStats<TStat extends string>(base: Record<TStat, number>, options?: CreateStatsOptions): Stats<TStat>;
|
|
42
|
+
export {};
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
function cloneModifierSet(modifiers) {
|
|
2
|
+
const copy = {};
|
|
3
|
+
for (const stat of Object.keys(modifiers)) {
|
|
4
|
+
const modifier = modifiers[stat];
|
|
5
|
+
if (modifier !== undefined)
|
|
6
|
+
copy[stat] = { ...modifier };
|
|
7
|
+
}
|
|
8
|
+
return copy;
|
|
9
|
+
}
|
|
1
10
|
/**
|
|
2
11
|
* A stat block whose base values take stacking, timed buffs and debuffs, resolving the modified value on read.
|
|
3
12
|
*
|
|
4
13
|
* @capability stat-block base stats with stacking, expiring buffs and debuffs applied on read
|
|
5
14
|
*/
|
|
6
15
|
export function createStats(base, options) {
|
|
7
|
-
|
|
16
|
+
let baseValues = { ...base };
|
|
8
17
|
const sourceEntries = new Map();
|
|
9
18
|
const clock = options?.now;
|
|
10
19
|
function resolveNow(nowMs) {
|
|
@@ -75,5 +84,25 @@ export function createStats(base, options) {
|
|
|
75
84
|
sources() {
|
|
76
85
|
return Array.from(sourceEntries.keys());
|
|
77
86
|
},
|
|
87
|
+
snapshot() {
|
|
88
|
+
return {
|
|
89
|
+
base: { ...baseValues },
|
|
90
|
+
sources: Array.from(sourceEntries, ([id, entry]) => ({
|
|
91
|
+
id,
|
|
92
|
+
modifiers: cloneModifierSet(entry.modifiers),
|
|
93
|
+
...(entry.expiresAtMs === undefined ? {} : { expiresAtMs: entry.expiresAtMs }),
|
|
94
|
+
})),
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
restore(snapshot) {
|
|
98
|
+
baseValues = { ...snapshot.base };
|
|
99
|
+
sourceEntries.clear();
|
|
100
|
+
for (const source of snapshot.sources) {
|
|
101
|
+
sourceEntries.set(source.id, {
|
|
102
|
+
modifiers: cloneModifierSet(source.modifiers),
|
|
103
|
+
expiresAtMs: source.expiresAtMs,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
},
|
|
78
107
|
};
|
|
79
108
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Serializable state for any bounded named resource. The caller defines what the resource means. */
|
|
2
|
+
export interface StatPool {
|
|
3
|
+
current: number;
|
|
4
|
+
max: number;
|
|
5
|
+
min: number;
|
|
6
|
+
}
|
|
7
|
+
/** Input used to create a normalized pool. `current` defaults to `max`; `min` defaults to zero. */
|
|
8
|
+
export interface StatPoolInput {
|
|
9
|
+
current?: number;
|
|
10
|
+
max: number;
|
|
11
|
+
min?: number;
|
|
12
|
+
}
|
|
13
|
+
/** Partial update applied without mutating the previous pool. */
|
|
14
|
+
export interface StatPoolPatch {
|
|
15
|
+
current?: number;
|
|
16
|
+
max?: number;
|
|
17
|
+
min?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Structural bridge to caller-owned resource state. Implement it over an ECS,
|
|
21
|
+
* Redux/Zustand store, plain object, database-backed model, or JGengine's native
|
|
22
|
+
* entity stats without moving ownership into the SDK.
|
|
23
|
+
*/
|
|
24
|
+
export interface StatPoolAccess<TOwnerId extends string = string, TStatId extends string = string> {
|
|
25
|
+
get(ownerId: TOwnerId, statId: TStatId): StatPool | null;
|
|
26
|
+
set(ownerId: TOwnerId, statId: TStatId, next: StatPool): unknown;
|
|
27
|
+
}
|
|
28
|
+
/** Inspectable result of a bounded pool change. */
|
|
29
|
+
export interface StatPoolChange {
|
|
30
|
+
previous: StatPool;
|
|
31
|
+
pool: StatPool;
|
|
32
|
+
/** Actual applied amount after clamping. */
|
|
33
|
+
applied: number;
|
|
34
|
+
hitMin: boolean;
|
|
35
|
+
hitMax: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Outcome of changing a pool through {@link StatPoolAccess}. */
|
|
38
|
+
export type StatPoolAccessResult = ({
|
|
39
|
+
status: "ok";
|
|
40
|
+
} & StatPoolChange) | {
|
|
41
|
+
status: "rejected";
|
|
42
|
+
reason: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Normalize caller data into a complete plain-data pool.
|
|
46
|
+
*
|
|
47
|
+
* @capability stat-pool create a serializable current/min/max pool for any caller-named resource
|
|
48
|
+
*/
|
|
49
|
+
export declare function createStatPool(input: StatPoolInput): StatPool;
|
|
50
|
+
/**
|
|
51
|
+
* Apply new bounds/current to a pool, clamping the result and leaving the input untouched.
|
|
52
|
+
*
|
|
53
|
+
* @capability stat-pool-patch immutably retune a bounded resource's current value or bounds
|
|
54
|
+
*/
|
|
55
|
+
export declare function patchStatPool(pool: StatPool, patch: StatPoolPatch): StatPool;
|
|
56
|
+
/**
|
|
57
|
+
* Pure bounded delta transition with the exact applied amount and boundary flags.
|
|
58
|
+
*
|
|
59
|
+
* @capability stat-pool-change immutably increase or decrease any named bounded resource with clamp evidence
|
|
60
|
+
*/
|
|
61
|
+
export declare function changeStatPool(pool: StatPool, amount: number): StatPoolChange;
|
|
62
|
+
/**
|
|
63
|
+
* Read, transition, and write a caller-owned pool through the structural
|
|
64
|
+
* adapter. Positive amounts increase the resource; negative amounts decrease
|
|
65
|
+
* it. The adapter receives a complete replacement value, never a hidden
|
|
66
|
+
* mutation.
|
|
67
|
+
*
|
|
68
|
+
* @capability portable-stat-pool apply deterministic bounded resource changes through a caller-owned stat adapter
|
|
69
|
+
*/
|
|
70
|
+
export declare function applyStatPoolDelta<TOwnerId extends string, TStatId extends string>(access: StatPoolAccess<TOwnerId, TStatId>, ownerId: TOwnerId, statId: TStatId, amount: number): StatPoolAccessResult;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { clamp } from "../math/scalar.js";
|
|
2
|
+
/**
|
|
3
|
+
* Normalize caller data into a complete plain-data pool.
|
|
4
|
+
*
|
|
5
|
+
* @capability stat-pool create a serializable current/min/max pool for any caller-named resource
|
|
6
|
+
*/
|
|
7
|
+
export function createStatPool(input) {
|
|
8
|
+
const min = input.min ?? 0;
|
|
9
|
+
return {
|
|
10
|
+
current: clamp(input.current ?? input.max, min, input.max),
|
|
11
|
+
max: input.max,
|
|
12
|
+
min,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Apply new bounds/current to a pool, clamping the result and leaving the input untouched.
|
|
17
|
+
*
|
|
18
|
+
* @capability stat-pool-patch immutably retune a bounded resource's current value or bounds
|
|
19
|
+
*/
|
|
20
|
+
export function patchStatPool(pool, patch) {
|
|
21
|
+
const max = patch.max ?? pool.max;
|
|
22
|
+
const min = patch.min ?? pool.min;
|
|
23
|
+
return {
|
|
24
|
+
current: clamp(patch.current ?? pool.current, min, max),
|
|
25
|
+
max,
|
|
26
|
+
min,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Pure bounded delta transition with the exact applied amount and boundary flags.
|
|
31
|
+
*
|
|
32
|
+
* @capability stat-pool-change immutably increase or decrease any named bounded resource with clamp evidence
|
|
33
|
+
*/
|
|
34
|
+
export function changeStatPool(pool, amount) {
|
|
35
|
+
const next = patchStatPool(pool, { current: pool.current + amount });
|
|
36
|
+
const applied = next.current - pool.current;
|
|
37
|
+
return {
|
|
38
|
+
previous: pool,
|
|
39
|
+
// Preserve the input reference on a no-op change (regen at max, damage at 0,
|
|
40
|
+
// clamp to a bound) so reference-equality guards in HUD hooks don't churn.
|
|
41
|
+
pool: applied === 0 ? pool : next,
|
|
42
|
+
applied,
|
|
43
|
+
hitMin: next.current === next.min,
|
|
44
|
+
hitMax: next.current === next.max,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Read, transition, and write a caller-owned pool through the structural
|
|
49
|
+
* adapter. Positive amounts increase the resource; negative amounts decrease
|
|
50
|
+
* it. The adapter receives a complete replacement value, never a hidden
|
|
51
|
+
* mutation.
|
|
52
|
+
*
|
|
53
|
+
* @capability portable-stat-pool apply deterministic bounded resource changes through a caller-owned stat adapter
|
|
54
|
+
*/
|
|
55
|
+
export function applyStatPoolDelta(access, ownerId, statId, amount) {
|
|
56
|
+
const pool = access.get(ownerId, statId);
|
|
57
|
+
if (pool === null)
|
|
58
|
+
return { status: "rejected", reason: `unknown stat "${statId}"` };
|
|
59
|
+
const change = changeStatPool(pool, amount);
|
|
60
|
+
access.set(ownerId, statId, change.pool);
|
|
61
|
+
return { status: "ok", ...change };
|
|
62
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
/** @internal */
|
|
1
2
|
export interface ChangeSignal {
|
|
2
3
|
subscribe(listener: () => void): () => void;
|
|
3
4
|
notify(): void;
|
|
4
5
|
version(): number;
|
|
5
6
|
}
|
|
7
|
+
/** @internal */
|
|
6
8
|
export declare function createChangeSignal(): ChangeSignal;
|
|
9
|
+
/** @internal */
|
|
7
10
|
export declare function notifyAfter<T extends object, K extends keyof T>(target: T, methods: readonly K[], notify: () => void): T;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @internal */
|
|
1
2
|
export function createChangeSignal() {
|
|
2
3
|
const listeners = new Set();
|
|
3
4
|
let version = 0;
|
|
@@ -18,6 +19,7 @@ export function createChangeSignal() {
|
|
|
18
19
|
},
|
|
19
20
|
};
|
|
20
21
|
}
|
|
22
|
+
/** @internal */
|
|
21
23
|
export function notifyAfter(target, methods, notify) {
|
|
22
24
|
const wrapped = { ...target };
|
|
23
25
|
for (const method of methods) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @internal */
|
|
1
2
|
export interface ObservableKeyedStore<T> {
|
|
2
3
|
set(key: string, value: T): void;
|
|
3
4
|
delete(key: string): void;
|
|
@@ -16,4 +17,5 @@ export interface ObservableKeyedStore<T> {
|
|
|
16
17
|
/** Replace all entries with `data` (clears keys absent from it), emitting once. */
|
|
17
18
|
hydrate(data: readonly (readonly [string, T])[]): void;
|
|
18
19
|
}
|
|
20
|
+
/** @internal */
|
|
19
21
|
export declare function createObservableKeyedStore<T>(areEqual?: (previous: T, next: T) => boolean): ObservableKeyedStore<T>;
|
package/dist/ui/hudLayout.d.ts
CHANGED
|
@@ -86,5 +86,10 @@ export declare function rectFromPlacement(placement: HudPlacement, size: HudSize
|
|
|
86
86
|
export declare function registerActiveHudLayout(store: HudLayoutStore): () => void;
|
|
87
87
|
/** @internal */
|
|
88
88
|
export declare function listActiveHudLayouts(): HudLayoutStore[];
|
|
89
|
+
/**
|
|
90
|
+
* @internal Notified whenever a `HudCanvas` mounts or unmounts (the active-layout set changes).
|
|
91
|
+
* Lets the editor's HUD-layout surface enable editing on layouts that mount after it opens.
|
|
92
|
+
*/
|
|
93
|
+
export declare function subscribeActiveHudLayouts(listener: () => void): () => void;
|
|
89
94
|
/** @internal */
|
|
90
95
|
export declare function createHudLayout(options?: HudLayoutOptions): HudLayoutStore;
|
package/dist/ui/hudLayout.js
CHANGED
|
@@ -116,17 +116,33 @@ function parseSerialized(raw) {
|
|
|
116
116
|
return out;
|
|
117
117
|
}
|
|
118
118
|
const activeHudLayouts = new Set();
|
|
119
|
+
const activeHudLayoutListeners = new Set();
|
|
119
120
|
/** @internal Agent bridge seam — HudCanvas registers its store while mounted so headless drivers can reach canvas mode. */
|
|
120
121
|
export function registerActiveHudLayout(store) {
|
|
121
122
|
activeHudLayouts.add(store);
|
|
123
|
+
for (const listener of [...activeHudLayoutListeners])
|
|
124
|
+
listener();
|
|
122
125
|
return () => {
|
|
123
|
-
activeHudLayouts.delete(store)
|
|
126
|
+
if (!activeHudLayouts.delete(store))
|
|
127
|
+
return;
|
|
128
|
+
for (const listener of [...activeHudLayoutListeners])
|
|
129
|
+
listener();
|
|
124
130
|
};
|
|
125
131
|
}
|
|
126
132
|
/** @internal */
|
|
127
133
|
export function listActiveHudLayouts() {
|
|
128
134
|
return [...activeHudLayouts];
|
|
129
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* @internal Notified whenever a `HudCanvas` mounts or unmounts (the active-layout set changes).
|
|
138
|
+
* Lets the editor's HUD-layout surface enable editing on layouts that mount after it opens.
|
|
139
|
+
*/
|
|
140
|
+
export function subscribeActiveHudLayouts(listener) {
|
|
141
|
+
activeHudLayoutListeners.add(listener);
|
|
142
|
+
return () => {
|
|
143
|
+
activeHudLayoutListeners.delete(listener);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
130
146
|
function panelToUi(panel) {
|
|
131
147
|
const layout = {
|
|
132
148
|
anchor: panel.placement.anchor,
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless, serializable model for a set of player-toggled windows/panels — the data layer behind a
|
|
3
|
+
* WoW-style UI where B opens the bag, C the character sheet, and ESC closes the topmost window. It
|
|
4
|
+
* carries *which panels are open*, their focus/z stacking order, per-panel position overrides, and
|
|
5
|
+
* exclusive-group membership, with zero rendering opinion. The React chrome in
|
|
6
|
+
* `@jgengine/react/panels` consumes this; a game keeps layout, skin, keybinds, and window content
|
|
7
|
+
* caller-owned. Pure and immutable: every reducer returns a new state and never mutates its input.
|
|
8
|
+
*/
|
|
9
|
+
/** A pixel position override for a panel (top-left, relative to the host surface). */
|
|
10
|
+
export interface PanelPosition {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A caller-authored panel/window declaration — the DATA input to the model. Only `id` is required so
|
|
16
|
+
* a bag, a character sheet, and a one-off dialog all share the shape.
|
|
17
|
+
*/
|
|
18
|
+
export interface PanelDef {
|
|
19
|
+
id: string;
|
|
20
|
+
/** Human title shown in the window's title bar; falls back to `id`. */
|
|
21
|
+
title?: string;
|
|
22
|
+
/** Keybind that opens/toggles the panel — a `KeyboardEvent.code` (`"KeyB"`) or key (`"b"`), matched case-insensitively by {@link panelByHotkey}. */
|
|
23
|
+
hotkey?: string;
|
|
24
|
+
/** Open on {@link createPanelState} (default false). */
|
|
25
|
+
initial?: boolean;
|
|
26
|
+
/** Exclusive group: opening one panel in a group closes the others in it (e.g. only one center window). */
|
|
27
|
+
group?: string;
|
|
28
|
+
/** Whether the player can close it (title-bar button / ESC). Default true; `false` pins it open. */
|
|
29
|
+
closable?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** @internal Serializable per-panel metadata lifted from {@link PanelDef} so reducers stay `(state, id)` pure. */
|
|
32
|
+
export interface PanelMeta {
|
|
33
|
+
group?: string;
|
|
34
|
+
closable?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Serializable state for a set of panels: which are `open`, their `z` focus stack (higher renders on
|
|
38
|
+
* top), optional per-panel `pos` overrides, and the `meta` registry (group/closable) captured from
|
|
39
|
+
* the defs so the reducers below need no defs argument. Plain JSON — persist and rehydrate it freely.
|
|
40
|
+
*/
|
|
41
|
+
export interface PanelState {
|
|
42
|
+
/** Open panels, keyed by id (`true` for open). Closed panels are absent. */
|
|
43
|
+
open: Record<string, true>;
|
|
44
|
+
/** z / focus order per open panel — the panel with the highest value is focused/on top. */
|
|
45
|
+
z: Record<string, number>;
|
|
46
|
+
/** Per-panel position overrides; remembered across close/reopen. */
|
|
47
|
+
pos: Record<string, PanelPosition>;
|
|
48
|
+
/** Group/closable metadata captured from the defs at construction. */
|
|
49
|
+
meta: Record<string, PanelMeta>;
|
|
50
|
+
/** Next z value to assign on open/focus — monotonic so focus order is deterministic. */
|
|
51
|
+
nextZ: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the initial {@link PanelState} from the panel defs: panels flagged `initial` start open (in
|
|
55
|
+
* declaration order, respecting group exclusivity), and every def's group/closable is captured into
|
|
56
|
+
* the state so later reducers need no defs.
|
|
57
|
+
*
|
|
58
|
+
* @capability panel-state serializable open/z/position state for a set of toggleable game windows
|
|
59
|
+
*/
|
|
60
|
+
export declare function createPanelState(defs: readonly PanelDef[]): PanelState;
|
|
61
|
+
/** Whether panel `id` is currently open. */
|
|
62
|
+
export declare function isOpen(state: PanelState, id: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Open panel `id`, raising it to the top of the focus stack. If it belongs to a `group`, the other
|
|
65
|
+
* open panels in that group are closed (exclusive windows). Opening an already-open panel just
|
|
66
|
+
* refocuses it. Position overrides are preserved.
|
|
67
|
+
*
|
|
68
|
+
* @capability panel-open open a window, raise its z, and enforce exclusive-group membership
|
|
69
|
+
*/
|
|
70
|
+
export declare function openPanel(state: PanelState, id: string): PanelState;
|
|
71
|
+
/**
|
|
72
|
+
* Close panel `id`. No-op if it is already closed. Its position override is kept so reopening restores
|
|
73
|
+
* the last placement.
|
|
74
|
+
*
|
|
75
|
+
* @capability panel-close close a window while remembering its last position
|
|
76
|
+
*/
|
|
77
|
+
export declare function closePanel(state: PanelState, id: string): PanelState;
|
|
78
|
+
/**
|
|
79
|
+
* Toggle panel `id` — open it (with group exclusivity) if closed, close it if open. The one-key
|
|
80
|
+
* behavior behind a keybind like `B` for the bag.
|
|
81
|
+
*
|
|
82
|
+
* @capability panel-toggle toggle a window open/closed from a single keybind
|
|
83
|
+
*/
|
|
84
|
+
export declare function togglePanel(state: PanelState, id: string): PanelState;
|
|
85
|
+
/**
|
|
86
|
+
* Raise open panel `id` to the top of the focus stack (assign it the highest z). No-op if it is
|
|
87
|
+
* closed or already on top, so a pointer-down that refocuses the top window returns the same state.
|
|
88
|
+
*
|
|
89
|
+
* @capability panel-focus raise a window to the top of the focus/z stack
|
|
90
|
+
*/
|
|
91
|
+
export declare function focusPanel(state: PanelState, id: string): PanelState;
|
|
92
|
+
/**
|
|
93
|
+
* Close the topmost closable open panel — the ESC handler. Skips panels pinned with `closable: false`
|
|
94
|
+
* and returns the same state when nothing closable is open.
|
|
95
|
+
*
|
|
96
|
+
* @capability panel-close-top close the focused (topmost) closable window — the ESC behavior
|
|
97
|
+
*/
|
|
98
|
+
export declare function closeTopPanel(state: PanelState): PanelState;
|
|
99
|
+
/**
|
|
100
|
+
* Find the panel whose `hotkey` matches `code`, case-insensitively — the keybind router. `code` may be
|
|
101
|
+
* a `KeyboardEvent.code` (`"KeyB"`) or key (`"b"`); it is compared verbatim to each def's `hotkey`, so
|
|
102
|
+
* author the hotkey in whichever form the caller feeds in. Returns the first match's id, or null.
|
|
103
|
+
*
|
|
104
|
+
* @capability panel-hotkey resolve a keybind (code or key) to the panel id it toggles
|
|
105
|
+
*/
|
|
106
|
+
export declare function panelByHotkey(defs: readonly PanelDef[], code: string): string | null;
|
|
107
|
+
/**
|
|
108
|
+
* Set panel `id`'s position override — the drag commit. Does not require the panel to be open, so a
|
|
109
|
+
* caller can pre-place a window before it is shown.
|
|
110
|
+
*
|
|
111
|
+
* @capability panel-move override a window's on-surface position (the drag commit)
|
|
112
|
+
*/
|
|
113
|
+
export declare function movePanel(state: PanelState, id: string, pos: PanelPosition): PanelState;
|
|
114
|
+
/**
|
|
115
|
+
* The open panels' defs sorted ascending by z — the render order for a window host (map to elements
|
|
116
|
+
* so the last, highest-z window paints on top). Defs unknown to the state are skipped.
|
|
117
|
+
*
|
|
118
|
+
* @capability panel-ordered-open open panels' defs in z/render order for a window host
|
|
119
|
+
*/
|
|
120
|
+
export declare function orderedOpen(state: PanelState, defs: readonly PanelDef[]): PanelDef[];
|