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';