@jgengine/core 0.25.0 → 0.27.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 +109 -0
- package/dist/ai/rvo.d.ts +106 -0
- package/dist/ai/rvo.js +581 -0
- package/dist/ai/utilityBrain.d.ts +173 -0
- package/dist/ai/utilityBrain.js +277 -0
- package/dist/combat/cover.d.ts +169 -0
- package/dist/combat/cover.js +264 -0
- package/dist/combat/damageRuntime.d.ts +150 -0
- package/dist/combat/damageRuntime.js +208 -0
- package/dist/combat/damageTable.d.ts +116 -0
- package/dist/combat/damageTable.js +165 -0
- package/dist/combat/hitZones.d.ts +70 -0
- package/dist/combat/hitZones.js +77 -0
- package/dist/combat/magazine.d.ts +8 -0
- package/dist/combat/magazine.js +20 -2
- package/dist/combat/squadCombat.d.ts +167 -0
- package/dist/combat/squadCombat.js +557 -0
- package/dist/combat/squadTactics.d.ts +73 -0
- package/dist/combat/squadTactics.js +49 -0
- package/dist/combat/statusRuntime.d.ts +142 -0
- package/dist/combat/statusRuntime.js +236 -0
- package/dist/combat/weaponDefinition.d.ts +106 -0
- package/dist/combat/weaponDefinition.js +108 -0
- package/dist/combat/weaponSystem.d.ts +189 -0
- package/dist/combat/weaponSystem.js +469 -0
- package/dist/combat.d.ts +9 -0
- package/dist/combat.js +9 -0
- package/dist/devtools/devtools.d.ts +4 -2
- package/dist/devtools/devtools.js +2 -1
- package/dist/devtools/discover.d.ts +2 -1
- package/dist/devtools/discover.js +38 -0
- package/dist/devtools/frame.d.ts +0 -2
- package/dist/devtools/frame.js +1 -2
- package/dist/devtools/logBus.d.ts +58 -0
- package/dist/devtools/logBus.js +79 -0
- package/dist/devtools/logs.d.ts +6 -3
- package/dist/devtools/logs.js +17 -13
- package/dist/devtools/report.d.ts +120 -0
- package/dist/devtools/report.js +49 -0
- package/dist/devtools/reportEndpoint.d.ts +17 -0
- package/dist/devtools/reportEndpoint.js +7 -0
- package/dist/devtools/types.d.ts +48 -11
- package/dist/editor/document.js +210 -0
- package/dist/editor/environment.d.ts +48 -1
- package/dist/editor/environment.js +161 -0
- package/dist/editor/index.d.ts +6 -2
- package/dist/editor/index.js +1 -1
- package/dist/editor/types.d.ts +54 -0
- package/dist/game/playableGame.d.ts +19 -2
- package/dist/gameplay.d.ts +9 -0
- package/dist/gameplay.js +9 -0
- package/dist/inventory/carryLoad.d.ts +48 -0
- package/dist/inventory/carryLoad.js +44 -0
- package/dist/inventory/equipment.d.ts +95 -0
- package/dist/inventory/equipment.js +169 -0
- package/dist/inventory/inventoryModel.d.ts +15 -6
- package/dist/inventory/inventoryModel.js +54 -5
- package/dist/inventory/shapedGrid.d.ts +26 -0
- package/dist/inventory/shapedGrid.js +47 -0
- package/dist/loot/lootGen.d.ts +186 -0
- package/dist/loot/lootGen.js +437 -0
- package/dist/loot/lootWeapon.d.ts +35 -0
- package/dist/loot/lootWeapon.js +70 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +92 -1
- package/dist/nav/laneGraph.d.ts +178 -0
- package/dist/nav/laneGraph.js +671 -0
- package/dist/nav/navCrowd.d.ts +138 -0
- package/dist/nav/navCrowd.js +412 -0
- package/dist/nav/navRuntime.d.ts +108 -0
- package/dist/nav/navRuntime.js +256 -0
- package/dist/nav/traffic.d.ts +223 -0
- package/dist/nav/traffic.js +919 -0
- package/dist/physics/kinematicVehicle.d.ts +36 -0
- package/dist/physics/kinematicVehicle.js +15 -1
- package/dist/physics/vehicleDamage.d.ts +136 -0
- package/dist/physics/vehicleDamage.js +214 -0
- package/dist/physics/vehicleParts.d.ts +62 -0
- package/dist/physics/vehicleParts.js +65 -0
- package/dist/progression/gearScore.d.ts +56 -0
- package/dist/progression/gearScore.js +59 -0
- package/dist/progression/skillTree.d.ts +129 -0
- package/dist/progression/skillTree.js +265 -0
- package/dist/progression/xpCurve.d.ts +60 -0
- package/dist/progression/xpCurve.js +141 -0
- package/dist/render/colorPipeline.d.ts +53 -0
- package/dist/render/colorPipeline.js +79 -0
- package/dist/render/lightProbes.d.ts +97 -0
- package/dist/render/lightProbes.js +221 -0
- package/dist/render/lookPreset.js +4 -4
- package/dist/render/physicalSky.d.ts +39 -0
- package/dist/render/physicalSky.js +89 -0
- package/dist/render/postProcessing.d.ts +40 -8
- package/dist/render/postProcessing.js +1 -1
- package/dist/render/skyOwnedLighting.d.ts +31 -0
- package/dist/render/skyOwnedLighting.js +46 -0
- package/dist/scene/builtinSceneKinds.js +2 -0
- package/dist/sensor/perception.d.ts +66 -5
- package/dist/sensor/perception.js +167 -42
- package/dist/sim/nearAgents.d.ts +68 -0
- package/dist/sim/nearAgents.js +152 -0
- package/dist/sim/needs.d.ts +60 -0
- package/dist/sim/needs.js +108 -0
- package/dist/sim/offScreen.d.ts +181 -0
- package/dist/sim/offScreen.js +450 -0
- package/dist/sim/routine.d.ts +79 -0
- package/dist/sim/routine.js +137 -0
- package/dist/visibility/visibilitySystem.js +21 -0
- package/dist/world/decalKind.d.ts +48 -0
- package/dist/world/decalKind.js +86 -0
- package/dist/world/features.d.ts +11 -1
- package/dist/world/features.js +1 -0
- package/dist/world/grassKind.d.ts +5 -0
- package/dist/world/grassKind.js +3 -0
- package/dist/world/terraform.d.ts +8 -0
- package/dist/world.d.ts +10 -1
- package/dist/world.js +9 -0
- package/package.json +1 -1
- package/skills/jgengine/api.md +59 -6
- package/skills/jgengine/capabilities.md +4 -0
- package/skills/jgengine-combat/SKILL.md +19 -0
- package/skills/jgengine-combat/api.md +246 -0
- package/skills/jgengine-combat/capabilities.md +112 -0
- package/skills/jgengine-combat/reference.md +32 -0
- package/skills/jgengine-gameplay/SKILL.md +39 -0
- package/skills/jgengine-gameplay/api.md +221 -7
- package/skills/jgengine-gameplay/capabilities.md +64 -3
- package/skills/jgengine-gameplay/reference-systems.md +118 -0
- package/skills/jgengine-world/SKILL.md +7 -1
- package/skills/jgengine-world/api.md +222 -8
- package/skills/jgengine-world/capabilities.md +42 -2
- package/skills/jgengine-world/recipes/npc-senses.md +8 -2
- package/skills/jgengine-world/recipes/vehicle-feel.md +5 -0
package/dist/editor/types.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { MinimapBakeBounds } from "../world/minimapBake.js";
|
|
|
7
7
|
import type { TerraformSnapshot } from "../world/terraform.js";
|
|
8
8
|
import type { EditorGridLayer } from "./grid.js";
|
|
9
9
|
import type { EnvironmentSource } from "../render/environment.js";
|
|
10
|
+
import type { IrradianceBakeSettings, IrradianceVolume } from "../render/lightProbes.js";
|
|
10
11
|
import type { PointLightingConfig } from "../game/playableGame.js";
|
|
11
12
|
/** Runtime point-light shape reused by authored editor light markers. */
|
|
12
13
|
export type { PointLightingConfig } from "../game/playableGame.js";
|
|
@@ -196,6 +197,17 @@ export interface EditorBake {
|
|
|
196
197
|
}
|
|
197
198
|
/** Named sky look stored on the scene document; matches runtime `SkyEnvironmentConfig.preset`. */
|
|
198
199
|
export type EditorSkyPreset = "day" | "dusk" | "night";
|
|
200
|
+
/**
|
|
201
|
+
* An irradiance volume authored on the scene document: a grid of baked SH probes over a box, plus
|
|
202
|
+
* the bake settings that produced them so a scene can re-bake without the editor session.
|
|
203
|
+
* @capability light-probes author a baked irradiance volume on the scene document
|
|
204
|
+
*/
|
|
205
|
+
export interface EditorLightProbe extends IrradianceVolume {
|
|
206
|
+
/** Label shown in the lighting workspace. */
|
|
207
|
+
label?: string;
|
|
208
|
+
/** The authored bake inputs; absent means the volume was authored elsewhere and only the probe data matters. */
|
|
209
|
+
bake?: IrradianceBakeSettings;
|
|
210
|
+
}
|
|
199
211
|
/**
|
|
200
212
|
* Linear distance fog authored on the scene document — serializable subset of sky/backdrop fog.
|
|
201
213
|
* Absent fields keep engine defaults when the document is applied at runtime.
|
|
@@ -205,6 +217,40 @@ export interface EditorFogConfig {
|
|
|
205
217
|
near?: number;
|
|
206
218
|
far?: number;
|
|
207
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Authored colour grading for the scene document's final image. The editor bakes a small
|
|
222
|
+
* display-space LUT from these controls and the shell applies it after tone mapping, so a game's
|
|
223
|
+
* grade is authored data like its sky, not a hardcoded pass in the game's config.
|
|
224
|
+
*
|
|
225
|
+
* The three band colours are display swatches (`#rrggbb`) that the pixels of that band are pulled
|
|
226
|
+
* toward, by `tint`. `saturation` and `contrast` are applied before the bands. Baking at a small cube
|
|
227
|
+
* size keeps the whole grade — including the LUT — a few hundred bytes of JSON.
|
|
228
|
+
*/
|
|
229
|
+
export interface EditorColorGrading {
|
|
230
|
+
/** Cube edge length the runtime bakes, 2..16. Default 8 — small enough to read, smooth enough for a grade. */
|
|
231
|
+
size?: number;
|
|
232
|
+
/** Swatch shadows are tinted toward, e.g. `#2a3a52` for a cool night. Absent leaves shadows alone. */
|
|
233
|
+
shadows?: string;
|
|
234
|
+
/** Swatch midtones are tinted toward. Absent leaves midtones alone. */
|
|
235
|
+
midtones?: string;
|
|
236
|
+
/** Swatch highlights are tinted toward, e.g. `#ffe9c4` for a warm sun. Absent leaves highlights alone. */
|
|
237
|
+
highlights?: string;
|
|
238
|
+
/** How far each band is pulled toward its swatch, 0..1. Default 0.45. */
|
|
239
|
+
tint?: number;
|
|
240
|
+
/** Display saturation around luma. Default 1. */
|
|
241
|
+
saturation?: number;
|
|
242
|
+
/** Display contrast around mid-grey. Default 1. */
|
|
243
|
+
contrast?: number;
|
|
244
|
+
/** Clamped auto-exposure authored with the grade; absent uses the pipeline's default range. */
|
|
245
|
+
autoExposure?: EditorAutoExposure;
|
|
246
|
+
}
|
|
247
|
+
/** Authored clamped auto-exposure range: how far the meter may pull a dark room or a bright street. */
|
|
248
|
+
export interface EditorAutoExposure {
|
|
249
|
+
/** Lowest exposure multiplier the meter may reach. Default 0.5. */
|
|
250
|
+
min?: number;
|
|
251
|
+
/** Highest exposure multiplier the meter may reach. Default 2.5. */
|
|
252
|
+
max?: number;
|
|
253
|
+
}
|
|
208
254
|
/**
|
|
209
255
|
* Scene-document environment/lighting authoring (#1110): sky preset, optional time-of-day drive,
|
|
210
256
|
* horizon/zenith tints, sun/ambient intensity, and fog. Serializable and genre-agnostic — games
|
|
@@ -216,8 +262,16 @@ export interface EditorFogConfig {
|
|
|
216
262
|
export interface EditorEnvironment {
|
|
217
263
|
/** Optional runtime environment map source; absent keeps the procedural gradient. */
|
|
218
264
|
source?: EnvironmentSource;
|
|
265
|
+
/** Authored clamped auto-exposure and colour grade; the shell applies it to the post chain. */
|
|
266
|
+
grading?: EditorColorGrading;
|
|
219
267
|
/** Authored local point lights; the runtime clamps these to its bounded light budget. */
|
|
220
268
|
pointLights?: readonly PointLightingConfig[];
|
|
269
|
+
/**
|
|
270
|
+
* Authored irradiance volumes: baked light probes for interiors and shaded streets. The runtime
|
|
271
|
+
* samples them at the camera and feeds three.js's scene light probe, so a room's ambient comes
|
|
272
|
+
* from its own lights instead of the sky.
|
|
273
|
+
*/
|
|
274
|
+
probes?: readonly EditorLightProbe[];
|
|
221
275
|
/** Fixed sky look when `timeOfDay` is off (or no clock is available). */
|
|
222
276
|
preset?: EditorSkyPreset;
|
|
223
277
|
/** Drive sun/sky from the world clock's day fraction instead of the fixed `preset`. */
|
|
@@ -250,9 +250,15 @@ export interface ModelConfig {
|
|
|
250
250
|
material?: ModelMaterialOverride;
|
|
251
251
|
/** `false` keeps this model's exported material params and skips the engine's default painted surface detail. Default true. */
|
|
252
252
|
surfaceDetail?: boolean;
|
|
253
|
+
/**
|
|
254
|
+
* Per-model wind sway for foliage: trunk and standing geometry stay planted while the upper
|
|
255
|
+
* canopy drifts in the shared gust field. `strength` is metres of tip travel, `speed` the gust
|
|
256
|
+
* rate, `height` the world height over which sway ramps in (defaults to the model's own height).
|
|
257
|
+
* `false` or omitted keeps the model rigid. Compiles into each cloned material.
|
|
258
|
+
*/
|
|
259
|
+
wind?: ModelWindConfig | false;
|
|
253
260
|
/** Reusable authored surfaces available to this model's named slot assignments. */
|
|
254
|
-
materialAssets?: readonly MaterialAsset[];
|
|
255
|
-
/** Sparse per-slot assignments; unselected imported materials keep their original appearance. */
|
|
261
|
+
materialAssets?: readonly MaterialAsset[]; /** Sparse per-slot assignments; unselected imported materials keep their original appearance. */
|
|
256
262
|
materialAssignments?: readonly MaterialAssignment[];
|
|
257
263
|
/** Hide named visual subtrees on this instance before placement measurement. Exact runtime names or unique original imported names resolve without removing bones or changing the cached model; missing or ambiguous names warn and leave existing visibility unchanged. Malformed lists reject before model or texture allocation. Omit or pass [] to retain imported visibility. */
|
|
258
264
|
hiddenNodes?: readonly string[];
|
|
@@ -282,6 +288,17 @@ export interface ModelConfig {
|
|
|
282
288
|
/** Tuning for the procedural part-motion driver when any part carries a `role`; omit for defaults. */
|
|
283
289
|
partMotion?: PartMotionParams;
|
|
284
290
|
}
|
|
291
|
+
/** Wind sway for a foliage model; see {@link ModelConfig.wind}. */
|
|
292
|
+
export interface ModelWindConfig {
|
|
293
|
+
/** Tip travel in metres. Default 0.35. */
|
|
294
|
+
strength?: number;
|
|
295
|
+
/** Gust rate multiplier. Default 1. */
|
|
296
|
+
speed?: number;
|
|
297
|
+
/** World height over which sway ramps from the base to full tip travel, in metres. Defaults to the model's measured height. */
|
|
298
|
+
height?: number;
|
|
299
|
+
/** Wind heading as a two-axis direction; defaults to the engine's shared gust direction. */
|
|
300
|
+
direction?: readonly [number, number];
|
|
301
|
+
}
|
|
285
302
|
/** Bone names and limits for {@link ModelConfig.ik}. */
|
|
286
303
|
export interface ModelIkConfig {
|
|
287
304
|
/** Thigh → shin → foot chains; omit to find them by bone name. `[]` turns foot IK off. */
|
package/dist/gameplay.d.ts
CHANGED
|
@@ -81,7 +81,12 @@ export { computeEffectiveStats, createModularItem, install, isComplete, missingR
|
|
|
81
81
|
export { createItemUse, type ItemUseHandler, type ItemUseInput } from "./item/use.js";
|
|
82
82
|
export { createUseBehaviorRegistry, type BehaviorConfig, type BehaviorState, type ComposedUse, type CompositionResult, type SerializedBehaviorState, type UseBehaviorContext, type UseBehaviorDef, type UseBehaviorOutcome, type UseBehaviorRef, type UseBehaviorRegistry, type UseBehaviorRejection, } from "./item/useBehavior.js";
|
|
83
83
|
export { createWeaponStats } from "./item/weapon.js";
|
|
84
|
+
export { MAX_LOOT_AFFIXES, assertLootCatalog, compareLootItems, createLootRoller, lootLevelScale, lootStatContributions, rollLoot, scoreLootStats, validateLootItem, type LootBaseDef, type LootCatalog, type LootComparison, type LootComparisonOptions, type LootInstalledPart, type LootIssueCode, type LootItem, type LootManufacturerDef, type LootPartDef, type LootRollOptions, type LootRoller, type LootRollerOptions, type LootRollerState, type LootStatDelta, type LootValidationIssue, } from "./loot/lootGen.js";
|
|
85
|
+
export { lootWeaponDefinition, type LootWeaponProjection, type LootWeaponStatMap } from "./loot/lootWeapon.js";
|
|
86
|
+
export { compareGearSets, gearScore, type GearScore, type GearScoreOptions, type GearSlotItem, } from "./progression/gearScore.js";
|
|
87
|
+
export { assertSkillTreeConfig, createSkillTree, type SkillActiveDef, type SkillNodeDef, type SkillRequirement, type SkillResolution, type SkillSpendReason, type SkillSpendResult, type SkillSynergyDef, type SkillTree, type SkillTreeConfig, type SkillTreeState, } from "./progression/skillTree.js";
|
|
84
88
|
export { createStatGraph, statModifierContributions, type StatContribution, type StatContributionStep, type StatDeriveContext, type StatDerivedDef, type StatExplanation, type StatGraph, type StatGraphDef, type StatInputDef, type StatModEntry, type StatOp, type StatSheet, type StatSheetState, } from "./progression/statGraph.js";
|
|
89
|
+
export { assertXpCurve, defineXpCurve, type XpCurve, type XpCurveDef, type XpGrowthRule, type XpLevelStep, } from "./progression/xpCurve.js";
|
|
85
90
|
export { type CellGrid } from "./puzzle/cellGrid.js";
|
|
86
91
|
export { type ShapeTable } from "./puzzle/fallingPiece.js";
|
|
87
92
|
export { createNameGenerator } from "./random/nameGen.js";
|
|
@@ -95,6 +100,10 @@ export { createTriggeredRuleEngine, type ActiveEffect, type EffectRef, type Firi
|
|
|
95
100
|
export { createRing, ringSampleAt, type Ring, type RingConfig, type RingPhase } from "./session/ring.js";
|
|
96
101
|
export { type RoleSpec } from "./session/roles.js";
|
|
97
102
|
export { type RoundConfig, type RoundSnapshot } from "./session/roundState.js";
|
|
103
|
+
export { createNearAgents, type NearAgentSnapshot, type NearAgentView, type NearAgentsOptions, type NearCrowdAgents, } from "./sim/nearAgents.js";
|
|
104
|
+
export { createNeedSet, type NeedDefinition, type NeedReading, type NeedSet, type NeedSnapshot } from "./sim/needs.js";
|
|
105
|
+
export { createOffScreenWorld, type NearAgents, type NearHandoff, type NearSpawn, type OffScreenAgentView, type OffScreenMode, type OffScreenOptions, type OffScreenPolicy, type OffScreenPolicyPatch, type OffScreenResidentSpec, type OffScreenSnapshot, type OffScreenStats, type OffScreenWorld, } from "./sim/offScreen.js";
|
|
106
|
+
export { resolveRoutine, type RoutineConfig, type RoutinePhase, type RoutineStop, type RoutineStopSpec, type RoutineTimeline, } from "./sim/routine.js";
|
|
98
107
|
export { createDecayMeterSet, decayMeterMoodles, decayMeterSnapshot, decayMeterState, decayMeters, initDecayMeters, refillMeter, type DecayMeterSet, type DecayMeterValues, type DecayModifier, } from "./survival/decayMeter.js";
|
|
99
108
|
export { createMoodleStack, stackMoodles, type Moodle, type MoodleStack } from "./survival/moodle.js";
|
|
100
109
|
export { createMultiRegionHealth, type MultiRegionHealth } from "./survival/regionHealth.js";
|
package/dist/gameplay.js
CHANGED
|
@@ -81,7 +81,12 @@ export { computeEffectiveStats, createModularItem, install, isComplete, missingR
|
|
|
81
81
|
export { createItemUse } from "./item/use.js";
|
|
82
82
|
export { createUseBehaviorRegistry, } from "./item/useBehavior.js";
|
|
83
83
|
export { createWeaponStats } from "./item/weapon.js";
|
|
84
|
+
export { MAX_LOOT_AFFIXES, assertLootCatalog, compareLootItems, createLootRoller, lootLevelScale, lootStatContributions, rollLoot, scoreLootStats, validateLootItem, } from "./loot/lootGen.js";
|
|
85
|
+
export { lootWeaponDefinition } from "./loot/lootWeapon.js";
|
|
86
|
+
export { compareGearSets, gearScore, } from "./progression/gearScore.js";
|
|
87
|
+
export { assertSkillTreeConfig, createSkillTree, } from "./progression/skillTree.js";
|
|
84
88
|
export { createStatGraph, statModifierContributions, } from "./progression/statGraph.js";
|
|
89
|
+
export { assertXpCurve, defineXpCurve, } from "./progression/xpCurve.js";
|
|
85
90
|
export {} from "./puzzle/cellGrid.js";
|
|
86
91
|
export {} from "./puzzle/fallingPiece.js";
|
|
87
92
|
export { createNameGenerator } from "./random/nameGen.js";
|
|
@@ -95,6 +100,10 @@ export { createTriggeredRuleEngine, } from "./rules/triggeredRules.js";
|
|
|
95
100
|
export { createRing, ringSampleAt } from "./session/ring.js";
|
|
96
101
|
export {} from "./session/roles.js";
|
|
97
102
|
export {} from "./session/roundState.js";
|
|
103
|
+
export { createNearAgents, } from "./sim/nearAgents.js";
|
|
104
|
+
export { createNeedSet } from "./sim/needs.js";
|
|
105
|
+
export { createOffScreenWorld, } from "./sim/offScreen.js";
|
|
106
|
+
export { resolveRoutine, } from "./sim/routine.js";
|
|
98
107
|
export { createDecayMeterSet, decayMeterMoodles, decayMeterSnapshot, decayMeterState, decayMeters, initDecayMeters, refillMeter, } from "./survival/decayMeter.js";
|
|
99
108
|
export { createMoodleStack, stackMoodles } from "./survival/moodle.js";
|
|
100
109
|
export { createMultiRegionHealth } from "./survival/regionHealth.js";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type EncumbranceConfig, type EncumbranceState, type LoadEntry } from "./encumbrance.js";
|
|
2
|
+
import type { InventoryState } from "./inventoryModel.js";
|
|
3
|
+
/** Per-item mass in the caller's weight unit; an unknown item weighs 0. */
|
|
4
|
+
export type WeightLookup = (itemId: string) => number;
|
|
5
|
+
/**
|
|
6
|
+
* Every filled stack in the given inventories as load entries, in slot order. Only the inventories
|
|
7
|
+
* the caller passes count, so a stash left out of the list adds no weight.
|
|
8
|
+
*
|
|
9
|
+
* @capability carry-load read carried stacks out of one or more inventories for a weight query
|
|
10
|
+
*/
|
|
11
|
+
export declare function carriedEntries(states: readonly InventoryState[]): LoadEntry[];
|
|
12
|
+
/** Everything a {@link characterLoad} reading takes: carried inventories, worn gear and the capacity they are measured against. */
|
|
13
|
+
export interface CharacterLoadInput {
|
|
14
|
+
/** Carried inventories (bag, rig, pockets); a stash left out does not weigh anything. */
|
|
15
|
+
carried?: readonly InventoryState[];
|
|
16
|
+
/** Worn item ids, one unit each — `Equipment.worn()` mapped to `itemId`. */
|
|
17
|
+
worn?: readonly string[];
|
|
18
|
+
weightOf: WeightLookup;
|
|
19
|
+
/** Carrying capacity in the same unit as `weightOf`; the caller adds level and gear bonuses. */
|
|
20
|
+
capacity: number;
|
|
21
|
+
/** Fraction of worn gear weight that counts against capacity (default `1`). */
|
|
22
|
+
wornFactor?: number;
|
|
23
|
+
encumbrance?: EncumbranceConfig;
|
|
24
|
+
}
|
|
25
|
+
/** A carrier's load: the encumbrance state, how it splits into carried and worn, and capacity left. */
|
|
26
|
+
export interface CharacterLoad extends EncumbranceState {
|
|
27
|
+
/** Mass of the carried inventories alone. */
|
|
28
|
+
carriedMass: number;
|
|
29
|
+
/** Mass worn gear adds after `wornFactor`. */
|
|
30
|
+
wornMass: number;
|
|
31
|
+
/** Capacity left before the carrier is immobile; negative when overloaded. */
|
|
32
|
+
remaining: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Weigh a character: carried stacks plus worn gear against a capacity, resolved into the shared
|
|
36
|
+
* encumbrance bands. Movement reads `moveMultiplier`, `canSprint`, `canJump` and `immobile`; pickup
|
|
37
|
+
* gates read `remaining` or {@link canCarry}. The stash is not part of `carried`, so it adds nothing.
|
|
38
|
+
*
|
|
39
|
+
* @capability carry-encumbrance weigh carried inventories plus worn gear against a capacity and hand back movement penalties
|
|
40
|
+
*/
|
|
41
|
+
export declare function characterLoad(input: CharacterLoadInput): CharacterLoad;
|
|
42
|
+
/**
|
|
43
|
+
* Whether `addedMass` more weight still fits under `capacity`. Pure: the caller decides what to do
|
|
44
|
+
* with a refusal, so an overload policy stays with the game.
|
|
45
|
+
*
|
|
46
|
+
* @capability carry-encumbrance gate a pickup against the carrier's remaining capacity
|
|
47
|
+
*/
|
|
48
|
+
export declare function canCarry(load: Pick<CharacterLoad, "mass" | "capacity">, addedMass: number): boolean;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { resolveEncumbrance, totalLoad, } from "./encumbrance.js";
|
|
2
|
+
/**
|
|
3
|
+
* Every filled stack in the given inventories as load entries, in slot order. Only the inventories
|
|
4
|
+
* the caller passes count, so a stash left out of the list adds no weight.
|
|
5
|
+
*
|
|
6
|
+
* @capability carry-load read carried stacks out of one or more inventories for a weight query
|
|
7
|
+
*/
|
|
8
|
+
export function carriedEntries(states) {
|
|
9
|
+
const entries = [];
|
|
10
|
+
for (const state of states) {
|
|
11
|
+
for (const slot of state.slots) {
|
|
12
|
+
if (slot !== null)
|
|
13
|
+
entries.push({ itemId: slot.itemId, quantity: slot.count });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return entries;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Weigh a character: carried stacks plus worn gear against a capacity, resolved into the shared
|
|
20
|
+
* encumbrance bands. Movement reads `moveMultiplier`, `canSprint`, `canJump` and `immobile`; pickup
|
|
21
|
+
* gates read `remaining` or {@link canCarry}. The stash is not part of `carried`, so it adds nothing.
|
|
22
|
+
*
|
|
23
|
+
* @capability carry-encumbrance weigh carried inventories plus worn gear against a capacity and hand back movement penalties
|
|
24
|
+
*/
|
|
25
|
+
export function characterLoad(input) {
|
|
26
|
+
const carriedMass = totalLoad(carriedEntries(input.carried ?? []), input.weightOf);
|
|
27
|
+
const factor = input.wornFactor !== undefined && Number.isFinite(input.wornFactor) && input.wornFactor >= 0
|
|
28
|
+
? input.wornFactor
|
|
29
|
+
: 1;
|
|
30
|
+
const wornEntries = (input.worn ?? []).map((itemId) => ({ itemId, quantity: 1 }));
|
|
31
|
+
const wornMass = totalLoad(wornEntries, input.weightOf) * factor;
|
|
32
|
+
const base = resolveEncumbrance(carriedMass + wornMass, input.capacity, input.encumbrance);
|
|
33
|
+
return { ...base, carriedMass, wornMass, remaining: input.capacity - base.mass };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Whether `addedMass` more weight still fits under `capacity`. Pure: the caller decides what to do
|
|
37
|
+
* with a refusal, so an overload policy stays with the game.
|
|
38
|
+
*
|
|
39
|
+
* @capability carry-encumbrance gate a pickup against the carrier's remaining capacity
|
|
40
|
+
*/
|
|
41
|
+
export function canCarry(load, addedMass) {
|
|
42
|
+
const added = Number.isFinite(addedMass) && addedMass > 0 ? addedMass : 0;
|
|
43
|
+
return load.mass + added <= load.capacity;
|
|
44
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/** One body slot: the item kinds it accepts and the slots it occupies while filled. */
|
|
2
|
+
export interface EquipSlotDef {
|
|
3
|
+
/** Stable slot id, in the caller's nouns ("weapon", "rig", "boots"). */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Item kinds this slot accepts; omitted accepts any kind. */
|
|
6
|
+
accepts?: readonly string[];
|
|
7
|
+
/** Slot ids this slot occupies while filled, e.g. a two-handed weapon blocking "offhand". */
|
|
8
|
+
blocks?: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The gear facts the equip rules read from an item. A rolled item resolves its runtime id through
|
|
12
|
+
* the instance registry to its base and answers from that base's data — this is not a second item type.
|
|
13
|
+
*/
|
|
14
|
+
export interface EquipItemDef {
|
|
15
|
+
/** Slot ids this item may occupy, most preferred first. */
|
|
16
|
+
slots: readonly string[];
|
|
17
|
+
/** Kind tag checked against {@link EquipSlotDef.accepts}. */
|
|
18
|
+
kind?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Item lookup the rules read; returns nothing for an unknown id. */
|
|
21
|
+
export type EquipItemLookup = (itemId: string) => EquipItemDef | null | undefined;
|
|
22
|
+
/** Serializable equip state: the filled slots as `[slotId, itemId]` pairs. */
|
|
23
|
+
export interface EquipmentState {
|
|
24
|
+
slots: readonly (readonly [string, string])[];
|
|
25
|
+
}
|
|
26
|
+
/** Why an equip attempt was refused; `detail` names the item and slots involved. */
|
|
27
|
+
export type EquipRejection = "unknown-slot" | "not-equippable" | "wrong-slot" | "already-equipped" | "blocked";
|
|
28
|
+
/** An equip attempt: the new worn set plus the replaced item, or the refusal with its reason and detail. */
|
|
29
|
+
export type EquipResult = {
|
|
30
|
+
status: "ok";
|
|
31
|
+
state: EquipmentState;
|
|
32
|
+
slotId: string;
|
|
33
|
+
replaced: string | null;
|
|
34
|
+
} | {
|
|
35
|
+
status: "rejected";
|
|
36
|
+
reason: EquipRejection;
|
|
37
|
+
detail: string;
|
|
38
|
+
};
|
|
39
|
+
/** An unequip attempt: the new worn set plus the item taken off, or the refusal with its detail. */
|
|
40
|
+
export type UnequipResult = {
|
|
41
|
+
status: "ok";
|
|
42
|
+
state: EquipmentState;
|
|
43
|
+
itemId: string;
|
|
44
|
+
} | {
|
|
45
|
+
status: "rejected";
|
|
46
|
+
reason: "unknown-slot" | "empty-slot";
|
|
47
|
+
detail: string;
|
|
48
|
+
};
|
|
49
|
+
/** One worn item and the slot holding it. */
|
|
50
|
+
export interface WornEntry {
|
|
51
|
+
slotId: string;
|
|
52
|
+
itemId: string;
|
|
53
|
+
}
|
|
54
|
+
/** What the debug overlay reads about a worn set. */
|
|
55
|
+
export interface EquipmentProbeReading {
|
|
56
|
+
filled: number;
|
|
57
|
+
slots: Readonly<Record<string, string>>;
|
|
58
|
+
}
|
|
59
|
+
/** A worn-gear set: one item per slot, rules from slot and item data, state out and back in. */
|
|
60
|
+
export interface Equipment {
|
|
61
|
+
/** Wear `itemId` in `slotId`; a filled slot is replaced and its item returned as `replaced`. */
|
|
62
|
+
equip(slotId: string, itemId: string): EquipResult;
|
|
63
|
+
/** Wear `itemId` in the first of its preferred slots that is empty, else the first slot that accepts it. */
|
|
64
|
+
equipBest(itemId: string): EquipResult;
|
|
65
|
+
/** Clear `slotId` and hand back the item it held. */
|
|
66
|
+
unequip(slotId: string): UnequipResult;
|
|
67
|
+
/** Worn items in declared slot order. */
|
|
68
|
+
worn(): readonly WornEntry[];
|
|
69
|
+
/** The slot holding `itemId`, or null when that item is not worn. */
|
|
70
|
+
slotOf(itemId: string): string | null;
|
|
71
|
+
/** Declared slot ids, in order. */
|
|
72
|
+
slotIds(): readonly string[];
|
|
73
|
+
state(): EquipmentState;
|
|
74
|
+
/** Replace the worn set; returns false without mutation when an entry breaks the rules. */
|
|
75
|
+
restore(next: EquipmentState): boolean;
|
|
76
|
+
reset(): void;
|
|
77
|
+
/** Drop the devtools probe registered by `devtoolsName`. Safe to call twice. */
|
|
78
|
+
dispose(): void;
|
|
79
|
+
}
|
|
80
|
+
/** Options for {@link createEquipment}. */
|
|
81
|
+
export interface EquipmentOptions {
|
|
82
|
+
/** Register a devtools probe under this name reporting which slots are filled. */
|
|
83
|
+
devtoolsName?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A worn-gear set over caller slot and item data.
|
|
87
|
+
*
|
|
88
|
+
* Slot defs own what a slot accepts and which slots it blocks; item defs own which slots an item may
|
|
89
|
+
* occupy and its kind. Equipping into a filled slot swaps and returns the replaced item, so gear is
|
|
90
|
+
* never silently lost: the caller puts `replaced` back. A two-handed item and the slot it blocks
|
|
91
|
+
* cannot both be filled, and the rejection names the occupied slot.
|
|
92
|
+
*
|
|
93
|
+
* @capability equipment-slot-inventory wear gear by slot with item-kind rules, two-handed blocking and replace-on-equip
|
|
94
|
+
*/
|
|
95
|
+
export declare function createEquipment(slotDefs: readonly EquipSlotDef[], lookup: EquipItemLookup, options?: EquipmentOptions): Equipment;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { devtools } from "../devtools/devtools.js";
|
|
2
|
+
function slotAcceptsKind(slot, kind) {
|
|
3
|
+
if (slot.accepts === undefined)
|
|
4
|
+
return true;
|
|
5
|
+
return kind !== undefined && slot.accepts.includes(kind);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A worn-gear set over caller slot and item data.
|
|
9
|
+
*
|
|
10
|
+
* Slot defs own what a slot accepts and which slots it blocks; item defs own which slots an item may
|
|
11
|
+
* occupy and its kind. Equipping into a filled slot swaps and returns the replaced item, so gear is
|
|
12
|
+
* never silently lost: the caller puts `replaced` back. A two-handed item and the slot it blocks
|
|
13
|
+
* cannot both be filled, and the rejection names the occupied slot.
|
|
14
|
+
*
|
|
15
|
+
* @capability equipment-slot-inventory wear gear by slot with item-kind rules, two-handed blocking and replace-on-equip
|
|
16
|
+
*/
|
|
17
|
+
export function createEquipment(slotDefs, lookup, options = {}) {
|
|
18
|
+
const byId = new Map();
|
|
19
|
+
for (const def of slotDefs) {
|
|
20
|
+
if (def.id.trim() === "")
|
|
21
|
+
throw new RangeError('Equipment slot field "id" must not be empty');
|
|
22
|
+
if (byId.has(def.id))
|
|
23
|
+
throw new RangeError(`Equipment slot "${def.id}" is declared twice`);
|
|
24
|
+
byId.set(def.id, def);
|
|
25
|
+
}
|
|
26
|
+
const order = slotDefs.map((def) => def.id);
|
|
27
|
+
const filled = new Map();
|
|
28
|
+
function readState() {
|
|
29
|
+
const slots = [];
|
|
30
|
+
for (const slotId of order) {
|
|
31
|
+
const itemId = filled.get(slotId);
|
|
32
|
+
if (itemId !== undefined)
|
|
33
|
+
slots.push([slotId, itemId]);
|
|
34
|
+
}
|
|
35
|
+
return { slots };
|
|
36
|
+
}
|
|
37
|
+
function conflicts(slotId, occupied = filled) {
|
|
38
|
+
const blockedByOwn = byId.get(slotId)?.blocks ?? [];
|
|
39
|
+
const blocking = order.filter((other) => other !== slotId && (byId.get(other)?.blocks ?? []).includes(slotId));
|
|
40
|
+
return [...new Set([...blockedByOwn, ...blocking])].filter((other) => occupied.has(other));
|
|
41
|
+
}
|
|
42
|
+
function check(slotId, itemId, occupied) {
|
|
43
|
+
const slot = byId.get(slotId);
|
|
44
|
+
if (slot === undefined) {
|
|
45
|
+
return { status: "rejected", reason: "unknown-slot", detail: `"${slotId}" is not a declared slot (${order.join(", ")})` };
|
|
46
|
+
}
|
|
47
|
+
const item = lookup(itemId);
|
|
48
|
+
if (item === null || item === undefined || item.slots.length === 0) {
|
|
49
|
+
return { status: "rejected", reason: "not-equippable", detail: `item "${itemId}" declares no equip slot` };
|
|
50
|
+
}
|
|
51
|
+
if (!item.slots.includes(slotId)) {
|
|
52
|
+
return {
|
|
53
|
+
status: "rejected",
|
|
54
|
+
reason: "wrong-slot",
|
|
55
|
+
detail: `item "${itemId}" belongs in ${item.slots.join(", ")}; "${slotId}" is not one of them`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (!slotAcceptsKind(slot, item.kind)) {
|
|
59
|
+
return {
|
|
60
|
+
status: "rejected",
|
|
61
|
+
reason: "wrong-slot",
|
|
62
|
+
detail: `slot "${slotId}" accepts ${(slot.accepts ?? []).join(", ")}; item "${itemId}" is ${item.kind ?? "untyped"}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const wornAt = [...occupied].find(([, worn]) => worn === itemId)?.[0];
|
|
66
|
+
if (wornAt !== undefined && wornAt !== slotId) {
|
|
67
|
+
return { status: "rejected", reason: "already-equipped", detail: `item "${itemId}" is already worn in "${wornAt}"` };
|
|
68
|
+
}
|
|
69
|
+
const conflict = conflicts(slotId, occupied).filter((other) => other !== slotId)[0];
|
|
70
|
+
if (conflict !== undefined) {
|
|
71
|
+
return {
|
|
72
|
+
status: "rejected",
|
|
73
|
+
reason: "blocked",
|
|
74
|
+
detail: `slot "${conflict}" is occupied and cannot be worn with "${slotId}"`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function equipInto(slotId, itemId) {
|
|
80
|
+
const refusal = check(slotId, itemId, filled);
|
|
81
|
+
if (refusal !== null)
|
|
82
|
+
return refusal;
|
|
83
|
+
const replaced = filled.get(slotId) ?? null;
|
|
84
|
+
filled.set(slotId, itemId);
|
|
85
|
+
return { status: "ok", state: readState(), slotId, replaced };
|
|
86
|
+
}
|
|
87
|
+
const probe = options.devtoolsName === undefined
|
|
88
|
+
? null
|
|
89
|
+
: devtools.probes.register(options.devtoolsName, () => ({
|
|
90
|
+
filled: filled.size,
|
|
91
|
+
slots: Object.fromEntries(filled),
|
|
92
|
+
}));
|
|
93
|
+
return {
|
|
94
|
+
equip: equipInto,
|
|
95
|
+
equipBest(itemId) {
|
|
96
|
+
const item = lookup(itemId);
|
|
97
|
+
if (item === null || item === undefined || item.slots.length === 0) {
|
|
98
|
+
return { status: "rejected", reason: "not-equippable", detail: `item "${itemId}" declares no equip slot` };
|
|
99
|
+
}
|
|
100
|
+
const allowed = item.slots.filter((slotId) => byId.has(slotId));
|
|
101
|
+
if (allowed.length === 0) {
|
|
102
|
+
return {
|
|
103
|
+
status: "rejected",
|
|
104
|
+
reason: "unknown-slot",
|
|
105
|
+
detail: `item "${itemId}" names no declared slot (${item.slots.join(", ")})`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const preferred = allowed.filter((slotId) => check(slotId, itemId, filled) === null);
|
|
109
|
+
if (preferred.length === 0)
|
|
110
|
+
return check(allowed[0], itemId, filled);
|
|
111
|
+
return equipInto(preferred.find((slotId) => !filled.has(slotId)) ?? preferred[0], itemId);
|
|
112
|
+
},
|
|
113
|
+
unequip(slotId) {
|
|
114
|
+
if (!byId.has(slotId)) {
|
|
115
|
+
return { status: "rejected", reason: "unknown-slot", detail: `"${slotId}" is not a declared slot (${order.join(", ")})` };
|
|
116
|
+
}
|
|
117
|
+
const itemId = filled.get(slotId);
|
|
118
|
+
if (itemId === undefined) {
|
|
119
|
+
return { status: "rejected", reason: "empty-slot", detail: `slot "${slotId}" holds nothing` };
|
|
120
|
+
}
|
|
121
|
+
filled.delete(slotId);
|
|
122
|
+
return { status: "ok", state: readState(), itemId };
|
|
123
|
+
},
|
|
124
|
+
worn() {
|
|
125
|
+
const out = [];
|
|
126
|
+
for (const slotId of order) {
|
|
127
|
+
const itemId = filled.get(slotId);
|
|
128
|
+
if (itemId !== undefined)
|
|
129
|
+
out.push({ slotId, itemId });
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
},
|
|
133
|
+
slotOf(itemId) {
|
|
134
|
+
for (const [slotId, worn] of filled)
|
|
135
|
+
if (worn === itemId)
|
|
136
|
+
return slotId;
|
|
137
|
+
return null;
|
|
138
|
+
},
|
|
139
|
+
slotIds: () => [...order],
|
|
140
|
+
state: readState,
|
|
141
|
+
restore(next) {
|
|
142
|
+
if (next === null || typeof next !== "object" || !Array.isArray(next.slots))
|
|
143
|
+
return false;
|
|
144
|
+
const staged = new Map();
|
|
145
|
+
for (const entry of next.slots) {
|
|
146
|
+
if (!Array.isArray(entry) || entry.length !== 2)
|
|
147
|
+
return false;
|
|
148
|
+
const [slotId, itemId] = entry;
|
|
149
|
+
if (typeof slotId !== "string" || typeof itemId !== "string" || itemId.trim() === "")
|
|
150
|
+
return false;
|
|
151
|
+
if (staged.has(slotId))
|
|
152
|
+
return false;
|
|
153
|
+
if (check(slotId, itemId, staged) !== null)
|
|
154
|
+
return false;
|
|
155
|
+
staged.set(slotId, itemId);
|
|
156
|
+
}
|
|
157
|
+
filled.clear();
|
|
158
|
+
for (const [slotId, itemId] of staged)
|
|
159
|
+
filled.set(slotId, itemId);
|
|
160
|
+
return true;
|
|
161
|
+
},
|
|
162
|
+
reset() {
|
|
163
|
+
filled.clear();
|
|
164
|
+
},
|
|
165
|
+
dispose() {
|
|
166
|
+
probe?.();
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -41,7 +41,7 @@ export type SplitResult = {
|
|
|
41
41
|
state: InventoryState;
|
|
42
42
|
} | {
|
|
43
43
|
status: "rejected";
|
|
44
|
-
reason: "invalid-slot" | "empty-slot" | "invalid-amount" | "no-space" | "slot-occupied";
|
|
44
|
+
reason: "invalid-slot" | "empty-slot" | "invalid-amount" | "no-space" | "slot-occupied" | "over-stack";
|
|
45
45
|
};
|
|
46
46
|
/**
|
|
47
47
|
* A bag of stackable items supporting add, remove, count, and move — the base inventory model.
|
|
@@ -63,13 +63,14 @@ export declare function hasItem(state: InventoryState, itemId: string, count: nu
|
|
|
63
63
|
export declare function moveItem(from: InventoryState, fromSlot: number, to: InventoryState, toLayout: InventoryLayout, traits: ItemTraits, toSlot?: number): MoveResult;
|
|
64
64
|
/**
|
|
65
65
|
* Splits `amount` off the stack in `slot` into `toSlot` (or the first empty slot when `toSlot` is
|
|
66
|
-
* omitted).
|
|
67
|
-
*
|
|
68
|
-
* leave both sides non-empty), no free
|
|
66
|
+
* omitted). Without `limit` it is limit-agnostic: a same-item target merges by simple addition.
|
|
67
|
+
* Pass the item's stack limit to keep the merge legal, which rejects `over-stack`. Rejects an empty
|
|
68
|
+
* source, `amount <= 0`, `amount >= stack count` (a split must leave both sides non-empty), no free
|
|
69
|
+
* slot, or a target holding a different item.
|
|
69
70
|
*
|
|
70
|
-
* @capability inventory-split split a stack into a target or first-empty slot
|
|
71
|
+
* @capability inventory-split split a stack into a target or first-empty slot, honoring a stack limit
|
|
71
72
|
*/
|
|
72
|
-
export declare function splitStack(state: InventoryState, slot: number, amount: number, toSlot?: number): SplitResult;
|
|
73
|
+
export declare function splitStack(state: InventoryState, slot: number, amount: number, toSlot?: number, limit?: number): SplitResult;
|
|
73
74
|
export interface InventorySet<TId extends string> {
|
|
74
75
|
put(id: TId, itemId: string, count: number, options?: {
|
|
75
76
|
slot?: number;
|
|
@@ -80,6 +81,14 @@ export interface InventorySet<TId extends string> {
|
|
|
80
81
|
has(id: TId, itemId: string, count: number): boolean;
|
|
81
82
|
state(id: TId): InventoryState;
|
|
82
83
|
replaceState(id: TId, state: InventoryState): void;
|
|
84
|
+
/** Detached copy of every declared inventory — a save, never a live alias. */
|
|
85
|
+
snapshot(): InventorySetState<TId>;
|
|
86
|
+
/** Replace every declared inventory from a snapshot; returns false without mutation when an entry is malformed or incomplete. */
|
|
87
|
+
restore(next: InventorySetState<TId>): boolean;
|
|
88
|
+
/** Empty every declared inventory. */
|
|
89
|
+
reset(): void;
|
|
83
90
|
}
|
|
91
|
+
/** Every inventory in a set, keyed by id, as plain slot data. */
|
|
92
|
+
export type InventorySetState<TId extends string> = Record<TId, InventoryState>;
|
|
84
93
|
/** Build a keyed set of named inventories from their `layouts`, sharing one `traits` table — the stateful façade over put/take/move/count. */
|
|
85
94
|
export declare function createInventorySet<TId extends string>(layouts: Record<TId, InventoryLayout>, traits: ItemTraits): InventorySet<TId>;
|