@jgengine/core 0.14.0 → 0.15.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 +500 -1
- package/dist/ai/difficulty.d.ts +154 -0
- package/dist/ai/difficulty.js +194 -0
- package/dist/ai/driver.d.ts +107 -0
- package/dist/ai/driver.js +175 -0
- package/dist/ai/flock.js +13 -23
- package/dist/ai/laneSelect.js +2 -1
- package/dist/ai/mobBrain.js +2 -1
- package/dist/ai/waveRunner.d.ts +81 -0
- package/dist/ai/waveRunner.js +104 -0
- package/dist/audio/musicState.d.ts +47 -0
- package/dist/audio/musicState.js +38 -0
- package/dist/audio/stepCadence.d.ts +47 -0
- package/dist/audio/stepCadence.js +29 -0
- package/dist/audio/synth.d.ts +12 -0
- package/dist/authoring.d.ts +6 -4
- package/dist/authoring.js +6 -4
- package/dist/combat/castRunner.d.ts +13 -1
- package/dist/combat/castRunner.js +5 -1
- package/dist/combat/comboMeter.d.ts +140 -0
- package/dist/combat/comboMeter.js +215 -0
- package/dist/combat/comboPoints.d.ts +11 -0
- package/dist/combat/comboPoints.js +11 -2
- package/dist/combat/damageResolution.d.ts +1 -1
- package/dist/combat/defensiveWindow.d.ts +5 -0
- package/dist/combat/effects.d.ts +24 -0
- package/dist/combat/effects.js +6 -1
- package/dist/combat/projectiles.d.ts +6 -0
- package/dist/combat/projectiles.js +2 -2
- package/dist/combat/regenShield.d.ts +40 -2
- package/dist/combat/regenShield.js +66 -18
- package/dist/combat/resourcePool.d.ts +19 -0
- package/dist/combat/resourcePool.js +20 -2
- package/dist/combat/shotOrigin.d.ts +5 -0
- package/dist/combat/statusApplication.d.ts +1 -1
- package/dist/combat/statusApplication.js +2 -1
- package/dist/combat/statusEffectView.d.ts +56 -0
- package/dist/combat/statusEffectView.js +52 -0
- package/dist/combat.d.ts +4 -2
- package/dist/combat.js +2 -0
- package/dist/devtools/fallbackSeams.d.ts +3 -2
- package/dist/devtools/fallbackSeams.js +1 -1
- package/dist/devtools/textureErrors.d.ts +40 -0
- package/dist/devtools/textureErrors.js +68 -0
- package/dist/economy/shopStock.d.ts +110 -0
- package/dist/economy/shopStock.js +157 -0
- package/dist/economy/wallet.d.ts +6 -0
- package/dist/economy/wallet.js +1 -0
- package/dist/game/achievements.d.ts +80 -0
- package/dist/game/achievements.js +124 -0
- package/dist/game/cameraConfig.d.ts +7 -0
- package/dist/game/codex.d.ts +60 -0
- package/dist/game/codex.js +89 -0
- package/dist/game/credits.d.ts +36 -0
- package/dist/game/credits.js +0 -0
- package/dist/game/defineGame.d.ts +28 -3
- package/dist/game/defineGame.js +5 -0
- package/dist/game/dialogueGraph.d.ts +116 -0
- package/dist/game/dialogueGraph.js +103 -0
- package/dist/game/eventTicker.d.ts +95 -0
- package/dist/game/eventTicker.js +91 -0
- package/dist/game/events.d.ts +24 -5
- package/dist/game/events.js +18 -0
- package/dist/game/gamePhase.d.ts +1 -1
- package/dist/game/gamePhase.js +1 -1
- package/dist/game/leaderboardRank.d.ts +71 -0
- package/dist/game/leaderboardRank.js +76 -0
- package/dist/game/lootTable.d.ts +10 -1
- package/dist/game/lootTable.js +4 -2
- package/dist/game/notifications.d.ts +57 -0
- package/dist/game/notifications.js +107 -0
- package/dist/game/playableGame.d.ts +39 -2
- package/dist/game/quest.d.ts +26 -0
- package/dist/game/quest.js +31 -0
- package/dist/game/runDraft.js +2 -1
- package/dist/game/saveSlots.d.ts +102 -0
- package/dist/game/saveSlots.js +136 -0
- package/dist/game/talentTreeView.d.ts +106 -0
- package/dist/game/talentTreeView.js +114 -0
- package/dist/game/worldItem.js +2 -1
- package/dist/gameplay.d.ts +15 -4
- package/dist/gameplay.js +13 -2
- package/dist/i18n/i18n.d.ts +51 -0
- package/dist/i18n/i18n.js +82 -0
- package/dist/input/axisInput.d.ts +1 -1
- package/dist/input/axisInput.js +18 -2
- package/dist/input/rebindSession.d.ts +141 -0
- package/dist/input/rebindSession.js +235 -0
- package/dist/input/touchControlsMode.d.ts +16 -0
- package/dist/input/touchControlsMode.js +23 -0
- package/dist/input/touchScheme.d.ts +30 -1
- package/dist/input/touchScheme.js +31 -1
- package/dist/interaction/promptRegistry.d.ts +78 -0
- package/dist/interaction/promptRegistry.js +130 -0
- package/dist/inventory/storageTier.js +3 -2
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +145 -1
- package/dist/movement/movementModel.d.ts +26 -3
- package/dist/movement/movementModel.js +121 -15
- package/dist/movement/playerMovement.js +161 -25
- package/dist/physics/drivableVehicle.js +3 -1
- package/dist/physics/kinematicVehicle.d.ts +56 -1
- package/dist/physics/kinematicVehicle.js +99 -10
- package/dist/physics/vehicleObstacles.js +19 -0
- package/dist/random/resolveRng.d.ts +14 -0
- package/dist/random/resolveRng.js +31 -0
- package/dist/render/postProcessing.d.ts +12 -1
- package/dist/runtime/cameraDirector.d.ts +2 -2
- package/dist/runtime/context/combat.js +8 -0
- package/dist/runtime/gameContext.js +6 -1
- package/dist/runtime/gameContextTypes.d.ts +18 -0
- package/dist/runtime/inputSnapshot.d.ts +10 -1
- package/dist/runtime/inputSnapshot.js +13 -1
- package/dist/runtime/runtimeSave.d.ts +3 -3
- package/dist/runtime/runtimeSave.js +11 -2
- package/dist/scene/assetDiagnostics.js +2 -2
- package/dist/scene/autoTarget.js +2 -1
- package/dist/scene/captureCheck.js +3 -2
- package/dist/scene/entityStore.d.ts +4 -1
- package/dist/scene/entityStore.js +3 -0
- package/dist/scene/objectStore.d.ts +10 -0
- package/dist/scene/objectStore.js +29 -6
- package/dist/scene/sceneKinds.d.ts +18 -0
- package/dist/scene/sceneKinds.js +19 -0
- package/dist/scene/sequenceDirector.d.ts +110 -0
- package/dist/scene/sequenceDirector.js +146 -0
- package/dist/session/roles.js +3 -2
- package/dist/settings/settingsModel.d.ts +1 -0
- package/dist/settings/settingsModel.js +1 -0
- package/dist/stats/rollCheck.js +4 -2
- package/dist/time/dayNightCycle.d.ts +114 -0
- package/dist/time/dayNightCycle.js +208 -0
- package/dist/time/timerSet.d.ts +132 -0
- package/dist/time/timerSet.js +243 -0
- package/dist/ui/accessibility.d.ts +51 -0
- package/dist/ui/accessibility.js +83 -0
- package/dist/ui/coachMarks.d.ts +99 -0
- package/dist/ui/coachMarks.js +126 -0
- package/dist/ui/floatingText.d.ts +114 -0
- package/dist/ui/floatingText.js +203 -0
- package/dist/ui/modalStack.d.ts +143 -0
- package/dist/ui/modalStack.js +160 -0
- package/dist/ui/objectiveBanner.d.ts +123 -0
- package/dist/ui/objectiveBanner.js +162 -0
- package/dist/ui/photoMode.d.ts +32 -0
- package/dist/ui/photoMode.js +51 -0
- package/dist/ui/radialMenu.d.ts +47 -0
- package/dist/ui/radialMenu.js +74 -0
- package/dist/ui/screenMarkers.d.ts +128 -0
- package/dist/ui/screenMarkers.js +118 -0
- package/dist/ui.d.ts +8 -0
- package/dist/ui.js +8 -0
- package/dist/vfx/cameraShake.d.ts +97 -0
- package/dist/vfx/cameraShake.js +136 -0
- package/dist/vfx/damageDirection.d.ts +102 -0
- package/dist/vfx/damageDirection.js +188 -0
- package/dist/vfx/particles.d.ts +103 -0
- package/dist/vfx/particles.js +256 -0
- package/dist/vfx/screenEffects.d.ts +149 -0
- package/dist/vfx/screenEffects.js +191 -0
- package/dist/world/authoredObjects.d.ts +18 -0
- package/dist/world/authoredObjects.js +21 -0
- package/dist/world/authoredSpawn.js +19 -0
- package/dist/world/buildingLots.d.ts +54 -11
- package/dist/world/buildingLots.js +116 -30
- package/dist/world/cityBlocks.d.ts +45 -0
- package/dist/world/cityBlocks.js +159 -0
- package/dist/world/cityContent.d.ts +46 -2
- package/dist/world/cityContent.js +160 -2
- package/dist/world/cityGenerator.d.ts +261 -23
- package/dist/world/cityGenerator.js +643 -33
- package/dist/world/cityGeometry.d.ts +20 -0
- package/dist/world/cityGeometry.js +91 -0
- package/dist/world/cityKind.js +1 -44
- package/dist/world/fastTravel.d.ts +64 -0
- package/dist/world/fastTravel.js +108 -0
- package/dist/world/features.d.ts +16 -0
- package/dist/world/features.js +2 -0
- package/dist/world/grounded.d.ts +11 -0
- package/dist/world/grounded.js +12 -0
- package/dist/world/mapAnnotations.d.ts +75 -0
- package/dist/world/mapAnnotations.js +139 -0
- package/dist/world/markers.js +1 -0
- package/dist/world/resourceNode.d.ts +1 -1
- package/dist/world/resourceNode.js +2 -1
- package/dist/world/roads.d.ts +294 -5
- package/dist/world/roads.js +1262 -21
- package/dist/world/sharedAnnotations.d.ts +84 -0
- package/dist/world/sharedAnnotations.js +76 -0
- package/dist/world/spawnOverride.d.ts +37 -0
- package/dist/world/spawnOverride.js +50 -0
- package/dist/world/streetGenerator.d.ts +60 -11
- package/dist/world/streetGenerator.js +1786 -129
- package/dist/world/terrain.d.ts +1 -0
- package/dist/world/terrain.js +2 -0
- package/dist/world/waypoints.d.ts +63 -0
- package/dist/world/waypoints.js +137 -0
- package/dist/world.d.ts +17 -2
- package/dist/world.js +17 -2
- package/package.json +1 -1
- package/skills/ce-handoff/SKILL.md +149 -0
- package/skills/game-design/SKILL.md +2 -0
- package/skills/jgengine/SKILL.md +3 -1
- package/skills/jgengine/api.md +17 -7
- package/skills/jgengine/capabilities.md +12 -0
- package/skills/jgengine/recipes/minimal-game.md +7 -4
- package/skills/jgengine-combat/api.md +40 -4
- package/skills/jgengine-combat/capabilities.md +12 -0
- package/skills/jgengine-combat/reference.md +2 -0
- package/skills/jgengine-gameplay/api.md +212 -11
- package/skills/jgengine-gameplay/capabilities.md +66 -1
- package/skills/jgengine-world/api.md +397 -11
- package/skills/jgengine-world/capabilities.md +123 -2
- package/skills/jgengine-world/reference.md +15 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { TalentNodeDef, TalentTree } from "./talents.js";
|
|
2
|
+
/**
|
|
3
|
+
* Render state of a talent node, derived from its rank and prerequisite satisfaction.
|
|
4
|
+
* - `locked` — rank 0 and at least one prerequisite unmet; cannot be trained yet.
|
|
5
|
+
* - `available` — rank 0, every prerequisite met; ready to take a first point.
|
|
6
|
+
* - `learned` — at least one rank invested, but below `maxRank`.
|
|
7
|
+
* - `maxed` — fully invested (`rank === maxRank`).
|
|
8
|
+
*/
|
|
9
|
+
export type TalentNodeState = "locked" | "available" | "learned" | "maxed";
|
|
10
|
+
/** One prerequisite edge into a node — the source node, the rank it demands, and whether that is met. */
|
|
11
|
+
export interface TalentEdgeView {
|
|
12
|
+
/** Prerequisite node id (edge source). */
|
|
13
|
+
from: string;
|
|
14
|
+
/** Rank the prerequisite must reach for this edge to be satisfied. */
|
|
15
|
+
rank: number;
|
|
16
|
+
/** Whether the prerequisite currently sits at or above the required rank. */
|
|
17
|
+
met: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** A per-node view for rendering: grid placement (tier/branch), rank, state, and inbound prerequisite edges. */
|
|
20
|
+
export interface TalentNodeView {
|
|
21
|
+
/** Stable node id (matches the {@link TalentNodeDef}). */
|
|
22
|
+
id: string;
|
|
23
|
+
/** Branch key the node belongs to (`""` when the def has none) — a free string the game styles/columns by. */
|
|
24
|
+
branch: string;
|
|
25
|
+
/** 0-based prerequisite depth: `0` for a root, else `1 + max(tier of prerequisites)`. Drives row/tier layout. */
|
|
26
|
+
tier: number;
|
|
27
|
+
/** Points currently invested in the node. */
|
|
28
|
+
rank: number;
|
|
29
|
+
/** Maximum points the node accepts. */
|
|
30
|
+
maxRank: number;
|
|
31
|
+
/** Derived render state (see {@link TalentNodeState}). */
|
|
32
|
+
state: TalentNodeState;
|
|
33
|
+
/** Whether a point can be spent on the node right now (mirrors `tree.canAllocate(id).ok`). */
|
|
34
|
+
allocatable: boolean;
|
|
35
|
+
/** Inbound prerequisite edges, in definition order. */
|
|
36
|
+
requires: readonly TalentEdgeView[];
|
|
37
|
+
}
|
|
38
|
+
/** A whole-tree render view: placed nodes plus branch/tier extents and point totals. */
|
|
39
|
+
export interface TalentTreeView {
|
|
40
|
+
/** Every node, in definition order, with its render placement and state. */
|
|
41
|
+
nodes: readonly TalentNodeView[];
|
|
42
|
+
/** Distinct branch keys, in first-seen (definition) order — the natural column order. */
|
|
43
|
+
branches: readonly string[];
|
|
44
|
+
/** Number of tiers, i.e. `max(tier) + 1` (0 when the tree is empty). */
|
|
45
|
+
tiers: number;
|
|
46
|
+
/** Points the player still has to spend. */
|
|
47
|
+
pointsAvailable: number;
|
|
48
|
+
/** Points already invested across the tree. */
|
|
49
|
+
pointsSpent: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Per-node status a game supplies to build a view from *any* unlock rule — not just point-spend.
|
|
53
|
+
* A game computes these from whatever it wants (a currency threshold, a level, a quest flag, a
|
|
54
|
+
* skill-point pool, nothing at all) so the same widget renders a buy-with-points talent tree, a
|
|
55
|
+
* money-gated upgrade tree, or a condition-unlocked ability web without changing the renderer.
|
|
56
|
+
*/
|
|
57
|
+
export interface TalentNodeStatus {
|
|
58
|
+
/** Points/levels invested in the node — `0` means "not taken yet"; a binary unlock uses `0`/`1`. */
|
|
59
|
+
rank: number;
|
|
60
|
+
/**
|
|
61
|
+
* Whether the node can be taken right now under the game's own rule (enough points, enough cash,
|
|
62
|
+
* a level reached, a flag set, …). Drives the `available` state and whether the widget fires
|
|
63
|
+
* `onLearn` for it. The view never interprets *why* — that stays the game's decision.
|
|
64
|
+
*/
|
|
65
|
+
allocatable: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** Optional overall point totals for the view header; omit (or 0) for trees that spend no point currency. */
|
|
68
|
+
export interface TalentPointTotals {
|
|
69
|
+
/** Points the player still has to spend. Default `0`. */
|
|
70
|
+
pointsAvailable?: number;
|
|
71
|
+
/** Points already invested across the tree. Default `0`. */
|
|
72
|
+
pointsSpent?: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build a flat, serializable render view of a node graph from a caller-supplied per-node status,
|
|
76
|
+
* decoupled from any particular progression model. For each node the game returns its `rank` and
|
|
77
|
+
* whether it is `allocatable` *right now* under whatever rule it likes — a skill-point pool, a cash
|
|
78
|
+
* threshold, a level gate, a quest flag, or nothing. The builder places every node by branch +
|
|
79
|
+
* prerequisite-depth tier, tags it learned/available/locked/maxed (`available` when rank 0 and every
|
|
80
|
+
* prerequisite is met — see below), and resolves inbound prerequisite edges (each `met` when the
|
|
81
|
+
* source node's supplied rank meets the demand). This is the general escape hatch behind
|
|
82
|
+
* {@link talentTreeView}: it lets the same widget render a buy-with-points talent tree, a money-gated
|
|
83
|
+
* upgrade tree, or a condition-unlocked ability web without the renderer changing. It never interprets
|
|
84
|
+
* what a node *means* — ids, branches, and ranks stay opaque game data.
|
|
85
|
+
*
|
|
86
|
+
* `state` is derived purely from the supplied `rank` vs `maxRank` and prerequisite satisfaction, so a
|
|
87
|
+
* node the game considers takeable-by-condition surfaces as `available` (rank 0, prerequisites met)
|
|
88
|
+
* and the widget only fires `onLearn` for nodes whose `allocatable` the game returned `true`.
|
|
89
|
+
*
|
|
90
|
+
* @capability talent-tree-view-from build a placed talent/upgrade-tree render view from any per-node unlock rule (points, currency, level, flag) — branch/tier layout, learned/available/locked/maxed state, prerequisite edges
|
|
91
|
+
*/
|
|
92
|
+
export declare function talentTreeViewFrom<TStat extends string = string>(nodes: readonly TalentNodeDef<TStat>[], status: (node: TalentNodeDef<TStat>) => TalentNodeStatus, totals?: TalentPointTotals): TalentTreeView;
|
|
93
|
+
/**
|
|
94
|
+
* Project the existing talent model (`createTalentTree`) into a flat, serializable render view: every
|
|
95
|
+
* node placed by branch + prerequisite-depth tier, tagged learned/available/locked/maxed, with its
|
|
96
|
+
* inbound prerequisite edges (and whether each is met) and whether a point can be spent right now. It is
|
|
97
|
+
* a pure read over the node defs plus a live {@link TalentTree}, so a React/canvas widget can lay out
|
|
98
|
+
* nodes, draw edges, and gate clicks without re-deriving topology or re-interpreting eligibility. The
|
|
99
|
+
* model still owns allocation, requirements, and point rules — this only reshapes them for drawing, and
|
|
100
|
+
* never interprets what a node *means* (ids, branches, and ranks stay opaque game data). This is the
|
|
101
|
+
* point-spend adapter over {@link talentTreeViewFrom}; pass a custom status to that builder instead to
|
|
102
|
+
* drive the tree from a currency, level, or any other unlock rule.
|
|
103
|
+
*
|
|
104
|
+
* @capability talent-tree-view flatten a talent tree into a placed, per-node render view — branch/tier layout, learned/available/locked/maxed state, prerequisite edges
|
|
105
|
+
*/
|
|
106
|
+
export declare function talentTreeView<TStat extends string = string>(nodes: readonly TalentNodeDef<TStat>[], tree: TalentTree<TStat>): TalentTreeView;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const DEFAULT_BRANCH = "";
|
|
2
|
+
function requirementNode(requirement) {
|
|
3
|
+
return typeof requirement === "string" ? requirement : requirement.nodeId;
|
|
4
|
+
}
|
|
5
|
+
function requirementRank(requirement) {
|
|
6
|
+
return typeof requirement === "string" ? 1 : requirement.rank;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Compute each node's tier as its longest prerequisite chain depth, memoized. Cycles and
|
|
10
|
+
* dangling prerequisite ids are treated as tier `0` contributions so the pass always terminates.
|
|
11
|
+
*/
|
|
12
|
+
function computeTiers(byId) {
|
|
13
|
+
const tiers = new Map();
|
|
14
|
+
const visiting = new Set();
|
|
15
|
+
function tierOf(id) {
|
|
16
|
+
const cached = tiers.get(id);
|
|
17
|
+
if (cached !== undefined)
|
|
18
|
+
return cached;
|
|
19
|
+
const node = byId.get(id);
|
|
20
|
+
if (node === undefined || visiting.has(id))
|
|
21
|
+
return 0;
|
|
22
|
+
visiting.add(id);
|
|
23
|
+
let depth = 0;
|
|
24
|
+
for (const requirement of node.requires ?? []) {
|
|
25
|
+
const reqId = requirementNode(requirement);
|
|
26
|
+
if (byId.has(reqId))
|
|
27
|
+
depth = Math.max(depth, tierOf(reqId) + 1);
|
|
28
|
+
}
|
|
29
|
+
visiting.delete(id);
|
|
30
|
+
tiers.set(id, depth);
|
|
31
|
+
return depth;
|
|
32
|
+
}
|
|
33
|
+
for (const id of byId.keys())
|
|
34
|
+
tierOf(id);
|
|
35
|
+
return tiers;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build a flat, serializable render view of a node graph from a caller-supplied per-node status,
|
|
39
|
+
* decoupled from any particular progression model. For each node the game returns its `rank` and
|
|
40
|
+
* whether it is `allocatable` *right now* under whatever rule it likes — a skill-point pool, a cash
|
|
41
|
+
* threshold, a level gate, a quest flag, or nothing. The builder places every node by branch +
|
|
42
|
+
* prerequisite-depth tier, tags it learned/available/locked/maxed (`available` when rank 0 and every
|
|
43
|
+
* prerequisite is met — see below), and resolves inbound prerequisite edges (each `met` when the
|
|
44
|
+
* source node's supplied rank meets the demand). This is the general escape hatch behind
|
|
45
|
+
* {@link talentTreeView}: it lets the same widget render a buy-with-points talent tree, a money-gated
|
|
46
|
+
* upgrade tree, or a condition-unlocked ability web without the renderer changing. It never interprets
|
|
47
|
+
* what a node *means* — ids, branches, and ranks stay opaque game data.
|
|
48
|
+
*
|
|
49
|
+
* `state` is derived purely from the supplied `rank` vs `maxRank` and prerequisite satisfaction, so a
|
|
50
|
+
* node the game considers takeable-by-condition surfaces as `available` (rank 0, prerequisites met)
|
|
51
|
+
* and the widget only fires `onLearn` for nodes whose `allocatable` the game returned `true`.
|
|
52
|
+
*
|
|
53
|
+
* @capability talent-tree-view-from build a placed talent/upgrade-tree render view from any per-node unlock rule (points, currency, level, flag) — branch/tier layout, learned/available/locked/maxed state, prerequisite edges
|
|
54
|
+
*/
|
|
55
|
+
export function talentTreeViewFrom(nodes, status, totals) {
|
|
56
|
+
const byId = new Map();
|
|
57
|
+
for (const node of nodes)
|
|
58
|
+
byId.set(node.id, node);
|
|
59
|
+
const tiers = computeTiers(byId);
|
|
60
|
+
// Resolve every node's status once up front so prerequisite `met` checks can read a sibling's rank.
|
|
61
|
+
const statusById = new Map();
|
|
62
|
+
for (const node of nodes)
|
|
63
|
+
statusById.set(node.id, status(node));
|
|
64
|
+
const branches = [];
|
|
65
|
+
const seenBranches = new Set();
|
|
66
|
+
let maxTier = -1;
|
|
67
|
+
const viewNodes = nodes.map((node) => {
|
|
68
|
+
const branch = node.branch ?? DEFAULT_BRANCH;
|
|
69
|
+
if (!seenBranches.has(branch)) {
|
|
70
|
+
seenBranches.add(branch);
|
|
71
|
+
branches.push(branch);
|
|
72
|
+
}
|
|
73
|
+
const tier = tiers.get(node.id) ?? 0;
|
|
74
|
+
if (tier > maxTier)
|
|
75
|
+
maxTier = tier;
|
|
76
|
+
const { rank, allocatable } = statusById.get(node.id);
|
|
77
|
+
const requires = (node.requires ?? []).map((requirement) => {
|
|
78
|
+
const from = requirementNode(requirement);
|
|
79
|
+
const required = requirementRank(requirement);
|
|
80
|
+
return { from, rank: required, met: (statusById.get(from)?.rank ?? 0) >= required };
|
|
81
|
+
});
|
|
82
|
+
let state;
|
|
83
|
+
if (rank >= node.maxRank)
|
|
84
|
+
state = "maxed";
|
|
85
|
+
else if (rank > 0)
|
|
86
|
+
state = "learned";
|
|
87
|
+
else
|
|
88
|
+
state = requires.every((edge) => edge.met) ? "available" : "locked";
|
|
89
|
+
return { id: node.id, branch, tier, rank, maxRank: node.maxRank, state, allocatable, requires };
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
nodes: viewNodes,
|
|
93
|
+
branches,
|
|
94
|
+
tiers: maxTier + 1,
|
|
95
|
+
pointsAvailable: totals?.pointsAvailable ?? 0,
|
|
96
|
+
pointsSpent: totals?.pointsSpent ?? 0,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Project the existing talent model (`createTalentTree`) into a flat, serializable render view: every
|
|
101
|
+
* node placed by branch + prerequisite-depth tier, tagged learned/available/locked/maxed, with its
|
|
102
|
+
* inbound prerequisite edges (and whether each is met) and whether a point can be spent right now. It is
|
|
103
|
+
* a pure read over the node defs plus a live {@link TalentTree}, so a React/canvas widget can lay out
|
|
104
|
+
* nodes, draw edges, and gate clicks without re-deriving topology or re-interpreting eligibility. The
|
|
105
|
+
* model still owns allocation, requirements, and point rules — this only reshapes them for drawing, and
|
|
106
|
+
* never interprets what a node *means* (ids, branches, and ranks stay opaque game data). This is the
|
|
107
|
+
* point-spend adapter over {@link talentTreeViewFrom}; pass a custom status to that builder instead to
|
|
108
|
+
* drive the tree from a currency, level, or any other unlock rule.
|
|
109
|
+
*
|
|
110
|
+
* @capability talent-tree-view flatten a talent tree into a placed, per-node render view — branch/tier layout, learned/available/locked/maxed state, prerequisite edges
|
|
111
|
+
*/
|
|
112
|
+
export function talentTreeView(nodes, tree) {
|
|
113
|
+
return talentTreeViewFrom(nodes, (node) => ({ rank: tree.rank(node.id), allocatable: tree.canAllocate(node.id).ok }), { pointsAvailable: tree.pointsAvailable(), pointsSpent: tree.pointsSpent() });
|
|
114
|
+
}
|
package/dist/game/worldItem.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { distanceBetween } from "../scene/spatial.js";
|
|
2
2
|
import { evaluateLootFilter } from "./lootFilter.js";
|
|
3
|
+
import { resolveRng } from "../random/resolveRng.js";
|
|
3
4
|
/** Scene-entity catalog name every dropped-item instance spawns under (see the three buckets: worldItem is an entity, never an inventory item or object). */
|
|
4
5
|
export const WORLD_ITEM_ENTITY_NAME = "world_item";
|
|
5
6
|
export const DEFAULT_RARITY = "common";
|
|
@@ -105,7 +106,7 @@ export function createWorldItemStore(deps) {
|
|
|
105
106
|
export function resolveDeathDrops(drops, options) {
|
|
106
107
|
if (options.mode !== "world")
|
|
107
108
|
return { worldSpawns: [], grants: [...drops] };
|
|
108
|
-
const rng = options.rng
|
|
109
|
+
const rng = resolveRng(options.rng, "worldItem");
|
|
109
110
|
const worldSpawns = [];
|
|
110
111
|
const grants = [];
|
|
111
112
|
for (const drop of drops) {
|
package/dist/gameplay.d.ts
CHANGED
|
@@ -9,40 +9,49 @@ export { createAuctionBook, type Auction, type AuctionSettlement } from "./econo
|
|
|
9
9
|
export { createListingBook, type Listing } from "./economy/listingBook.js";
|
|
10
10
|
export { createPriceHistory, type PriceStats, type SaleRecord } from "./economy/priceHistory.js";
|
|
11
11
|
export { addScheduledRule, advanceLedger, annotate, balanceOf, cancelRule, capAmount, createResourceLedger, curveScale, pauseRule, redirect, rejectWhen, resumeRule, taxFraction, thresholdScale, type AdvanceOptions, type AdvanceResult, type AppliedTransaction, type CatchUpPolicy, type LedgerEvent, type LedgerEventKind, type PolicyContext, type PolicyRead, type Precision, type ResourceLedger, type ResourcePolicy, type ResourceTransaction, type RuleCursor, type ScheduledRule, type ThresholdBand, } from "./economy/resourceLedger.js";
|
|
12
|
+
export { createShopStock, type ShopBuyRejection, type ShopBuyResult, type ShopPrice, type ShopSellRejection, type ShopSellResult, type ShopStock, type ShopStockConfig, type ShopStockEntry, type ShopStockSnapshot, } from "./economy/shopStock.js";
|
|
12
13
|
export { type TechNodeDef } from "./economy/techTree.js";
|
|
13
|
-
export { balance, charge, chargeAll, createEmptyWallet, grant, isOverdrawn } from "./economy/wallet.js";
|
|
14
|
+
export { balance, canAfford, charge, chargeAll, createEmptyWallet, grant, isOverdrawn, type ChargeOptions, type ChargeResult, type Overdraft, } from "./economy/wallet.js";
|
|
15
|
+
export { createAchievementTracker, type AchievementDef, type AchievementSnapshot, type AchievementTracker, type AchievementTrackerOptions, type AchievementUnlock, type AchievementView, } from "./game/achievements.js";
|
|
14
16
|
export { DEFAULT_CHAT_BODY_LENGTH, DEFAULT_CHAT_HISTORY_LIMIT, DEFAULT_CHAT_RATE_LIMIT, createChatRateLimiter, type Chat, type ChatMessage, type ChatRateLimit, type ChatSendResult, } from "./game/chat.js";
|
|
17
|
+
export { createCodex, type Codex, type CodexEntryDef, type CodexEntryView, type CodexOptions, type CodexSnapshot, } from "./game/codex.js";
|
|
15
18
|
export { playControlsActive } from "./game/controlGate.js";
|
|
16
19
|
export { createCosmetics } from "./game/cosmetics.js";
|
|
17
|
-
export { type GameDefinition, type GameDefinitionConfig, type GameLoop, type InventoryDeclaration, type LifecycleConfig, type PhysicsConfig, } from "./game/defineGame.js";
|
|
20
|
+
export { type GameDefinition, type GameDefinitionConfig, type GameLifecycle, type GameLoop, type InventoryDeclaration, type LifecycleConfig, type PhysicsConfig, } from "./game/defineGame.js";
|
|
18
21
|
export { defineSystem, type SystemDefinition, type SystemEventHandlers, type SystemTick } from "./game/defineSystem.js";
|
|
19
22
|
export { createGameDialogue, dialogueSlot } from "./game/dialogue.js";
|
|
23
|
+
export { createDialogueRun, selectDialogueView, type DialogueGraph, type DialogueGraphChoice, type DialogueGraphNode, type DialogueGraphSnapshot, type DialogueGraphView, type DialogueRun, type DialogueRunOptions, } from "./game/dialogueGraph.js";
|
|
24
|
+
export { createEventTicker, type EventTicker, type EventTickerEntry, type EventTickerInput, type EventTickerOptions, type EventTickerSnapshot, type EventTickerView, } from "./game/eventTicker.js";
|
|
20
25
|
export { createGameEvents, type CombatTelegraphEvent, type CombatVfxEvent, type EntityDiedEvent, type EntityFloatTextEvent, type GameEventMap, type GameEvents, type RetainedVfxKind, type StatLevelUpEvent, type VfxKind, type VfxRef, } from "./game/events.js";
|
|
21
26
|
export { appendFeed, createGameFeed, pruneFeed, type FeedEntry, type FeedWindow, type TimedFeedEntry, } from "./game/feed.js";
|
|
22
27
|
export { gamePhase, setGamePhase, type GamePhase } from "./game/gamePhase.js";
|
|
23
28
|
export { createKeyValueStore, type KeyValueStorage } from "./game/keyValueStore.js";
|
|
24
29
|
export { createLeaderboard, type LeaderboardRow, type LeaderboardScope } from "./game/leaderboard.js";
|
|
30
|
+
export { medalFor, rankLeaderboard, type RankLeaderboardOptions, type RankableRow, type RankedEntry, type TieMode, } from "./game/leaderboardRank.js";
|
|
25
31
|
export { createLevelSequence, type LevelSequence } from "./game/levelSequence.js";
|
|
26
32
|
export { createLoadouts, type LoadoutDef } from "./game/loadout.js";
|
|
27
33
|
export { evaluateLootFilter, lootFilter, type LootFilterRule } from "./game/lootFilter.js";
|
|
28
34
|
export { createLootPipeline, defineLootPipeline, type LootDropProvenance, type LootModifier, type LootPipeline, type LootPipelineDef, type LootPipelineDeps, type LootPlanEntry, type LootResolution, type LootResolveContext, type LootRollPlan, type LootStage, type LootStageKind, type LootStageStatus, type LootStageTrace, } from "./game/lootPipeline.js";
|
|
29
35
|
export { createLootRegistry, lootTable, type Drop, type LootTableDef } from "./game/lootTable.js";
|
|
30
36
|
export { resolveOneShotClip } from "./game/modelAnimation.js";
|
|
37
|
+
export { createNotificationCenter, type NotificationCenterOptions, type NotificationEntry, type NotificationFilter, type NotificationInput, type NotificationStore, } from "./game/notifications.js";
|
|
31
38
|
export { evaluateObjective } from "./game/objectives.js";
|
|
32
39
|
export { createLayerRegistry, diffParams, orderLayers, resolveParams, resolveSelection, validateLayers, type LayerConflict, type LayerOps, type LayerRegistry, type LayerSelection, type ParamContribution, type ParamDelta, type ParamLayer, type ParamOp, type ParamSnapshot, } from "./game/paramLayers.js";
|
|
33
40
|
export { DEFAULT_PING_CATEGORIES, PING_FEED_ACTION, createPingSystem, type PingCategory, type PingSystem, } from "./game/ping.js";
|
|
34
41
|
export { CAMERA_FRUSTUM_DEFAULTS, worldHealthBarAllowsRole, type BackdropConfig, type CameraKeyframe, type CameraRigKind, type ChaseCameraConfig, type CinematicCameraConfig, type DirectionalLightingConfig, type EntitySpriteConfig, type FirstPersonCameraConfig, type GameCameraConfig, type InspectionCameraConfig, type InspectionZoomAnchor, type LightingConfig, type LockOnCameraConfig, type ModelConfig, type ModelMaterialMaps, type ModelMaterialOverride, type ObjectStyle, type ObserverCameraConfig, type PlayableGame, type PointerConfig, type RtsCameraConfig, type ShoulderCameraConfig, type SideScrollCameraConfig, type TopDownCameraConfig, type WorldItemRenderConfig, type WorldOverlayProps, } from "./game/playableGame.js";
|
|
35
42
|
export { curve, evalCurve, leveling, type Curve, type LevelProgress, type LevelingConfig, type LevelingTrack, } from "./game/progression.js";
|
|
36
|
-
export { createQuestJournal, type QuestDef, type QuestInstance, type QuestRewards } from "./game/quest.js";
|
|
43
|
+
export { createQuestJournal, defaultObjectiveLabel, describeTrackedQuest, type QuestDef, type QuestInstance, type QuestRewards, type TrackedObjectiveView, type TrackedQuestView, } from "./game/quest.js";
|
|
37
44
|
export { RaceState, createLapTimer, createRaceState, finishRaceSession, firstPastPost, idleRaceSession, lapDurations, parDelta, placementOf, raceOutcomeOf, racePlacements, raceTrack, splitSegments, startRaceCountdown, tickRaceSession, } from "./game/race.js";
|
|
38
45
|
export { createRecordBook } from "./game/recordBook.js";
|
|
39
46
|
export { createRuleRegistry, rerollRules, selectRules, type RuleDef, type RuleRegistry, type RuleSelection, type RuleSelectionConfig, } from "./game/ruleSelection.js";
|
|
40
47
|
export { createRunDraft, type RunDraft, type RunModifierOffer } from "./game/runDraft.js";
|
|
48
|
+
export { createSaveSlots, type SaveSlotMeta, type SaveSlotWrite, type SaveSlots, type SaveSlotsConfig, type SaveSlotsSnapshot, } from "./game/saveSlots.js";
|
|
41
49
|
export { createSaveStore, localSaveBackend, memorySaveBackend, remoteSaveBackend, type SaveBackend, type SaveStatus, type SaveStore, } from "./game/saveStore.js";
|
|
42
50
|
export { createSocial, type FriendEntry, type FriendRequestEntry, type Friends, type Party, type PartyInviteEntry, type PartyMemberEntry, type PresenceInfo, type Social, type SocialDeps, type WorldInvite, type WorldInviteTarget, } from "./game/social.js";
|
|
43
51
|
export { createSpawnPoints } from "./game/spawnPoints.js";
|
|
44
52
|
export { composeGameLoop } from "./game/systemRuntime.js";
|
|
45
53
|
export { DEFAULT_FIXED_STAGES, DEFAULT_FRAME_STAGES, compileSystemSchedule, type CompiledSystemSchedule, } from "./game/systemSchedule.js";
|
|
54
|
+
export { talentTreeView, talentTreeViewFrom, type TalentEdgeView, type TalentNodeState, type TalentNodeStatus, type TalentNodeView, type TalentPointTotals, type TalentTreeView, } from "./game/talentTreeView.js";
|
|
46
55
|
export { createTalentTree, type TalentNodeDef, type TalentTree } from "./game/talents.js";
|
|
47
56
|
export { appendToast, createToastQueue, pruneToasts, type Toast } from "./game/toasts.js";
|
|
48
57
|
export { createUnlockCatalog, createUnlocks, type UnlockDef } from "./game/unlocks.js";
|
|
@@ -54,8 +63,10 @@ export { applyBindingOverrides, clearBindingOverride, loadBindingOverrides, save
|
|
|
54
63
|
export { createGestureSurfaceTracker } from "./input/gestureSurface.js";
|
|
55
64
|
export { aimToPoint, type PointerHit, type PointerVec3 } from "./input/pointer.js";
|
|
56
65
|
export { normalizePointerToAxis, type PointerAxisState } from "./input/pointerAxis.js";
|
|
66
|
+
export { createRebindSession, type RebindActionConfig, type RebindConflict, type RebindRow, type RebindSession, type RebindSessionActionsConfig, type RebindSessionConfig, type RebindSessionMapConfig, type RebindSessionSnapshot, } from "./input/rebindSession.js";
|
|
67
|
+
export { activeTouchControlsMode, setTouchControlsMode } from "./input/touchControlsMode.js";
|
|
57
68
|
export { createTouchGestureTracker } from "./input/touchGestures.js";
|
|
58
|
-
export { DEFAULT_TOUCH_STYLE, TOUCH_STYLES, TOUCH_STYLE_OPTIONS, deriveTouchScheme, touchButtonShape, touchCode, withTouchCodes, type TouchAnchor, type TouchButton, type TouchButtonShape, type TouchJoystick, type TouchScheme, type TouchStyle, } from "./input/touchScheme.js";
|
|
69
|
+
export { DEFAULT_TOUCH_STYLE, TOUCH_STYLES, TOUCH_STYLE_OPTIONS, deriveTouchScheme, touchButtonShape, touchCode, withTouchCodes, type TouchAnchor, type TouchButton, type TouchButtonShape, type TouchControlsConfig, type TouchControlsModeConfig, type TouchJoystick, type TouchScheme, type TouchStyle, } from "./input/touchScheme.js";
|
|
59
70
|
export { type InventorySlot, type InventoryState } from "./inventory/inventoryModel.js";
|
|
60
71
|
export { type Cell, type Rotation } from "./inventory/shapedGrid.js";
|
|
61
72
|
export { type SlotGrid } from "./inventory/slotModel.js";
|
package/dist/gameplay.js
CHANGED
|
@@ -9,40 +9,49 @@ export { createAuctionBook } from "./economy/auctionBook.js";
|
|
|
9
9
|
export { createListingBook } from "./economy/listingBook.js";
|
|
10
10
|
export { createPriceHistory } from "./economy/priceHistory.js";
|
|
11
11
|
export { addScheduledRule, advanceLedger, annotate, balanceOf, cancelRule, capAmount, createResourceLedger, curveScale, pauseRule, redirect, rejectWhen, resumeRule, taxFraction, thresholdScale, } from "./economy/resourceLedger.js";
|
|
12
|
+
export { createShopStock, } from "./economy/shopStock.js";
|
|
12
13
|
export {} from "./economy/techTree.js";
|
|
13
|
-
export { balance, charge, chargeAll, createEmptyWallet, grant, isOverdrawn } from "./economy/wallet.js";
|
|
14
|
+
export { balance, canAfford, charge, chargeAll, createEmptyWallet, grant, isOverdrawn, } from "./economy/wallet.js";
|
|
15
|
+
export { createAchievementTracker, } from "./game/achievements.js";
|
|
14
16
|
export { DEFAULT_CHAT_BODY_LENGTH, DEFAULT_CHAT_HISTORY_LIMIT, DEFAULT_CHAT_RATE_LIMIT, createChatRateLimiter, } from "./game/chat.js";
|
|
17
|
+
export { createCodex, } from "./game/codex.js";
|
|
15
18
|
export { playControlsActive } from "./game/controlGate.js";
|
|
16
19
|
export { createCosmetics } from "./game/cosmetics.js";
|
|
17
20
|
export {} from "./game/defineGame.js";
|
|
18
21
|
export { defineSystem } from "./game/defineSystem.js";
|
|
19
22
|
export { createGameDialogue, dialogueSlot } from "./game/dialogue.js";
|
|
23
|
+
export { createDialogueRun, selectDialogueView, } from "./game/dialogueGraph.js";
|
|
24
|
+
export { createEventTicker, } from "./game/eventTicker.js";
|
|
20
25
|
export { createGameEvents, } from "./game/events.js";
|
|
21
26
|
export { appendFeed, createGameFeed, pruneFeed, } from "./game/feed.js";
|
|
22
27
|
export { gamePhase, setGamePhase } from "./game/gamePhase.js";
|
|
23
28
|
export { createKeyValueStore } from "./game/keyValueStore.js";
|
|
24
29
|
export { createLeaderboard } from "./game/leaderboard.js";
|
|
30
|
+
export { medalFor, rankLeaderboard, } from "./game/leaderboardRank.js";
|
|
25
31
|
export { createLevelSequence } from "./game/levelSequence.js";
|
|
26
32
|
export { createLoadouts } from "./game/loadout.js";
|
|
27
33
|
export { evaluateLootFilter, lootFilter } from "./game/lootFilter.js";
|
|
28
34
|
export { createLootPipeline, defineLootPipeline, } from "./game/lootPipeline.js";
|
|
29
35
|
export { createLootRegistry, lootTable } from "./game/lootTable.js";
|
|
30
36
|
export { resolveOneShotClip } from "./game/modelAnimation.js";
|
|
37
|
+
export { createNotificationCenter, } from "./game/notifications.js";
|
|
31
38
|
export { evaluateObjective } from "./game/objectives.js";
|
|
32
39
|
export { createLayerRegistry, diffParams, orderLayers, resolveParams, resolveSelection, validateLayers, } from "./game/paramLayers.js";
|
|
33
40
|
export { DEFAULT_PING_CATEGORIES, PING_FEED_ACTION, createPingSystem, } from "./game/ping.js";
|
|
34
41
|
export { CAMERA_FRUSTUM_DEFAULTS, worldHealthBarAllowsRole, } from "./game/playableGame.js";
|
|
35
42
|
export { curve, evalCurve, leveling, } from "./game/progression.js";
|
|
36
|
-
export { createQuestJournal } from "./game/quest.js";
|
|
43
|
+
export { createQuestJournal, defaultObjectiveLabel, describeTrackedQuest, } from "./game/quest.js";
|
|
37
44
|
export { RaceState, createLapTimer, createRaceState, finishRaceSession, firstPastPost, idleRaceSession, lapDurations, parDelta, placementOf, raceOutcomeOf, racePlacements, raceTrack, splitSegments, startRaceCountdown, tickRaceSession, } from "./game/race.js";
|
|
38
45
|
export { createRecordBook } from "./game/recordBook.js";
|
|
39
46
|
export { createRuleRegistry, rerollRules, selectRules, } from "./game/ruleSelection.js";
|
|
40
47
|
export { createRunDraft } from "./game/runDraft.js";
|
|
48
|
+
export { createSaveSlots, } from "./game/saveSlots.js";
|
|
41
49
|
export { createSaveStore, localSaveBackend, memorySaveBackend, remoteSaveBackend, } from "./game/saveStore.js";
|
|
42
50
|
export { createSocial, } from "./game/social.js";
|
|
43
51
|
export { createSpawnPoints } from "./game/spawnPoints.js";
|
|
44
52
|
export { composeGameLoop } from "./game/systemRuntime.js";
|
|
45
53
|
export { DEFAULT_FIXED_STAGES, DEFAULT_FRAME_STAGES, compileSystemSchedule, } from "./game/systemSchedule.js";
|
|
54
|
+
export { talentTreeView, talentTreeViewFrom, } from "./game/talentTreeView.js";
|
|
46
55
|
export { createTalentTree } from "./game/talents.js";
|
|
47
56
|
export { appendToast, createToastQueue, pruneToasts } from "./game/toasts.js";
|
|
48
57
|
export { createUnlockCatalog, createUnlocks } from "./game/unlocks.js";
|
|
@@ -54,6 +63,8 @@ export { applyBindingOverrides, clearBindingOverride, loadBindingOverrides, save
|
|
|
54
63
|
export { createGestureSurfaceTracker } from "./input/gestureSurface.js";
|
|
55
64
|
export { aimToPoint } from "./input/pointer.js";
|
|
56
65
|
export { normalizePointerToAxis } from "./input/pointerAxis.js";
|
|
66
|
+
export { createRebindSession, } from "./input/rebindSession.js";
|
|
67
|
+
export { activeTouchControlsMode, setTouchControlsMode } from "./input/touchControlsMode.js";
|
|
57
68
|
export { createTouchGestureTracker } from "./input/touchGestures.js";
|
|
58
69
|
export { DEFAULT_TOUCH_STYLE, TOUCH_STYLES, TOUCH_STYLE_OPTIONS, deriveTouchScheme, touchButtonShape, touchCode, withTouchCodes, } from "./input/touchScheme.js";
|
|
59
70
|
export {} from "./inventory/inventoryModel.js";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** A locale tag (e.g. `"en"`, `"es"`, `"ja"`) — a key into a {@link Catalog}. */
|
|
2
|
+
export type Locale = string;
|
|
3
|
+
/** Flat message table for one locale: key → template string with `{param}` slots. */
|
|
4
|
+
export type Messages = Readonly<Record<string, string>>;
|
|
5
|
+
/** All locales' message tables, keyed by locale. */
|
|
6
|
+
export type Catalog = Readonly<Record<Locale, Messages>>;
|
|
7
|
+
/** Interpolation values for a message template. */
|
|
8
|
+
export type TParams = Readonly<Record<string, string | number>>;
|
|
9
|
+
/**
|
|
10
|
+
* Replace `{name}` placeholders in `template` with `params[name]`. Unknown
|
|
11
|
+
* placeholders are left intact. Pure and locale-independent.
|
|
12
|
+
*/
|
|
13
|
+
export declare function interpolate(template: string, params?: TParams): string;
|
|
14
|
+
/** Configuration for {@link createI18n}. */
|
|
15
|
+
export interface I18nOptions {
|
|
16
|
+
catalog: Catalog;
|
|
17
|
+
/** Active locale. Must be a key of `catalog` for lookups to resolve there. */
|
|
18
|
+
locale: Locale;
|
|
19
|
+
/** Locale consulted when a key is missing in the active locale. */
|
|
20
|
+
fallbackLocale?: Locale;
|
|
21
|
+
/** Invoked when a key resolves nowhere (dev warnings/telemetry); `t` still returns the key. */
|
|
22
|
+
onMissing?: (key: string, locale: Locale) => void;
|
|
23
|
+
}
|
|
24
|
+
/** Runtime translator over a message {@link Catalog} — active locale, lookup with fallback, interpolation, and pluralization. */
|
|
25
|
+
export interface I18n {
|
|
26
|
+
locale(): Locale;
|
|
27
|
+
/** Switch the active locale; notifies subscribers when it actually changes. */
|
|
28
|
+
setLocale(locale: Locale): void;
|
|
29
|
+
/** Locales present in the catalog. */
|
|
30
|
+
locales(): readonly Locale[];
|
|
31
|
+
/** True when `key` resolves in the active or fallback locale. */
|
|
32
|
+
has(key: string): boolean;
|
|
33
|
+
/** Translate `key`, interpolating `{param}` slots; returns `key` itself if it resolves nowhere. */
|
|
34
|
+
t(key: string, params?: TParams): string;
|
|
35
|
+
/**
|
|
36
|
+
* Plural-aware translate: picks `${key}.${category}` for the active locale's
|
|
37
|
+
* Intl plural category (falling back to `${key}.other`, then `key`), with
|
|
38
|
+
* `count` available as the `{count}` interpolation param.
|
|
39
|
+
*/
|
|
40
|
+
plural(key: string, count: number, params?: TParams): string;
|
|
41
|
+
subscribe(listener: () => void): () => void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create a translator over a message catalog: active-locale lookup with a
|
|
45
|
+
* fallback-locale chain, `{param}` interpolation, and `Intl.PluralRules`-based
|
|
46
|
+
* pluralization. Observable (`subscribe`) so React re-renders on `setLocale`;
|
|
47
|
+
* the catalog is caller-owned static data, the locale is the only state.
|
|
48
|
+
*
|
|
49
|
+
* @capability i18n translate a message catalog with fallback-locale lookup, `{param}` interpolation, and Intl pluralization; observable active-locale switching
|
|
50
|
+
*/
|
|
51
|
+
export declare function createI18n(options: I18nOptions): I18n;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replace `{name}` placeholders in `template` with `params[name]`. Unknown
|
|
3
|
+
* placeholders are left intact. Pure and locale-independent.
|
|
4
|
+
*/
|
|
5
|
+
export function interpolate(template, params) {
|
|
6
|
+
if (params === undefined)
|
|
7
|
+
return template;
|
|
8
|
+
return template.replace(/\{(\w+)\}/g, (whole, name) => {
|
|
9
|
+
const value = params[name];
|
|
10
|
+
return value === undefined ? whole : String(value);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function pluralCategory(locale, count) {
|
|
14
|
+
try {
|
|
15
|
+
return new Intl.PluralRules(locale).select(count);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return count === 1 ? "one" : "other";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create a translator over a message catalog: active-locale lookup with a
|
|
23
|
+
* fallback-locale chain, `{param}` interpolation, and `Intl.PluralRules`-based
|
|
24
|
+
* pluralization. Observable (`subscribe`) so React re-renders on `setLocale`;
|
|
25
|
+
* the catalog is caller-owned static data, the locale is the only state.
|
|
26
|
+
*
|
|
27
|
+
* @capability i18n translate a message catalog with fallback-locale lookup, `{param}` interpolation, and Intl pluralization; observable active-locale switching
|
|
28
|
+
*/
|
|
29
|
+
export function createI18n(options) {
|
|
30
|
+
const { catalog, fallbackLocale, onMissing } = options;
|
|
31
|
+
let active = options.locale;
|
|
32
|
+
const listeners = new Set();
|
|
33
|
+
function resolve(key) {
|
|
34
|
+
const inActive = catalog[active]?.[key];
|
|
35
|
+
if (inActive !== undefined)
|
|
36
|
+
return inActive;
|
|
37
|
+
if (fallbackLocale !== undefined && fallbackLocale !== active)
|
|
38
|
+
return catalog[fallbackLocale]?.[key];
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const i18n = {
|
|
42
|
+
locale() {
|
|
43
|
+
return active;
|
|
44
|
+
},
|
|
45
|
+
setLocale(locale) {
|
|
46
|
+
if (locale === active)
|
|
47
|
+
return;
|
|
48
|
+
active = locale;
|
|
49
|
+
for (const listener of listeners)
|
|
50
|
+
listener();
|
|
51
|
+
},
|
|
52
|
+
locales() {
|
|
53
|
+
return Object.keys(catalog);
|
|
54
|
+
},
|
|
55
|
+
has(key) {
|
|
56
|
+
return resolve(key) !== undefined;
|
|
57
|
+
},
|
|
58
|
+
t(key, params) {
|
|
59
|
+
const template = resolve(key);
|
|
60
|
+
if (template === undefined) {
|
|
61
|
+
onMissing?.(key, active);
|
|
62
|
+
return key;
|
|
63
|
+
}
|
|
64
|
+
return interpolate(template, params);
|
|
65
|
+
},
|
|
66
|
+
plural(key, count, params) {
|
|
67
|
+
const category = pluralCategory(active, count);
|
|
68
|
+
const template = resolve(`${key}.${category}`) ?? resolve(`${key}.other`) ?? resolve(key);
|
|
69
|
+
const merged = { count, ...(params ?? {}) };
|
|
70
|
+
if (template === undefined) {
|
|
71
|
+
onMissing?.(`${key}.${category}`, active);
|
|
72
|
+
return key;
|
|
73
|
+
}
|
|
74
|
+
return interpolate(template, merged);
|
|
75
|
+
},
|
|
76
|
+
subscribe(listener) {
|
|
77
|
+
listeners.add(listener);
|
|
78
|
+
return () => listeners.delete(listener);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
return i18n;
|
|
82
|
+
}
|
|
@@ -73,6 +73,6 @@ export interface GenericAxisChannel<TAxes extends string> {
|
|
|
73
73
|
* is active), clamped to the axis range; axes without a listed range default to bipolar `[-1, 1]`.
|
|
74
74
|
* @internal
|
|
75
75
|
*/
|
|
76
|
-
export declare function sampleAxisBindings<TAxes extends string>(bindings: Record<TAxes, AxisBinding>, isDown: (code: string) => boolean, pointer?: PointerAxisState | null, ranges?: Partial<Record<TAxes, AxisRange
|
|
76
|
+
export declare function sampleAxisBindings<TAxes extends string>(bindings: Record<TAxes, AxisBinding>, isDown: (code: string) => boolean, pointer?: PointerAxisState | null, ranges?: Partial<Record<TAxes, AxisRange>>, value?: (code: string) => number): Record<TAxes, number>;
|
|
77
77
|
/** @internal */
|
|
78
78
|
export declare function createAxisChannel<TAxes extends string>(config: GenericAxisConfig<TAxes>): GenericAxisChannel<TAxes>;
|
package/dist/input/axisInput.js
CHANGED
|
@@ -25,6 +25,21 @@ function digitalTarget(binding, isDown) {
|
|
|
25
25
|
const negative = binding.negative !== undefined && binding.negative.some(isDown) ? 1 : 0;
|
|
26
26
|
return positive - negative;
|
|
27
27
|
}
|
|
28
|
+
/** Strongest magnitude across a side's codes — with a `value` source an analog stick contributes its actual deflection; without one this degrades to {@link digitalTarget}'s 0/1. */
|
|
29
|
+
function sideValue(codes, value) {
|
|
30
|
+
if (codes === undefined)
|
|
31
|
+
return 0;
|
|
32
|
+
let strongest = 0;
|
|
33
|
+
for (const code of codes) {
|
|
34
|
+
const magnitude = value(code);
|
|
35
|
+
if (magnitude > strongest)
|
|
36
|
+
strongest = magnitude;
|
|
37
|
+
}
|
|
38
|
+
return strongest;
|
|
39
|
+
}
|
|
40
|
+
function analogTarget(binding, value) {
|
|
41
|
+
return sideValue(binding.positive, value) - sideValue(binding.negative, value);
|
|
42
|
+
}
|
|
28
43
|
/**
|
|
29
44
|
* Analog control channel — distinct from the digital action bindings. Throttle/brake/steer/handbrake
|
|
30
45
|
* are continuous values ramped from held keys (a keyboard feels like a pedal) or driven directly from
|
|
@@ -85,13 +100,14 @@ const BIPOLAR_RANGE = { min: -1, max: 1 };
|
|
|
85
100
|
* is active), clamped to the axis range; axes without a listed range default to bipolar `[-1, 1]`.
|
|
86
101
|
* @internal
|
|
87
102
|
*/
|
|
88
|
-
export function sampleAxisBindings(bindings, isDown, pointer, ranges) {
|
|
103
|
+
export function sampleAxisBindings(bindings, isDown, pointer, ranges, value) {
|
|
89
104
|
const out = {};
|
|
90
105
|
for (const axis of Object.keys(bindings)) {
|
|
91
106
|
const binding = bindings[axis];
|
|
92
107
|
const range = ranges?.[axis] ?? BIPOLAR_RANGE;
|
|
93
108
|
const fromPointer = binding.pointer === undefined ? null : pointerAxisValue(binding.pointer, pointer ?? null);
|
|
94
|
-
|
|
109
|
+
const fromBindings = value === undefined ? digitalTarget(binding, isDown) : analogTarget(binding, value);
|
|
110
|
+
out[axis] = clampAxis(fromPointer ?? fromBindings, range);
|
|
95
111
|
}
|
|
96
112
|
return out;
|
|
97
113
|
}
|