// ─── Derived Stats Hooks ─────────────────────────────────────────────────────── // Custom hooks for computing derived game stats from the store import { useMemo } from 'react'; import { useGameStore } from '../stores/gameStore'; import { useCraftingStore } from '../stores/craftingStore'; import { useManaStore } from '../stores/manaStore'; import { useCombatStore } from '../stores/combatStore'; import { usePrestigeStore } from '../stores/prestigeStore'; import { useAttunementStore } from '../stores/attunementStore'; import { computeEffects } from '../effects/upgrade-effects'; import { computeMaxMana, computeRegen, computeClickMana, getMeditationBonus, getIncursionStrength, getFloorElement, calcDamage, getElementalBonus, getBoonBonuses, } from '../utils'; import { computeEquipmentEffects } from '../effects'; 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 attunements = useAttunementStore((s) => s.attunements); const disciplineEffects = useMemo( () => computeDisciplineEffects(), [] ); const upgradeEffects = useMemo( () => computeEffects({}, {}), [] ); const maxMana = useMemo( () => computeMaxMana({ prestigeUpgrades }, upgradeEffects, disciplineEffects), [prestigeUpgrades, upgradeEffects, disciplineEffects] ); const baseRegen = useMemo( () => computeRegen({ prestigeUpgrades, attunements } as any, upgradeEffects), [prestigeUpgrades, upgradeEffects, attunements] ); const clickMana = useMemo( () => computeClickMana(), [] ); const meditationCap = 5.0 + disciplineEffects.meditationCapBonus; 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, meditationCap, incursionStrength, effectiveRegenWithSpecials, manaCascadeBonus, manaWaterfallBonus, effectiveRegen, disciplineMaxManaBonus: disciplineEffects.bonuses.maxManaBonus || 0, 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 equipmentInstances = useCraftingStore((s) => s.equipmentInstances); const equippedInstances = useCraftingStore((s) => s.equippedInstances); 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 pactInterferenceMitigation = usePrestigeStore((s) => s.prestigeUpgrades.pactInterferenceMitigation || 0); const pactMultiplier = useMemo( () => computePactMultiplier({ signedPacts, pactInterferenceMitigation }), [signedPacts, pactInterferenceMitigation] ); const pactInsightMultiplier = useMemo( () => computePactInsightMultiplier({ signedPacts, pactInterferenceMitigation }), [signedPacts, pactInterferenceMitigation] ); // 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({ 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({ signedPacts }, activeSpell, floorElem), }; }, [activeSpellDef, signedPacts, activeSpell, floorElem, isGuardianFloor, pactMultiplier]); // Crit chance: sum equipment bonus (decimal) + pact boon bonus (percentage points) + upgrade bonus (decimal) const critChance = useMemo(() => { const equipEffects = computeEquipmentEffects(equipmentInstances, equippedInstances); const equipCritBonus = equipEffects.bonuses.critChance || 0; const boons = getBoonBonuses(signedPacts); const boonCritChance = boons.critChance / 100; // Convert percentage points to decimal const upgradeCritBonus = upgradeEffects.critChanceBonus || 0; return equipCritBonus + boonCritChance + upgradeCritBonus; }, [equipmentInstances, equippedInstances, signedPacts, upgradeEffects.critChanceBonus]); // Crit damage: base multiplier (1.5) + pact boon bonus (percentage points) const critDamage = useMemo(() => { const boons = getBoonBonuses(signedPacts); return (upgradeEffects.critDamageMultiplier || 1.5) + boons.critDamage / 100; }, [signedPacts, upgradeEffects.critDamageMultiplier]); return { floorElem, floorElemDef, isGuardianFloor, currentGuardian, activeSpellDef, pactMultiplier, pactInsightMultiplier, dps, damageBreakdown, critChance, critDamage, }; } /** * 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), }; }