321 lines
9.1 KiB
TypeScript
Executable File
321 lines
9.1 KiB
TypeScript
Executable File
// ─── Combat Store ─────────────────────────────────────────────────────────────
|
|
// Handles floors, spells, guardians, combat, and casting
|
|
|
|
import { create } from 'zustand';
|
|
import { persist } from 'zustand/middleware';
|
|
import { createSafeStorage } from '../utils/safe-persist';
|
|
import type { GameAction, SpellState, FloorState, GolemancyState, ActivityLogEntry, AchievementState, EquipmentSpellState, ActivityEventType } from '../types';
|
|
import { getFloorMaxHP } from '../utils';
|
|
import { usePrestigeStore } from './prestigeStore';
|
|
import { generateFloorState } from '../utils/room-utils';
|
|
import { addActivityLogEntry } from '../utils/activity-log';
|
|
import { processCombatTick, makeInitialSpells } from './combat-actions';
|
|
import type { CombatStore } from './combat-state.types';
|
|
|
|
export const useCombatStore = create<CombatStore>()(
|
|
persist(
|
|
(set, get) => ({
|
|
currentFloor: 1,
|
|
floorHP: getFloorMaxHP(1),
|
|
floorMaxHP: getFloorMaxHP(1),
|
|
maxFloorReached: 1,
|
|
activeSpell: 'manaBolt',
|
|
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,
|
|
floorHP: getFloorMaxHP(floor),
|
|
floorMaxHP: getFloorMaxHP(floor),
|
|
});
|
|
},
|
|
|
|
advanceFloor: () => {
|
|
set((state) => {
|
|
const newFloor = Math.min(state.currentFloor + 1, 100);
|
|
return {
|
|
currentFloor: newFloor,
|
|
floorHP: getFloorMaxHP(newFloor),
|
|
floorMaxHP: getFloorMaxHP(newFloor),
|
|
maxFloorReached: Math.max(state.maxFloorReached, newFloor),
|
|
castProgress: 0,
|
|
};
|
|
});
|
|
},
|
|
|
|
setFloorHP: (hp: number) => {
|
|
set({ floorHP: Math.max(0, hp) });
|
|
},
|
|
|
|
setMaxFloorReached: (floor: number) => {
|
|
set((state) => ({
|
|
maxFloorReached: Math.max(state.maxFloorReached, floor),
|
|
}));
|
|
},
|
|
|
|
setAction: (action: GameAction) => {
|
|
set({ currentAction: action });
|
|
},
|
|
|
|
setSpell: (spellId: string) => {
|
|
const state = get();
|
|
if (state.spells[spellId]?.learned) {
|
|
set({ activeSpell: spellId });
|
|
}
|
|
},
|
|
|
|
setCastProgress: (progress: number) => {
|
|
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,
|
|
currentAction: 'meditate',
|
|
climbDirection: null,
|
|
isDescending: false,
|
|
currentFloor: 1,
|
|
floorHP: getFloorMaxHP(1),
|
|
floorMaxHP: getFloorMaxHP(1),
|
|
currentRoom: generateFloorState(1),
|
|
castProgress: 0,
|
|
clearedFloors: {},
|
|
});
|
|
},
|
|
|
|
startClimbUp: () => set({ climbDirection: 'up', currentAction: 'climb' }),
|
|
|
|
startClimbDown: () => set({ climbDirection: 'down', currentAction: 'climb' }),
|
|
|
|
// 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: () => {
|
|
const freshRoom = generateFloorState(1);
|
|
set({
|
|
spireMode: true,
|
|
currentFloor: 1,
|
|
floorHP: getFloorMaxHP(1),
|
|
floorMaxHP: getFloorMaxHP(1),
|
|
currentRoom: freshRoom,
|
|
castProgress: 0,
|
|
climbDirection: null,
|
|
isDescending: false,
|
|
clearedFloors: {},
|
|
});
|
|
},
|
|
|
|
learnSpell: (spellId: string) => {
|
|
set((state) => ({
|
|
spells: {
|
|
...state.spells,
|
|
[spellId]: { learned: true, level: 1, studyProgress: 0 },
|
|
},
|
|
}));
|
|
},
|
|
|
|
setSpellState: (spellId: string, spellState: Partial<SpellState>) => {
|
|
set((state) => ({
|
|
spells: {
|
|
...state.spells,
|
|
[spellId]: { ...(state.spells[spellId] || { learned: false, level: 0, studyProgress: 0 }), ...spellState },
|
|
},
|
|
}));
|
|
},
|
|
|
|
// 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: (
|
|
rawMana: number,
|
|
elements: Record<string, { current: number; max: number; unlocked: boolean }>,
|
|
maxMana: number,
|
|
attackSpeedMult: number,
|
|
onFloorCleared: (floor: number, wasGuardian: boolean) => void,
|
|
onDamageDealt: (damage: number) => { rawMana: number; elements: Record<string, { current: number; max: number; unlocked: boolean }> },
|
|
signedPacts: number[],
|
|
) => {
|
|
return processCombatTick(
|
|
get,
|
|
set,
|
|
rawMana,
|
|
elements,
|
|
maxMana,
|
|
attackSpeedMult,
|
|
onFloorCleared,
|
|
onDamageDealt,
|
|
signedPacts,
|
|
);
|
|
},
|
|
|
|
resetCombat: (startFloor: number, spellsToKeep: string[] = []) => {
|
|
const startSpells = makeInitialSpells(spellsToKeep);
|
|
|
|
set({
|
|
currentFloor: startFloor,
|
|
floorHP: getFloorMaxHP(startFloor),
|
|
floorMaxHP: getFloorMaxHP(startFloor),
|
|
maxFloorReached: startFloor,
|
|
activeSpell: 'manaBolt',
|
|
currentAction: 'meditate',
|
|
castProgress: 0,
|
|
spells: startSpells,
|
|
});
|
|
},
|
|
|
|
// Debug helpers
|
|
debugSetFloor: (floor: number) => {
|
|
set({
|
|
currentFloor: floor,
|
|
floorHP: getFloorMaxHP(floor),
|
|
floorMaxHP: getFloorMaxHP(floor),
|
|
});
|
|
},
|
|
|
|
resetFloorHP: () => {
|
|
set((state) => ({
|
|
floorHP: state.floorMaxHP,
|
|
}));
|
|
},
|
|
|
|
|
|
}),
|
|
{
|
|
storage: createSafeStorage(),
|
|
name: 'mana-loop-combat',
|
|
partialize: (state) => ({
|
|
currentFloor: state.currentFloor,
|
|
maxFloorReached: state.maxFloorReached,
|
|
spells: state.spells,
|
|
activeSpell: state.activeSpell,
|
|
floorHP: state.floorHP,
|
|
floorMaxHP: state.floorMaxHP,
|
|
castProgress: state.castProgress,
|
|
spireMode: state.spireMode,
|
|
clearedFloors: state.clearedFloors,
|
|
golemancy: state.golemancy,
|
|
equipmentSpellStates: state.equipmentSpellStates,
|
|
activityLog: state.activityLog,
|
|
achievements: state.achievements,
|
|
}),
|
|
}
|
|
)
|
|
);
|
|
|
|
// makeInitialSpells is now in combat-actions.ts
|
|
// Re-export for backward compatibility
|
|
export { makeInitialSpells } from './combat-actions';
|