// ─── Discipline Store Slice ──────────────────────────────────────────────────── import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import type { DisciplineState } from '../types/disciplines'; import { calculateManaDrain, calculateStatBonus, canProceedDiscipline, } from '../utils/discipline-math'; import { baseDisciplines } from '../data/disciplines/base'; import { enchanterDisciplines } from '../data/disciplines/enchanter'; import { fabricatorDisciplines } from '../data/disciplines/fabricator'; import { invokerDisciplines } from '../data/disciplines/invoker'; import { MAX_CONCURRENT_DISCIPLINES } from '../types/disciplines'; const ALL_DISCIPLINES = [ ...baseDisciplines, ...enchanterDisciplines, ...fabricatorDisciplines, ...invokerDisciplines, ]; const DISCIPLINE_MAP = Object.fromEntries(ALL_DISCIPLINES.map((d) => [d.id, d])); export interface DisciplineStoreState { disciplines: Record; activeIds: string[]; concurrentLimit: number; totalXP: number; } export interface DisciplineStoreActions { activate: (id: string, gameState?: { elements?: Record }) => void; deactivate: (id: string) => void; processTick: (mana: { rawMana: number; elements: Record }) => { rawMana: number; elements: Record; }; } export type DisciplineStore = DisciplineStoreState & DisciplineStoreActions; export const useDisciplineStore = create()( persist( (set, get) => ({ disciplines: {}, activeIds: [], concurrentLimit: MAX_CONCURRENT_DISCIPLINES, totalXP: 0, activate(id, gameState) { set((s) => { const def = DISCIPLINE_MAP[id]; if (!def) return s; if (s.activeIds.includes(id)) return s; const nonPaused = s.activeIds.filter((aid) => { const d = s.disciplines[aid]; return d && !d.paused; }).length; if (nonPaused >= s.concurrentLimit) return s; if (!canProceedDiscipline(id, gameState)) return s; const existing = s.disciplines[id] || { id, xp: 0, paused: false }; return { disciplines: { ...s.disciplines, [id]: { ...existing, paused: false } }, activeIds: [...s.activeIds, id], }; }); }, deactivate(id) { set((s) => ({ activeIds: s.activeIds.filter((aid) => aid !== id), })); }, processTick(mana) { const s = get(); let rawMana = mana.rawMana; const elements = { ...mana.elements }; let newXP = s.totalXP; for (const id of s.activeIds) { const disc = s.disciplines[id]; if (!disc) continue; if (disc.paused) continue; const def = DISCIPLINE_MAP[id]; if (!def) continue; const drain = calculateManaDrain(def.drainBase, disc.xp, def.difficultyFactor); const element = elements[def.manaType]; const available = def.manaType === 'raw' ? rawMana : element?.current; if (!available || available < drain) { disc.paused = true; continue; } if (def.manaType === 'raw') { rawMana -= drain; } else { elements[def.manaType].current -= drain; } disc.xp += 1; newXP += 1; } const newLimit = Math.min( MAX_CONCURRENT_DISCIPLINES + Math.floor(newXP / 500), MAX_CONCURRENT_DISCIPLINES + 3 ); set({ disciplines: s.disciplines, totalXP: newXP, concurrentLimit: Math.max(s.concurrentLimit, newLimit), }); return { rawMana, elements }; }, }), { name: 'mana-loop-discipline-store' } ) );