// ─── Mana Slice ─────────────────────────────────────────────────────────────── // Manages raw mana, elements, and meditation import type { StateCreator } from 'zustand'; import type { GameState, ElementState, SpellCost } from '../types'; import { ELEMENTS, MANA_PER_ELEMENT, BASE_UNLOCKED_ELEMENTS } from '../constants'; import { computeMaxMana, computeElementMax, computeClickMana, canAffordSpellCost, getMeditationBonus } from './computed'; import { computeEffects } from '../upgrade-effects'; export interface ManaSlice { // State rawMana: number; totalManaGathered: number; meditateTicks: number; elements: Record; // Actions gatherMana: () => void; convertMana: (element: string, amount: number) => void; unlockElement: (element: string) => void; craftComposite: (target: string) => void; // Computed getters getMaxMana: () => number; getRegen: () => number; getClickMana: () => number; getMeditationMultiplier: () => number; } export const createManaSlice = ( set: StateCreator['set'], get: () => GameState ): ManaSlice => ({ rawMana: 10, totalManaGathered: 0, meditateTicks: 0, elements: (() => { const elems: Record = {}; const pu = get().prestigeUpgrades; const elemMax = computeElementMax(get()); Object.keys(ELEMENTS).forEach((k) => { const isUnlocked = BASE_UNLOCKED_ELEMENTS.includes(k); let startAmount = 0; if (isUnlocked && pu.elemStart) { startAmount = pu.elemStart * 5; } elems[k] = { current: startAmount, max: elemMax, unlocked: isUnlocked, }; }); return elems; })(), gatherMana: () => { const state = get(); let cm = computeClickMana(state); // Mana overflow bonus const overflowBonus = 1 + (state.skills.manaOverflow || 0) * 0.25; cm = Math.floor(cm * overflowBonus); const effects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {}); const max = computeMaxMana(state, effects); // Mana Echo: 10% chance to gain double mana from clicks const hasManaEcho = effects.specials?.has('MANA_ECHO') ?? false; if (hasManaEcho && Math.random() < 0.1) { cm *= 2; } set({ rawMana: Math.min(state.rawMana + cm, max), totalManaGathered: state.totalManaGathered + cm, }); }, convertMana: (element: string, amount: number = 1) => { const state = get(); const e = state.elements[element]; if (!e?.unlocked) return; const cost = MANA_PER_ELEMENT * amount; if (state.rawMana < cost) return; if (e.current >= e.max) return; const canConvert = Math.min( amount, Math.floor(state.rawMana / MANA_PER_ELEMENT), e.max - e.current ); set({ rawMana: state.rawMana - canConvert * MANA_PER_ELEMENT, elements: { ...state.elements, [element]: { ...e, current: e.current + canConvert }, }, }); }, unlockElement: (element: string) => { const state = get(); if (state.elements[element]?.unlocked) return; const cost = 500; if (state.rawMana < cost) return; set({ rawMana: state.rawMana - cost, elements: { ...state.elements, [element]: { ...state.elements[element], unlocked: true }, }, }); }, craftComposite: (target: string) => { const state = get(); const edef = ELEMENTS[target]; if (!edef?.recipe) return; const recipe = edef.recipe; const costs: Record = {}; recipe.forEach((r) => { costs[r] = (costs[r] || 0) + 1; }); // Check ingredients for (const [r, amt] of Object.entries(costs)) { if ((state.elements[r]?.current || 0) < amt) return; } const newElems = { ...state.elements }; for (const [r, amt] of Object.entries(costs)) { newElems[r] = { ...newElems[r], current: newElems[r].current - amt }; } // Elemental crafting bonus const craftBonus = 1 + (state.skills.elemCrafting || 0) * 0.25; const outputAmount = Math.floor(craftBonus); const effects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {}); const elemMax = computeElementMax(state, effects); newElems[target] = { ...(newElems[target] || { current: 0, max: elemMax, unlocked: false }), current: (newElems[target]?.current || 0) + outputAmount, max: elemMax, unlocked: true, }; set({ elements: newElems, }); }, getMaxMana: () => computeMaxMana(get()), getRegen: () => { const state = get(); const effects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {}); // This would need proper regen calculation return 2; }, getClickMana: () => computeClickMana(get()), getMeditationMultiplier: () => { const state = get(); const effects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {}); return getMeditationBonus(state.meditateTicks, state.skills, effects.meditationEfficiency); }, }); // Helper function to deduct spell cost export function deductSpellCost( cost: SpellCost, rawMana: number, elements: Record ): { rawMana: number; elements: Record } { const newElements = { ...elements }; if (cost.type === 'raw') { return { rawMana: rawMana - cost.amount, elements: newElements }; } else if (cost.element && newElements[cost.element]) { newElements[cost.element] = { ...newElements[cost.element], current: newElements[cost.element].current - cost.amount, }; return { rawMana, elements: newElements }; } return { rawMana, elements: newElements }; } export { canAffordSpellCost };