Fix mana conversion visibility and UI improvements
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 3m9s
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 3m9s
- Increase attunement mana conversion rates (0.2 -> 2 for Enchanter) - Hide mana types with current < 1 in ManaDisplay and LabTab - Only show owned equipment types when designing enchantments
This commit is contained in:
221
src/lib/game/hooks/useGameDerived.ts
Executable file
221
src/lib/game/hooks/useGameDerived.ts
Executable file
@@ -0,0 +1,221 @@
|
||||
// ─── Derived Stats Hooks ───────────────────────────────────────────────────────
|
||||
// Custom hooks for computing derived game stats from the store
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { useGameStore } from '../store';
|
||||
import { computeEffects } from '../upgrade-effects';
|
||||
import {
|
||||
computeMaxMana,
|
||||
computeRegen,
|
||||
computeClickMana,
|
||||
getMeditationBonus,
|
||||
getIncursionStrength,
|
||||
getFloorElement,
|
||||
calcDamage,
|
||||
computePactMultiplier,
|
||||
computePactInsightMultiplier,
|
||||
getElementalBonus,
|
||||
} from '../store/computed';
|
||||
import { ELEMENTS, GUARDIANS, SPELLS_DEF, getStudySpeedMultiplier, getStudyCostMultiplier, HOURS_PER_TICK, TICK_MS } from '../constants';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from '../upgrade-effects';
|
||||
|
||||
/**
|
||||
* Hook for all mana-related derived stats
|
||||
*/
|
||||
export function useManaStats() {
|
||||
const store = useGameStore();
|
||||
|
||||
const upgradeEffects = useMemo(
|
||||
() => computeEffects(store.skillUpgrades || {}, store.skillTiers || {}),
|
||||
[store.skillUpgrades, store.skillTiers]
|
||||
);
|
||||
|
||||
const maxMana = useMemo(
|
||||
() => computeMaxMana(store, upgradeEffects),
|
||||
[store, upgradeEffects]
|
||||
);
|
||||
|
||||
const baseRegen = useMemo(
|
||||
() => computeRegen(store, upgradeEffects),
|
||||
[store, upgradeEffects]
|
||||
);
|
||||
|
||||
const clickMana = useMemo(
|
||||
() => computeClickMana(store),
|
||||
[store]
|
||||
);
|
||||
|
||||
const meditationMultiplier = useMemo(
|
||||
() => getMeditationBonus(store.meditateTicks, store.skills, upgradeEffects.meditationEfficiency),
|
||||
[store.meditateTicks, store.skills, upgradeEffects.meditationEfficiency]
|
||||
);
|
||||
|
||||
const incursionStrength = useMemo(
|
||||
() => getIncursionStrength(store.day, store.hour),
|
||||
[store.day, store.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;
|
||||
|
||||
// Final effective regen
|
||||
const effectiveRegen = (effectiveRegenWithSpecials + manaCascadeBonus) * meditationMultiplier;
|
||||
|
||||
return {
|
||||
upgradeEffects,
|
||||
maxMana,
|
||||
baseRegen,
|
||||
clickMana,
|
||||
meditationMultiplier,
|
||||
incursionStrength,
|
||||
effectiveRegenWithSpecials,
|
||||
manaCascadeBonus,
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for combat-related derived stats
|
||||
*/
|
||||
export function useCombatStats() {
|
||||
const store = useGameStore();
|
||||
const { upgradeEffects } = useManaStats();
|
||||
|
||||
const floorElem = useMemo(
|
||||
() => getFloorElement(store.currentFloor),
|
||||
[store.currentFloor]
|
||||
);
|
||||
|
||||
const floorElemDef = useMemo(
|
||||
() => ELEMENTS[floorElem],
|
||||
[floorElem]
|
||||
);
|
||||
|
||||
const isGuardianFloor = useMemo(
|
||||
() => !!GUARDIANS[store.currentFloor],
|
||||
[store.currentFloor]
|
||||
);
|
||||
|
||||
const currentGuardian = useMemo(
|
||||
() => GUARDIANS[store.currentFloor],
|
||||
[store.currentFloor]
|
||||
);
|
||||
|
||||
const activeSpellDef = useMemo(
|
||||
() => SPELLS_DEF[store.activeSpell],
|
||||
[store.activeSpell]
|
||||
);
|
||||
|
||||
const pactMultiplier = useMemo(
|
||||
() => computePactMultiplier(store),
|
||||
[store]
|
||||
);
|
||||
|
||||
const pactInsightMultiplier = useMemo(
|
||||
() => computePactInsightMultiplier(store),
|
||||
[store]
|
||||
);
|
||||
|
||||
// DPS calculation
|
||||
const dps = useMemo(() => {
|
||||
if (!activeSpellDef) return 0;
|
||||
|
||||
const spellCastSpeed = activeSpellDef.castSpeed || 1;
|
||||
const quickCastBonus = 1 + (store.skills.quickCast || 0) * 0.05;
|
||||
const attackSpeedMult = upgradeEffects.attackSpeedMultiplier;
|
||||
const totalCastSpeed = spellCastSpeed * quickCastBonus * attackSpeedMult;
|
||||
|
||||
const damagePerCast = calcDamage(store, store.activeSpell, floorElem);
|
||||
const castsPerSecond = totalCastSpeed * HOURS_PER_TICK / (TICK_MS / 1000);
|
||||
|
||||
return damagePerCast * castsPerSecond;
|
||||
}, [activeSpellDef, store, floorElem, upgradeEffects.attackSpeedMultiplier]);
|
||||
|
||||
// Damage breakdown for display
|
||||
const damageBreakdown = useMemo(() => {
|
||||
if (!activeSpellDef) return null;
|
||||
|
||||
const baseDmg = activeSpellDef.dmg;
|
||||
const combatTrainBonus = (store.skills.combatTrain || 0) * 5;
|
||||
const arcaneFuryMult = 1 + (store.skills.arcaneFury || 0) * 0.1;
|
||||
const elemMasteryMult = 1 + (store.skills.elementalMastery || 0) * 0.15;
|
||||
const guardianBaneMult = isGuardianFloor ? (1 + (store.skills.guardianBane || 0) * 0.2) : 1;
|
||||
const precisionChance = (store.skills.precision || 0) * 0.05;
|
||||
|
||||
// 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(store, store.activeSpell, floorElem),
|
||||
};
|
||||
}, [activeSpellDef, store, floorElem, isGuardianFloor, pactMultiplier]);
|
||||
|
||||
return {
|
||||
floorElem,
|
||||
floorElemDef,
|
||||
isGuardianFloor,
|
||||
currentGuardian,
|
||||
activeSpellDef,
|
||||
pactMultiplier,
|
||||
pactInsightMultiplier,
|
||||
dps,
|
||||
damageBreakdown,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for study-related derived stats
|
||||
*/
|
||||
export function useStudyStats() {
|
||||
const store = useGameStore();
|
||||
|
||||
const studySpeedMult = useMemo(
|
||||
() => getStudySpeedMultiplier(store.skills),
|
||||
[store.skills]
|
||||
);
|
||||
|
||||
const studyCostMult = useMemo(
|
||||
() => getStudyCostMultiplier(store.skills),
|
||||
[store.skills]
|
||||
);
|
||||
|
||||
const upgradeEffects = useMemo(
|
||||
() => computeEffects(store.skillUpgrades || {}, store.skillTiers || {}),
|
||||
[store.skillUpgrades, store.skillTiers]
|
||||
);
|
||||
|
||||
const effectiveStudySpeedMult = studySpeedMult * upgradeEffects.studySpeedMultiplier;
|
||||
|
||||
return {
|
||||
studySpeedMult,
|
||||
studyCostMult,
|
||||
effectiveStudySpeedMult,
|
||||
hasParallelStudy: hasSpecial(upgradeEffects, SPECIAL_EFFECTS.PARALLEL_STUDY),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user