@jgengine/core 0.11.0 → 0.12.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 +26 -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.d.ts +1 -0
- package/dist/combat.js +1 -0
- 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/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/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/playableGame.d.ts +34 -435
- package/dist/game/playableGame.js +1 -1
- package/dist/game/progression.d.ts +35 -0
- package/dist/game/progression.js +19 -0
- 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/item/durability.js +1 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +24 -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 +130 -0
- 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 +59 -0
- package/dist/runtime/context/scene.js +291 -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 +88 -501
- package/dist/runtime/gameContextTypes.d.ts +467 -0
- package/dist/runtime/gameContextTypes.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +4 -2
- package/dist/runtime/headlessRunner.js +1 -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 +5 -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 +58 -0
- package/dist/scene/colliders.js +108 -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 +10 -14
- 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/statPool.d.ts +70 -0
- package/dist/stats/statPool.js +59 -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/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/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/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/features.d.ts +18 -3
- package/dist/world/features.js +10 -1
- 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/pathNetwork.d.ts +159 -0
- package/dist/world/pathNetwork.js +684 -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/scatter.js +2 -20
- package/dist/world/streets.d.ts +29 -0
- package/dist/world/streets.js +55 -0
- package/dist/world/terraform.js +58 -88
- package/dist/world/terrain.d.ts +45 -0
- package/dist/world/terrain.js +78 -0
- 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 +7 -4
- package/skills/jgengine/api.md +172 -79
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-combat/SKILL.md +5 -1
- package/skills/jgengine-combat/api.md +36 -0
- package/skills/jgengine-combat/capabilities.md +44 -0
- package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
- package/skills/jgengine-combat/reference.md +12 -2
- package/skills/jgengine-gameplay/api.md +152 -26
- package/skills/jgengine-gameplay/capabilities.md +78 -2
- package/skills/jgengine-gameplay/reference-systems.md +1 -5
- package/skills/jgengine-world/SKILL.md +13 -0
- package/skills/jgengine-world/api.md +419 -15
- package/skills/jgengine-world/capabilities.md +108 -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,147 @@
|
|
|
1
|
+
import type { MobVec3 } from "../ai/mobBrain.js";
|
|
2
|
+
import type { StatCatalog, StatValue, StatValueMap } from "./entityStats.js";
|
|
3
|
+
/**
|
|
4
|
+
* A tamed companion's standing order. Genre-agnostic aggression + posture preset:
|
|
5
|
+
* - `follow` — heel to the owner and assist whatever the owner is fighting.
|
|
6
|
+
* - `stay` — hold at `home` and assist the owner's target from there.
|
|
7
|
+
* - `passive` — heel to the owner and never engage (a pacifist escort).
|
|
8
|
+
* - `neutral` — heel to the owner and only fight back nearby threats (defensive).
|
|
9
|
+
* - `aggressive` — heel to the owner and proactively engage nearby threats and the owner's target.
|
|
10
|
+
*/
|
|
11
|
+
export type CompanionCommand = "follow" | "stay" | "passive" | "neutral" | "aggressive";
|
|
12
|
+
/**
|
|
13
|
+
* Serializable per-companion state: who owns it, its standing order, where it holds, its
|
|
14
|
+
* leash radius, and its levelled stat pool. Plain data — JSON round-trips without loss.
|
|
15
|
+
*/
|
|
16
|
+
export interface CompanionRecord {
|
|
17
|
+
id: string;
|
|
18
|
+
/** Owning user/entity id — the roster ownership tie. */
|
|
19
|
+
ownerId: string;
|
|
20
|
+
/** The {@link RosterEntry.id} this companion was tamed from, or `null` when free-standing. */
|
|
21
|
+
sourceId: string | null;
|
|
22
|
+
command: CompanionCommand;
|
|
23
|
+
/** Hold/return anchor used by `stay`; `null` until the owner parks it somewhere. */
|
|
24
|
+
home: MobVec3 | null;
|
|
25
|
+
/** Radius the game keeps the companion within of its resolved anchor (feeds a brain's leash distance). */
|
|
26
|
+
leash: number;
|
|
27
|
+
level: number;
|
|
28
|
+
/** Stat points earned from levels and not yet spent via {@link CompanionRoster.spend}. */
|
|
29
|
+
unspentPoints: number;
|
|
30
|
+
/** Levelled stat pool, composed over `scene/entityStats`. */
|
|
31
|
+
stats: StatValueMap;
|
|
32
|
+
}
|
|
33
|
+
/** Per-tick world context the game supplies so a command resolves into a concrete intent. */
|
|
34
|
+
export interface CompanionIntentContext {
|
|
35
|
+
/** Owner's current world position — the anchor for every non-`stay` mode; `null` when absent. */
|
|
36
|
+
ownerPosition?: MobVec3 | null;
|
|
37
|
+
/** The owner's current attack target — the companion assists/aggros it per its command. */
|
|
38
|
+
ownerTargetId?: string | null;
|
|
39
|
+
/** Threat ids near the companion the game offers as engage candidates (already proximity-filtered upstream). */
|
|
40
|
+
threats?: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The companion's decision for one tick, shaped for `ai/mobBrain`: feed `candidates` into
|
|
44
|
+
* `MobBrainDeps.candidates()`, treat `leashTo` as the brain's `home`, and pass `leashDistance`
|
|
45
|
+
* as `leashDistance`. `kind` summarises the posture for HUD/animation.
|
|
46
|
+
*/
|
|
47
|
+
export interface CompanionIntent {
|
|
48
|
+
/** `follow`/`hold` when idle at the anchor; `engage` a fresh threat; `assist` the owner's target. */
|
|
49
|
+
kind: "follow" | "hold" | "engage" | "assist";
|
|
50
|
+
/** Whether the companion leashes to the owner (follow modes) or its `home` (stay). */
|
|
51
|
+
anchor: "owner" | "home";
|
|
52
|
+
/** Resolved anchor world point — owner position or `home`; `null` when that point is unknown. */
|
|
53
|
+
leashTo: MobVec3 | null;
|
|
54
|
+
/** Leash radius (copied from the record) the game passes to the brain / clamps movement against. */
|
|
55
|
+
leashDistance: number;
|
|
56
|
+
/** Threat ids to expose as `MobBrainDeps.candidates()`; empty when the command forbids engaging. */
|
|
57
|
+
candidates: readonly string[];
|
|
58
|
+
/** The primary threat to engage/assist this tick — `candidates[0]`; `null` while following/holding. */
|
|
59
|
+
targetId: string | null;
|
|
60
|
+
}
|
|
61
|
+
/** Rule for turning one spent level-point into a stat gain. Absent from config ⇒ the stat can't be upgraded. */
|
|
62
|
+
export interface CompanionStatUpgrade {
|
|
63
|
+
/** Amount a single point adds to the stat's `max` (and heals into `current`). */
|
|
64
|
+
increment: number;
|
|
65
|
+
/** Hard ceiling for the stat's `max`; spends that would exceed it are rejected. */
|
|
66
|
+
maxCap?: number;
|
|
67
|
+
}
|
|
68
|
+
/** Outcome of {@link CompanionRoster.spend}: the updated record + stat, or a rejection reason. */
|
|
69
|
+
export type CompanionSpendResult = {
|
|
70
|
+
status: "ok";
|
|
71
|
+
record: CompanionRecord;
|
|
72
|
+
stat: StatValue;
|
|
73
|
+
} | {
|
|
74
|
+
status: "rejected";
|
|
75
|
+
reason: string;
|
|
76
|
+
};
|
|
77
|
+
/** Per-companion configuration shared by every companion the roster mints. */
|
|
78
|
+
export interface CompanionConfig {
|
|
79
|
+
/** Stat catalog seeded into each new companion's pool (via `entityStats.seedStatValues`). */
|
|
80
|
+
stats?: StatCatalog;
|
|
81
|
+
/** Per-stat upgrade rules consulted by {@link CompanionRoster.spend}. */
|
|
82
|
+
upgrades?: Record<string, CompanionStatUpgrade>;
|
|
83
|
+
/** Points granted on reaching a given level; default `1` per level. Inject `game/progression` curves here. */
|
|
84
|
+
pointsPerLevel?: (level: number) => number;
|
|
85
|
+
/** Level ceiling; `levelUp` past it is a no-op. Default `Infinity`. */
|
|
86
|
+
maxLevel?: number;
|
|
87
|
+
/** Level every companion starts at; default `1`. */
|
|
88
|
+
startLevel?: number;
|
|
89
|
+
/** Default leash radius for new companions; default `12`. */
|
|
90
|
+
leash?: number;
|
|
91
|
+
}
|
|
92
|
+
/** Options for {@link CompanionRoster.adopt}. */
|
|
93
|
+
export interface CompanionAdoptOptions {
|
|
94
|
+
id?: string;
|
|
95
|
+
/** The {@link RosterEntry.id} this companion springs from — the ownership/catalog tie. */
|
|
96
|
+
sourceId?: string;
|
|
97
|
+
command?: CompanionCommand;
|
|
98
|
+
home?: MobVec3 | null;
|
|
99
|
+
leash?: number;
|
|
100
|
+
level?: number;
|
|
101
|
+
/** Override the config stat catalog for this one companion. */
|
|
102
|
+
stats?: StatCatalog;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A set of tamed companions keyed by companion id, scoped to owners. Wraps allegiance/command
|
|
106
|
+
* state, per-companion levelled stats, and intent resolution behind create/command/level/
|
|
107
|
+
* snapshot/hydrate — the counterpart of `scene/roster` for creatures that fight beside you.
|
|
108
|
+
*/
|
|
109
|
+
export interface CompanionRoster {
|
|
110
|
+
/** Mint a companion for an owner (optionally tied to a {@link RosterEntry}). */
|
|
111
|
+
adopt(ownerId: string, options?: CompanionAdoptOptions): CompanionRecord;
|
|
112
|
+
release(companionId: string): boolean;
|
|
113
|
+
get(companionId: string): CompanionRecord | null;
|
|
114
|
+
ownerOf(companionId: string): string | null;
|
|
115
|
+
list(ownerId: string): readonly CompanionRecord[];
|
|
116
|
+
/** Set a companion's standing order; `null` if the id is unknown. */
|
|
117
|
+
command(companionId: string, command: CompanionCommand): CompanionRecord | null;
|
|
118
|
+
/** Park/clear a companion's `stay` anchor. */
|
|
119
|
+
setHome(companionId: string, home: MobVec3 | null): CompanionRecord | null;
|
|
120
|
+
setLeash(companionId: string, leash: number): CompanionRecord | null;
|
|
121
|
+
/** Gain `levels` levels (bounded by `maxLevel`), accruing points from `pointsPerLevel`. */
|
|
122
|
+
levelUp(companionId: string, levels?: number): CompanionRecord | null;
|
|
123
|
+
/** Spend one earned point into a stat per its {@link CompanionStatUpgrade} rule. */
|
|
124
|
+
spend(companionId: string, statId: string): CompanionSpendResult;
|
|
125
|
+
/** Resolve the companion's per-tick {@link CompanionIntent}; `null` if the id is unknown. */
|
|
126
|
+
resolveIntent(companionId: string, context: CompanionIntentContext): CompanionIntent | null;
|
|
127
|
+
/** Whole-store serializable copy — the save/replication seam. */
|
|
128
|
+
snapshot(): Record<string, CompanionRecord>;
|
|
129
|
+
hydrate(data: Record<string, CompanionRecord>): void;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Pure command → intent resolver. Maps a companion's standing order plus this tick's owner
|
|
133
|
+
* position, owner target, and nearby threats into the {@link CompanionIntent} a game feeds into
|
|
134
|
+
* `ai/mobBrain` (or executes directly). Deterministic and allocation-light — no import of the
|
|
135
|
+
* `@internal` brain factory; the two interoperate purely by shape.
|
|
136
|
+
*
|
|
137
|
+
* @capability companion-intent resolve a companion's command + threat context into a per-tick follow/hold/engage/assist intent
|
|
138
|
+
*/
|
|
139
|
+
export declare function resolveCompanionIntent(record: CompanionRecord, context: CompanionIntentContext): CompanionIntent;
|
|
140
|
+
/**
|
|
141
|
+
* Build a {@link CompanionRoster}. Composes `scene/roster` ownership (via `ownerId`/`sourceId`),
|
|
142
|
+
* `scene/entityStats` for the per-companion stat pool, and {@link resolveCompanionIntent} for
|
|
143
|
+
* `ai/mobBrain`-shaped behavior — turning a captured creature into a loyal, levelling ally.
|
|
144
|
+
*
|
|
145
|
+
* @capability companion-roster manage tamed companions — allegiance, commands, per-companion stats/leveling, and mobBrain-shaped intent
|
|
146
|
+
*/
|
|
147
|
+
export declare function createCompanionRoster(config?: CompanionConfig): CompanionRoster;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { seedStatValues, setStatValue } from "./entityStats.js";
|
|
2
|
+
function dedupePush(out, id) {
|
|
3
|
+
if (!out.includes(id))
|
|
4
|
+
out.push(id);
|
|
5
|
+
}
|
|
6
|
+
function candidatesFor(command, ownerTargetId, threats) {
|
|
7
|
+
switch (command) {
|
|
8
|
+
case "passive":
|
|
9
|
+
return [];
|
|
10
|
+
case "follow":
|
|
11
|
+
case "stay":
|
|
12
|
+
return ownerTargetId === null ? [] : [ownerTargetId];
|
|
13
|
+
case "neutral":
|
|
14
|
+
return [...threats];
|
|
15
|
+
case "aggressive": {
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const threat of threats)
|
|
18
|
+
dedupePush(out, threat);
|
|
19
|
+
if (ownerTargetId !== null)
|
|
20
|
+
dedupePush(out, ownerTargetId);
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Pure command → intent resolver. Maps a companion's standing order plus this tick's owner
|
|
27
|
+
* position, owner target, and nearby threats into the {@link CompanionIntent} a game feeds into
|
|
28
|
+
* `ai/mobBrain` (or executes directly). Deterministic and allocation-light — no import of the
|
|
29
|
+
* `@internal` brain factory; the two interoperate purely by shape.
|
|
30
|
+
*
|
|
31
|
+
* @capability companion-intent resolve a companion's command + threat context into a per-tick follow/hold/engage/assist intent
|
|
32
|
+
*/
|
|
33
|
+
export function resolveCompanionIntent(record, context) {
|
|
34
|
+
const ownerTargetId = context.ownerTargetId ?? null;
|
|
35
|
+
const threats = context.threats ?? [];
|
|
36
|
+
const anchor = record.command === "stay" ? "home" : "owner";
|
|
37
|
+
const leashTo = anchor === "home" ? record.home : (context.ownerPosition ?? null);
|
|
38
|
+
const candidates = candidatesFor(record.command, ownerTargetId, threats);
|
|
39
|
+
const targetId = candidates.length > 0 ? candidates[0] : null;
|
|
40
|
+
let kind;
|
|
41
|
+
if (targetId === null) {
|
|
42
|
+
kind = anchor === "home" ? "hold" : "follow";
|
|
43
|
+
}
|
|
44
|
+
else if (targetId === ownerTargetId) {
|
|
45
|
+
kind = "assist";
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
kind = "engage";
|
|
49
|
+
}
|
|
50
|
+
return { kind, anchor, leashTo, leashDistance: record.leash, candidates, targetId };
|
|
51
|
+
}
|
|
52
|
+
function cloneStats(stats) {
|
|
53
|
+
const out = {};
|
|
54
|
+
for (const [statId, value] of Object.entries(stats))
|
|
55
|
+
out[statId] = { ...value };
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
function cloneRecord(record) {
|
|
59
|
+
return {
|
|
60
|
+
id: record.id,
|
|
61
|
+
ownerId: record.ownerId,
|
|
62
|
+
sourceId: record.sourceId,
|
|
63
|
+
command: record.command,
|
|
64
|
+
home: record.home === null ? null : [record.home[0], record.home[1], record.home[2]],
|
|
65
|
+
leash: record.leash,
|
|
66
|
+
level: record.level,
|
|
67
|
+
unspentPoints: record.unspentPoints,
|
|
68
|
+
stats: cloneStats(record.stats),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build a {@link CompanionRoster}. Composes `scene/roster` ownership (via `ownerId`/`sourceId`),
|
|
73
|
+
* `scene/entityStats` for the per-companion stat pool, and {@link resolveCompanionIntent} for
|
|
74
|
+
* `ai/mobBrain`-shaped behavior — turning a captured creature into a loyal, levelling ally.
|
|
75
|
+
*
|
|
76
|
+
* @capability companion-roster manage tamed companions — allegiance, commands, per-companion stats/leveling, and mobBrain-shaped intent
|
|
77
|
+
*/
|
|
78
|
+
export function createCompanionRoster(config = {}) {
|
|
79
|
+
const records = new Map();
|
|
80
|
+
const pointsPerLevel = config.pointsPerLevel ?? (() => 1);
|
|
81
|
+
const maxLevel = config.maxLevel ?? Number.POSITIVE_INFINITY;
|
|
82
|
+
const startLevel = config.startLevel ?? 1;
|
|
83
|
+
const defaultLeash = config.leash ?? 12;
|
|
84
|
+
let counter = 0;
|
|
85
|
+
const write = (record) => {
|
|
86
|
+
records.set(record.id, record);
|
|
87
|
+
return record;
|
|
88
|
+
};
|
|
89
|
+
const patch = (companionId, fn) => {
|
|
90
|
+
const record = records.get(companionId);
|
|
91
|
+
if (record === undefined)
|
|
92
|
+
return null;
|
|
93
|
+
return write(fn(record));
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
adopt(ownerId, options = {}) {
|
|
97
|
+
counter += 1;
|
|
98
|
+
const id = options.id ?? `companion_${counter}`;
|
|
99
|
+
const seed = options.stats ?? config.stats ?? {};
|
|
100
|
+
const record = {
|
|
101
|
+
id,
|
|
102
|
+
ownerId,
|
|
103
|
+
sourceId: options.sourceId ?? null,
|
|
104
|
+
command: options.command ?? "follow",
|
|
105
|
+
home: options.home ?? null,
|
|
106
|
+
leash: options.leash ?? defaultLeash,
|
|
107
|
+
level: options.level ?? startLevel,
|
|
108
|
+
unspentPoints: 0,
|
|
109
|
+
stats: seedStatValues(seed),
|
|
110
|
+
};
|
|
111
|
+
return write(record);
|
|
112
|
+
},
|
|
113
|
+
release(companionId) {
|
|
114
|
+
return records.delete(companionId);
|
|
115
|
+
},
|
|
116
|
+
get(companionId) {
|
|
117
|
+
return records.get(companionId) ?? null;
|
|
118
|
+
},
|
|
119
|
+
ownerOf(companionId) {
|
|
120
|
+
return records.get(companionId)?.ownerId ?? null;
|
|
121
|
+
},
|
|
122
|
+
list(ownerId) {
|
|
123
|
+
const out = [];
|
|
124
|
+
for (const record of records.values())
|
|
125
|
+
if (record.ownerId === ownerId)
|
|
126
|
+
out.push(record);
|
|
127
|
+
return out;
|
|
128
|
+
},
|
|
129
|
+
command(companionId, command) {
|
|
130
|
+
return patch(companionId, (record) => ({ ...record, command }));
|
|
131
|
+
},
|
|
132
|
+
setHome(companionId, home) {
|
|
133
|
+
return patch(companionId, (record) => ({
|
|
134
|
+
...record,
|
|
135
|
+
home: home === null ? null : [home[0], home[1], home[2]],
|
|
136
|
+
}));
|
|
137
|
+
},
|
|
138
|
+
setLeash(companionId, leash) {
|
|
139
|
+
return patch(companionId, (record) => ({ ...record, leash }));
|
|
140
|
+
},
|
|
141
|
+
levelUp(companionId, levels = 1) {
|
|
142
|
+
return patch(companionId, (record) => {
|
|
143
|
+
let level = record.level;
|
|
144
|
+
let points = record.unspentPoints;
|
|
145
|
+
for (let i = 0; i < levels && level < maxLevel; i += 1) {
|
|
146
|
+
level += 1;
|
|
147
|
+
points += pointsPerLevel(level);
|
|
148
|
+
}
|
|
149
|
+
return { ...record, level, unspentPoints: points };
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
spend(companionId, statId) {
|
|
153
|
+
const record = records.get(companionId);
|
|
154
|
+
if (record === undefined)
|
|
155
|
+
return { status: "rejected", reason: `unknown companion "${companionId}"` };
|
|
156
|
+
if (record.unspentPoints <= 0)
|
|
157
|
+
return { status: "rejected", reason: "no unspent points" };
|
|
158
|
+
const upgrade = config.upgrades?.[statId];
|
|
159
|
+
if (upgrade === undefined)
|
|
160
|
+
return { status: "rejected", reason: `stat "${statId}" is not upgradable` };
|
|
161
|
+
const existing = record.stats[statId];
|
|
162
|
+
if (existing === undefined)
|
|
163
|
+
return { status: "rejected", reason: `unknown stat "${statId}"` };
|
|
164
|
+
const nextMax = existing.max + upgrade.increment;
|
|
165
|
+
if (upgrade.maxCap !== undefined && nextMax > upgrade.maxCap) {
|
|
166
|
+
return { status: "rejected", reason: `stat "${statId}" is at its cap` };
|
|
167
|
+
}
|
|
168
|
+
const stats = setStatValue(record.stats, statId, {
|
|
169
|
+
max: nextMax,
|
|
170
|
+
current: existing.current + upgrade.increment,
|
|
171
|
+
});
|
|
172
|
+
const updated = write({ ...record, unspentPoints: record.unspentPoints - 1, stats });
|
|
173
|
+
return { status: "ok", record: updated, stat: updated.stats[statId] };
|
|
174
|
+
},
|
|
175
|
+
resolveIntent(companionId, context) {
|
|
176
|
+
const record = records.get(companionId);
|
|
177
|
+
if (record === undefined)
|
|
178
|
+
return null;
|
|
179
|
+
return resolveCompanionIntent(record, context);
|
|
180
|
+
},
|
|
181
|
+
snapshot() {
|
|
182
|
+
const out = {};
|
|
183
|
+
for (const [id, record] of records)
|
|
184
|
+
out[id] = cloneRecord(record);
|
|
185
|
+
return out;
|
|
186
|
+
},
|
|
187
|
+
hydrate(data) {
|
|
188
|
+
records.clear();
|
|
189
|
+
for (const [id, record] of Object.entries(data))
|
|
190
|
+
records.set(id, cloneRecord(record));
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
min: number;
|
|
1
|
+
import { type StatPool, type StatPoolAccess, type StatPoolPatch } from "../stats/statPool.js";
|
|
2
|
+
/** Native entity-stat name retained as a compatibility bridge to the portable pool model. */
|
|
3
|
+
export interface StatValue extends StatPool {
|
|
5
4
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
max?: number;
|
|
9
|
-
min?: number;
|
|
5
|
+
/** Native entity-stat patch retained as a compatibility bridge. */
|
|
6
|
+
export interface StatValuePatch extends StatPoolPatch {
|
|
10
7
|
}
|
|
11
8
|
export type StatValueMap = Record<string, StatValue>;
|
|
12
9
|
export type StatCatalog = Record<string, {
|
|
@@ -40,7 +37,8 @@ export declare function snapshotEntityStats(store: ReadonlyMap<string, StatValue
|
|
|
40
37
|
* @internal
|
|
41
38
|
*/
|
|
42
39
|
export declare function hydrateEntityStats(store: Map<string, StatValueMap>, data: Record<string, StatValueMap>): void;
|
|
43
|
-
|
|
40
|
+
/** Native entity-stat adapter over the portable {@link StatPoolAccess} contract. */
|
|
41
|
+
export interface EntityStatsApi extends StatPoolAccess {
|
|
44
42
|
get(instanceId: string, statId: string): StatValue | null;
|
|
45
43
|
set(instanceId: string, statId: string, patch: StatValuePatch): boolean;
|
|
46
44
|
delta(instanceId: string, statId: string, amount: number): null | {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
return Math.min(max, Math.max(min, value));
|
|
3
|
-
}
|
|
1
|
+
import { changeStatPool, createStatPool, patchStatPool, } from "../stats/statPool.js";
|
|
4
2
|
/** @internal */
|
|
5
3
|
export function getStatValue(map, statId) {
|
|
6
4
|
return map[statId] ?? null;
|
|
@@ -8,10 +6,10 @@ export function getStatValue(map, statId) {
|
|
|
8
6
|
/** @internal */
|
|
9
7
|
export function setStatValue(map, statId, patch) {
|
|
10
8
|
const existing = map[statId];
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return { ...map, [statId]:
|
|
9
|
+
const next = existing === undefined
|
|
10
|
+
? createStatPool({ max: patch.max ?? 0, min: patch.min, current: patch.current })
|
|
11
|
+
: patchStatPool(existing, patch);
|
|
12
|
+
return { ...map, [statId]: next };
|
|
15
13
|
}
|
|
16
14
|
/** @internal */
|
|
17
15
|
export function applyPoolDelta(map, statId, amount) {
|
|
@@ -19,23 +17,21 @@ export function applyPoolDelta(map, statId, amount) {
|
|
|
19
17
|
if (existing === undefined) {
|
|
20
18
|
return { status: "rejected", reason: `unknown stat "${statId}"` };
|
|
21
19
|
}
|
|
22
|
-
const
|
|
23
|
-
const stat =
|
|
20
|
+
const change = changeStatPool(existing, amount);
|
|
21
|
+
const stat = change.pool;
|
|
24
22
|
return {
|
|
25
23
|
status: "ok",
|
|
26
24
|
map: { ...map, [statId]: stat },
|
|
27
25
|
stat,
|
|
28
|
-
hitMin:
|
|
29
|
-
hitMax:
|
|
26
|
+
hitMin: change.hitMin,
|
|
27
|
+
hitMax: change.hitMax,
|
|
30
28
|
};
|
|
31
29
|
}
|
|
32
30
|
/** @internal */
|
|
33
31
|
export function seedStatValues(catalogStats) {
|
|
34
32
|
const map = {};
|
|
35
33
|
for (const [statId, declaration] of Object.entries(catalogStats)) {
|
|
36
|
-
|
|
37
|
-
const current = clamp(declaration.current ?? declaration.max, min, declaration.max);
|
|
38
|
-
map[statId] = { current, max: declaration.max, min };
|
|
34
|
+
map[statId] = createStatPool(declaration);
|
|
39
35
|
}
|
|
40
36
|
return map;
|
|
41
37
|
}
|
|
@@ -121,12 +121,28 @@ export interface ParamGroup {
|
|
|
121
121
|
/** Start collapsed. Default expanded. */
|
|
122
122
|
collapsed?: boolean;
|
|
123
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* A named slider/weight bundle for a kind — a saved set of field values the inspector (or the
|
|
126
|
+
* `apply_preset` verb) writes into an object's `meta` in one patch, ready to tweak afterwards.
|
|
127
|
+
* Presets are plain data on the schema, so kinds ship archetypes and games can override the
|
|
128
|
+
* registration with their own.
|
|
129
|
+
*/
|
|
130
|
+
export interface ParamPreset {
|
|
131
|
+
id: string;
|
|
132
|
+
label?: string;
|
|
133
|
+
/** Field values the preset writes; fields not listed keep their current values. */
|
|
134
|
+
values: Readonly<Record<string, unknown>>;
|
|
135
|
+
}
|
|
124
136
|
/** A kind's full parameter surface: an ordered list of fields the inspector renders top-to-bottom. */
|
|
125
137
|
export interface ParamSchema {
|
|
126
138
|
fields: readonly ParamField[];
|
|
127
139
|
/** Optional named sections; fields reference one by `group`. Ungrouped fields render first, headerless. */
|
|
128
140
|
groups?: readonly ParamGroup[];
|
|
141
|
+
/** Optional named field-value bundles the inspector offers as one-click applies. */
|
|
142
|
+
presets?: readonly ParamPreset[];
|
|
129
143
|
}
|
|
144
|
+
/** Look up a schema preset by id, or undefined — the seam `apply_preset` and games resolve bundles through. */
|
|
145
|
+
export declare function findSchemaPreset(schema: ParamSchema, id: string): ParamPreset | undefined;
|
|
130
146
|
/** Parsed params after `parseParams`: every schema field present with a validated, defaulted value. */
|
|
131
147
|
export type ParsedParams = Record<string, number | boolean | string | WeightedParamEntry[]>;
|
|
132
148
|
/**
|
package/dist/scene/sceneKinds.js
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* party ship a new authorable "studio" (pole line, water, bookcase) — schema + resolver here, a
|
|
6
6
|
* matching renderer in `shell` — without editing editor or engine files. Scatter is the proof adopter.
|
|
7
7
|
*/
|
|
8
|
+
/** Look up a schema preset by id, or undefined — the seam `apply_preset` and games resolve bundles through. */
|
|
9
|
+
export function findSchemaPreset(schema, id) {
|
|
10
|
+
return schema.presets?.find((preset) => preset.id === id);
|
|
11
|
+
}
|
|
8
12
|
function num(value, fallback, min, max) {
|
|
9
13
|
let n = typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
10
14
|
if (min !== undefined)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { colliderBounds, colliderWorldCenter, defaultEntityColliders, defaultObjectColliders, resolveColliders, } from "./colliders.js";
|
|
2
|
+
import { raycastCollisionMesh } from "./collisionMesh.js";
|
|
2
3
|
import { intersectAabb, normalizeDirection } from "./objectQuery.js";
|
|
3
4
|
function excluded(exclude, id) {
|
|
4
5
|
if (exclude === undefined)
|
|
@@ -54,6 +55,20 @@ function rayHitsCollider(origin, direction, maxDistance, collider, position, rot
|
|
|
54
55
|
],
|
|
55
56
|
};
|
|
56
57
|
}
|
|
58
|
+
if (collider.shape.kind === "mesh") {
|
|
59
|
+
const hit = raycastCollisionMesh(collider.shape.mesh, origin, direction, maxDistance, position, rotationY, collider.shape.meshScale, collider.shape.meshTranslate);
|
|
60
|
+
if (hit === null)
|
|
61
|
+
return null;
|
|
62
|
+
return {
|
|
63
|
+
distance: hit.distance,
|
|
64
|
+
normal: hit.normal,
|
|
65
|
+
point: [
|
|
66
|
+
origin[0] + direction[0] * hit.distance,
|
|
67
|
+
origin[1] + direction[1] * hit.distance,
|
|
68
|
+
origin[2] + direction[2] * hit.distance,
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
57
72
|
const bounds = colliderBounds(collider, position, rotationY);
|
|
58
73
|
const hit = intersectAabb(origin, direction, bounds.min, bounds.max, maxDistance);
|
|
59
74
|
if (hit === null)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-stat growth definition for one species. `base` is the level-1 value of the stat; `growth`
|
|
3
|
+
* is how much a single distributed point adds, expressed as a fraction of `base` — so a point on a
|
|
4
|
+
* `growth: 0.1` stat is worth +10% of base. The resolved value is `base * (1 + points * growth)`.
|
|
5
|
+
*/
|
|
6
|
+
export interface SpawnStatDef {
|
|
7
|
+
base: number;
|
|
8
|
+
growth: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A species' stat table: every stat the creature owns keyed to its {@link SpawnStatDef}. The key
|
|
12
|
+
* order is the deterministic order random points are distributed over, so a serialized species
|
|
13
|
+
* definition reproduces the same rolls under the same rng sequence.
|
|
14
|
+
*/
|
|
15
|
+
export type SpawnSpeciesDef<TStat extends string> = Record<TStat, SpawnStatDef>;
|
|
16
|
+
/**
|
|
17
|
+
* A single creature's serializable stat record — a reference to its species (`speciesId`), the
|
|
18
|
+
* current `level`, the per-stat count of distributed points, and how many post-capture domestic
|
|
19
|
+
* levels have been spent. Plain data: round-trips through JSON, save/load, and multiplayer sync.
|
|
20
|
+
* Resolve display values with {@link resolveSpawnStats}.
|
|
21
|
+
*/
|
|
22
|
+
export interface CreatureStatInstance<TStat extends string> {
|
|
23
|
+
speciesId: string;
|
|
24
|
+
level: number;
|
|
25
|
+
points: Record<TStat, number>;
|
|
26
|
+
domesticLevels: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Outcome of {@link applyCaptureEffectiveness}: the granted `bonusLevels`, the `tamedLevel`
|
|
30
|
+
* (wild level plus bonus levels), and the post-capture {@link CreatureStatInstance}.
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export interface CaptureResult<TStat extends string> {
|
|
34
|
+
bonusLevels: number;
|
|
35
|
+
tamedLevel: number;
|
|
36
|
+
instance: CreatureStatInstance<TStat>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Options for {@link spendDomesticLevel}. `maxDomesticLevels` caps how many post-capture levels a
|
|
40
|
+
* creature may spend; once reached, further spends are refused.
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
export interface SpendLevelOptions {
|
|
44
|
+
maxDomesticLevels?: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Result of {@link spendDomesticLevel}: the resulting instance and whether the level was applied
|
|
48
|
+
* (`spent: false` when the domestic-level cap blocked it, leaving the instance untouched).
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
export interface SpendLevelResult<TStat extends string> {
|
|
52
|
+
instance: CreatureStatInstance<TStat>;
|
|
53
|
+
spent: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Roll a fresh wild creature at `level`: distribute `(level - 1)` points randomly across the
|
|
57
|
+
* species' stats using the injected rng and return a serializable {@link CreatureStatInstance}.
|
|
58
|
+
* Deterministic — the same rng sequence yields the same point spread. Levels below 1 clamp to 1
|
|
59
|
+
* (no points). Use `seededRng` from `../random/rng` for a persisted, reproducible stream.
|
|
60
|
+
*
|
|
61
|
+
* @capability spawn-level-roll distribute a wild creature's spawn level into random per-stat points
|
|
62
|
+
*/
|
|
63
|
+
export declare function rollSpawnInstance<TStat extends string>(speciesId: string, species: SpawnSpeciesDef<TStat>, level: number, rng: () => number): CreatureStatInstance<TStat>;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve an instance's live stat values from its distributed points:
|
|
66
|
+
* `value = base * (1 + points * growth)` per stat. Stats with zero points return their base.
|
|
67
|
+
*
|
|
68
|
+
* @capability spawn-stat-values resolve a creature's per-stat values from its distributed points
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveSpawnStats<TStat extends string>(species: SpawnSpeciesDef<TStat>, instance: CreatureStatInstance<TStat>): Record<TStat, number>;
|
|
71
|
+
/**
|
|
72
|
+
* Bonus levels granted by a capture at the given effectiveness:
|
|
73
|
+
* `floor(level * clamp01(effectiveness) / 2)`. A perfect (1.0) capture grants half the wild
|
|
74
|
+
* level; a botched (0) capture grants none. Effectiveness outside `[0,1]` clamps.
|
|
75
|
+
*
|
|
76
|
+
* @capability capture-bonus-levels convert wild level and capture effectiveness into bonus levels
|
|
77
|
+
*/
|
|
78
|
+
export declare function captureBonusLevels(level: number, effectiveness: number): number;
|
|
79
|
+
/**
|
|
80
|
+
* Apply a capture at the given effectiveness: compute bonus levels via {@link captureBonusLevels},
|
|
81
|
+
* distribute those extra points randomly with the injected rng, and return the post-capture
|
|
82
|
+
* instance (level raised to the tamed total) alongside the granted bonus levels and tamed level.
|
|
83
|
+
* The input instance is not mutated.
|
|
84
|
+
*
|
|
85
|
+
* @capability capture-effectiveness-tame apply capture effectiveness to grant and distribute bonus levels
|
|
86
|
+
*/
|
|
87
|
+
export declare function applyCaptureEffectiveness<TStat extends string>(species: SpawnSpeciesDef<TStat>, instance: CreatureStatInstance<TStat>, effectiveness: number, rng: () => number): CaptureResult<TStat>;
|
|
88
|
+
/**
|
|
89
|
+
* Spend one earned post-capture level into `stat`, adding a single growth increment (one point)
|
|
90
|
+
* and raising both `level` and `domesticLevels`. When `maxDomesticLevels` is set and already
|
|
91
|
+
* reached, the spend is refused (`spent: false`) and the instance is returned untouched. Pure:
|
|
92
|
+
* returns a new instance rather than mutating the input.
|
|
93
|
+
*
|
|
94
|
+
* @capability domestic-level-spend spend an earned post-capture level into a chosen stat under a cap
|
|
95
|
+
*/
|
|
96
|
+
export declare function spendDomesticLevel<TStat extends string>(instance: CreatureStatInstance<TStat>, stat: TStat, options?: SpendLevelOptions): SpendLevelResult<TStat>;
|
|
97
|
+
/**
|
|
98
|
+
* Deep-copy an instance into a detached, serializable record — the transport/save counterpart of
|
|
99
|
+
* {@link restoreSpawnInstance}. Copies the points map so later edits don't leak into the snapshot.
|
|
100
|
+
*
|
|
101
|
+
* @capability spawn-instance-snapshot deep-copy a creature stat instance into a serializable record
|
|
102
|
+
*/
|
|
103
|
+
export declare function snapshotSpawnInstance<TStat extends string>(instance: CreatureStatInstance<TStat>): CreatureStatInstance<TStat>;
|
|
104
|
+
/**
|
|
105
|
+
* Rehydrate a creature stat instance from decoded data (e.g. `JSON.parse` output), returning a
|
|
106
|
+
* fresh deep copy the caller owns. Inverse of {@link snapshotSpawnInstance}.
|
|
107
|
+
*
|
|
108
|
+
* @capability spawn-instance-restore rehydrate a creature stat instance from decoded snapshot data
|
|
109
|
+
*/
|
|
110
|
+
export declare function restoreSpawnInstance<TStat extends string>(data: CreatureStatInstance<TStat>): CreatureStatInstance<TStat>;
|