fix: update StatsTab, DebugTab and all child components to use modular stores
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m42s

- Updated StatsTab to use hooks directly (useSkillStore, usePrestigeStore, etc.)
- Updated DebugTab to use hooks directly
- Updated all debug child components (GameStateDebug, SkillDebug, AttunementDebug, etc.)
- Updated all stats child components (ManaStatsSection, CombatStatsSection, etc.)
- Fixed UpgradeEffectsSection.tsx syntax errors
- Updated page.tsx to not pass store prop to StatsTab and DebugTab
- All components now use modular stores directly instead of receiving store prop
This commit is contained in:
Refactoring Agent
2026-05-02 23:43:49 +02:00
parent f1499046b5
commit ca07719456
14 changed files with 387 additions and 338 deletions
@@ -1,20 +1,21 @@
'use client';
import { GUARDIANS } from '@/lib/game/constants';
import { fmtDec } from '@/lib/game/store';
import type { GameStore } from '@/lib/game/types';
import { fmtDec } from '@/lib/game/stores';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Swords } from 'lucide-react';
export interface CombatStatsSectionProps {
store: GameStore;
}
// Modular stores
import { useSkillStore, useCombatStore } from '@/lib/game/stores';
export function CombatStatsSection() {
// Get state from modular stores
const skills = useSkillStore((s) => s.skills);
const signedPacts = useCombatStore((s) => s.signedPacts);
export function CombatStatsSection({ store }: CombatStatsSectionProps) {
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-red-400 game-panel-title text-xs flex items-center gap-2">
<CardTitle className="text-red-400 text-sm flex items-center gap-2">
<Swords className="w-4 h-4" />
Combat Stats
</CardTitle>
@@ -24,25 +25,25 @@ export function CombatStatsSection({ store }: CombatStatsSectionProps) {
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-400">Combat Training Bonus:</span>
<span className="text-red-300">+{(store.skills.combatTrain || 0) * 5}</span>
<span className="text-red-300">+{(skills.combatTrain || 0) * 5}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Arcane Fury Multiplier:</span>
<span className="text-red-300">×{fmtDec(1 + (store.skills.arcaneFury || 0) * 0.1, 2)}</span>
<span className="text-red-300">×{fmtDec(1 + (skills.arcaneFury || 0) * 0.1, 2)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Elemental Mastery:</span>
<span className="text-red-300">×{fmtDec(1 + (store.skills.elementalMastery || 0) * 0.15, 2)}</span>
<span className="text-red-300">×{fmtDec(1 + (skills.elementalMastery || 0) * 0.15, 2)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Guardian Bane:</span>
<span className="text-red-300">×{fmtDec(1 + (store.skills.guardianBane || 0) * 0.2, 2)} (vs guardians)</span>
<span className="text-red-300">×{fmtDec(1 + (skills.guardianBane || 0) * 0.2, 2)} (vs guardians)</span>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-400">Critical Hit Chance:</span>
<span className="text-amber-300">{((store.skills.precision || 0) * 5)}%</span>
<span className="text-amber-300">{(skills.precision || 0) * 5}%</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Critical Multiplier:</span>
@@ -50,11 +51,10 @@ export function CombatStatsSection({ store }: CombatStatsSectionProps) {
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Spell Echo Chance:</span>
<span className="text-amber-300">{((store.skills.spellEcho || 0) * 10)}%</span>
<span className="text-amber-300">{(skills.spellEcho || 0) * 10}%</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Pact Multiplier:</span>
<span className="text-amber-300">×{fmtDec(store.signedPacts.reduce((m, f) => m * (GUARDIANS[f]?.pact || 1), 1), 2)}</span>
<span className="text-amber-300">×{fmtDec(signedPacts.reduce((m, f) => m * (GUARDIANS[f]?.pact || 1), 1), 2)}</span>
</div>
</div>
</div>
+28 -19
View File
@@ -2,14 +2,16 @@
import { getTierMultiplier } from '@/lib/game/skill-evolution';
import { hasSpecial, SPECIAL_EFFECTS } from '@/lib/game/effects';
import { fmt, fmtDec } from '@/lib/game/store';
import type { GameStore, UnifiedEffects } from '@/lib/game/types';
import { fmt, fmtDec } from '@/lib/game/stores';
import type { UnifiedEffects } from '@/lib/game/types';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { Droplet } from 'lucide-react';
// Modular stores
import { useSkillStore, usePrestigeStore, useManaStore } from '@/lib/game/stores';
export interface ManaStatsSectionProps {
store: GameStore;
upgradeEffects: UnifiedEffects;
maxMana: number;
baseRegen: number;
@@ -26,7 +28,6 @@ export interface ManaStatsSectionProps {
}
export function ManaStatsSection({
store,
upgradeEffects,
maxMana,
baseRegen,
@@ -41,6 +42,12 @@ export function ManaStatsSection({
hasManaOverflow,
hasEternalFlow,
}: ManaStatsSectionProps) {
// Get state from modular stores
const skills = useSkillStore((s) => s.skills);
const skillTiers = useSkillStore((s) => s.skillTiers);
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
const rawMana = useManaStore((s) => s.rawMana);
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
@@ -60,9 +67,9 @@ export function ManaStatsSection({
<span className="text-gray-400">Mana Well Bonus:</span>
<span className="text-blue-300">
{(() => {
const mw = store.skillTiers?.manaWell || 1;
const mw = skillTiers?.manaWell || 1;
const tieredSkillId = mw > 1 ? `manaWell_t${mw}` : 'manaWell';
const level = store.skills[tieredSkillId] || store.skills.manaWell || 0;
const level = skills[tieredSkillId] || skills.manaWell || 0;
const tierMult = getTierMultiplier(tieredSkillId);
return `+${fmt(level * 100 * tierMult)} (${level} lvl × 100 × ${tierMult}x tier)`;
})()}
@@ -70,7 +77,7 @@ export function ManaStatsSection({
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Prestige Mana Well:</span>
<span className="text-blue-300">+{fmt((store.prestigeUpgrades.manaWell || 0) * 500)}</span>
<span className="text-blue-300">+{fmt((prestigeUpgrades.manaWell || 0) * 500)}</span>
</div>
{upgradeEffects.maxManaBonus > 0 && (
<div className="flex justify-between text-sm">
@@ -98,9 +105,9 @@ export function ManaStatsSection({
<span className="text-gray-400">Mana Flow Bonus:</span>
<span className="text-blue-300">
{(() => {
const mf = store.skillTiers?.manaFlow || 1;
const mf = skillTiers?.manaFlow || 1;
const tieredSkillId = mf > 1 ? `manaFlow_t${mf}` : 'manaFlow';
const level = store.skills[tieredSkillId] || store.skills.manaFlow || 0;
const level = skills[tieredSkillId] || skills.manaFlow || 0;
const tierMult = getTierMultiplier(tieredSkillId);
return `+${fmtDec(level * 1 * tierMult)}/hr (${level} lvl × 1 × ${tierMult}x tier)`;
})()}
@@ -108,15 +115,15 @@ export function ManaStatsSection({
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Mana Spring Bonus:</span>
<span className="text-blue-300">+{(store.skills.manaSpring || 0) * 2}/hr</span>
<span className="text-blue-300">+{(skills.manaSpring || 0) * 2}/hr</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Prestige Mana Flow:</span>
<span className="text-blue-300">+{fmtDec((store.prestigeUpgrades.manaFlow || 0) * 0.5)}/hr</span>
<span className="text-blue-300">+{fmtDec((prestigeUpgrades.manaFlow || 0) * 0.5)}/hr</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Temporal Echo:</span>
<span className="text-blue-300">×{fmtDec(1 + (store.prestigeUpgrades.temporalEcho || 0) * 0.1, 2)}</span>
<span className="text-blue-300">×{fmtDec(1 + (prestigeUpgrades.temporalEcho || 0) * 0.1, 2)}</span>
</div>
<div className="flex justify-between text-sm font-semibold border-t border-gray-700 pt-2">
<span className="text-gray-300">Base Regen:</span>
@@ -167,26 +174,28 @@ export function ManaStatsSection({
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Mana Tap Bonus:</span>
<span className="text-purple-300">+{store.skills.manaTap || 0}</span>
<span className="text-purple-300">+{skills.manaTap || 0}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Mana Surge Bonus:</span>
<span className="text-purple-300">+{(store.skills.manaSurge || 0) * 3}</span>
<span className="text-purple-300">+{(skills.manaSurge || 0) * 3}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Mana Overflow:</span>
<span className="text-purple-300">×{fmtDec(1 + (store.skills.manaOverflow || 0) * 0.25, 2)}</span>
<span className="text-purple-300">×{fmtDec(1 + (skills.manaOverflow || 0) * 0.25, 2)}</span>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-400">Meditation Multiplier:</span>
<span className={`font-semibold ${meditationMultiplier > 1.5 ? 'text-purple-400' : 'text-gray-300'}`}>
Meditation Multiplier:
</span>
<span className={`font-semibold ${meditationMultiplier > 1.5 ? 'text-purple-400' : 'text-gray-300'}`}>
{fmtDec(meditationMultiplier, 2)}x
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Effective Regen:</span>
<span className="text-gray-300">Effective Regen:</span>
<span className="text-green-400 font-semibold">{fmtDec(effectiveRegen, 2)}/hr</span>
</div>
{incursionStrength > 0 && !hasSpecial(upgradeEffects, SPECIAL_EFFECTS.STEADY_STREAM) && (
@@ -237,13 +246,13 @@ export function ManaStatsSection({
<span className="text-green-400">Regen immune to ALL penalties</span>
</div>
)}
{hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_TORRENT) && store.rawMana > maxMana * 0.75 && (
{hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_TORRENT) && rawMana > maxMana * 0.75 && (
<div className="flex justify-between text-sm">
<span className="text-cyan-400">Mana Torrent:</span>
<span className="text-cyan-400">+50% regen (high mana)</span>
</div>
)}
{hasSpecial(upgradeEffects, SPECIAL_EFFECTS.DESPERATE_WELLS) && store.rawMana < maxMana * 0.25 && (
{hasSpecial(upgradeEffects, SPECIAL_EFFECTS.DESPERATE_WELLS) && rawMana < maxMana * 0.25 && (
<div className="flex justify-between text-sm">
<span className="text-cyan-400">Desperate Wells:</span>
<span className="text-cyan-400">+50% regen (low mana)</span>
+53 -20
View File
@@ -1,37 +1,68 @@
'use client';
import { ELEMENTS } from '@/lib/game/constants';
import { fmt, fmtDec } from '@/lib/game/store';
import type { GameStore } from '@/lib/game/types';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { fmt, fmtDec } from '@/lib/game/stores';
import { ATTUNEMENTS_DEF, getAttunementConversionRate } from '@/lib/game/data/attunements';
import { computeMaxMana, computeElementMax } from '@/lib/game/store';
import { computeMaxMana, computeElementMax } from '@/lib/game/stores';
import { computeEffectiveRegenForDisplay } from '@/lib/game/store-modules/computed-stats';
import { getUnifiedEffects } from '@/lib/game/effects';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Droplet } from 'lucide-react';
import { Separator } from '@/components/ui/separator';
export interface ManaTypeBreakdownProps {
store: GameStore;
}
// Modular stores
import { useCombatStore, useManaStore, useSkillStore, usePrestigeStore } from '@/lib/game/stores';
export function ManaTypeBreakdown({ store }: ManaTypeBreakdownProps) {
export function ManaTypeBreakdown() {
// Get state from modular stores
const skills = useSkillStore((s) => s.skills);
const skillTiers = useSkillStore((s) => s.skillTiers);
const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
const elements = useManaStore((s) => s.elements);
const rawMana = useManaStore((s) => s.rawMana);
const attunements = useCombatStore((s) => s.attunements);
// Compute unified effects for regen calculations
const effects = getUnifiedEffects(store);
const effects = getUnifiedEffects({
skillUpgrades,
skillTiers,
equippedInstances: {},
equipmentInstances: {}
});
// Get effective regen info for raw mana
const regenInfo = computeEffectiveRegenForDisplay(store, effects);
const regenInfo = computeEffectiveRegenForDisplay({
skills,
prestigeUpgrades,
skillUpgrades,
skillTiers,
elements,
rawMana,
attunements
} as any, effects);
// Compute max mana
const maxMana = computeMaxMana(store, effects);
const maxMana = computeMaxMana({
skills,
prestigeUpgrades,
skillUpgrades,
skillTiers
}, effects);
// Get unlocked elements sorted by category then name
const unlockedElements = Object.entries(store.elements)
const unlockedElements = Object.entries(elements)
.filter(([, state]) => state.unlocked)
.map(([id, state]) => {
const def = ELEMENTS[id];
if (!def) return null;
const elemMax = computeElementMax(store, effects, id);
const elemMax = computeElementMax({
skills,
prestigeUpgrades,
skillUpgrades,
skillTiers
} as any, effects, id);
return {
id,
name: def.name,
@@ -69,7 +100,7 @@ export function ManaTypeBreakdown({ store }: ManaTypeBreakdownProps) {
<span className="text-xs text-gray-500">(base)</span>
</div>
<div className="text-sm">
<span className="text-gray-300">{fmt(store.rawMana)}</span>
<span className="text-gray-300">{fmt(rawMana)}</span>
<span className="text-gray-500"> / </span>
<span className="text-gray-400">{fmt(maxMana)}</span>
</div>
@@ -79,7 +110,7 @@ export function ManaTypeBreakdown({ store }: ManaTypeBreakdownProps) {
<div className="w-full bg-gray-700 rounded-full h-2 mb-3">
<div
className="h-2 rounded-full transition-all duration-300 bg-purple-500"
style={{ width: `${Math.min(100, (store.rawMana / maxMana) * 100)}%` }}
style={{ width: `${Math.min(100, (rawMana / maxMana) * 100)}%` }}
/>
</div>
@@ -102,13 +133,15 @@ export function ManaTypeBreakdown({ store }: ManaTypeBreakdownProps) {
</div>
{/* Show conversion drains by attunement */}
{store.conversionDrains && Object.keys(store.conversionDrains).length > 0 && (
{attunements && Object.keys(attunements).length > 0 && (
<>
<Separator className="bg-gray-700 my-1" />
<div className="text-gray-400 mb-1">Conversion Drains:</div>
{Object.entries(store.conversionDrains).map(([attId, rate]) => {
{Object.entries(attunements).map(([attId, attState]) => {
const attDef = ATTUNEMENTS_DEF[attId];
if (!attDef || rate <= 0) return null;
if (!attDef || attState.level === 0) return null;
const rate = getAttunementConversionRate(attId, attState.level);
if (rate <= 0) return null;
return (
<div key={attId} className="flex justify-between pl-2">
<span className="text-gray-500">{attDef.name}:</span>
@@ -128,7 +161,7 @@ export function ManaTypeBreakdown({ store }: ManaTypeBreakdownProps) {
if (!elem) return null;
// Find attunements that convert TO this element
const convertingAttunements = Object.entries(store.attunements || {})
const convertingAttunements = Object.entries(attunements || {})
.filter(([attId, attState]) => {
if (!attState.active) return false;
const attDef = ATTUNEMENTS_DEF[attId];
@@ -177,7 +210,7 @@ export function ManaTypeBreakdown({ store }: ManaTypeBreakdownProps) {
<div className="text-gray-500 pl-2">
Source: {convertingAttunements.map(([attId]) => {
const attDef = ATTUNEMENTS_DEF[attId];
const level = store.attunements[attId]?.level || 1;
const level = attunements[attId]?.level || 1;
return `${attDef?.name} (Lv.${level})`;
}).join(', ')}
</div>
@@ -1,17 +1,22 @@
'use client';
import { fmtDec } from '@/lib/game/store';
import type { GameStore } from '@/lib/game/types';
import { fmtDec } from '@/lib/game/stores';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { BookOpen } from 'lucide-react';
// Modular stores
import { useSkillStore, usePrestigeStore } from '@/lib/game/stores';
export interface StudyStatsSectionProps {
store: GameStore;
studySpeedMult: number;
studyCostMult: number;
}
export function StudyStatsSection({ store, studySpeedMult, studyCostMult }: StudyStatsSectionProps) {
export function StudyStatsSection({ studySpeedMult, studyCostMult }: StudyStatsSectionProps) {
// Get state from modular stores
const skills = useSkillStore((s) => s.skills);
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
@@ -29,7 +34,7 @@ export function StudyStatsSection({ store, studySpeedMult, studyCostMult }: Stud
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Quick Learner Bonus:</span>
<span className="text-purple-300">+{((store.skills.quickLearner || 0) * 10)}%</span>
<span className="text-purple-300">+{((skills.quickLearner || 0) * 10)}%</span>
</div>
</div>
<div className="space-y-2">
@@ -39,13 +44,13 @@ export function StudyStatsSection({ store, studySpeedMult, studyCostMult }: Stud
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Focused Mind Bonus:</span>
<span className="text-purple-300">-{((store.skills.focusedMind || 0) * 5)}%</span>
<span className="text-purple-300">-{((skills.focusedMind || 0) * 5)}%</span>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-400">Progress Retention:</span>
<span className="text-purple-300">{Math.round((1 + (store.skills.knowledgeRetention || 0) * 0.2) * 100)}%</span>
<span className="text-purple-300">{Math.round((1 + (skills.knowledgeRetention || 0) * 0.2) * 100)}%</span>
</div>
</div>
</div>
@@ -1,44 +1,46 @@
'use client';
import { SKILL_EVOLUTION_PATHS, getTierMultiplier } from '@/lib/game/skill-evolution';
import { SKILL_EVOLUTION_PATHS } from '@/lib/game/skill-evolution';
import { SKILLS_DEF } from '@/lib/game/constants';
import type { GameStore, SkillUpgradeChoice } from '@/lib/game/types';
import type { SkillUpgradeChoice } from '@/lib/game/types';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Star } from 'lucide-react';
export interface UpgradeEffectsSectionProps {
store: GameStore;
}
// Modular stores
import { useSkillStore } from '@/lib/game/stores';
// Helper function to get all selected skill upgrades
function getAllSelectedUpgrades(store: GameStore) {
const upgrades: { skillId: string; upgrade: SkillUpgradeChoice }[] = [];
for (const [skillId, selectedIds] of Object.entries(store.skillUpgrades)) {
const baseSkillId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId;
const path = SKILL_EVOLUTION_PATHS[baseSkillId];
if (!path) continue;
for (const tier of path.tiers) {
if (tier.skillId === skillId) {
for (const upgradeId of selectedIds) {
const upgrade = tier.upgrades.find(u => u.id === upgradeId);
if (upgrade) {
upgrades.push({ skillId, upgrade });
export function UpgradeEffectsSection() {
// Get state from modular stores
const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
// Helper function to get all selected skill upgrades
function getAllSelectedUpgrades() {
const upgrades = [];
for (const [skillId, selectedIds] of Object.entries(skillUpgrades)) {
const baseSkillId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId;
const path = SKILL_EVOLUTION_PATHS[baseSkillId];
if (!path) continue;
for (const tier of path.tiers) {
if (tier.skillId === skillId) {
for (const upgradeId of selectedIds) {
const upgrade = tier.upgrades.find(u => u.id === upgradeId);
if (upgrade) {
upgrades.push({ skillId, upgrade });
}
}
}
}
}
return upgrades;
}
return upgrades;
}
export function UpgradeEffectsSection({ store }: UpgradeEffectsSectionProps) {
const selectedUpgrades = getAllSelectedUpgrades(store);
const selectedUpgrades = getAllSelectedUpgrades();
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-amber-400 game-panel-title text-xs flex items-center gap-2">
<CardTitle className="text-amber-400 text-sm flex items-center gap-2">
<Star className="w-4 h-4" />
Active Skill Upgrades ({selectedUpgrades.length})
</CardTitle>
@@ -57,17 +59,17 @@ export function UpgradeEffectsSection({ store }: UpgradeEffectsSectionProps) {
</Badge>
</div>
<div className="text-xs text-gray-400 mt-1">{upgrade.desc}</div>
{upgrade.effect.type === 'multiplier' && (
{upgrade.effect && upgrade.effect.type === 'multiplier' && (
<div className="text-xs text-green-400 mt-1">
+{Math.round((upgrade.effect.value! - 1) * 100)}% {upgrade.effect.stat}
+{Math.round((upgrade.effect.value - 1) * 100)}% {upgrade.effect.stat}
</div>
)}
{upgrade.effect.type === 'bonus' && (
{upgrade.effect && upgrade.effect.type === 'bonus' && (
<div className="text-xs text-blue-400 mt-1">
+{upgrade.effect.value} {upgrade.effect.stat}
</div>
)}
{upgrade.effect.type === 'special' && (
{upgrade.effect && upgrade.effect.type === 'special' && (
<div className="text-xs text-cyan-400 mt-1">
{upgrade.effect.specialDesc || 'Special effect active'}
</div>