@jgengine/core 0.6.0 → 0.8.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 +214 -2
- package/README.md +2 -0
- package/dist/ai/crowd.d.ts +36 -0
- package/dist/ai/crowd.js +213 -0
- package/dist/ai/jobBoard.d.ts +49 -0
- package/dist/ai/jobBoard.js +155 -0
- package/dist/ai/spawnDirector.d.ts +61 -0
- package/dist/ai/spawnDirector.js +162 -0
- package/dist/ai/threat.d.ts +25 -0
- package/dist/ai/threat.js +67 -0
- package/dist/anim/easing.d.ts +14 -0
- package/dist/anim/easing.js +55 -0
- package/dist/audio/audioFalloff.d.ts +38 -0
- package/dist/audio/audioFalloff.js +32 -0
- package/dist/behaviour/behaviour.d.ts +136 -0
- package/dist/behaviour/behaviour.js +388 -0
- package/dist/board/laneBoard.d.ts +63 -0
- package/dist/board/laneBoard.js +114 -0
- package/dist/board/timelineBoard.d.ts +36 -0
- package/dist/board/timelineBoard.js +70 -0
- package/dist/cards/cardPile.d.ts +64 -0
- package/dist/cards/cardPile.js +175 -0
- package/dist/cards/modifierPipeline.d.ts +33 -0
- package/dist/cards/modifierPipeline.js +48 -0
- package/dist/combat/abilityKit.d.ts +47 -0
- package/dist/combat/abilityKit.js +139 -0
- package/dist/combat/animationState.d.ts +47 -0
- package/dist/combat/animationState.js +101 -0
- package/dist/combat/attackTags.d.ts +14 -0
- package/dist/combat/attackTags.js +26 -0
- package/dist/combat/breakMeters.d.ts +32 -0
- package/dist/combat/breakMeters.js +51 -0
- package/dist/combat/comboString.d.ts +35 -0
- package/dist/combat/comboString.js +59 -0
- package/dist/combat/death.d.ts +8 -0
- package/dist/combat/death.js +5 -2
- package/dist/combat/defensiveWindow.d.ts +34 -0
- package/dist/combat/defensiveWindow.js +66 -0
- package/dist/combat/downed.d.ts +35 -0
- package/dist/combat/downed.js +104 -0
- package/dist/combat/effects.d.ts +12 -1
- package/dist/combat/effects.js +31 -19
- package/dist/combat/hitReaction.d.ts +23 -0
- package/dist/combat/hitReaction.js +20 -0
- package/dist/combat/projectiles.d.ts +42 -5
- package/dist/combat/projectiles.js +98 -12
- package/dist/combat/resistance.d.ts +13 -0
- package/dist/combat/resistance.js +46 -0
- package/dist/combat/telegraph.d.ts +31 -0
- package/dist/combat/telegraph.js +51 -0
- package/dist/commands/commandRegistry.d.ts +1 -1
- package/dist/commands/commandRegistry.js +2 -1
- package/dist/crafting/crop.d.ts +59 -0
- package/dist/crafting/crop.js +181 -0
- package/dist/crafting/production.d.ts +64 -0
- package/dist/crafting/production.js +130 -0
- package/dist/crafting/recipe.d.ts +62 -0
- package/dist/crafting/recipe.js +101 -0
- package/dist/data/dataSource.d.ts +26 -0
- package/dist/data/dataSource.js +93 -0
- package/dist/data/devProxy.d.ts +11 -0
- package/dist/data/devProxy.js +38 -0
- package/dist/data/fetchJson.d.ts +20 -0
- package/dist/data/fetchJson.js +43 -0
- package/dist/data/jsonDataSource.d.ts +4 -0
- package/dist/data/jsonDataSource.js +6 -0
- package/dist/devtools/devtools.d.ts +157 -0
- package/dist/devtools/devtools.js +484 -0
- package/dist/devtools/transformTunables.d.ts +14 -0
- package/dist/devtools/transformTunables.js +38 -0
- package/dist/economy/sharedWallet.d.ts +28 -0
- package/dist/economy/sharedWallet.js +62 -0
- package/dist/economy/techTree.d.ts +40 -0
- package/dist/economy/techTree.js +66 -0
- package/dist/game/chat.d.ts +66 -0
- package/dist/game/chat.js +165 -0
- package/dist/game/cosmetics.d.ts +25 -0
- package/dist/game/cosmetics.js +51 -0
- package/dist/game/defineGame.d.ts +10 -2
- package/dist/game/defineGame.js +2 -1
- package/dist/game/events.d.ts +87 -0
- package/dist/game/levelSequence.d.ts +44 -0
- package/dist/game/levelSequence.js +78 -0
- package/dist/game/lootFilter.d.ts +33 -0
- package/dist/game/lootFilter.js +48 -0
- package/dist/game/ping.d.ts +67 -0
- package/dist/game/ping.js +86 -0
- package/dist/game/playableGame.d.ts +516 -8
- package/dist/game/playableGame.js +6 -1
- package/dist/game/progression.d.ts +1 -0
- package/dist/game/progression.js +23 -2
- package/dist/game/race.d.ts +100 -0
- package/dist/game/race.js +236 -0
- package/dist/game/runDraft.d.ts +34 -0
- package/dist/game/runDraft.js +97 -0
- package/dist/game/social.d.ts +69 -0
- package/dist/game/social.js +138 -0
- package/dist/game/spawnPoints.d.ts +19 -0
- package/dist/game/spawnPoints.js +23 -0
- package/dist/game/worldItem.d.ts +95 -0
- package/dist/game/worldItem.js +138 -0
- package/dist/input/actionBindings.d.ts +10 -0
- package/dist/input/actionBindings.js +14 -0
- package/dist/input/axisInput.d.ts +43 -0
- package/dist/input/axisInput.js +70 -0
- package/dist/input/gestureSurface.d.ts +31 -0
- package/dist/input/gestureSurface.js +120 -0
- package/dist/input/pointer.d.ts +69 -0
- package/dist/input/pointer.js +65 -0
- package/dist/input/touchScheme.d.ts +83 -0
- package/dist/input/touchScheme.js +115 -0
- package/dist/interaction/contextMenu.d.ts +30 -0
- package/dist/interaction/contextMenu.js +22 -0
- package/dist/interaction/qte.d.ts +20 -0
- package/dist/interaction/qte.js +24 -0
- package/dist/interaction/skillCheck.d.ts +20 -0
- package/dist/interaction/skillCheck.js +26 -0
- package/dist/inventory/shapedGrid.d.ts +56 -0
- package/dist/inventory/shapedGrid.js +176 -0
- package/dist/inventory/storageTier.d.ts +52 -0
- package/dist/inventory/storageTier.js +77 -0
- package/dist/item/affix.d.ts +54 -0
- package/dist/item/affix.js +115 -0
- package/dist/item/durability.d.ts +51 -0
- package/dist/item/durability.js +78 -0
- package/dist/item/modularItem.d.ts +46 -0
- package/dist/item/modularItem.js +75 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +71 -1
- package/dist/movement/dash.d.ts +37 -0
- package/dist/movement/dash.js +65 -0
- package/dist/movement/movementModel.d.ts +36 -3
- package/dist/movement/movementModel.js +81 -5
- package/dist/movement/voxelController.d.ts +61 -0
- package/dist/movement/voxelController.js +229 -0
- package/dist/multiplayer/chatContract.d.ts +42 -0
- package/dist/multiplayer/chatContract.js +57 -0
- package/dist/multiplayer/combatSnapshot.d.ts +45 -0
- package/dist/multiplayer/combatSnapshot.js +89 -0
- package/dist/multiplayer/identity.d.ts +13 -0
- package/dist/multiplayer/identity.js +20 -0
- package/dist/multiplayer/lagCompensation.d.ts +43 -0
- package/dist/multiplayer/lagCompensation.js +154 -0
- package/dist/multiplayer/matchmaking.d.ts +33 -0
- package/dist/multiplayer/matchmaking.js +60 -0
- package/dist/multiplayer/poseSyncGate.d.ts +1 -0
- package/dist/multiplayer/poseSyncGate.js +13 -1
- package/dist/multiplayer/presenceModel.d.ts +3 -0
- package/dist/multiplayer/presenceModel.js +15 -1
- package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
- package/dist/multiplayer/simultaneousCommit.js +53 -0
- package/dist/multiplayer/voiceContract.d.ts +44 -0
- package/dist/multiplayer/voiceContract.js +108 -0
- package/dist/nav/navConstrain.d.ts +14 -0
- package/dist/nav/navConstrain.js +26 -0
- package/dist/nav/navFromEnvironment.d.ts +11 -0
- package/dist/nav/navFromEnvironment.js +16 -0
- package/dist/nav/navGrid.d.ts +56 -0
- package/dist/nav/navGrid.js +265 -0
- package/dist/nav/pathFollow.d.ts +31 -0
- package/dist/nav/pathFollow.js +69 -0
- package/dist/physics/ballisticSweep.d.ts +18 -0
- package/dist/physics/ballisticSweep.js +49 -0
- package/dist/physics/buoyancy.d.ts +49 -0
- package/dist/physics/buoyancy.js +112 -0
- package/dist/physics/carryable.d.ts +46 -0
- package/dist/physics/carryable.js +110 -0
- package/dist/physics/damageZones.d.ts +56 -0
- package/dist/physics/damageZones.js +107 -0
- package/dist/physics/forceVolume.d.ts +48 -0
- package/dist/physics/forceVolume.js +119 -0
- package/dist/physics/physicsWorld.d.ts +124 -2
- package/dist/physics/physicsWorld.js +493 -41
- package/dist/physics/ragdoll.d.ts +49 -0
- package/dist/physics/ragdoll.js +103 -0
- package/dist/physics/spatialGrid.d.ts +41 -0
- package/dist/physics/spatialGrid.js +143 -0
- package/dist/physics/structure.d.ts +83 -0
- package/dist/physics/structure.js +0 -0
- package/dist/physics/traversal.d.ts +75 -0
- package/dist/physics/traversal.js +138 -0
- package/dist/physics/vehicleBody.d.ts +99 -0
- package/dist/physics/vehicleBody.js +233 -0
- package/dist/puzzle/cellGrid.d.ts +26 -0
- package/dist/puzzle/cellGrid.js +110 -0
- package/dist/puzzle/fallingPiece.d.ts +24 -0
- package/dist/puzzle/fallingPiece.js +51 -0
- package/dist/random/nameGen.d.ts +18 -0
- package/dist/random/nameGen.js +35 -0
- package/dist/random/rng.d.ts +2 -0
- package/dist/random/rng.js +23 -0
- package/dist/runtime/adapter.d.ts +35 -0
- package/dist/runtime/adapter.js +37 -1
- package/dist/runtime/cameraDirector.d.ts +10 -0
- package/dist/runtime/cameraDirector.js +25 -0
- package/dist/runtime/gameContext.d.ts +107 -3
- package/dist/runtime/gameContext.js +253 -7
- package/dist/runtime/hostPersistence.d.ts +39 -0
- package/dist/runtime/hostPersistence.js +54 -0
- package/dist/runtime/inputSnapshot.d.ts +7 -0
- package/dist/runtime/inputSnapshot.js +12 -0
- package/dist/runtime/motionIntents.d.ts +25 -0
- package/dist/runtime/motionIntents.js +25 -0
- package/dist/runtime/persistenceScope.d.ts +32 -0
- package/dist/runtime/persistenceScope.js +64 -0
- package/dist/runtime/transport.d.ts +32 -0
- package/dist/scene/autoTarget.d.ts +15 -0
- package/dist/scene/autoTarget.js +54 -0
- package/dist/scene/behaviors.d.ts +13 -1
- package/dist/scene/behaviors.js +3 -0
- package/dist/scene/captureCheck.d.ts +7 -0
- package/dist/scene/captureCheck.js +11 -0
- package/dist/scene/entityStore.d.ts +9 -1
- package/dist/scene/entityStore.js +25 -2
- package/dist/scene/form.d.ts +40 -0
- package/dist/scene/form.js +56 -0
- package/dist/scene/mount.d.ts +62 -0
- package/dist/scene/mount.js +107 -0
- package/dist/scene/movementSpeed.d.ts +35 -0
- package/dist/scene/movementSpeed.js +20 -0
- package/dist/scene/objectQuery.d.ts +18 -0
- package/dist/scene/objectQuery.js +108 -0
- package/dist/scene/objectStore.d.ts +11 -0
- package/dist/scene/objectStore.js +109 -4
- package/dist/scene/paintLayer.d.ts +15 -0
- package/dist/scene/paintLayer.js +53 -0
- package/dist/scene/possession.d.ts +30 -0
- package/dist/scene/possession.js +51 -0
- package/dist/scene/roster.d.ts +25 -0
- package/dist/scene/roster.js +57 -0
- package/dist/scene/selection.d.ts +30 -0
- package/dist/scene/selection.js +38 -0
- package/dist/scene/spatial.d.ts +21 -0
- package/dist/scene/spatial.js +63 -1
- package/dist/scene/stationClaim.d.ts +43 -0
- package/dist/scene/stationClaim.js +109 -0
- package/dist/sensor/concealment.d.ts +21 -0
- package/dist/sensor/concealment.js +63 -0
- package/dist/sensor/freezeMonitor.d.ts +17 -0
- package/dist/sensor/freezeMonitor.js +35 -0
- package/dist/sensor/frustumSensor.d.ts +60 -0
- package/dist/sensor/frustumSensor.js +104 -0
- package/dist/sensor/hiddenStateProbe.d.ts +31 -0
- package/dist/sensor/hiddenStateProbe.js +40 -0
- package/dist/sensor/recordingBuffer.d.ts +29 -0
- package/dist/sensor/recordingBuffer.js +61 -0
- package/dist/sensor/revealQuery.d.ts +22 -0
- package/dist/sensor/revealQuery.js +36 -0
- package/dist/session/contestedChannel.d.ts +36 -0
- package/dist/session/contestedChannel.js +100 -0
- package/dist/session/extraction.d.ts +54 -0
- package/dist/session/extraction.js +108 -0
- package/dist/session/ring.d.ts +36 -0
- package/dist/session/ring.js +88 -0
- package/dist/session/roles.d.ts +6 -0
- package/dist/session/roles.js +32 -0
- package/dist/session/roundState.d.ts +79 -0
- package/dist/session/roundState.js +158 -0
- package/dist/stats/accumulatorMeter.d.ts +31 -0
- package/dist/stats/accumulatorMeter.js +81 -0
- package/dist/stats/eventMeter.d.ts +34 -0
- package/dist/stats/eventMeter.js +76 -0
- package/dist/stats/rollCheck.d.ts +15 -0
- package/dist/stats/rollCheck.js +15 -0
- package/dist/survival/decayMeter.d.ts +59 -0
- package/dist/survival/decayMeter.js +95 -0
- package/dist/survival/moodle.d.ts +48 -0
- package/dist/survival/moodle.js +91 -0
- package/dist/survival/regionHealth.d.ts +72 -0
- package/dist/survival/regionHealth.js +129 -0
- package/dist/tactics/fallingGrid.d.ts +62 -0
- package/dist/tactics/fallingGrid.js +158 -0
- package/dist/tactics/predictiveQuery.d.ts +34 -0
- package/dist/tactics/predictiveQuery.js +44 -0
- package/dist/tactics/snapshot.d.ts +18 -0
- package/dist/tactics/snapshot.js +65 -0
- package/dist/tactics/surface.d.ts +52 -0
- package/dist/tactics/surface.js +135 -0
- package/dist/tactics/tacticalGrid.d.ts +68 -0
- package/dist/tactics/tacticalGrid.js +261 -0
- package/dist/time/beatClock.d.ts +40 -0
- package/dist/time/beatClock.js +73 -0
- package/dist/time/simClock.d.ts +25 -1
- package/dist/time/simClock.js +28 -3
- package/dist/turn/commit.d.ts +27 -0
- package/dist/turn/commit.js +72 -0
- package/dist/turn/intent.d.ts +14 -0
- package/dist/turn/intent.js +29 -0
- package/dist/turn/turnLoop.d.ts +60 -0
- package/dist/turn/turnLoop.js +223 -0
- package/dist/world/buildPermissions.d.ts +47 -0
- package/dist/world/buildPermissions.js +134 -0
- package/dist/world/carve.d.ts +105 -0
- package/dist/world/carve.js +175 -0
- package/dist/world/connectors.d.ts +40 -0
- package/dist/world/connectors.js +67 -0
- package/dist/world/envField.d.ts +71 -0
- package/dist/world/envField.js +96 -0
- package/dist/world/environmentSummary.d.ts +53 -0
- package/dist/world/environmentSummary.js +129 -0
- package/dist/world/features.d.ts +87 -6
- package/dist/world/features.js +52 -2
- package/dist/world/fog.d.ts +40 -0
- package/dist/world/fog.js +137 -0
- package/dist/world/gridInstances.d.ts +13 -0
- package/dist/world/gridInstances.js +22 -0
- package/dist/world/markers.d.ts +52 -0
- package/dist/world/markers.js +96 -0
- package/dist/world/minimap.d.ts +42 -0
- package/dist/world/minimap.js +72 -0
- package/dist/world/placedStructureStore.d.ts +39 -0
- package/dist/world/placedStructureStore.js +95 -0
- package/dist/world/placementController.d.ts +69 -0
- package/dist/world/placementController.js +153 -0
- package/dist/world/realm.d.ts +61 -0
- package/dist/world/realm.js +66 -0
- package/dist/world/support.d.ts +27 -0
- package/dist/world/support.js +75 -0
- package/dist/world/terraform.d.ts +54 -0
- package/dist/world/terraform.js +186 -0
- package/dist/world/terrain.d.ts +23 -1
- package/dist/world/terrain.js +64 -1
- package/dist/world/voxelField.d.ts +54 -0
- package/dist/world/voxelField.js +241 -0
- package/dist/world/walls.d.ts +61 -0
- package/dist/world/walls.js +221 -0
- package/dist/world/weather.d.ts +92 -0
- package/dist/world/weather.js +169 -0
- package/llms.txt +4102 -0
- package/package.json +3 -2
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const DEFAULT_PHASE_ORDER = ["buy", "live", "end"];
|
|
2
|
+
export function lossBonusFor(rule, streak) {
|
|
3
|
+
if (rule === undefined)
|
|
4
|
+
return 0;
|
|
5
|
+
return Math.min(rule.max, rule.base + rule.step * Math.max(0, streak));
|
|
6
|
+
}
|
|
7
|
+
function normalizeTeams(teams) {
|
|
8
|
+
if (teams.length === 0)
|
|
9
|
+
return [{ id: "a" }, { id: "b" }];
|
|
10
|
+
const normalized = [];
|
|
11
|
+
for (const team of teams) {
|
|
12
|
+
normalized.push(typeof team === "string" ? { id: team } : team);
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
}
|
|
16
|
+
function isConcludable(order, phase) {
|
|
17
|
+
const idx = order.indexOf(phase);
|
|
18
|
+
return idx > 0 && idx < order.length - 1;
|
|
19
|
+
}
|
|
20
|
+
export function createRoundState(config) {
|
|
21
|
+
const order = config.phaseOrder ?? DEFAULT_PHASE_ORDER;
|
|
22
|
+
const durations = config.phases;
|
|
23
|
+
const teamRecords = normalizeTeams(config.teams);
|
|
24
|
+
const teamIds = teamRecords.map((t) => t.id);
|
|
25
|
+
const roles = {};
|
|
26
|
+
const scores = {};
|
|
27
|
+
const lossStreaks = {};
|
|
28
|
+
for (const team of teamRecords) {
|
|
29
|
+
roles[team.id] = team.role;
|
|
30
|
+
scores[team.id] = 0;
|
|
31
|
+
lossStreaks[team.id] = 0;
|
|
32
|
+
}
|
|
33
|
+
let round = 1;
|
|
34
|
+
let phase = order[0];
|
|
35
|
+
let timeLeft = durations[phase] ?? 0;
|
|
36
|
+
let matchOver = false;
|
|
37
|
+
let pendingWinner = null;
|
|
38
|
+
const hooks = new Set();
|
|
39
|
+
function durationOf(next) {
|
|
40
|
+
return durations[next] ?? 0;
|
|
41
|
+
}
|
|
42
|
+
function nextPhaseOf(current) {
|
|
43
|
+
const idx = order.indexOf(current);
|
|
44
|
+
return idx >= order.length - 1 ? order[0] : order[idx + 1];
|
|
45
|
+
}
|
|
46
|
+
function economyFor(team) {
|
|
47
|
+
const won = pendingWinner === team;
|
|
48
|
+
return {
|
|
49
|
+
team,
|
|
50
|
+
reward: won ? config.winReward ?? 0 : lossBonusFor(config.lossBonus, lossStreaks[team]),
|
|
51
|
+
won,
|
|
52
|
+
lossStreak: lossStreaks[team],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function settleEconomy(winner) {
|
|
56
|
+
const economy = [];
|
|
57
|
+
for (const team of teamIds) {
|
|
58
|
+
const won = team === winner;
|
|
59
|
+
const reward = won ? config.winReward ?? 0 : lossBonusFor(config.lossBonus, lossStreaks[team]);
|
|
60
|
+
economy.push({ team, reward, won, lossStreak: lossStreaks[team] });
|
|
61
|
+
}
|
|
62
|
+
for (const team of teamIds) {
|
|
63
|
+
if (team === winner)
|
|
64
|
+
lossStreaks[team] = 0;
|
|
65
|
+
else
|
|
66
|
+
lossStreaks[team] += 1;
|
|
67
|
+
}
|
|
68
|
+
return economy;
|
|
69
|
+
}
|
|
70
|
+
function enter(next, events) {
|
|
71
|
+
phase = next;
|
|
72
|
+
timeLeft = durationOf(next);
|
|
73
|
+
events.push({ kind: "phase.start", round, phase: next });
|
|
74
|
+
}
|
|
75
|
+
function endPhase(events) {
|
|
76
|
+
const ending = phase;
|
|
77
|
+
if (ending === order[order.length - 1]) {
|
|
78
|
+
if (config.maxRounds !== undefined && round >= config.maxRounds) {
|
|
79
|
+
matchOver = true;
|
|
80
|
+
events.push({ kind: "phase.end", round, phase: ending, nextPhase: order[0] });
|
|
81
|
+
events.push({ kind: "match.end", round });
|
|
82
|
+
for (const hook of hooks)
|
|
83
|
+
hook(ending, order[0], round);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
round += 1;
|
|
87
|
+
pendingWinner = null;
|
|
88
|
+
}
|
|
89
|
+
const next = nextPhaseOf(ending);
|
|
90
|
+
const endedRound = ending === order[order.length - 1] ? round - 1 : round;
|
|
91
|
+
events.push({ kind: "phase.end", round: endedRound, phase: ending, nextPhase: next });
|
|
92
|
+
for (const hook of hooks)
|
|
93
|
+
hook(ending, next, endedRound);
|
|
94
|
+
enter(next, events);
|
|
95
|
+
}
|
|
96
|
+
function currentSnapshot() {
|
|
97
|
+
return {
|
|
98
|
+
round,
|
|
99
|
+
phase,
|
|
100
|
+
timeLeft: Math.max(0, timeLeft),
|
|
101
|
+
scores: { ...scores },
|
|
102
|
+
lossStreaks: { ...lossStreaks },
|
|
103
|
+
roles: { ...roles },
|
|
104
|
+
matchOver,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function concludeRoundImpl(winner) {
|
|
108
|
+
if (matchOver || !isConcludable(order, phase))
|
|
109
|
+
return [];
|
|
110
|
+
const events = [];
|
|
111
|
+
pendingWinner = winner;
|
|
112
|
+
scores[winner] = (scores[winner] ?? 0) + 1;
|
|
113
|
+
events.push({ kind: "round.win", round, winner });
|
|
114
|
+
const economy = settleEconomy(winner);
|
|
115
|
+
events.push({ kind: "round.economy", round, winner, economy });
|
|
116
|
+
endPhase(events);
|
|
117
|
+
return events;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
tick(dt) {
|
|
121
|
+
if (dt <= 0 || matchOver)
|
|
122
|
+
return [];
|
|
123
|
+
const events = [];
|
|
124
|
+
timeLeft -= dt;
|
|
125
|
+
let guard = 0;
|
|
126
|
+
while (timeLeft <= 0 && !matchOver && guard < order.length + 2) {
|
|
127
|
+
const carry = timeLeft;
|
|
128
|
+
if (isConcludable(order, phase))
|
|
129
|
+
pendingWinner = null;
|
|
130
|
+
endPhase(events);
|
|
131
|
+
if (!matchOver)
|
|
132
|
+
timeLeft += carry;
|
|
133
|
+
guard += 1;
|
|
134
|
+
}
|
|
135
|
+
return events;
|
|
136
|
+
},
|
|
137
|
+
concludeRound: concludeRoundImpl,
|
|
138
|
+
evaluate() {
|
|
139
|
+
if (config.winCondition === undefined)
|
|
140
|
+
return [];
|
|
141
|
+
const winner = config.winCondition(currentSnapshot());
|
|
142
|
+
return winner === null ? [] : concludeRoundImpl(winner);
|
|
143
|
+
},
|
|
144
|
+
onPhaseEnd(hook) {
|
|
145
|
+
hooks.add(hook);
|
|
146
|
+
return () => {
|
|
147
|
+
hooks.delete(hook);
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
phase: () => phase,
|
|
151
|
+
round: () => round,
|
|
152
|
+
timeLeft: () => Math.max(0, timeLeft),
|
|
153
|
+
score: (team) => scores[team] ?? 0,
|
|
154
|
+
economyFor,
|
|
155
|
+
roleOf: (team) => roles[team],
|
|
156
|
+
snapshot: currentSnapshot,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface MeterTier {
|
|
2
|
+
id: string;
|
|
3
|
+
at: number;
|
|
4
|
+
}
|
|
5
|
+
export type MeterMode = "hold" | "reset";
|
|
6
|
+
export interface AccumulatorMeterConfig {
|
|
7
|
+
max: number;
|
|
8
|
+
mode?: MeterMode;
|
|
9
|
+
decayPerSecond?: number;
|
|
10
|
+
decayDelayMs?: number;
|
|
11
|
+
tiers?: readonly MeterTier[];
|
|
12
|
+
}
|
|
13
|
+
export interface MeterAddResult {
|
|
14
|
+
value: number;
|
|
15
|
+
fired: boolean;
|
|
16
|
+
overflow: number;
|
|
17
|
+
tier: string | null;
|
|
18
|
+
tierChanged: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface AccumulatorMeter {
|
|
21
|
+
value(): number;
|
|
22
|
+
fraction(): number;
|
|
23
|
+
tier(): string | null;
|
|
24
|
+
broke(): boolean;
|
|
25
|
+
add(amount: number): MeterAddResult;
|
|
26
|
+
drain(amount: number): void;
|
|
27
|
+
reset(): void;
|
|
28
|
+
tick(dtSeconds: number): void;
|
|
29
|
+
}
|
|
30
|
+
export declare function tierAt(value: number, tiers: readonly MeterTier[]): string | null;
|
|
31
|
+
export declare function createAccumulatorMeter(config: AccumulatorMeterConfig): AccumulatorMeter;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
function sortedTiers(tiers) {
|
|
2
|
+
if (tiers === undefined)
|
|
3
|
+
return [];
|
|
4
|
+
return [...tiers].sort((a, b) => a.at - b.at);
|
|
5
|
+
}
|
|
6
|
+
export function tierAt(value, tiers) {
|
|
7
|
+
let current = null;
|
|
8
|
+
for (const tier of tiers) {
|
|
9
|
+
if (value >= tier.at)
|
|
10
|
+
current = tier.id;
|
|
11
|
+
else
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
return current;
|
|
15
|
+
}
|
|
16
|
+
export function createAccumulatorMeter(config) {
|
|
17
|
+
const mode = config.mode ?? "hold";
|
|
18
|
+
const tiers = sortedTiers(config.tiers);
|
|
19
|
+
const decayPerSecond = config.decayPerSecond ?? 0;
|
|
20
|
+
const decayDelayMs = config.decayDelayMs ?? 0;
|
|
21
|
+
let value = 0;
|
|
22
|
+
let broken = false;
|
|
23
|
+
let idleMs = 0;
|
|
24
|
+
function clamp(next) {
|
|
25
|
+
return Math.max(0, Math.min(config.max, next));
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
value() {
|
|
29
|
+
return value;
|
|
30
|
+
},
|
|
31
|
+
fraction() {
|
|
32
|
+
return config.max <= 0 ? 0 : value / config.max;
|
|
33
|
+
},
|
|
34
|
+
tier() {
|
|
35
|
+
return tierAt(value, tiers);
|
|
36
|
+
},
|
|
37
|
+
broke() {
|
|
38
|
+
return broken;
|
|
39
|
+
},
|
|
40
|
+
add(amount) {
|
|
41
|
+
idleMs = 0;
|
|
42
|
+
const tierBefore = tierAt(value, tiers);
|
|
43
|
+
const raw = value + amount;
|
|
44
|
+
const fired = amount > 0 && raw >= config.max && !(mode === "hold" && broken);
|
|
45
|
+
if (mode === "reset" && fired) {
|
|
46
|
+
value = clamp(raw - config.max);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
value = clamp(raw);
|
|
50
|
+
if (fired && mode === "hold")
|
|
51
|
+
broken = true;
|
|
52
|
+
}
|
|
53
|
+
const tierAfter = tierAt(value, tiers);
|
|
54
|
+
return {
|
|
55
|
+
value,
|
|
56
|
+
fired,
|
|
57
|
+
overflow: fired && mode === "reset" ? Math.max(0, raw - config.max) : 0,
|
|
58
|
+
tier: tierAfter,
|
|
59
|
+
tierChanged: tierAfter !== tierBefore,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
drain(amount) {
|
|
63
|
+
value = clamp(value - amount);
|
|
64
|
+
if (value <= 0)
|
|
65
|
+
broken = false;
|
|
66
|
+
},
|
|
67
|
+
reset() {
|
|
68
|
+
value = 0;
|
|
69
|
+
broken = false;
|
|
70
|
+
idleMs = 0;
|
|
71
|
+
},
|
|
72
|
+
tick(dtSeconds) {
|
|
73
|
+
if (dtSeconds <= 0)
|
|
74
|
+
return;
|
|
75
|
+
idleMs += dtSeconds * 1000;
|
|
76
|
+
if (broken || decayPerSecond <= 0 || idleMs < decayDelayMs)
|
|
77
|
+
return;
|
|
78
|
+
value = clamp(value - decayPerSecond * dtSeconds);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type MeterMode, type MeterTier } from "./accumulatorMeter.js";
|
|
2
|
+
export interface EventMeterConfig {
|
|
3
|
+
max: number;
|
|
4
|
+
mode?: MeterMode;
|
|
5
|
+
decayPerSecond?: number;
|
|
6
|
+
decayDelayMs?: number;
|
|
7
|
+
tiers?: readonly MeterTier[];
|
|
8
|
+
gains: Record<string, number>;
|
|
9
|
+
resets?: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
export interface EventMeterFeedResult {
|
|
12
|
+
tag: string;
|
|
13
|
+
reset: boolean;
|
|
14
|
+
amount: number;
|
|
15
|
+
value: number;
|
|
16
|
+
fraction: number;
|
|
17
|
+
fired: boolean;
|
|
18
|
+
ready: boolean;
|
|
19
|
+
overflow: number;
|
|
20
|
+
tier: string | null;
|
|
21
|
+
tierChanged: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface EventMeter {
|
|
24
|
+
value(): number;
|
|
25
|
+
fraction(): number;
|
|
26
|
+
tier(): string | null;
|
|
27
|
+
ready(): boolean;
|
|
28
|
+
feed(tag: string, scale?: number): EventMeterFeedResult;
|
|
29
|
+
consume(): boolean;
|
|
30
|
+
drain(amount: number): void;
|
|
31
|
+
reset(): void;
|
|
32
|
+
tick(dtSeconds: number): void;
|
|
33
|
+
}
|
|
34
|
+
export declare function createEventMeter(config: EventMeterConfig): EventMeter;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createAccumulatorMeter, } from "./accumulatorMeter.js";
|
|
2
|
+
function readyOf(meter, mode) {
|
|
3
|
+
return mode === "hold" ? meter.broke() : meter.value() >= 0 && meter.fraction() >= 1;
|
|
4
|
+
}
|
|
5
|
+
export function createEventMeter(config) {
|
|
6
|
+
const mode = config.mode ?? "hold";
|
|
7
|
+
const resets = new Set(config.resets ?? []);
|
|
8
|
+
const meter = createAccumulatorMeter({
|
|
9
|
+
max: config.max,
|
|
10
|
+
mode,
|
|
11
|
+
decayPerSecond: config.decayPerSecond,
|
|
12
|
+
decayDelayMs: config.decayDelayMs,
|
|
13
|
+
tiers: config.tiers,
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
value() {
|
|
17
|
+
return meter.value();
|
|
18
|
+
},
|
|
19
|
+
fraction() {
|
|
20
|
+
return meter.fraction();
|
|
21
|
+
},
|
|
22
|
+
tier() {
|
|
23
|
+
return meter.tier();
|
|
24
|
+
},
|
|
25
|
+
ready() {
|
|
26
|
+
return readyOf(meter, mode);
|
|
27
|
+
},
|
|
28
|
+
feed(tag, scale = 1) {
|
|
29
|
+
const tierBefore = meter.tier();
|
|
30
|
+
if (resets.has(tag)) {
|
|
31
|
+
meter.reset();
|
|
32
|
+
return {
|
|
33
|
+
tag,
|
|
34
|
+
reset: true,
|
|
35
|
+
amount: 0,
|
|
36
|
+
value: meter.value(),
|
|
37
|
+
fraction: meter.fraction(),
|
|
38
|
+
fired: false,
|
|
39
|
+
ready: readyOf(meter, mode),
|
|
40
|
+
overflow: 0,
|
|
41
|
+
tier: meter.tier(),
|
|
42
|
+
tierChanged: meter.tier() !== tierBefore,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const amount = (config.gains[tag] ?? 0) * scale;
|
|
46
|
+
const result = meter.add(amount);
|
|
47
|
+
return {
|
|
48
|
+
tag,
|
|
49
|
+
reset: false,
|
|
50
|
+
amount,
|
|
51
|
+
value: result.value,
|
|
52
|
+
fraction: meter.fraction(),
|
|
53
|
+
fired: result.fired,
|
|
54
|
+
ready: readyOf(meter, mode),
|
|
55
|
+
overflow: result.overflow,
|
|
56
|
+
tier: result.tier,
|
|
57
|
+
tierChanged: result.tierChanged,
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
consume() {
|
|
61
|
+
if (!readyOf(meter, mode))
|
|
62
|
+
return false;
|
|
63
|
+
meter.reset();
|
|
64
|
+
return true;
|
|
65
|
+
},
|
|
66
|
+
drain(amount) {
|
|
67
|
+
meter.drain(amount);
|
|
68
|
+
},
|
|
69
|
+
reset() {
|
|
70
|
+
meter.reset();
|
|
71
|
+
},
|
|
72
|
+
tick(dtSeconds) {
|
|
73
|
+
meter.tick(dtSeconds);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type CheckAdvantage = "advantage" | "disadvantage" | "normal";
|
|
2
|
+
export interface CheckInput {
|
|
3
|
+
modifier: number;
|
|
4
|
+
dc: number;
|
|
5
|
+
advantage?: CheckAdvantage;
|
|
6
|
+
diceSides?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface CheckResult {
|
|
9
|
+
rolls: readonly number[];
|
|
10
|
+
roll: number;
|
|
11
|
+
total: number;
|
|
12
|
+
success: boolean;
|
|
13
|
+
critical: "success" | "failure" | null;
|
|
14
|
+
}
|
|
15
|
+
export declare function rollCheck(input: CheckInput, rng?: () => number): CheckResult;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function rollCheck(input, rng = Math.random) {
|
|
2
|
+
const sides = input.diceSides ?? 20;
|
|
3
|
+
const advantage = input.advantage ?? "normal";
|
|
4
|
+
const rollDie = () => Math.floor(rng() * sides) + 1;
|
|
5
|
+
const rolls = advantage === "normal" ? [rollDie()] : [rollDie(), rollDie()];
|
|
6
|
+
const roll = advantage === "advantage"
|
|
7
|
+
? Math.max(...rolls)
|
|
8
|
+
: advantage === "disadvantage"
|
|
9
|
+
? Math.min(...rolls)
|
|
10
|
+
: rolls[0];
|
|
11
|
+
const total = roll + input.modifier;
|
|
12
|
+
const success = total >= input.dc;
|
|
13
|
+
const critical = roll === sides ? "success" : roll === 1 ? "failure" : null;
|
|
14
|
+
return { rolls, roll, total, success, critical };
|
|
15
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Moodle, MoodleSeverity } from "./moodle.js";
|
|
2
|
+
export interface MeterThreshold {
|
|
3
|
+
/** Moodle id raised while the meter is on the wrong side of `at`. */
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
/** Boundary value. */
|
|
7
|
+
at: number;
|
|
8
|
+
/** Raise the moodle when the meter is `below` (hunger/thirst) or `above` (temperature, radiation) this value. */
|
|
9
|
+
when: "below" | "above";
|
|
10
|
+
severity?: MoodleSeverity;
|
|
11
|
+
icon?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface DecayMeterConfig {
|
|
14
|
+
id: string;
|
|
15
|
+
max: number;
|
|
16
|
+
min?: number;
|
|
17
|
+
/** Starting value; defaults to `max` (a full stomach). */
|
|
18
|
+
start?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Units changed per game-second at rate-modifier 1. Positive drains toward `min`
|
|
21
|
+
* (hunger, oxygen), negative fills toward `max` (a warmth meter recovering by a fire).
|
|
22
|
+
*/
|
|
23
|
+
rate: number;
|
|
24
|
+
/** Status thresholds → moodles, distinct from the raw numeric bar. */
|
|
25
|
+
thresholds?: readonly MeterThreshold[];
|
|
26
|
+
}
|
|
27
|
+
export interface DecayMeterState {
|
|
28
|
+
id: string;
|
|
29
|
+
value: number;
|
|
30
|
+
max: number;
|
|
31
|
+
min: number;
|
|
32
|
+
/** 0..1 fill. */
|
|
33
|
+
fraction: number;
|
|
34
|
+
}
|
|
35
|
+
export interface DecayMeterSet {
|
|
36
|
+
/** Drain/fill every meter by `rate * rateModifier * dt`. Call once per game tick. */
|
|
37
|
+
tick(dt: number): void;
|
|
38
|
+
value(id: string): number;
|
|
39
|
+
state(id: string): DecayMeterState;
|
|
40
|
+
/** Consumable/action refill: `refill("hunger", 40)` after eating. Negative drains. */
|
|
41
|
+
refill(id: string, amount: number): void;
|
|
42
|
+
/** Overwrite the base drain rate (e.g. a game mode with harsher hunger). */
|
|
43
|
+
setRate(id: string, rate: number): void;
|
|
44
|
+
/** Multiply the drain rate this tick onward — cold biome speeds warmth loss, sprint burns stamina. */
|
|
45
|
+
setRateModifier(id: string, multiplier: number): void;
|
|
46
|
+
/** Moodles for every crossed threshold, worst-first per meter. */
|
|
47
|
+
moodles(): Moodle[];
|
|
48
|
+
/** True while any threshold with the given severity (or worse) is active — cheap game gate. */
|
|
49
|
+
snapshot(): Record<string, DecayMeterState>;
|
|
50
|
+
ids(): readonly string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Named decay meters — hunger, thirst, oxygen, sanity, warmth, stamina. Each drains
|
|
54
|
+
* (or recovers) on game-time `dt` at a configurable rate, refills from consumables or
|
|
55
|
+
* actions, and raises moodle statuses at thresholds. Rate modifiers let the environment
|
|
56
|
+
* drive them (colder → faster warmth loss; toxic biome → oxygen drops), so a game reads
|
|
57
|
+
* an environment field then calls `setRateModifier`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createDecayMeterSet(configs: readonly DecayMeterConfig[]): DecayMeterSet;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
function clamp(value, min, max) {
|
|
2
|
+
return value < min ? min : value > max ? max : value;
|
|
3
|
+
}
|
|
4
|
+
function toState(runtime) {
|
|
5
|
+
const span = runtime.max - runtime.min;
|
|
6
|
+
return {
|
|
7
|
+
id: runtime.config.id,
|
|
8
|
+
value: runtime.value,
|
|
9
|
+
max: runtime.max,
|
|
10
|
+
min: runtime.min,
|
|
11
|
+
fraction: span > 0 ? (runtime.value - runtime.min) / span : 0,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function crossed(threshold, value) {
|
|
15
|
+
return threshold.when === "below" ? value < threshold.at : value > threshold.at;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Named decay meters — hunger, thirst, oxygen, sanity, warmth, stamina. Each drains
|
|
19
|
+
* (or recovers) on game-time `dt` at a configurable rate, refills from consumables or
|
|
20
|
+
* actions, and raises moodle statuses at thresholds. Rate modifiers let the environment
|
|
21
|
+
* drive them (colder → faster warmth loss; toxic biome → oxygen drops), so a game reads
|
|
22
|
+
* an environment field then calls `setRateModifier`.
|
|
23
|
+
*/
|
|
24
|
+
export function createDecayMeterSet(configs) {
|
|
25
|
+
const meters = new Map();
|
|
26
|
+
const order = [];
|
|
27
|
+
for (const config of configs) {
|
|
28
|
+
const min = config.min ?? 0;
|
|
29
|
+
const max = config.max;
|
|
30
|
+
const value = clamp(config.start ?? max, min, max);
|
|
31
|
+
meters.set(config.id, { config, min, max, value, rate: config.rate, modifier: 1 });
|
|
32
|
+
order.push(config.id);
|
|
33
|
+
}
|
|
34
|
+
const require = (id) => {
|
|
35
|
+
const runtime = meters.get(id);
|
|
36
|
+
if (runtime === undefined)
|
|
37
|
+
throw new Error(`unknown decay meter "${id}"`);
|
|
38
|
+
return runtime;
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
tick(dt) {
|
|
42
|
+
if (dt <= 0)
|
|
43
|
+
return;
|
|
44
|
+
for (const runtime of meters.values()) {
|
|
45
|
+
runtime.value = clamp(runtime.value - runtime.rate * runtime.modifier * dt, runtime.min, runtime.max);
|
|
46
|
+
runtime.modifier = 1;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
value(id) {
|
|
50
|
+
return require(id).value;
|
|
51
|
+
},
|
|
52
|
+
state(id) {
|
|
53
|
+
return toState(require(id));
|
|
54
|
+
},
|
|
55
|
+
refill(id, amount) {
|
|
56
|
+
const runtime = require(id);
|
|
57
|
+
runtime.value = clamp(runtime.value + amount, runtime.min, runtime.max);
|
|
58
|
+
},
|
|
59
|
+
setRate(id, rate) {
|
|
60
|
+
require(id).rate = rate;
|
|
61
|
+
},
|
|
62
|
+
setRateModifier(id, multiplier) {
|
|
63
|
+
require(id).modifier = multiplier;
|
|
64
|
+
},
|
|
65
|
+
moodles() {
|
|
66
|
+
const out = [];
|
|
67
|
+
for (const id of order) {
|
|
68
|
+
const runtime = meters.get(id);
|
|
69
|
+
for (const threshold of runtime.config.thresholds ?? []) {
|
|
70
|
+
if (!crossed(threshold, runtime.value))
|
|
71
|
+
continue;
|
|
72
|
+
out.push({
|
|
73
|
+
id: threshold.id,
|
|
74
|
+
label: threshold.label,
|
|
75
|
+
severity: threshold.severity ?? "warning",
|
|
76
|
+
source: "meter",
|
|
77
|
+
stacks: 1,
|
|
78
|
+
...(threshold.icon === undefined ? {} : { icon: threshold.icon }),
|
|
79
|
+
fraction: toState(runtime).fraction,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
},
|
|
85
|
+
snapshot() {
|
|
86
|
+
const out = {};
|
|
87
|
+
for (const id of order)
|
|
88
|
+
out[id] = toState(meters.get(id));
|
|
89
|
+
return out;
|
|
90
|
+
},
|
|
91
|
+
ids() {
|
|
92
|
+
return order;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type MoodleSeverity = "good" | "neutral" | "warning" | "critical";
|
|
2
|
+
export type MoodleSource = "meter" | "ailment" | "buff";
|
|
3
|
+
export interface Moodle {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
severity: MoodleSeverity;
|
|
7
|
+
source: MoodleSource;
|
|
8
|
+
stacks: number;
|
|
9
|
+
icon?: string;
|
|
10
|
+
/** 0..1 progress ring — remaining buff time, wound treatment, meter fill. Absent = no ring. */
|
|
11
|
+
fraction?: number;
|
|
12
|
+
/** Free-form note surfaced under the label (e.g. "-3°C", "needs bandage"). */
|
|
13
|
+
note?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const MOODLE_SEVERITY_ORDER: Record<MoodleSeverity, number>;
|
|
16
|
+
/**
|
|
17
|
+
* Merge any number of moodle groups into one stack — meters, ailments, and buffs
|
|
18
|
+
* share this display. Same-id moodles fold together (stacks add, worst severity wins);
|
|
19
|
+
* the result is ordered worst-first so the HUD reads critical statuses at a glance.
|
|
20
|
+
*/
|
|
21
|
+
export declare function stackMoodles(...groups: readonly (readonly Moodle[])[]): Moodle[];
|
|
22
|
+
export interface TimedMoodleInput {
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
severity?: MoodleSeverity;
|
|
26
|
+
icon?: string;
|
|
27
|
+
note?: string;
|
|
28
|
+
stacks?: number;
|
|
29
|
+
/** Game-seconds until the moodle drops. Omit for a moodle that stays until removed. */
|
|
30
|
+
duration?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface MoodleStack {
|
|
33
|
+
/** Add or refresh a timed moodle (Valheim food buff, a temporary shelter status). */
|
|
34
|
+
add(input: TimedMoodleInput): void;
|
|
35
|
+
remove(id: string): void;
|
|
36
|
+
has(id: string): boolean;
|
|
37
|
+
/** Advance timers by a game-time delta; drops expired moodles. */
|
|
38
|
+
tick(dt: number): void;
|
|
39
|
+
/** The live timed moodles (buffs, temporary statuses). */
|
|
40
|
+
list(): Moodle[];
|
|
41
|
+
clear(): void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A stateful holder for timed status moodles (food buffs, temporary shelter, warmth).
|
|
45
|
+
* Meters and multi-region health derive their own moodles on read; combine all three
|
|
46
|
+
* through `stackMoodles(stack.list(), meterMoodles, ailmentMoodles)` for one display.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createMoodleStack(): MoodleStack;
|