From 33be133813471cd4350ac574d16158eeddb17ffb Mon Sep 17 00:00:00 2001 From: n8n-gitea Date: Wed, 10 Jun 2026 20:49:46 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20resolve=203=20critical=20bugs=20?= =?UTF-8?q?=E2=80=94=20#354=20attunement=20ReferenceError,=20#353=20prepar?= =?UTF-8?q?ation=20mana=20exploit,=20#352=20golem=20mana=20wipe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #354: unlockAttunement now uses _get() instead of undefined 'state' variable - #353: startPreparing now deducts raw mana from the mana store after validation - #352: processGolemAttacks/processBasicAttack accept current mana as params instead of initializing to 0/{} - Updated golem-combat-actions.test.ts to pass new currentRawMana/currentElements params - Added regression tests for all 3 bugs (16 new tests, all passing) --- docs/circular-deps.txt | 2 +- docs/dependency-graph.json | 2 +- docs/project-structure.txt | 3 + .../__tests__/bug-352-golem-mana-wipe.test.ts | 263 ++++++++++++++++++ .../bug-353-preparation-mana.test.ts | 129 +++++++++ .../bug-354-unlock-attunement.test.ts | 57 ++++ .../crafting-actions/preparation-actions.ts | 3 + src/lib/game/stores/attunementStore.ts | 3 +- src/lib/game/stores/combat-actions.ts | 2 + .../game/stores/golem-combat-actions.test.ts | 22 +- src/lib/game/stores/golem-combat-actions.ts | 8 +- src/lib/game/stores/golem-combat-helpers.ts | 10 +- 12 files changed, 490 insertions(+), 14 deletions(-) create mode 100644 src/lib/game/__tests__/bug-352-golem-mana-wipe.test.ts create mode 100644 src/lib/game/__tests__/bug-353-preparation-mana.test.ts create mode 100644 src/lib/game/__tests__/bug-354-unlock-attunement.test.ts diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index 35889bb..158d04d 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,5 +1,5 @@ # Circular Dependencies -Generated: 2026-06-10T11:09:41.397Z +Generated: 2026-06-10T11:13:04.646Z Found: 3 circular chain(s) — these MUST be fixed before modifying involved files. 1. 1) stores/golem-combat-actions.ts > stores/golem-combat-helpers.ts diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index 079375f..e51140d 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-06-10T11:09:38.970Z", + "generated": "2026-06-10T11:13:02.589Z", "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." }, diff --git a/docs/project-structure.txt b/docs/project-structure.txt index e3a8abd..61a7f51 100644 --- a/docs/project-structure.txt +++ b/docs/project-structure.txt @@ -198,6 +198,9 @@ Mana-Loop/ │ │ │ │ ├── achievements.test.ts │ │ │ │ ├── activity-log.test.ts │ │ │ │ ├── attunement-conversion-fix.test.ts +│ │ │ │ ├── bug-352-golem-mana-wipe.test.ts +│ │ │ │ ├── bug-353-preparation-mana.test.ts +│ │ │ │ ├── bug-354-unlock-attunement.test.ts │ │ │ │ ├── bug-fixes.test.ts │ │ │ │ ├── combat-actions.test.ts │ │ │ │ ├── combat-utils.test.ts diff --git a/src/lib/game/__tests__/bug-352-golem-mana-wipe.test.ts b/src/lib/game/__tests__/bug-352-golem-mana-wipe.test.ts new file mode 100644 index 0000000..bab3527 --- /dev/null +++ b/src/lib/game/__tests__/bug-352-golem-mana-wipe.test.ts @@ -0,0 +1,263 @@ +// ─── Regression Test: Issue #352 ────────────────────────────────────────────── +// Golem combat wipes all mana when no enemies are alive +// +// Bug: processGolemAttacks initialized rawMana=0 and elements={} as local +// accumulators. When no enemies were alive, processBasicAttack never called +// onDamageDealt, so these zero/empty values were returned. The caller in +// combat-actions.ts then overwrote the game's actual mana state with zeros. +// +// Fix: processGolemAttacks and processBasicAttack now accept currentRawMana +// and currentElements as parameters and initialize from them instead of 0/{}. + +import { describe, it, expect } from 'vitest'; +import { processGolemAttacks } from '../stores/golem-combat-actions'; +import { processBasicAttack } from '../stores/golem-combat-helpers'; +import type { EnemyState, RuntimeActiveGolem } from '../types'; + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +function makeEnemy(overrides: Partial = {}): EnemyState { + return { + id: 'enemy-1', + name: 'Test Enemy', + hp: 0, // Dead enemy — this triggers the bug + maxHP: 100, + armor: 0.2, + dodgeChance: 0, + element: 'fire', + activeEffects: [], + effectiveArmor: 0.2, + ...overrides, + }; +} + +function makeAliveEnemy(overrides: Partial = {}): EnemyState { + return { + id: 'enemy-1', + name: 'Test Enemy', + hp: 100, + maxHP: 100, + armor: 0.2, + dodgeChance: 0, + element: 'fire', + activeEffects: [], + effectiveArmor: 0.2, + ...overrides, + }; +} + +// ─── Tests ──────────────────────────────────────────────────────────────────── + +describe('Issue #352 — Golem combat mana wipe', () => { + const testRawMana = 500; + const testElements: Record = { + fire: { current: 200, max: 500, unlocked: true }, + water: { current: 100, max: 500, unlocked: true }, + }; + + describe('processBasicAttack preserves mana when no targets', () => { + it('should return current rawMana (not 0) when no enemies are alive', () => { + const result = processBasicAttack( + { + frame: { baseDamage: 10, armorPierce: 0.15, element: 'fire', aoeTargets: 1 }, + bonusArmorPierce: 0, + enchantmentEffects: [], + enemyElement: 'fire', + getTargetEnemy: () => null, // No living target + getTargetEnemies: () => [], + onDamageDealt: () => ({ rawMana: testRawMana, elements: testElements }), + applyDamageToRoom: () => ({ floorHP: 50, floorMaxHP: 100, roomCleared: false }), + onApplyEnchantmentEffects: () => {}, + }, + testRawMana, + testElements, + ); + + // rawMana should be preserved, not wiped to 0 + expect(result.rawMana).toBe(testRawMana); + }); + + it('should return current elements (not {}) when no enemies are alive', () => { + const result = processBasicAttack( + { + frame: { baseDamage: 10, armorPierce: 0.15, element: 'fire', aoeTargets: 1 }, + bonusArmorPierce: 0, + enchantmentEffects: [], + enemyElement: 'fire', + getTargetEnemy: () => null, + getTargetEnemies: () => [], + onDamageDealt: () => ({ rawMana: testRawMana, elements: testElements }), + applyDamageToRoom: () => ({ floorHP: 50, floorMaxHP: 100, roomCleared: false }), + onApplyEnchantmentEffects: () => {}, + }, + testRawMana, + testElements, + ); + + // elements should be preserved, not wiped to {} + expect(result.elements).toEqual(testElements); + }); + + it('should return current rawMana when AoE attack has no targets', () => { + const result = processBasicAttack( + { + frame: { baseDamage: 10, armorPierce: 0.15, element: 'fire', aoeTargets: 3 }, + bonusArmorPierce: 0, + enchantmentEffects: [], + enemyElement: 'fire', + getTargetEnemy: () => null, + getTargetEnemies: () => [], // No living enemies for AoE + onDamageDealt: () => ({ rawMana: 0, elements: {} }), + applyDamageToRoom: () => ({ floorHP: 50, floorMaxHP: 100, roomCleared: false }), + onApplyEnchantmentEffects: () => {}, + }, + testRawMana, + testElements, + ); + + // When no targets for AoE, mana should be preserved + expect(result.rawMana).toBe(testRawMana); + expect(result.elements).toEqual(testElements); + }); + }); + + describe('processGolemAttacks preserves mana when no golems attack', () => { + it('should return current rawMana when golem has no attack progress', () => { + // Golem with attackProgress=0 won't enter the while loop + const activeGolem: RuntimeActiveGolem = { + designId: 'test_golem', + summonedFloor: 1, + attackProgress: 0, // Not ready to attack + roomsRemaining: 3, + currentMana: 50, + spellCastIndex: 0, + }; + + const serializedDesign = { + id: 'test_golem', + name: 'Test Golem', + coreId: 'basic', + frameId: 'earth', + mindCircuitId: 'simple', + enchantmentIds: [], + selectedManaTypes: [], + selectedSpells: [], + }; + + const result = processGolemAttacks( + [activeGolem], + { test_golem: serializedDesign }, + () => ({ rawMana: 0, elements: {} }), + () => ({ floorHP: 50, floorMaxHP: 100, roomCleared: false }), + 'fire', + () => null, + () => [], + () => {}, + testRawMana, + testElements, + ); + + // rawMana should be preserved, not wiped to 0 + expect(result.rawMana).toBe(testRawMana); + }); + + it('should return current elements when golem has no attack progress', () => { + const activeGolem: RuntimeActiveGolem = { + designId: 'test_golem', + summonedFloor: 1, + attackProgress: 0, + roomsRemaining: 3, + currentMana: 50, + spellCastIndex: 0, + }; + + const serializedDesign = { + id: 'test_golem', + name: 'Test Golem', + coreId: 'basic', + frameId: 'earth', + mindCircuitId: 'simple', + enchantmentIds: [], + selectedManaTypes: [], + selectedSpells: [], + }; + + const result = processGolemAttacks( + [activeGolem], + { test_golem: serializedDesign }, + () => ({ rawMana: 0, elements: {} }), + () => ({ floorHP: 50, floorMaxHP: 100, roomCleared: false }), + 'fire', + () => null, + () => [], + () => {}, + testRawMana, + testElements, + ); + + // elements should be preserved, not wiped to {} + expect(result.elements).toEqual(testElements); + }); + + it('should return current rawMana when no active golems', () => { + const result = processGolemAttacks( + [], // No golems + {}, + () => ({ rawMana: 0, elements: {} }), + () => ({ floorHP: 50, floorMaxHP: 100, roomCleared: false }), + 'fire', + () => null, + () => [], + () => {}, + testRawMana, + testElements, + ); + + expect(result.rawMana).toBe(testRawMana); + expect(result.elements).toEqual(testElements); + }); + }); + + describe('processGolemAttacks still works when enemies are alive', () => { + it('should process damage and update mana when enemies are alive', () => { + const aliveEnemy = makeAliveEnemy(); + const activeGolem: RuntimeActiveGolem = { + designId: 'test_golem', + summonedFloor: 1, + attackProgress: 1.0, + roomsRemaining: 3, + currentMana: 50, + spellCastIndex: 0, + }; + + const serializedDesign = { + id: 'test_golem', + name: 'Test Golem', + coreId: 'basic', + frameId: 'earth', + mindCircuitId: 'simple', + enchantmentIds: [], + selectedManaTypes: [], + selectedSpells: [], + }; + + const updatedElements = { fire: { current: 210, max: 500, unlocked: true } }; + + const result = processGolemAttacks( + [activeGolem], + { test_golem: serializedDesign }, + () => ({ rawMana: testRawMana + 5, elements: updatedElements }), + () => ({ floorHP: 50, floorMaxHP: 100, roomCleared: false }), + 'fire', + () => aliveEnemy, + () => [aliveEnemy], + () => {}, + testRawMana, + testElements, + ); + + // When enemies are alive, mana should be updated from onDamageDealt callback + expect(result.rawMana).toBe(testRawMana + 5); + }); + }); +}); diff --git a/src/lib/game/__tests__/bug-353-preparation-mana.test.ts b/src/lib/game/__tests__/bug-353-preparation-mana.test.ts new file mode 100644 index 0000000..d6e9af4 --- /dev/null +++ b/src/lib/game/__tests__/bug-353-preparation-mana.test.ts @@ -0,0 +1,129 @@ +// ─── Regression Test: Issue #353 ────────────────────────────────────────────── +// Preparation mana never deducted — free equipment preparation exploit +// +// Bug: startPreparing() validated rawMana >= costs.manaTotal but never called +// useManaStore.setState() to deduct the cost. cancelPreparation() refunds +// manaCostPaid, so canceling after preparing created mana from nothing. +// +// Fix: Added useManaStore.setState() call to deduct costs in startPreparing(). + +import { describe, it, expect, beforeEach } from 'vitest'; +import { useManaStore } from '../stores/manaStore'; +import { useCraftingStore } from '../stores/craftingStore'; +import { startPreparing, cancelPreparation } from '../crafting-actions/preparation-actions'; + +describe('Issue #353 — Preparation mana deduction', () => { + beforeEach(() => { + localStorage.removeItem('mana-loop-mana'); + localStorage.removeItem('mana-loop-crafting'); + useManaStore.getState().resetMana({}); + useCraftingStore.getState().resetCrafting?.(); + }); + + it('should deduct raw mana when starting preparation', () => { + // Set up: give the player plenty of raw mana + useManaStore.setState({ rawMana: 10000 }); + + // Create a basic equipment instance in the crafting store + const testInstanceId = 'test-staff-1'; + const testEquipment = { + instanceId: testInstanceId, + typeId: 'basicStaff', + name: 'Test Staff', + enchantments: [], + usedCapacity: 0, + totalCapacity: 50, + rarity: 'common' as const, + quality: 1, + tags: [], + }; + + useCraftingStore.setState((s) => ({ + equipmentInstances: { ...s.equipmentInstances, [testInstanceId]: testEquipment }, + })); + + const rawManaBefore = useManaStore.getState().rawMana; + const get = () => useCraftingStore.getState(); + const set = (partial: any) => useCraftingStore.setState(partial); + + const result = startPreparing(testInstanceId, rawManaBefore, get, set); + + expect(result).toBe(true); + // Mana should have been deducted + const rawManaAfter = useManaStore.getState().rawMana; + expect(rawManaAfter).toBeLessThan(rawManaBefore); + }); + + it('should not deduct mana when starting preparation fails (insufficient mana)', () => { + // Set up: give the player zero raw mana + useManaStore.setState({ rawMana: 0 }); + + const testInstanceId = 'test-staff-2'; + const testEquipment = { + instanceId: testInstanceId, + typeId: 'basicStaff', + name: 'Test Staff', + enchantments: [], + usedCapacity: 0, + totalCapacity: 50, + rarity: 'common' as const, + quality: 1, + tags: [], + }; + + useCraftingStore.setState((s) => ({ + equipmentInstances: { ...s.equipmentInstances, [testInstanceId]: testEquipment }, + })); + + const rawManaBefore = useManaStore.getState().rawMana; + const get = () => useCraftingStore.getState(); + const set = (partial: any) => useCraftingStore.setState(partial); + + const result = startPreparing(testInstanceId, rawManaBefore, get, set); + + expect(result).toBe(false); + // Mana should NOT have changed + expect(useManaStore.getState().rawMana).toBe(rawManaBefore); + }); + + it('cancelPreparation should not create mana (no exploit)', () => { + // Set up: give the player exactly 5000 raw mana + const initialMana = 5000; + useManaStore.setState({ rawMana: initialMana }); + + const testInstanceId = 'test-staff-3'; + const testEquipment = { + instanceId: testInstanceId, + typeId: 'basicStaff', + name: 'Test Staff', + enchantments: [], + usedCapacity: 0, + totalCapacity: 50, + rarity: 'common' as const, + quality: 1, + tags: [], + }; + + useCraftingStore.setState((s) => ({ + equipmentInstances: { ...s.equipmentInstances, [testInstanceId]: testEquipment }, + })); + + const get = () => useCraftingStore.getState(); + const set = (partial: any) => useCraftingStore.setState(partial); + + // Start preparation (deducts mana) + const result = startPreparing(testInstanceId, initialMana, get, set); + expect(result).toBe(true); + + const manaAfterPrepare = useManaStore.getState().rawMana; + expect(manaAfterPrepare).toBeLessThan(initialMana); + + // Cancel preparation (refunds proportionally) + cancelPreparation(get, set); + + const manaAfterCancel = useManaStore.getState().rawMana; + // After cancel, mana should be LESS than or equal to initial (not more!) + // The refund is partial (50% for spent progress), so player loses some mana + expect(manaAfterCancel).toBeLessThanOrEqual(initialMana); + }); +}); diff --git a/src/lib/game/__tests__/bug-354-unlock-attunement.test.ts b/src/lib/game/__tests__/bug-354-unlock-attunement.test.ts new file mode 100644 index 0000000..4243a02 --- /dev/null +++ b/src/lib/game/__tests__/bug-354-unlock-attunement.test.ts @@ -0,0 +1,57 @@ +// ─── Regression Test: Issue #354 ────────────────────────────────────────────── +// unlockAttunement crashes with ReferenceError — 'state' is undefined +// +// Bug: unlockAttunement referenced `state.attunements[attunementId]?.active` +// but `state` was not in scope. The function signature is +// `unlockAttunement(attunementId, defeatedGuardians)` — no `state` parameter. +// +// Fix: Use `_get()` to read current state instead of referencing undefined `state`. + +import { describe, it, expect, beforeEach } from 'vitest'; +import { useAttunementStore } from '../stores/attunementStore'; + +describe('Issue #354 — unlockAttunement ReferenceError', () => { + beforeEach(() => { + // Reset to initial state: only enchanter active + useAttunementStore.getState().resetAttunements(); + // Clear localStorage to avoid persisted state leaking between tests + localStorage.removeItem('mana-loop-attunements'); + }); + + it('should not throw ReferenceError when checking if attunement is already active', () => { + // This was the crash: calling unlockAttunement on an already-active attunement + // would throw "ReferenceError: state is not defined" + expect(() => { + useAttunementStore.getState().unlockAttunement('enchanter', []); + }).not.toThrow(); + }); + + it('should return false when attunement is already active', () => { + const result = useAttunementStore.getState().unlockAttunement('enchanter', []); + expect(result).toBe(false); + }); + + it('should return false for unknown attunement', () => { + const result = useAttunementStore.getState().unlockAttunement('unknown', []); + expect(result).toBe(false); + }); + + it('should return false for invoker when floor 10 guardian not defeated', () => { + const result = useAttunementStore.getState().unlockAttunement('invoker', []); + expect(result).toBe(false); + }); + + it('should unlock invoker when floor 10 guardian is defeated', () => { + const result = useAttunementStore.getState().unlockAttunement('invoker', [10]); + expect(result).toBe(true); + + const state = useAttunementStore.getState(); + expect(state.attunements['invoker']?.active).toBe(true); + expect(state.attunements['invoker']?.level).toBe(1); + }); + + it('should return false for fabricator (gating not implemented)', () => { + const result = useAttunementStore.getState().unlockAttunement('fabricator', [10, 20, 30]); + expect(result).toBe(false); + }); +}); diff --git a/src/lib/game/crafting-actions/preparation-actions.ts b/src/lib/game/crafting-actions/preparation-actions.ts index 6b28b16..c0ebce4 100644 --- a/src/lib/game/crafting-actions/preparation-actions.ts +++ b/src/lib/game/crafting-actions/preparation-actions.ts @@ -26,6 +26,9 @@ export function startPreparing( if (rawMana < costs.manaTotal) return false; + // Deduct the preparation mana cost from the player's raw mana pool + useManaStore.setState((s) => ({ rawMana: s.rawMana - costs.manaTotal })); + set({ preparationProgress: CraftingPrep.initializePreparationProgress( equipmentInstanceId, diff --git a/src/lib/game/stores/attunementStore.ts b/src/lib/game/stores/attunementStore.ts index 84bec9e..3692082 100644 --- a/src/lib/game/stores/attunementStore.ts +++ b/src/lib/game/stores/attunementStore.ts @@ -80,7 +80,8 @@ export const useAttunementStore = create()( unlockAttunement: (attunementId: string, defeatedGuardians: number[]) => { const def = ATTUNEMENTS_DEF[attunementId]; if (!def) return false; - if (state.attunements[attunementId]?.active) return false; + const currentState = _get(); + if (currentState.attunements[attunementId]?.active) return false; // Check unlock conditions if (attunementId === 'invoker') { diff --git a/src/lib/game/stores/combat-actions.ts b/src/lib/game/stores/combat-actions.ts index 3bc677f..03ee568 100644 --- a/src/lib/game/stores/combat-actions.ts +++ b/src/lib/game/stores/combat-actions.ts @@ -323,6 +323,8 @@ export function processCombatTick( getFloorElement(currentFloor), get, set, + rawMana, + elements, ); rawMana = golemResult.rawMana; elements = golemResult.elements; diff --git a/src/lib/game/stores/golem-combat-actions.test.ts b/src/lib/game/stores/golem-combat-actions.test.ts index af5bb90..f7fd570 100644 --- a/src/lib/game/stores/golem-combat-actions.test.ts +++ b/src/lib/game/stores/golem-combat-actions.test.ts @@ -96,6 +96,8 @@ function runGolemAttacks( () => enemy, () => [enemy], () => {}, + 100, + {}, ); return { result, capturedDamage }; } @@ -136,7 +138,9 @@ describe('processGolemAttacks - spell damage and mana cost (fixes #1, #2)', () = () => enemy, () => [enemy], () => {}, - ); + 100, + {}, + ); expect(spellCastCount).toBeGreaterThan(0); }); @@ -160,7 +164,9 @@ describe('processGolemAttacks - spell damage and mana cost (fixes #1, #2)', () = () => enemy, () => [enemy], () => {}, - ); + 100, + {}, + ); // Should fall back to basic attack since mana is insufficient expect(damageCount).toBeGreaterThan(0); @@ -245,7 +251,9 @@ describe('processGolemAttacks - enchantment effects (fix #4)', () => { () => enemy, () => [enemy], (enemyId, effects) => { appliedEffects.push({ enemyId, effects }); }, - ); + 100, + {}, + ); expect(appliedEffects.length).toBeGreaterThan(0); expect(appliedEffects[0].effects.some(e => e.type === 'burn')).toBe(true); @@ -267,7 +275,9 @@ describe('processGolemAttacks - enchantment effects (fix #4)', () => { () => enemy, () => [enemy], (_, effects) => { appliedEffects.push(effects); }, - ); + 100, + {}, + ); const allEffectTypes = appliedEffects.flat().map(e => e.type); expect(allEffectTypes).toContain('burn'); @@ -291,7 +301,9 @@ describe('processGolemAttacks - enchantment effects (fix #4)', () => { () => enemy, () => [enemy], () => { effectsCalled = true; }, - ); + 100, + {}, + ); expect(effectsCalled).toBe(false); }); diff --git a/src/lib/game/stores/golem-combat-actions.ts b/src/lib/game/stores/golem-combat-actions.ts index 514c326..1e5ce57 100644 --- a/src/lib/game/stores/golem-combat-actions.ts +++ b/src/lib/game/stores/golem-combat-actions.ts @@ -248,9 +248,11 @@ export function processGolemAttacks( getTargetEnemy: () => EnemyState | null, getTargetEnemies: () => EnemyState[], onApplyEnchantmentEffects: (enemyId: string, effects: GolemEnchantmentEffect[]) => void, + currentRawMana: number, + currentElements: Record, ): GolemCombatResult { - let rawMana = 0; - let elements: Record = {}; + let rawMana = currentRawMana; + let elements: Record = { ...currentElements }; let floorHP = 0; let floorMaxHP = 0; const logMessages: string[] = []; @@ -321,7 +323,7 @@ export function processGolemAttacks( onDamageDealt, applyDamageToRoom, onApplyEnchantmentEffects, - }); + }, rawMana, elements); rawMana = basicResult.rawMana; elements = basicResult.elements; floorHP = basicResult.floorHP; diff --git a/src/lib/game/stores/golem-combat-helpers.ts b/src/lib/game/stores/golem-combat-helpers.ts index aee0f5e..b7e373a 100644 --- a/src/lib/game/stores/golem-combat-helpers.ts +++ b/src/lib/game/stores/golem-combat-helpers.ts @@ -95,9 +95,9 @@ export interface BasicAttackResult { * AoE frames distribute damage across up to frame.aoeTargets enemies (spec §11). * Single-target frames attack the lowest-HP enemy. */ -export function processBasicAttack(ctx: BasicAttackContext): BasicAttackResult { - let rawMana = 0; - let elements: Record = {}; +export function processBasicAttack(ctx: BasicAttackContext, currentRawMana: number, currentElements: Record): BasicAttackResult { + let rawMana = currentRawMana; + let elements: Record = { ...currentElements }; let floorHP = 0; let floorMaxHP = 0; let totalDamageDealt = 0; @@ -174,6 +174,8 @@ export function processGolemAttacksFromStore( enemyElement: string, get: () => CombatStore, set: (s: Partial) => void, + currentRawMana: number, + currentElements: Record, ): GolemCombatResult { return processGolemAttacks( activeGolems, @@ -212,5 +214,7 @@ export function processGolemAttacksFromStore( }); set({ currentRoom: { ...room, enemies: updatedEnemies } }); }, + currentRawMana, + currentElements, ); }