diff --git a/docs/project-structure.txt b/docs/project-structure.txt index 7495532..2c838be 100644 --- a/docs/project-structure.txt +++ b/docs/project-structure.txt @@ -386,6 +386,7 @@ Mana-Loop/ │ │ │ │ ├── store-methods.test.ts │ │ │ │ └── stores.test.ts │ │ │ ├── attunementStore.ts +│ │ │ ├── combat-actions.ts │ │ │ ├── combatStore.ts │ │ │ ├── craftingStore.ts │ │ │ ├── gameActions.ts diff --git a/src/components/game/crafting/EquipmentCrafter.tsx b/src/components/game/crafting/EquipmentCrafter.tsx index ab6f154..0d5eaad 100644 --- a/src/components/game/crafting/EquipmentCrafter.tsx +++ b/src/components/game/crafting/EquipmentCrafter.tsx @@ -11,15 +11,15 @@ import { CRAFTING_RECIPES, canCraftRecipe } from '@/lib/game/data/crafting-recip import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops'; import type { EquipmentInstance, AppliedEnchantment, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types'; import { fmt } from '@/lib/game/stores'; -import { useGameStore, useCraftingStore } from '@/lib/game/stores'; +import { useCraftingStore, useCombatStore, useManaStore } from '@/lib/game/stores'; export function EquipmentCrafter() { const lootInventory = useCraftingStore((s) => s.lootInventory); - const equipmentCraftingProgress = useGameStore((s) => s.equipmentCraftingProgress); - const rawMana = useGameStore((s) => s.rawMana); - const currentAction = useGameStore((s) => s.currentAction); - const startCraftingEquipment = useGameStore((s) => s.startCraftingEquipment); - const cancelEquipmentCrafting = useGameStore((s) => s.cancelEquipmentCrafting); + const equipmentCraftingProgress = useCraftingStore((s) => s.equipmentCraftingProgress); + const rawMana = useManaStore((s) => s.rawMana); + const currentAction = useCombatStore((s) => s.currentAction); + const startCraftingEquipment = useCraftingStore((s) => s.startCraftingEquipment); + const cancelEquipmentCrafting = useCraftingStore((s) => s.cancelEquipmentCrafting); const deleteMaterial = useCraftingStore((s) => s.deleteMaterial); return ( diff --git a/src/components/game/tabs/AchievementsTab.tsx b/src/components/game/tabs/AchievementsTab.tsx index 39ca513..4e3c1a8 100755 --- a/src/components/game/tabs/AchievementsTab.tsx +++ b/src/components/game/tabs/AchievementsTab.tsx @@ -1,16 +1,16 @@ 'use client'; import { AchievementsDisplay } from '@/components/game/AchievementsDisplay'; -import { useGameStore } from '@/lib/game/stores'; +import { useCombatStore, useManaStore, usePrestigeStore } from '@/lib/game/stores'; export function AchievementsTab() { - const achievements = useGameStore((s) => s.achievements); - const maxFloorReached = useGameStore((s) => s.maxFloorReached); - const totalManaGathered = useGameStore((s) => s.totalManaGathered); - const signedPacts = useGameStore((s) => s.signedPacts); - const totalSpellsCast = useGameStore((s) => s.totalSpellsCast); - const totalDamageDealt = useGameStore((s) => s.totalDamageDealt); - const totalCraftsCompleted = useGameStore((s) => s.totalCraftsCompleted); + const achievements = useCombatStore((s) => s.achievements); + const maxFloorReached = useCombatStore((s) => s.maxFloorReached); + const totalManaGathered = useManaStore((s) => s.totalManaGathered); + const signedPacts = usePrestigeStore((s) => s.signedPacts); + const totalSpellsCast = useCombatStore((s) => s.totalSpellsCast); + const totalDamageDealt = useCombatStore((s) => s.totalDamageDealt); + const totalCraftsCompleted = useCombatStore((s) => s.totalCraftsCompleted); return (
diff --git a/src/components/game/tabs/CraftingTab.tsx b/src/components/game/tabs/CraftingTab.tsx index eeade5a..789aaac 100755 --- a/src/components/game/tabs/CraftingTab.tsx +++ b/src/components/game/tabs/CraftingTab.tsx @@ -13,18 +13,18 @@ import { EnchantmentApplier, EquipmentCrafter, } from '@/components/game/crafting'; -import { useGameStore } from '@/lib/game/stores'; +import { useCombatStore, useCraftingStore } from '@/lib/game/stores'; import { useGameToast } from '@/components/game/GameToast'; export function CraftingTab() { const showToast = useGameToast(); - const currentAction = useGameStore((s) => s.currentAction); - const designProgress = useGameStore((s) => s.designProgress); - const preparationProgress = useGameStore((s) => s.preparationProgress); - const applicationProgress = useGameStore((s) => s.applicationProgress); - const equipmentCraftingProgress = useGameStore((s) => s.equipmentCraftingProgress); - const pauseApplication = useGameStore((s) => s.pauseApplication); - const resumeApplication = useGameStore((s) => s.resumeApplication); + const currentAction = useCombatStore((s) => s.currentAction); + const designProgress = useCraftingStore((s) => s.designProgress); + const preparationProgress = useCraftingStore((s) => s.preparationProgress); + const applicationProgress = useCraftingStore((s) => s.applicationProgress); + const equipmentCraftingProgress = useCraftingStore((s) => s.equipmentCraftingProgress); + const pauseApplication = useCraftingStore((s) => s.pauseApplication); + const resumeApplication = useCraftingStore((s) => s.resumeApplication); const [activeTab, setActiveTab] = useState<'fabricate' | 'enchant'>('fabricate'); const [enchantStage, setEnchantStage] = useState<'design' | 'prepare' | 'apply'>('design'); @@ -176,7 +176,7 @@ export function CraftingTab() { useGameStore.getState().cancelDesign()}> + useCraftingStore.getState().cancelDesign()}> Cancel } @@ -198,7 +198,7 @@ export function CraftingTab() { useGameStore.getState().cancelPreparation()}> + useCraftingStore.getState().cancelPreparation()}> Cancel } @@ -230,7 +230,7 @@ export function CraftingTab() { <> Pause { - useGameStore.getState().cancelApplication(); + useCraftingStore.getState().cancelApplication(); showToast('warning', 'Enchantment Cancelled', 'The enchantment application was cancelled.'); }}>Cancel diff --git a/src/components/game/tabs/GolemancyTab.tsx b/src/components/game/tabs/GolemancyTab.tsx index dcb3c0e..aec5262 100755 --- a/src/components/game/tabs/GolemancyTab.tsx +++ b/src/components/game/tabs/GolemancyTab.tsx @@ -9,16 +9,16 @@ import { } from 'lucide-react'; import { GOLEMS_DEF, getGolemSlots, isGolemUnlocked, getGolemDamage, getGolemAttackSpeed, getGolemFloorDuration } from '@/lib/game/data/golems'; import { ELEMENTS } from '@/lib/game/constants'; -import { useGameStore, useManaStore, useSkillStore, useCombatStore, useAttunementStore } from '@/lib/game/stores'; +import { useManaStore, useSkillStore, useCombatStore, useAttunementStore } from '@/lib/game/stores'; export function GolemancyTab() { const attunements = useAttunementStore((s) => s.attunements); const elements = useManaStore((s) => s.elements); const skills = useSkillStore((s) => s.skills); - const golemancy = useGameStore((s) => s.golemancy); + const golemancy = useCombatStore((s) => s.golemancy); const currentFloor = useCombatStore((s) => s.currentFloor); - const currentRoom = useGameStore((s) => s.currentRoom); - const toggleGolem = useGameStore((s) => s.toggleGolem); + const currentRoom = useCombatStore((s) => s.currentRoom); + const toggleGolem = useCombatStore((s) => s.toggleGolem); const rawMana = useManaStore((s) => s.rawMana); // Get Fabricator level and golem slots diff --git a/src/components/game/tabs/SkillsTab.tsx b/src/components/game/tabs/SkillsTab.tsx index 8a91a6f..3aa9723 100755 --- a/src/components/game/tabs/SkillsTab.tsx +++ b/src/components/game/tabs/SkillsTab.tsx @@ -39,7 +39,7 @@ import { ChevronDown, ChevronRight } from 'lucide-react'; import { SkillRow } from './SkillRow'; import { useSkillUpgradeSelection } from '@/lib/game/hooks/useSkillUpgradeSelection'; import { CategorySkillsList } from './CategorySkillsList'; -import { useGameStore, useSkillStore, usePrestigeStore } from '@/lib/game/stores'; +import { useSkillStore, usePrestigeStore } from '@/lib/game/stores'; export function SkillsTab() { const showToast = useGameToast(); @@ -55,11 +55,11 @@ export function SkillsTab() { const skillUpgrades = useSkillStore((s) => s.skillUpgrades); const skillTiers = useSkillStore((s) => s.skillTiers); const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades); - const currentStudyTarget = useGameStore((s) => s.currentStudyTarget); - const parallelStudyTarget = useGameStore((s) => s.parallelStudyTarget); + const currentStudyTarget = useSkillStore((s) => s.currentStudyTarget); + const parallelStudyTarget = useSkillStore((s) => s.parallelStudyTarget); const startStudyingSkill = useSkillStore((s) => s.startStudyingSkill); const startParallelStudySkill = useSkillStore((s) => s.startParallelStudySkill); - const cancelStudy = useGameStore((s) => s.cancelStudy); + const cancelStudy = useSkillStore((s) => s.cancelStudy); const commitSkillUpgrades = useSkillStore((s) => s.commitSkillUpgrades); const tierUpSkill = useSkillStore((s) => s.tierUpSkill); diff --git a/src/components/game/tabs/SpireTab.tsx b/src/components/game/tabs/SpireTab.tsx index 2f0161c..2048aed 100755 --- a/src/components/game/tabs/SpireTab.tsx +++ b/src/components/game/tabs/SpireTab.tsx @@ -12,7 +12,7 @@ import { getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/computed-stats import { getUnifiedEffects } from '@/lib/game/effects'; import { formatSpellCost, getSpellCostColor } from '@/lib/game/formatting'; import { canAffordSpellCost, getFloorElement } from '@/lib/game/stores'; -import { useGameStore, useManaStore, useSkillStore, useCombatStore, usePrestigeStore, useCraftingStore } from '@/lib/game/stores'; +import { useManaStore, useSkillStore, useCombatStore, usePrestigeStore, useCraftingStore } from '@/lib/game/stores'; import { GOLEMS_DEF, getGolemDamage, getGolemAttackSpeed } from '@/lib/game/data/golems'; // Extracted components @@ -52,16 +52,16 @@ export function SpireTab({ simpleMode = false }: SpireTabProps) { const activeSpell = useCombatStore((s) => s.activeSpell); const startClimbUp = useCombatStore((s) => s.startClimbUp); const startClimbDown = useCombatStore((s) => s.startClimbDown); - const enterSpireMode = useGameStore((s) => s.enterSpireMode); - const spireMode = useGameStore((s) => s.spireMode); - const climbDirection = useGameStore((s) => s.climbDirection) || 'up'; - const clearedFloors = useGameStore((s) => s.clearedFloors || {}); - const currentRoom = useGameStore((s) => s.currentRoom); - const equipmentSpellStates = useGameStore((s) => s.equipmentSpellStates); - const golemancy = useGameStore((s) => s.golemancy); - const activityLog = useGameStore((s) => s.activityLog); - const currentStudyTarget = useGameStore((s) => s.currentStudyTarget); - const parallelStudyTarget = useGameStore((s) => s.parallelStudyTarget); + const enterSpireMode = useCombatStore((s) => s.enterSpireMode); + const spireMode = useCombatStore((s) => s.spireMode); + const climbDirection = useCombatStore((s) => s.climbDirection) || 'up'; + const clearedFloors = useCombatStore((s) => s.clearedFloors || {}); + const currentRoom = useCombatStore((s) => s.currentRoom); + const equipmentSpellStates = useCombatStore((s) => s.equipmentSpellStates); + const golemancy = useCombatStore((s) => s.golemancy); + const activityLog = useCombatStore((s) => s.activityLog); + const currentStudyTarget = useSkillStore((s) => s.currentStudyTarget); + const parallelStudyTarget = useSkillStore((s) => s.parallelStudyTarget); const signedPacts = usePrestigeStore((s) => s.signedPacts); const skills = useSkillStore((s) => s.skills); const skillUpgrades = useSkillStore((s) => s.skillUpgrades); @@ -73,8 +73,8 @@ export function SpireTab({ simpleMode = false }: SpireTabProps) { const designProgress = useCraftingStore((s) => s.designProgress); const designProgress2 = useCraftingStore((s) => s.designProgress2); const preparationProgress = useCraftingStore((s) => s.preparationProgress); - const applicationProgress = useGameStore((s) => s.applicationProgress); - const equipmentCraftingProgress = useGameStore((s) => s.equipmentCraftingProgress); + const applicationProgress = useCraftingStore((s) => s.applicationProgress); + const equipmentCraftingProgress = useCraftingStore((s) => s.equipmentCraftingProgress); // Derived data const floorElem = getFloorElement(currentFloor); diff --git a/src/lib/game/stores/combat-actions.ts b/src/lib/game/stores/combat-actions.ts new file mode 100644 index 0000000..0fde2c8 --- /dev/null +++ b/src/lib/game/stores/combat-actions.ts @@ -0,0 +1,117 @@ +// ─── 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 + 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!`); + } + } + } + + set({ + currentFloor, + floorHP, + floorMaxHP: getFloorMaxHP(currentFloor), + maxFloorReached: Math.max(state.maxFloorReached, currentFloor), + castProgress, + }); + + 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; +} diff --git a/src/lib/game/stores/combatStore.ts b/src/lib/game/stores/combatStore.ts index 31004e9..ebb9cee 100755 --- a/src/lib/game/stores/combatStore.ts +++ b/src/lib/game/stores/combatStore.ts @@ -3,11 +3,13 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; -import { SPELLS_DEF, GUARDIANS, HOURS_PER_TICK } from '../constants'; -import type { GameAction, SpellState } from '../types'; -import { getFloorMaxHP, getFloorElement, calcDamage, canAffordSpellCost, deductSpellCost } from '../utils'; +import type { GameAction, SpellState, FloorState, GolemancyState, ActivityLogEntry, AchievementState, EquipmentSpellState, ActivityEventType } from '../types'; +import { getFloorMaxHP } from '../utils'; import { usePrestigeStore } from './prestigeStore'; import { useGameStore } from './gameStore'; +import { generateFloorState } from '../store-modules/room-utils'; +import { addActivityLogEntry } from '../store-modules/activity-log'; +import { processCombatTick, makeInitialSpells } from './combat-actions'; export interface CombatState { // Floor state @@ -24,9 +26,38 @@ export interface CombatState { // Spire mode spireMode: boolean; + // Room system for special floors + currentRoom: FloorState; + + // Spire climbing state + clearedFloors: Record; + climbDirection: 'up' | 'down' | null; + isDescending: boolean; + + // Golemancy (summoned golems) + golemancy: GolemancyState; + + // Equipment spell states for multi-casting + equipmentSpellStates: EquipmentSpellState[]; + + // Combat special effect tracking + comboHitCount: number; + floorHitCount: number; + // Spells spells: Record; + // Activity Log (for Spire Mode UI) + activityLog: ActivityLogEntry[]; + + // Achievements + achievements: AchievementState; + + // Stats tracking + totalSpellsCast: number; + totalDamageDealt: number; + totalCraftsCompleted: number; + // Actions setCurrentFloor: (floor: number) => void; advanceFloor: () => void; @@ -37,10 +68,32 @@ export interface CombatState { setSpell: (spellId: string) => void; setCastProgress: (progress: number) => void; + // Room state + setCurrentRoom: (room: FloorState) => void; + + // Spire climbing + setClimbDirection: (direction: 'up' | 'down' | null) => void; + setClearedFloor: (floor: number, cleared: boolean) => void; + setIsDescending: (descending: boolean) => void; + climbDownFloor: () => void; + exitSpireMode: () => void; + + // Golemancy + toggleGolem: (golemId: string) => void; + setEnabledGolems: (golemIds: string[]) => void; + // Spells learnSpell: (spellId: string) => void; setSpellState: (spellId: string, state: Partial) => void; + // Activity Log + addActivityLog: (eventType: ActivityEventType, message: string, details?: ActivityLogEntry['details']) => void; + + // Stats + incrementSpellsCast: () => void; + addDamageDealt: (damage: number) => void; + incrementCraftsCompleted: () => void; + // Spire mode enterSpireMode: () => void; @@ -75,10 +128,48 @@ export const useCombatStore = create()( currentAction: 'meditate', castProgress: 0, spireMode: false, + + // Room system + currentRoom: generateFloorState(1), + + // Spire climbing state + clearedFloors: {}, + climbDirection: null, + isDescending: false, + + // Golemancy + golemancy: { + enabledGolems: [], + summonedGolems: [], + lastSummonFloor: 0, + }, + + // Equipment spell states + equipmentSpellStates: [], + + // Combat tracking + comboHitCount: 0, + floorHitCount: 0, + + // Spells spells: { manaBolt: { learned: true, level: 1, studyProgress: 0 }, }, + // Activity Log + activityLog: [], + + // Achievements + achievements: { + unlocked: [], + progress: {}, + }, + + // Stats tracking + totalSpellsCast: 0, + totalDamageDealt: 0, + totalCraftsCompleted: 0, + setCurrentFloor: (floor: number) => { set({ currentFloor: floor, @@ -125,6 +216,66 @@ export const useCombatStore = create()( set({ castProgress: progress }); }, + // Room state + setCurrentRoom: (room: FloorState) => { + set({ currentRoom: room }); + }, + + // Spire climbing + setClimbDirection: (direction: 'up' | 'down' | null) => { + set({ climbDirection: direction }); + }, + + setClearedFloor: (floor: number, cleared: boolean) => { + set((state) => ({ + clearedFloors: { ...state.clearedFloors, [floor]: cleared }, + })); + }, + + setIsDescending: (descending: boolean) => { + set({ isDescending: descending }); + }, + + climbDownFloor: () => { + set((s) => { + if (s.currentFloor <= 1) return s; + const newFloor = s.currentFloor - 1; + return { + currentFloor: newFloor, + currentRoom: generateFloorState(newFloor), + floorMaxHP: getFloorMaxHP(newFloor), + floorHP: getFloorMaxHP(newFloor), + castProgress: 0, + }; + }); + }, + + exitSpireMode: () => { + set({ spireMode: false, climbDirection: null, isDescending: false }); + }, + + // Golemancy + toggleGolem: (golemId: string) => { + set((s) => { + const enabledGolems = s.golemancy?.enabledGolems || []; + const isEnabled = enabledGolems.includes(golemId); + return { + golemancy: { + ...s.golemancy, + enabledGolems: isEnabled + ? enabledGolems.filter(id => id !== golemId) + : [...enabledGolems, golemId] + }, + }; + }); + }, + + setEnabledGolems: (golemIds: string[]) => { + set((s) => ({ + golemancy: { ...s.golemancy, enabledGolems: golemIds }, + })); + }, + enterSpireMode: () => { set({ spireMode: true }); }, @@ -147,6 +298,26 @@ export const useCombatStore = create()( })); }, + // Activity Log + addActivityLog: (eventType: ActivityEventType, message: string, details?: ActivityLogEntry['details']) => { + set((state) => ({ + activityLog: addActivityLogEntry(state, eventType, message, details), + })); + }, + + // Stats + incrementSpellsCast: () => { + set((state) => ({ totalSpellsCast: state.totalSpellsCast + 1 })); + }, + + addDamageDealt: (damage: number) => { + set((state) => ({ totalDamageDealt: state.totalDamageDealt + damage })); + }, + + incrementCraftsCompleted: () => { + set((state) => ({ totalCraftsCompleted: state.totalCraftsCompleted + 1 })); + }, + processCombatTick: ( skills: Record, rawMana: number, @@ -156,84 +327,17 @@ export const useCombatStore = create()( onFloorCleared: (floor: number, wasGuardian: boolean) => void, onDamageDealt: (damage: number) => { rawMana: number; elements: Record }, ) => { - 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 - 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!`); - } - } - } - - set({ - currentFloor, - floorHP, - floorMaxHP: getFloorMaxHP(currentFloor), - maxFloorReached: Math.max(state.maxFloorReached, currentFloor), - castProgress, - }); - - return { rawMana, elements, logMessages, totalManaGathered }; + return processCombatTick( + get, + set, + skills, + rawMana, + elements, + maxMana, + attackSpeedMult, + onFloorCleared, + onDamageDealt, + ); }, resetCombat: (startFloor: number, spellsToKeep: string[] = []) => { @@ -282,16 +386,6 @@ export const useCombatStore = create()( ) ); -// 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; -} +// makeInitialSpells is now in combat-actions.ts +// Re-export for backward compatibility +export { makeInitialSpells } from './combat-actions';