From 1a0886f70266df254865e34ac5c1e3a1782298c9 Mon Sep 17 00:00:00 2001 From: n8n-gitea Date: Sun, 7 Jun 2026 12:54:12 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20golemancy=20redesign=20cleanup=20?= =?UTF-8?q?=E2=80=94=20remove=20orphaned=20legacy=20code=20and=20update=20?= =?UTF-8?q?docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/GAME_BRIEFING.md | 42 ++---- docs/circular-deps.txt | 2 +- docs/dependency-graph.json | 4 +- .../attunements/fabricator/fabricator-spec.md | 2 +- .../fabricator/systems/golemancy-spec.md | 2 +- docs/specs/spire-combat-spec.md | 135 +++++++++++------- src/app/components/LeftPanel.tsx | 2 + .../game/data/golems/golemancy-data.test.ts | 7 +- src/lib/game/data/golems/index.ts | 2 +- src/lib/game/data/golems/types.ts | 15 -- src/lib/game/data/golems/utils.ts | 47 +----- src/lib/game/stores/gameStore.ts | 9 +- src/lib/game/stores/manaStore.ts | 12 +- 13 files changed, 128 insertions(+), 153 deletions(-) diff --git a/docs/GAME_BRIEFING.md b/docs/GAME_BRIEFING.md index f4e61e4..abe4e69 100644 --- a/docs/GAME_BRIEFING.md +++ b/docs/GAME_BRIEFING.md @@ -768,43 +768,23 @@ Level 8: 4 slots Level 10: 5 slots ``` -### Golem Types (10 Total — undergoing redesign, see issue #268) +### Component-Based Construction -#### Base Golems (1) +Golems are designed by assembling **three mandatory components** plus optional enchantments: -| Golem | Element | Damage | Speed | HP | Pierce | Unlock | -|-------|---------|--------|-------|----|--------|--------| -| Earth Golem | Earth | 8 | 1.5/s | 50 | 15% | Fabricator 2 | - -#### Elemental Variant Golems (3) - -| Golem | Element | Damage | Speed | HP | Pierce | Unlock | -|-------|---------|--------|-------|----|--------|--------| -| Steel Golem | Metal | 12 | 1.2/s | 60 | 35% | Metal mana unlocked | -| Crystal Golem | Crystal | 18 | 1.0/s | 40 | 25% | Crystal mana unlocked | -| Sand Golem | Sand | 10 | 2.0/hr | 45 | 15% | Sand mana unlocked | - -#### Hybrid Golems (6) — Require Enchanter 5 + Fabricator 5 - -| Golem | Elements | Damage | Speed | HP | Pierce | Special | -|-------|----------|--------|-------|----|--------|---------| -| Lava Golem | Earth + Fire | 15 | 1.0/s | 70 | 20% | AOE 2 | -| Galvanic Golem | Metal + Lightning | 10 | 3.5/s | 45 | 45% | Fast | -| Obsidian Golem | Earth + Dark | 25 | 0.8/s | 55 | 50% | High damage | -| Prism Golem | Crystal + Light | 28 | 2.0/hr | 60 | 45% | AOE 3 | -| Quicksilver Golem | Metal + Water | 14 | 4.0/hr | 55 | 35% | Very fast | -| Voidstone Golem | Earth + Void | 40 | 0.6/s | 100 | 60% | AOE 3, ultimate | - -### Golem Combat - -> ⚠ The golemancy system is undergoing a full redesign (see issue #268). The current data definitions exist but are disconnected from the combat pipeline. +| Component | Role | Count | Examples | +|-----------|------|-------|----------| +| **Core** | Power source: mana types, capacity, regen, upkeep, duration | 4 (Basic, Intermediate, Advanced, Guardian) | Basic Core (Earth only), Guardian Core (all guardian mana types) | +| **Frame** | Combat stats: damage, speed, armor pierce, magic affinity, special | 7 (Earth, Sand, Frost, Crystal, Steel, Shadowglass, Crystal-Steel Hybrid) | Earth (balanced), Shadowglass (fast + AoE), Crystal-Steel Hybrid (guardian constructs) | +| **Mind Circuit** | Behavior: basic attacks, spell casting, spell cycling | 4 (Simple, Intermediate, Advanced, Guardian) | Simple (basic only), Guardian (cycle all spells) | +| **Enchantments** | Sword effects on basic attacks (optional) | 8 | Burn, Slow, Shock, Weaken, Armor Pierce, Crit Chance | +**Player upkeep formula:** ``` -progressPerTick = HOURS_PER_TICK × attackSpeed × efficiencyBonus -damage = baseDamage × (1 + golemMasteryBonus) +Upkeep per hour = Core.manaRegen × 2 ``` -Golems last `1 + golemLongevity` floors. Maintenance cost multiplier: `1 - (golemSiphon × 0.1)`. +Golems last `Core.maxRoomDuration` rooms (3–8 depending on core tier). Stats are derived from components via `computeGolemStats()`. --- diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index de01f87..11ce638 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,4 +1,4 @@ # Circular Dependencies -Generated: 2026-06-06T16:37:23.532Z +Generated: 2026-06-06T17:19:20.910Z No circular dependencies found. ✅ diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index c038292..57a4a44 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-06-06T16:37:21.673Z", + "generated": "2026-06-06T17:19:19.033Z", "description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.", "usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry." }, @@ -557,6 +557,8 @@ ], "stores/combat-descent-actions.ts": [ "data/guardian-encounters.ts", + "effects/discipline-effects.ts", + "stores/attunementStore.ts", "stores/combat-state.types.ts", "stores/golem-combat-actions.ts", "stores/manaStore.ts", diff --git a/docs/specs/attunements/fabricator/fabricator-spec.md b/docs/specs/attunements/fabricator/fabricator-spec.md index b4e4f7e..ff280ef 100644 --- a/docs/specs/attunements/fabricator/fabricator-spec.md +++ b/docs/specs/attunements/fabricator/fabricator-spec.md @@ -253,7 +253,7 @@ study-fabricator-recipes (root) |---|---| | `src/lib/game/data/attunements.ts` | Fabricator definition | | `src/lib/game/data/disciplines/fabricator.ts` | Fabricator disciplines (5) | -| `src/lib/game/data/golems/` | Golem definitions (10 golems) | +| `src/lib/game/data/golems/` | Golem component definitions (4 cores, 7 frames, 4 mind circuits, 8 enchantments) | | `src/lib/game/crafting-fabricator.ts` | Fabrication crafting logic | | `src/lib/game/data/fabricator-recipes.ts` | Core equipment recipes | | `src/lib/game/data/fabricator-material-recipes.ts` | Material recipes | diff --git a/docs/specs/attunements/fabricator/systems/golemancy-spec.md b/docs/specs/attunements/fabricator/systems/golemancy-spec.md index edce7cf..007163c 100644 --- a/docs/specs/attunements/fabricator/systems/golemancy-spec.md +++ b/docs/specs/attunements/fabricator/systems/golemancy-spec.md @@ -550,4 +550,4 @@ Directly determines base golem slots: `floor(fabricatorLevel / 2)`. | `src/lib/game/stores/golem-combat-actions.ts` | Golem combat actions (rewrite) | | `src/lib/game/stores/pipelines/golem-combat.ts` | Golem combat pipeline (rewrite) | | `src/components/game/tabs/GolemancyTab.tsx` | Golemancy UI (major rewrite — design builder) | -| `docs/specs/spire-combat-spec.md §9` | Authoritative runtime spec (needs update) | \ No newline at end of file +| `docs/specs/spire-combat-spec.md §9` | Authoritative runtime spec | \ No newline at end of file diff --git a/docs/specs/spire-combat-spec.md b/docs/specs/spire-combat-spec.md index 3b04bc6..d3f626d 100644 --- a/docs/specs/spire-combat-spec.md +++ b/docs/specs/spire-combat-spec.md @@ -414,68 +414,81 @@ At peak incursion (day 30), regen falls to 5% of base. Practical effects: ### 9.1 Overview -Golemancy is the **Fabricator attunement's** combat contribution. Golems are -summoned automatically at room entry, fight alongside the player, and disappear -after a fixed number of rooms or if their maintenance cost cannot be met. +Golemancy is the **Fabricator attunement's** combat contribution. Players design +custom golems from components (Core + Frame + Mind Circuit + Enchantments), then +configure a loadout. Golems are summoned automatically at room entry, fight alongside +the player, and disappear after a fixed number of rooms or if their maintenance cost +cannot be met. ### 9.2 Golem Loadout (Outside Spire) The player configures a **golem loadout** from the Golemancy tab before entering -the spire. The loadout defines which golems to attempt to summon and in what order. -This configuration persists across rooms but not across spire runs. +the spire. The loadout defines which golem designs to attempt to summon and in what +order. This configuration persists across rooms but not across spire runs. ### 9.3 Summoning on Room Entry -When the player enters a new combat room: +When the player enters a new combat room, `summonGolemsOnRoomEntry()` iterates the +loadout in priority order: ``` -onRoomEntry(): - for each golem in golemLoadout: - if player has enough mana of golem.summonCostType >= golem.summonCost: - deductMana(golem.summonCost, golem.summonCostType) +summonGolemsOnRoomEntry(loadout, rawMana, elements, currentFloor, existingActiveGolems, disciplineSlotsBonus, fabricatorLevel): + for each entry in loadout: + if !entry.enabled → skip + if activeGolems.length >= totalSlots → break // max 7 + if already active → skip + resolve components (Core, Frame, Mind Circuit) from design + stats = computeGolemStats(componentDesign) + if player can afford stats.totalSummonCost: + deduct summon cost from player mana activeGolems.push({ - ...golemDef, - roomsRemaining: golemDef.maxRoomDuration, + designId: entry.designId, + summonedFloor: currentFloor, attackProgress: 0, + roomsRemaining: stats.maxRoomDuration, + currentMana: stats.manaCapacity, // starts full + spellCastIndex: 0, }) - activityLog("${golem.name} summoned") else: - activityLog("Not enough mana to summon ${golem.name} — skipped") + log "Not enough mana — skipped" ``` +Total slots = `min(7, floor(fabricatorLevel / 2) + disciplineBonus)`. + Golems that could not be summoned (insufficient mana) are **not re-attempted** within the same room. They will be attempted again on the next room entry. ### 9.4 Golem Combat -Each active golem attacks on its own `attackProgress` timer, identical to swords: +Each active golem attacks on its own `attackProgress` timer: ``` -golemProgress += HOURS_PER_TICK × golem.attackSpeed -while golemProgress >= 1: - dmg = golem.baseDamage - // Apply golem's own elemental type if it has one - if golem.element: - dmg ×= getElementalBonus(golem.element, enemy.element) - // Apply golem special effects (DoT, armor pierce, AoE, etc.) - applyGolemEffects(golem, dmg, enemy) - applyDamageToRoom(dmg) - golemProgress -= 1 +attackProgress += HOURS_PER_TICK × frame.attackSpeed +while attackProgress >= 1: + if mindCircuit has spells && golem.currentMana >= spellCost: + cast spell: damage = baseSpellDamage × frame.magicAffinity + golem.currentMana -= spellCost + spellCastIndex = (spellCastIndex + 1) % selectedSpells.length + else: + dmg = frame.baseDamage × (1 + frame.armorPierce) + apply enchantment effects (burn, slow, etc.) + applyDamageToRoom(dmg) + attackProgress -= 1 ``` Golems ignore Executioner and Berserker discipline specials. ### 9.5 Maintenance Cost -Each tick, each active golem checks its maintenance cost: +Each tick, `processGolemMaintenance()` checks upkeep for each active golem: ``` -tickGolemMaintenance(golem): - if player mana[golem.maintenanceCostType] >= golem.maintenanceCost × HOURS_PER_TICK: - deductMana(golem.maintenanceCost × HOURS_PER_TICK, golem.maintenanceCostType) - else: - dismiss(golem) - activityLog("${golem.name} dismissed — insufficient ${golem.maintenanceCostType} mana") +upkeepPerTick = core.manaRegen × 2 × HOURS_PER_TICK +if player has enough of core.primaryManaType: + deduct upkeepPerTick from player element mana +else: + dismiss(golem) + log "${name} dismissed — insufficient mana for upkeep" ``` A dismissed golem is **not re-summoned mid-room**. It will be re-attempted on the @@ -483,13 +496,14 @@ next room entry if mana has recovered. ### 9.6 Room Duration Limit +`countdownGolemRoomDuration()` runs on room clear: + ``` -onRoomCleared(): - for each activeGolem: - activeGolem.roomsRemaining -= 1 - if activeGolem.roomsRemaining <= 0: - dismiss(golem) - activityLog("${golem.name} has faded after ${maxRoomDuration} rooms") +for each activeGolem: + golem.roomsRemaining -= 1 + if golem.roomsRemaining <= 0: + dismiss(golem) + log "${name} has faded after ${maxRoomDuration} rooms" ``` Room duration ticks down on room clear, not on room entry — golems persist through @@ -497,25 +511,38 @@ the full room they were summoned in. ### 9.7 Golem Data Shape +The runtime active golem type (`RuntimeActiveGolem` in `types/game.ts`): + ```typescript -interface GolemDefinition { - id: string; - name: string; - tier: number; // 1–4 (determines general power) - baseDamage: number; - attackSpeed: number; // attacks per in-game hour - element?: ElementType; // optional elemental type for matchup - maxRoomDuration: number; // rooms before disappearing - summonCost: number; - summonCostType: ElementType | 'raw'; - maintenanceCost: number; // per in-game hour - maintenanceCostType: ElementType | 'raw'; - onHitEffect?: GolemHitEffect; // DoT, AoE, etc. - armorPierce?: number; // 0-1, bypasses this fraction of enemy armor - aoe?: boolean; +interface RuntimeActiveGolem { + designId: string; // Reference to the player's GolemDesign + summonedFloor: number; // Floor when golem was summoned + attackProgress: number; // Progress toward next attack (accumulated) + roomsRemaining: number; // Rooms before golem fades + currentMana: number; // Current mana in golem's own pool + spellCastIndex: number; // For alternating/cycling spell circuits } ``` +The serialized design type (`SerializedGolemDesign` in `types/game.ts`): + +```typescript +interface SerializedGolemDesign { + id: string; + name: string; + coreId: string; + frameId: string; + mindCircuitId: string; + enchantmentIds: string[]; + selectedManaTypes: string[]; + selectedSpells: string[]; +} +``` + +Golem stats are computed from components via `computeGolemStats()` in +`data/golems/utils.ts`, which sums summon costs from all components and derives +upkeep from `core.manaRegen × 2`. + --- ## 10. In-Game Time Display @@ -545,7 +572,7 @@ They are **in scope for the implementation this spec describes**: | Mage barrier recharge | `MODIFIER_CONFIG.mage.barrierRechargeRate` | Data-only | Tick in `onDamageDealt` §5.2 | | Guardian armor | `GuardianDef.armor` | Data-only | Add check to guardian pipeline §5.3 | | DoT / debuff system | Spell/enchantment type defs | **Implemented** — `dot-runtime.ts` complete and wired into combat tick; curse amplification added (issue #286) | Verified working | -| Golemancy combat | Full golem data exists | Disconnected | Implement per §9 | +| Golemancy combat | Full golem data + runtime | **Implemented** — component-based system complete | Verified working | | Sword melee attacks | Weapon type exists | **Implemented** — meleeProgress with enemy defense application (issue #285) | Add `meleeProgress` per §3.1 | | AoE target distribution | `SpellDefinition.aoe` flag | Partial | Implement per §3.2 | | `elemMasteryBonus` | Stub in `calcDamage` | Hardcoded `1` | Future — leave as `1` for now | diff --git a/src/app/components/LeftPanel.tsx b/src/app/components/LeftPanel.tsx index f8ab0a8..7d6b74e 100644 --- a/src/app/components/LeftPanel.tsx +++ b/src/app/components/LeftPanel.tsx @@ -21,6 +21,7 @@ export function LeftPanel() { const rawMana = useManaStore((s) => s.rawMana); const elements = useManaStore((s) => s.elements); const meditateTicks = useManaStore((s) => s.meditateTicks); + const elementRegen = useManaStore((s) => s.elementRegen); const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades); const equippedInstances = useCraftingStore((s) => s.equippedInstances); const equipmentInstances = useCraftingStore((s) => s.equipmentInstances); @@ -77,6 +78,7 @@ export function LeftPanel() { onGatherStart={handleGatherStart} onGatherEnd={handleGatherEnd} elements={elements} + elementRegen={elementRegen} /> diff --git a/src/lib/game/data/golems/golemancy-data.test.ts b/src/lib/game/data/golems/golemancy-data.test.ts index 1f6c372..9abcfa3 100644 --- a/src/lib/game/data/golems/golemancy-data.test.ts +++ b/src/lib/game/data/golems/golemancy-data.test.ts @@ -14,6 +14,7 @@ import { createActiveGolem, } from '@/lib/game/data/golems/utils'; import type { GolemDesign } from '@/lib/game/data/golems/types'; +import type { RuntimeActiveGolem } from '@/lib/game/types'; // ─── Helper ─────────────────────────────────────────────────────────────────── @@ -274,9 +275,9 @@ describe('canAffordGolemDesign', () => { // ─── Active Golem Creation ──────────────────────────────────────────────────── describe('createActiveGolem', () => { - it('creates active golem with correct initial state', () => { + it('creates RuntimeActiveGolem with correct initial state', () => { const design = makeDesign('basic', 'earth', 'simple'); - const active = createActiveGolem(design, 5); + const active = createActiveGolem(design, 5) as RuntimeActiveGolem; expect(active.designId).toBe(design.id); expect(active.summonedFloor).toBe(5); @@ -288,7 +289,7 @@ describe('createActiveGolem', () => { it('guardian golem starts with full mana', () => { const design = makeDesign('guardian', 'crystalSteelHybrid', 'guardian'); - const active = createActiveGolem(design, 10); + const active = createActiveGolem(design, 10) as RuntimeActiveGolem; expect(active.currentMana).toBe(500); expect(active.roomsRemaining).toBe(8); diff --git a/src/lib/game/data/golems/index.ts b/src/lib/game/data/golems/index.ts index a902bc8..c4a9a64 100644 --- a/src/lib/game/data/golems/index.ts +++ b/src/lib/game/data/golems/index.ts @@ -16,7 +16,7 @@ export type { ComputedGolemStats, GolemManaCost, GolemUnlockRequirement, - ActiveGolemV2, + } from './types'; export { elemCost, rawCost } from './types'; diff --git a/src/lib/game/data/golems/types.ts b/src/lib/game/data/golems/types.ts index 41b684d..64d9e24 100644 --- a/src/lib/game/data/golems/types.ts +++ b/src/lib/game/data/golems/types.ts @@ -135,18 +135,3 @@ export interface ComputedGolemStats { specialEffect: FrameSpecial; } -// ─── Runtime Active Golem (in combat) ─────────────────────────────────── - -export interface ActiveGolemV2 { - /** Reference to the GolemDesign used */ - designId: string; - design: GolemDesign; - summonedFloor: number; - attackProgress: number; - roomsRemaining: number; - currentMana: number; - /** Index for alternating/cycling spells */ - spellCastIndex: number; -} - - diff --git a/src/lib/game/data/golems/utils.ts b/src/lib/game/data/golems/utils.ts index 878efb3..27f9fdc 100644 --- a/src/lib/game/data/golems/utils.ts +++ b/src/lib/game/data/golems/utils.ts @@ -1,12 +1,12 @@ // ─── Golem Helper Functions ────────────────────────────────────────────── // Component-based construction system utilities. +import type { RuntimeActiveGolem } from '../../types'; import type { ComputedGolemStats, GolemDesign, GolemManaCost, GolemUnlockRequirement, - ActiveGolemV2, } from './types'; import { CORES } from './cores'; import { FRAMES } from './frames'; @@ -144,22 +144,21 @@ export function canAffordGolemDesign( return { canAfford: true, missing: '' }; } -// ─── Active Golem V2 Helpers ────────────────────────────────────────────── +// ─── Active Golem Helpers ───────────────────────────────────────────────── /** - * Create a new ActiveGolemV2 from a GolemDesign for combat. + * Create a new RuntimeActiveGolem from a GolemDesign for combat. */ export function createActiveGolem( design: GolemDesign, currentFloor: number, -): ActiveGolemV2 { +): RuntimeActiveGolem { return { designId: design.id, - design, summonedFloor: currentFloor, attackProgress: 0, roomsRemaining: design.core.maxRoomDuration, - currentMana: design.core.manaCapacity, // Starts full + currentMana: design.core.manaCapacity, spellCastIndex: 0, }; } @@ -181,39 +180,3 @@ export function getMindCircuit(id: string) { return MIND_CIRCUITS[id] || null; } -// ─── Legacy Compatibility ──────────────────────────────────────────────── - -/** - * @deprecated Use getGolemSlots instead - */ -export function getGolemFloorDuration(_skills: Record): number { - return 3; // Default room duration for legacy calls -} - -/** - * @deprecated Use computeGolemStats instead - */ -export function getGolemDamage( - golemId: string, - _skills: Record, -): number { - // Legacy lookup — returns 0 for component-based golems - return 0; -} - -/** - * @deprecated Use computeGolemStats instead - */ -export function getGolemAttackSpeed( - golemId: string, - _skills: Record, -): number { - return 0; -} - -/** - * @deprecated Component-based system doesn't use skill-based maintenance multiplier - */ -export function getGolemMaintenanceMultiplier(_skills: Record): number { - return 1; -} diff --git a/src/lib/game/stores/gameStore.ts b/src/lib/game/stores/gameStore.ts index 95e8b2e..c1efff3 100644 --- a/src/lib/game/stores/gameStore.ts +++ b/src/lib/game/stores/gameStore.ts @@ -190,6 +190,13 @@ export const useGameStore = create()( if (!elements[elem].unlocked) elements[elem] = { ...elements[elem], unlocked: true }; elements[elem] = { ...elements[elem], current: Math.min(elements[elem].max, elements[elem].current + entry.finalRate * HOURS_PER_TICK) }; } + // Compute per-element net regen: produced rate - drain from being used as component + const elementRegen: Record = {}; + for (const [elem, entry] of Object.entries(conversionResult.rates)) { + const produced = entry.finalRate; + const drained = conversionResult.elementDrain[elem] || 0; + elementRegen[elem] = produced - drained; + } // Net raw regen = gross regen - conversion drains - incursion const netRawRegen = Math.max(0, baseRegen * (1 - incursionStrength) * meditationMultiplier - conversionResult.totalRawDrain); const actualRegen = Math.floor(Math.min(netRawRegen * HOURS_PER_TICK, maxMana - rawMana) * 1000) / 1000; @@ -305,7 +312,7 @@ export const useGameStore = create()( // Phase 3: Write writes.game = { day, hour, incursionStrength }; - writes.mana = { rawMana, meditateTicks, totalManaGathered, elements }; + writes.mana = { rawMana, meditateTicks, totalManaGathered, elements, elementRegen }; applyTickWrites(writes, storeSetters); } catch (error: unknown) { diff --git a/src/lib/game/stores/manaStore.ts b/src/lib/game/stores/manaStore.ts index 16860ea..883026d 100755 --- a/src/lib/game/stores/manaStore.ts +++ b/src/lib/game/stores/manaStore.ts @@ -19,6 +19,8 @@ export interface ManaState { meditateTicks: number; totalManaGathered: number; elements: Record; + /** Per-element net regen rates (from unified conversion system) */ + elementRegen: Record; } // ─── Mana Actions ──────────────────────────────────────────────────────────── @@ -40,6 +42,7 @@ export interface ManaActions { spendElementMana: (element: string, amount: number) => Result; setElementMax: (max: number) => void; computeElementMaxWithBonuses: (perElementBonuses: Record) => void; + setElementRegen: (regen: Record) => void; // Reset resetMana: (prestigeUpgrades: Record) => void; @@ -66,6 +69,7 @@ export const useManaStore = create()( } ]) ) as Record, + elementRegen: {}, setRawMana: (amount: number) => { set({ rawMana: Math.max(0, amount) }); @@ -148,17 +152,21 @@ export const useManaStore = create()( }); }, + setElementRegen: (regen: Record) => { + set({ elementRegen: regen }); + }, + resetMana: (prestigeUpgrades: Record) => { const elementMax = 10 + (prestigeUpgrades.elementalAttune || 0) * 25; const startingMana = 10 + (prestigeUpgrades.manaStart || 0) * 10; - set({ rawMana: startingMana, meditateTicks: 0, totalManaGathered: 0, elements: makeInitialElements(elementMax, prestigeUpgrades) }); + set({ rawMana: startingMana, meditateTicks: 0, totalManaGathered: 0, elements: makeInitialElements(elementMax, prestigeUpgrades), elementRegen: {} }); }, }), { storage: createSafeStorage(), name: 'mana-loop-mana', version: 2, - partialize: (state) => ({ rawMana: state.rawMana, meditateTicks: state.meditateTicks, totalManaGathered: state.totalManaGathered, elements: state.elements }), + partialize: (state) => ({ rawMana: state.rawMana, meditateTicks: state.meditateTicks, totalManaGathered: state.totalManaGathered, elements: state.elements, elementRegen: state.elementRegen }), migrate: (persistedState: any, _version) => { if (persistedState && persistedState.elements) { for (const k of Object.keys(persistedState.elements)) {