// ─── Derived Stats Hooks ─────────────────────────────────────────────────────── // Custom hooks for computing derived game stats from the store import { useMemo } from 'react'; import { useGameStore } from '../stores/gameStore'; import { useManaStore } from '../stores/manaStore'; import { useCombatStore } from '../stores/combatStore'; import { usePrestigeStore } from '../stores/prestigeStore'; import { computeEffects } from '../effects/upgrade-effects'; import { computeMaxMana, computeRegen, computeClickMana, getMeditationBonus, getIncursionStrength, getFloorElement, calcDamage, getElementalBonus, } from '../utils'; import { computePactMultiplier, computePactInsightMultiplier } from '../utils/pact-utils'; import { ELEMENTS, SPELLS_DEF, getStudySpeedMultiplier, getStudyCostMultiplier, HOURS_PER_TICK, TICK_MS } from '../constants'; import { getGuardianForFloor } from '../data/guardian-encounters'; import { hasSpecial, SPECIAL_EFFECTS } from '../effects/special-effects'; import { computeDisciplineEffects } from '../effects/discipline-effects'; /** * Hook for all mana-related derived stats */ export function useManaStats() { const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades); const _rawMana = useManaStore((s) => s.rawMana); const meditateTicks = useManaStore((s) => s.meditateTicks); const day = useGameStore((s) => s.day); const hour = useGameStore((s) => s.hour); const upgradeEffects = useMemo( () => computeEffects({}, {}), [] ); const maxMana = useMemo( () => computeMaxMana({ skills: {}, prestigeUpgrades, skillUpgrades: {}, skillTiers: {} }, upgradeEffects), [prestigeUpgrades, upgradeEffects] ); const baseRegen = useMemo( () => computeRegen({ skills: {} as Record, prestigeUpgrades, attunements: {} } as any, upgradeEffects), [prestigeUpgrades, upgradeEffects] ); const clickMana = useMemo( () => computeClickMana({}), [] ); const disciplineEffects = computeDisciplineEffects(); const meditationMultiplier = useMemo( () => getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus), [meditateTicks, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus] ); const incursionStrength = useMemo( () => getIncursionStrength(day, hour), [day, hour] ); // Effective regen with incursion penalty const effectiveRegenWithSpecials = baseRegen * (1 - incursionStrength); // Mana Cascade bonus const manaCascadeBonus = hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_CASCADE) ? Math.floor(maxMana / 100) * 0.1 : 0; // Mana Waterfall bonus const manaWaterfallBonus = hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_WATERFALL) ? Math.floor(maxMana / 100) * 0.25 : 0; // Final effective regen const effectiveRegen = (effectiveRegenWithSpecials + manaCascadeBonus + manaWaterfallBonus) * meditationMultiplier; return { upgradeEffects, maxMana, baseRegen, clickMana, meditationMultiplier, incursionStrength, effectiveRegenWithSpecials, manaCascadeBonus, manaWaterfallBonus, effectiveRegen, hasSteadyStream: hasSpecial(upgradeEffects, SPECIAL_EFFECTS.STEADY_STREAM), hasManaTorrent: hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_TORRENT), hasDesperateWells: hasSpecial(upgradeEffects, SPECIAL_EFFECTS.DESPERATE_WELLS), hasManaEcho: hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_ECHO), hasManaWaterfall: hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_WATERFALL), hasFlowSurge: hasSpecial(upgradeEffects, SPECIAL_EFFECTS.FLOW_SURGE), hasManaOverflow: hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_OVERFLOW), hasEternalFlow: hasSpecial(upgradeEffects, SPECIAL_EFFECTS.ETERNAL_FLOW), }; } /** * Hook for combat-related derived stats */ export function useCombatStats() { const signedPacts = usePrestigeStore((s) => s.signedPacts); const currentFloor = useCombatStore((s) => s.currentFloor); const activeSpell = useCombatStore((s) => s.activeSpell); const { upgradeEffects } = useManaStats(); const floorElem = useMemo( () => getFloorElement(currentFloor), [currentFloor] ); const floorElemDef = useMemo( () => ELEMENTS[floorElem], [floorElem] ); const isGuardianFloor = useMemo( () => !!getGuardianForFloor(currentFloor), [currentFloor] ); const currentGuardian = useMemo( () => getGuardianForFloor(currentFloor), [currentFloor] ); const activeSpellDef = useMemo( () => SPELLS_DEF[activeSpell], [activeSpell] ); const pactMultiplier = useMemo( () => computePactMultiplier({ signedPacts }), [signedPacts] ); const pactInsightMultiplier = useMemo( () => computePactInsightMultiplier({ signedPacts }), [signedPacts] ); // DPS calculation const dps = useMemo(() => { if (!activeSpellDef) return 0; const spellCastSpeed = activeSpellDef.castSpeed || 1; const quickCastBonus = 1; const attackSpeedMult = upgradeEffects.attackSpeedMultiplier; const totalCastSpeed = spellCastSpeed * quickCastBonus * attackSpeedMult; const damagePerCast = calcDamage({ skills: {}, signedPacts }, activeSpell, floorElem); const castsPerSecond = totalCastSpeed * HOURS_PER_TICK / (TICK_MS / 1000); return damagePerCast * castsPerSecond; }, [activeSpellDef, signedPacts, activeSpell, floorElem, upgradeEffects.attackSpeedMultiplier]); // Damage breakdown for display const damageBreakdown = useMemo(() => { if (!activeSpellDef) return null; const baseDmg = activeSpellDef.dmg; const combatTrainBonus = 0; const arcaneFuryMult = 1; const elemMasteryMult = 1; const guardianBaneMult = 1; const precisionChance = 0; // Calculate elemental bonus const elemBonus = getElementalBonus(activeSpellDef.elem, floorElem); let elemBonusText = ''; if (activeSpellDef.elem !== 'raw' && floorElem) { if (activeSpellDef.elem === floorElem) { elemBonusText = '+25% same element'; } else if (elemBonus === 1.5) { elemBonusText = '+50% super effective'; } } return { base: baseDmg, combatTrainBonus, arcaneFuryMult, elemMasteryMult, guardianBaneMult, pactMult: pactMultiplier, precisionChance, elemBonus, elemBonusText, total: calcDamage({ skills: {}, signedPacts }, activeSpell, floorElem), }; }, [activeSpellDef, signedPacts, activeSpell, floorElem, isGuardianFloor, pactMultiplier]); return { floorElem, floorElemDef, isGuardianFloor, currentGuardian, activeSpellDef, pactMultiplier, pactInsightMultiplier, dps, damageBreakdown, }; } /** * Hook for study-related derived stats */ export function useStudyStats() { const studySpeedMult = useMemo( () => getStudySpeedMultiplier({}), [] ); const studyCostMult = useMemo( () => getStudyCostMultiplier({}), [] ); const upgradeEffects = useMemo( () => computeEffects({}, {}), [] ); const effectiveStudySpeedMult = studySpeedMult * upgradeEffects.studySpeedMultiplier; return { studySpeedMult, studyCostMult, effectiveStudySpeedMult, hasParallelStudy: hasSpecial(upgradeEffects, SPECIAL_EFFECTS.PARALLEL_STUDY), }; }