// ─── Combat Actions ───────────────────────────────────────────────────────────── // Extracted combat logic from combatStore.ts import { SPELLS_DEF, GUARDIANS, HOURS_PER_TICK } from '../constants'; import type { CombatState } from './combatStore'; import type { SpellState } from '../types'; import { getFloorMaxHP, getFloorElement, calcDamage, canAffordSpellCost, deductSpellCost } from '../utils'; import { usePrestigeStore } from './prestigeStore'; export function processCombatTick( get: () => CombatState, set: (state: Partial) => void, skills: Record, rawMana: number, elements: Record, maxMana: number, attackSpeedMult: number, onFloorCleared: (floor: number, wasGuardian: boolean) => void, onDamageDealt: (damage: number) => { rawMana: number; elements: Record; modifiedDamage?: number; }, ) { const state = get(); const logMessages: string[] = []; let totalManaGathered = 0; if (state.currentAction !== 'climb') { return { rawMana, elements, logMessages, totalManaGathered }; } const spellId = state.activeSpell; const spellDef = SPELLS_DEF[spellId]; if (!spellDef) { return { rawMana, elements, logMessages, totalManaGathered }; } // Calculate cast speed const baseAttackSpeed = 1 + (skills.quickCast || 0) * 0.05; const totalAttackSpeed = baseAttackSpeed * attackSpeedMult; const spellCastSpeed = spellDef.castSpeed || 1; const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed; let castProgress = (state.castProgress || 0) + progressPerTick; let floorHP = state.floorHP; let currentFloor = state.currentFloor; let floorMaxHP = state.floorMaxHP; // Process complete casts for active spell while (castProgress >= 1 && canAffordSpellCost(spellDef.cost, rawMana, elements)) { // Deduct spell cost const afterCost = deductSpellCost(spellDef.cost, rawMana, elements); rawMana = afterCost.rawMana; elements = afterCost.elements; totalManaGathered += spellDef.cost.amount; // Calculate base damage const floorElement = getFloorElement(currentFloor); const damage = calcDamage( { skills, signedPacts: usePrestigeStore.getState().signedPacts }, spellId, floorElement, ); // Let gameStore apply damage modifiers (executioner, berserker, spell echo) const result = onDamageDealt(damage); rawMana = result.rawMana; elements = result.elements; const finalDamage = result.modifiedDamage || damage; // Apply damage floorHP = Math.max(0, floorHP - finalDamage); castProgress -= 1; // Check if floor is cleared if (floorHP <= 0) { const wasGuardian = GUARDIANS[currentFloor]; onFloorCleared(currentFloor, !!wasGuardian); currentFloor = Math.min(currentFloor + 1, 100); floorMaxHP = getFloorMaxHP(currentFloor); floorHP = floorMaxHP; castProgress = 0; if (wasGuardian) { logMessages.push(`⚔️ ${wasGuardian.name} defeated!`); } else if (currentFloor % 5 === 0) { logMessages.push(`🏰 Floor ${currentFloor - 1} cleared!`); } } } // Process equipment spell states (for progress bars in UI) const updatedEquipmentSpellStates = [...state.equipmentSpellStates]; for (let i = 0; i < updatedEquipmentSpellStates.length; i++) { const eSpell = updatedEquipmentSpellStates[i]; const eSpellDef = SPELLS_DEF[eSpell.spellId]; if (!eSpellDef) continue; // Calculate progress for this equipment spell const eSpellCastSpeed = eSpellDef.castSpeed || 1; const eProgressPerTick = HOURS_PER_TICK * eSpellCastSpeed * totalAttackSpeed; let eCastProgress = (eSpell.castProgress || 0) + eProgressPerTick; // Process complete casts for equipment spells while (eCastProgress >= 1 && canAffordSpellCost(eSpellDef.cost, rawMana, elements)) { // Deduct cost const eAfterCost = deductSpellCost(eSpellDef.cost, rawMana, elements); rawMana = eAfterCost.rawMana; elements = eAfterCost.elements; totalManaGathered += eSpellDef.cost.amount; // Calculate damage const eFloorElement = getFloorElement(currentFloor); const eDamage = calcDamage( { skills, signedPacts: usePrestigeStore.getState().signedPacts }, eSpell.spellId, eFloorElement, ); const eResult = onDamageDealt(eDamage); rawMana = eResult.rawMana; elements = eResult.elements; const eFinalDamage = eResult.modifiedDamage || eDamage; floorHP = Math.max(0, floorHP - eFinalDamage); eCastProgress -= 1; if (floorHP <= 0) break; // Floor cleared, stop processing } // Update equipment spell state updatedEquipmentSpellStates[i] = { ...eSpell, castProgress: eCastProgress % 1 }; } set({ currentFloor, floorHP, floorMaxHP: getFloorMaxHP(currentFloor), maxFloorReached: Math.max(state.maxFloorReached, currentFloor), castProgress, equipmentSpellStates: updatedEquipmentSpellStates, }); return { rawMana, elements, logMessages, totalManaGathered }; } // Helper function to create initial spells export function makeInitialSpells(spellsToKeep: string[] = []): Record { const startSpells: Record = { manaBolt: { learned: true, level: 1, studyProgress: 0 }, }; // Add kept spells for (const spellId of spellsToKeep) { startSpells[spellId] = { learned: true, level: 1, studyProgress: 0 }; } return startSpells; }