fix: complete store migration — fix all tab crashes and ghost field reads
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m22s

This commit is contained in:
Refactoring Agent
2026-05-05 12:45:07 +02:00
parent dc1aad3700
commit 221d3e4b41
20 changed files with 399 additions and 754 deletions
-2
View File
@@ -106,7 +106,6 @@ Mana-Loop/
│ │ │ │ ├── AchievementsTab.tsx │ │ │ │ ├── AchievementsTab.tsx
│ │ │ │ ├── ActivityLog.tsx │ │ │ │ ├── ActivityLog.tsx
│ │ │ │ ├── AttunementsTab.tsx │ │ │ │ ├── AttunementsTab.tsx
│ │ │ │ ├── AttunementsTab.tsx.backup
│ │ │ │ ├── CategorySkillsList.tsx │ │ │ │ ├── CategorySkillsList.tsx
│ │ │ │ ├── CombatStatsPanel.tsx │ │ │ │ ├── CombatStatsPanel.tsx
│ │ │ │ ├── CraftingTab.tsx │ │ │ │ ├── CraftingTab.tsx
@@ -137,7 +136,6 @@ Mana-Loop/
│ │ │ │ └── index.ts │ │ │ │ └── index.ts
│ │ │ ├── AchievementsDisplay.tsx │ │ │ ├── AchievementsDisplay.tsx
│ │ │ ├── ActionButtons.tsx │ │ │ ├── ActionButtons.tsx
│ │ │ ├── ActionButtons.tsx.backup
│ │ │ ├── CalendarDisplay.tsx │ │ │ ├── CalendarDisplay.tsx
│ │ │ ├── ConfirmDialog.tsx │ │ │ ├── ConfirmDialog.tsx
│ │ │ ├── CraftingProgress.tsx │ │ │ ├── CraftingProgress.tsx
+1
View File
@@ -73,6 +73,7 @@ function GrimoireTab() {
// Only access SPELLS_DEF on client-side // Only access SPELLS_DEF on client-side
if (typeof window !== 'undefined' && SPELLS_DEF) { if (typeof window !== 'undefined' && SPELLS_DEF) {
const filtered = Object.values(SPELLS_DEF || {}).filter((s: any) => s.grimoire); const filtered = Object.values(SPELLS_DEF || {}).filter((s: any) => s.grimoire);
// eslint-disable-next-line react-hooks/set-state-in-effect
setGrimoireSpells(filtered); setGrimoireSpells(filtered);
} }
}, []); }, []);
@@ -1,152 +0,0 @@
'use client';
import { Sparkles, Swords, BookOpen, Target, FlaskConical, Cog, Hammer } from 'lucide-react';
import type { GameAction } from '@/lib/game/types';
interface ActionButtonsProps {
currentAction: GameAction;
currentStudyTarget: { type: 'skill' | 'spell'; id: string; progress: number; required: number } | null;
designProgress: { progress: number; required: number } | null;
designProgress2: { progress: number; required: number } | null;
preparationProgress: { progress: number; required: number } | null;
applicationProgress: { progress: number; required: number } | null;
equipmentCraftingProgress: { progress: number; required: number } | null;
}
// Map action IDs to labels and icons
const ACTION_CONFIG: Record<string, { label: string; icon: typeof Sparkles; color: string }> = {
meditate: { label: 'Meditating', icon: Sparkles, color: 'text-blue-400' },
climb: { label: 'Climbing', icon: Swords, color: 'text-green-400' },
study: { label: 'Studying', icon: BookOpen, color: 'text-yellow-400' },
design: { label: 'Designing Enchantment', icon: Target, color: 'text-purple-400' },
prepare: { label: 'Preparing Equipment', icon: FlaskConical, color: 'text-purple-400' },
enchant: { label: 'Enchanting', icon: Sparkles, color: 'text-purple-400' },
craft: { label: 'Crafting Equipment', icon: Hammer, color: 'text-orange-400' },
convert: { label: 'Converting Mana', icon: Cog, color: 'text-cyan-400' },
};
function ProgressBar({ progress, required, label }: { progress: number; required: number; label?: string }) {
const percentage = Math.min(100, (progress / required) * 100);
return (
<div className="mt-1">
{label && <div className="text-xs text-gray-400 mb-0.5">{label}</div>}
<div className="w-full bg-gray-700 rounded-full h-1.5">
<div
className="bg-blue-500 h-1.5 rounded-full transition-all duration-300"
style={{ width: `${percentage}%` }}
/>
</div>
</div>
);
}
export function ActionButtons({
currentAction,
currentStudyTarget,
designProgress,
designProgress2,
preparationProgress,
applicationProgress,
equipmentCraftingProgress,
}: ActionButtonsProps) {
const config = ACTION_CONFIG[currentAction] || { label: currentAction, icon: Sparkles, color: 'text-gray-400' };
const Icon = config.icon;
// Calculate additional info for specific actions
const getActionDetails = () => {
switch (currentAction) {
case 'study':
if (currentStudyTarget) {
const progress = currentStudyTarget.progress;
const required = currentStudyTarget.required;
const percentage = Math.min(100, (progress / required) * 100);
return (
<ProgressBar
progress={progress}
required={required}
label={`${currentStudyTarget.type === 'skill' ? 'Skill' : 'Spell'}: ${percentage.toFixed(0)}%`}
/>
);
}
break;
case 'design':
if (designProgress) {
return (
<ProgressBar
progress={designProgress.progress}
required={designProgress.required}
label="Design progress"
/>
);
}
break;
case 'prepare':
if (preparationProgress) {
return (
<ProgressBar
progress={preparationProgress.progress}
required={preparationProgress.required}
label="Preparation progress"
/>
);
}
break;
case 'enchant':
if (applicationProgress) {
return (
<ProgressBar
progress={applicationProgress.progress}
required={applicationProgress.required}
label="Enchantment progress"
/>
);
}
break;
case 'craft':
if (equipmentCraftingProgress) {
return (
<ProgressBar
progress={equipmentCraftingProgress.progress}
required={equipmentCraftingProgress.required}
label="Crafting progress"
/>
);
}
break;
}
return null;
};
return (
<div className="space-y-2">
<div className="bg-gray-800/50 rounded-lg p-3 border border-gray-700">
<div className="flex items-center gap-2">
<Icon className={`w-4 h-4 ${config.color}`} />
<span className="text-sm font-medium text-gray-200">Current Activity</span>
</div>
<div className={`text-lg font-semibold mt-1 ${config.color}`}>
{config.label}
</div>
{getActionDetails()}
{/* Show second design slot if active */}
{designProgress2 && (
<div className="mt-2 pt-2 border-t border-gray-700">
<div className="flex items-center gap-2">
<Target className="w-3 h-3 text-purple-400" />
<span className="text-xs text-gray-400">Second Design Slot</span>
</div>
<ProgressBar
progress={designProgress2.progress}
required={designProgress2.required}
label="Design progress"
/>
</div>
)}
</div>
</div>
);
}
ActionButtons.displayName = "ActionButtons";
ProgressBar.displayName = "ProgressBar";
+20 -15
View File
@@ -1,13 +1,18 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState } from 'react';
import { useGameStore } from '@/lib/game/store'; import { useManaStore } from '@/lib/game/stores';
import { ELEMENTS, MANA_PER_ELEMENT } from '@/lib/game/constants'; import { ELEMENTS, MANA_PER_ELEMENT } from '@/lib/game/constants';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
export function LabTab() { export function LabTab() {
const store = useGameStore(); const elements = useManaStore((s) => s.elements);
const rawMana = useManaStore((s) => s.rawMana);
const convertMana = useManaStore((s) => s.convertMana);
const unlockElement = useManaStore((s) => s.unlockElement);
const craftComposite = useManaStore((s) => s.craftComposite);
const [convertTarget, setConvertTarget] = useState('fire'); const [convertTarget, setConvertTarget] = useState('fire');
return ( return (
@@ -19,7 +24,7 @@ export function LabTab() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8 gap-2"> <div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8 gap-2">
{Object.entries(store.elements) {Object.entries(elements)
.filter(([, state]) => state.unlocked && state.current >= 1) .filter(([, state]) => state.unlocked && state.current >= 1)
.map(([id, state]) => { .map(([id, state]) => {
const def = ELEMENTS[id]; const def = ELEMENTS[id];
@@ -55,24 +60,24 @@ export function LabTab() {
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
onClick={() => store.convertMana(convertTarget, 1)} onClick={() => convertMana(convertTarget, 1)}
disabled={!store.elements[convertTarget]?.unlocked || store.rawMana < MANA_PER_ELEMENT} disabled={!elements[convertTarget]?.unlocked || rawMana < MANA_PER_ELEMENT}
> >
+1 ({MANA_PER_ELEMENT}) +1 ({MANA_PER_ELEMENT})
</Button> </Button>
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
onClick={() => store.convertMana(convertTarget, 10)} onClick={() => convertMana(convertTarget, 10)}
disabled={!store.elements[convertTarget]?.unlocked || store.rawMana < MANA_PER_ELEMENT * 10} disabled={!elements[convertTarget]?.unlocked || rawMana < MANA_PER_ELEMENT * 10}
> >
+10 ({MANA_PER_ELEMENT * 10}) +10 ({MANA_PER_ELEMENT * 10})
</Button> </Button>
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
onClick={() => store.convertMana(convertTarget, 100)} onClick={() => convertMana(convertTarget, 100)}
disabled={!store.elements[convertTarget]?.unlocked || store.rawMana < MANA_PER_ELEMENT * 100} disabled={!elements[convertTarget]?.unlocked || rawMana < MANA_PER_ELEMENT * 100}
> >
+100 ({MANA_PER_ELEMENT * 100}) +100 ({MANA_PER_ELEMENT * 100})
</Button> </Button>
@@ -91,7 +96,7 @@ export function LabTab() {
</p> </p>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2"> <div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
{Object.entries(store.elements) {Object.entries(elements)
.filter(([id, state]) => !state.unlocked && ELEMENTS[id]?.cat !== 'exotic') .filter(([id, state]) => !state.unlocked && ELEMENTS[id]?.cat !== 'exotic')
.map(([id]) => { .map(([id]) => {
const def = ELEMENTS[id]; const def = ELEMENTS[id];
@@ -106,8 +111,8 @@ export function LabTab() {
size="sm" size="sm"
variant="outline" variant="outline"
className="mt-1 w-full" className="mt-1 w-full"
disabled={store.rawMana < 500} disabled={rawMana < 500}
onClick={() => store.unlockElement(id)} onClick={() => unlockElement(id, 500)}
> >
Unlock Unlock
</Button> </Button>
@@ -128,10 +133,10 @@ export function LabTab() {
{Object.entries(ELEMENTS) {Object.entries(ELEMENTS)
.filter(([, def]) => def.recipe) .filter(([, def]) => def.recipe)
.map(([id, def]) => { .map(([id, def]) => {
const state = store.elements[id]; const state = elements[id];
const recipe = def.recipe!; const recipe = def.recipe!;
const canCraft = recipe.every( const canCraft = recipe.every(
(r) => (store.elements[r]?.current || 0) >= recipe.filter((x) => x === r).length (r) => (elements[r]?.current || 0) >= recipe.filter((x) => x === r).length
); );
return ( return (
@@ -156,7 +161,7 @@ export function LabTab() {
variant={canCraft ? 'default' : 'outline'} variant={canCraft ? 'default' : 'outline'}
className="w-full" className="w-full"
disabled={!canCraft} disabled={!canCraft}
onClick={() => store.craftComposite(id)} onClick={() => craftComposite(id, recipe)}
> >
Craft Craft
</Button> </Button>
+26 -17
View File
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useGameStore, fmt, fmtDec } from '@/lib/game/stores'; import { useSkillStore, useManaStore, useCombatStore, fmt, fmtDec } from '@/lib/game/stores';
import { SKILLS_DEF, SKILL_CATEGORIES } from '@/lib/game/constants'; import { SKILLS_DEF, SKILL_CATEGORIES } from '@/lib/game/constants';
import { getNextTierSkill, getTierMultiplier } from '@/lib/game/skill-evolution'; import { getNextTierSkill, getTierMultiplier } from '@/lib/game/skill-evolution';
import { computeEffects } from '@/lib/game/upgrade-effects'; import { computeEffects } from '@/lib/game/upgrade-effects';
@@ -16,10 +16,15 @@ interface SkillRowProps {
} }
export function SkillRow({ skillId, onUpgradeClick }: SkillRowProps) { export function SkillRow({ skillId, onUpgradeClick }: SkillRowProps) {
const store = useGameStore(); const skillState = useSkillStore((s) => s);
const rawMana = useManaStore((s) => s.rawMana);
const startStudyingSkill = useSkillStore((s) => s.startStudyingSkill);
const tierUpSkill = useSkillStore((s) => s.tierUpSkill);
const startParallelStudySkill = useSkillStore((s) => s.startParallelStudySkill);
const { studySpeedMult, studyCostMult, hasParallelStudy } = useStudyStats(); const { studySpeedMult, studyCostMult, hasParallelStudy } = useStudyStats();
const skillInfo = getSkillDisplayInfo(store, skillId); const skillInfo = getSkillDisplayInfo(skillState, skillId);
const { const {
currentTier, currentTier,
tieredSkillId, tieredSkillId,
@@ -27,13 +32,14 @@ export function SkillRow({ skillId, onUpgradeClick }: SkillRowProps) {
level, level,
maxed, maxed,
isStudying, isStudying,
savedProgress,
skillDisplayName, skillDisplayName,
prereqMet, prereqMet,
def def
} = skillInfo; } = skillInfo;
// Apply skill modifiers // Apply skill modifiers
const studyEffects = computeEffects(store.skillUpgrades || {}, store.skillTiers || {}); const studyEffects = computeEffects(skillState.skillUpgrades || {}, skillState.skillTiers || {});
const effectiveSpeedMult = studySpeedMult * studyEffects.studySpeedMultiplier; const effectiveSpeedMult = studySpeedMult * studyEffects.studySpeedMultiplier;
const tierStudyTime = def.studyTime * currentTier; const tierStudyTime = def.studyTime * currentTier;
@@ -43,15 +49,16 @@ export function SkillRow({ skillId, onUpgradeClick }: SkillRowProps) {
const cost = Math.floor(baseCost * studyCostMult); const cost = Math.floor(baseCost * studyCostMult);
// Check if any study is in progress (prevent switching topics) // Check if any study is in progress (prevent switching topics)
const isAnyStudyInProgress = store.currentAction === 'study' && store.currentStudyTarget; const currentAction = useCombatStore((s) => s.currentAction);
const isAnyStudyInProgress = currentAction === 'study' && skillState.currentStudyTarget;
// Can only study if: not maxed, prereqs met, has mana, and either no study in progress or already studying this skill // Can only study if: not maxed, prereqs met, has mana, and either no study in progress or already studying this skill
const canStudy = !maxed && prereqMet && store.rawMana >= cost && (!isAnyStudyInProgress || isStudying); const canStudy = !maxed && prereqMet && rawMana >= cost && (!isAnyStudyInProgress || isStudying);
const milestoneInfo = hasMilestoneUpgrade(store, tieredSkillId, level); const milestoneInfo = hasMilestoneUpgrade(skillState, tieredSkillId, level);
const nextTierSkill = getNextTierSkill(tieredSkillId); const nextTierSkill = getNextTierSkill(tieredSkillId);
const canTierUp = maxed && nextTierSkill; const canTierUp = maxed && nextTierSkill;
const selectedUpgrades = store.skillUpgrades[tieredSkillId] || []; const selectedUpgrades = skillState.skillUpgrades[tieredSkillId] || [];
const selectedL5 = selectedUpgrades.filter(u => u.includes('_l5')); const selectedL5 = selectedUpgrades.filter(u => u.includes('_l5'));
const selectedL10 = selectedUpgrades.filter(u => u.includes('_l10')); const selectedL10 = selectedUpgrades.filter(u => u.includes('_l10'));
@@ -84,7 +91,7 @@ export function SkillRow({ skillId, onUpgradeClick }: SkillRowProps) {
<div className="text-xs text-gray-400 italic">{def.desc}{currentTier > 1 && ` (Tier ${currentTier}: ${fmtDec(tierMultiplier, 0)}x effect)`}</div> <div className="text-xs text-gray-400 italic">{def.desc}{currentTier > 1 && ` (Tier ${currentTier}: ${fmtDec(tierMultiplier, 0)}x effect)`}</div>
{!prereqMet && def.req && ( {!prereqMet && def.req && (
<div className="text-xs text-red-400 mt-1"> <div className="text-xs text-red-400 mt-1">
Requires: {Object.entries(def.req).map(([r, rl]) => `${SKILLS_DEF[r]?.name} Lv.${rl}`).join(', ')} Requires: {Object.entries(def.req).map(([r, rl]) => `${SKILLS_DEF[r]?.name || r} Lv.${rl}`).join(', ')}
</div> </div>
)} )}
<div className="text-xs text-gray-500 mt-1"> <div className="text-xs text-gray-500 mt-1">
@@ -121,7 +128,7 @@ export function SkillRow({ skillId, onUpgradeClick }: SkillRowProps) {
{isStudying ? ( {isStudying ? (
<div className="text-xs text-purple-400"> <div className="text-xs text-purple-400">
{formatStudyTime(store.currentStudyTarget?.progress || 0)}/{formatStudyTime(tierStudyTime)} {formatStudyTime(savedProgress || 0)}/{formatStudyTime(tierStudyTime)}
</div> </div>
) : milestoneInfo ? ( ) : milestoneInfo ? (
<Button <Button
@@ -137,7 +144,7 @@ export function SkillRow({ skillId, onUpgradeClick }: SkillRowProps) {
<Button <Button
size="sm" size="sm"
className="bg-purple-600 hover:bg-purple-700" className="bg-purple-600 hover:bg-purple-700"
onClick={() => store.tierUpSkill(tieredSkillId)} onClick={() => tierUpSkill(tieredSkillId)}
> >
Tier Up Tier Up
</Button> </Button>
@@ -153,23 +160,23 @@ export function SkillRow({ skillId, onUpgradeClick }: SkillRowProps) {
variant={canStudy ? 'default' : 'outline'} variant={canStudy ? 'default' : 'outline'}
disabled={!canStudy} disabled={!canStudy}
className={canStudy ? 'bg-purple-600 hover:bg-purple-700' : 'opacity-50'} className={canStudy ? 'bg-purple-600 hover:bg-purple-700' : 'opacity-50'}
onClick={() => store.startStudyingSkill(tieredSkillId)} onClick={() => startStudyingSkill(tieredSkillId)}
> >
Study ({fmt(cost)}) Study ({fmt(cost)})
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
{!canStudy && isAnyStudyInProgress && !isStudying && ( {!canStudy && isAnyStudyInProgress && !isStudying && (
<TooltipContent> <TooltipContent>
<p>Cannot switch topics while studying {SKILLS_DEF[store.currentStudyTarget?.id || '']?.name || 'another skill'}</p> <p>Cannot switch topics while studying {SKILLS_DEF[skillState.currentStudyTarget?.id || '']?.name || 'another skill'}</p>
</TooltipContent> </TooltipContent>
)} )}
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
{/* Parallel Study button */} {/* Parallel Study button */}
{hasParallelStudy && {hasParallelStudy &&
store.currentStudyTarget && skillState.currentStudyTarget &&
!store.parallelStudyTarget && !skillState.parallelStudyTarget &&
store.currentStudyTarget.id !== tieredSkillId && skillState.currentStudyTarget.id !== tieredSkillId &&
canStudy && ( canStudy && (
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
@@ -178,7 +185,7 @@ export function SkillRow({ skillId, onUpgradeClick }: SkillRowProps) {
size="sm" size="sm"
variant="outline" variant="outline"
className="border-cyan-500 text-cyan-400 hover:bg-cyan-900/30" className="border-cyan-500 text-cyan-400 hover:bg-cyan-900/30"
onClick={() => store.startParallelStudySkill(tieredSkillId)} onClick={() => startParallelStudySkill(tieredSkillId)}
> >
</Button> </Button>
@@ -195,3 +202,5 @@ export function SkillRow({ skillId, onUpgradeClick }: SkillRowProps) {
</div> </div>
); );
} }
SkillRow.displayName = "SkillRow";
+16 -16
View File
@@ -1,6 +1,6 @@
import { SKILLS_DEF } from '@/lib/game/constants'; import { SKILLS_DEF } from '@/lib/game/constants';
import { SKILL_EVOLUTION_PATHS, getUpgradesForSkillAtMilestone, getTierMultiplier } from '@/lib/game/skill-evolution'; import { SKILL_EVOLUTION_PATHS, getUpgradesForSkillAtMilestone, getTierMultiplier } from '@/lib/game/skill-evolution';
import type { GameStore } from '@/lib/game/store'; import type { SkillState } from '@/lib/game/stores';
// Format study time // Format study time
export function formatStudyTime(hours: number): string { export function formatStudyTime(hours: number): string {
@@ -8,9 +8,9 @@ export function formatStudyTime(hours: number): string {
return `${hours.toFixed(1)}h`; return `${hours.toFixed(1)}h`;
} }
// Check if skill has milestone available // Check if skill has milestone upgrade available
export function hasMilestoneUpgrade( export function hasMilestoneUpgrade(
store: GameStore, skillState: SkillState,
skillId: string, skillId: string,
level: number level: number
): { milestone: 5 | 10; hasUpgrades: boolean; selectedCount: number } | null { ): { milestone: 5 | 10; hasUpgrades: boolean; selectedCount: number } | null {
@@ -19,16 +19,16 @@ export function hasMilestoneUpgrade(
if (!path) return null; if (!path) return null;
if (level >= 5) { if (level >= 5) {
const upgrades5 = getUpgradesForSkillAtMilestone(skillId, 5, store.skillTiers); const upgrades5 = getUpgradesForSkillAtMilestone(skillId, 5, skillState.skillTiers);
const selected5 = (store.skillUpgrades[skillId] || []).filter(id => id.includes('_l5')); const selected5 = (skillState.skillUpgrades[skillId] || []).filter(id => id.includes('_l5'));
if (upgrades5.length > 0 && selected5.length < 2) { if (upgrades5.length > 0 && selected5.length < 2) {
return { milestone: 5, hasUpgrades: true, selectedCount: selected5.length }; return { milestone: 5, hasUpgrades: true, selectedCount: selected5.length };
} }
} }
if (level >= 10) { if (level >= 10) {
const upgrades10 = getUpgradesForSkillAtMilestone(skillId, 10, store.skillTiers); const upgrades10 = getUpgradesForSkillAtMilestone(skillId, 10, skillState.skillTiers);
const selected10 = (store.skillUpgrades[skillId] || []).filter(id => id.includes('_l10')); const selected10 = (skillState.skillUpgrades[skillId] || []).filter(id => id.includes('_l10'));
if (upgrades10.length > 0 && selected10.length < 2) { if (upgrades10.length > 0 && selected10.length < 2) {
return { milestone: 10, hasUpgrades: true, selectedCount: selected10.length }; return { milestone: 10, hasUpgrades: true, selectedCount: selected10.length };
} }
@@ -39,28 +39,28 @@ export function hasMilestoneUpgrade(
// Get skill display info // Get skill display info
export function getSkillDisplayInfo( export function getSkillDisplayInfo(
store: GameStore, skillState: SkillState,
skillId: string skillId: string
) { ) {
const currentTier = store.skillTiers?.[skillId] || 1; const currentTier = skillState.skillTiers?.[skillId] || 1;
const tieredSkillId = currentTier > 1 ? `${skillId}_t${currentTier}` : skillId; const tieredSkillId = currentTier > 1 ? `${skillId}_t${currentTier}` : skillId;
const def = SKILLS_DEF[skillId]; const def = SKILLS_DEF[skillId];
const tierMultiplier = getTierMultiplier(tieredSkillId); const tierMultiplier = getTierMultiplier(tieredSkillId);
const level = store.skills[tieredSkillId] || store.skills[skillId] || 0; const level = skillState.skills[tieredSkillId] || skillState.skills[skillId] || 0;
const maxed = level >= def.max; const maxed = level >= (def?.max || 10);
const isStudying = (store.currentStudyTarget?.id === skillId || store.currentStudyTarget?.id === tieredSkillId) && store.currentStudyTarget?.type === 'skill'; const isStudying = (skillState.currentStudyTarget?.id === skillId || skillState.currentStudyTarget?.id === tieredSkillId) && skillState.currentStudyTarget?.type === 'skill';
const savedProgress = store.skillProgress[tieredSkillId] || store.skillProgress[skillId] || 0; const savedProgress = skillState.skillProgress[tieredSkillId] || skillState.skillProgress[skillId] || 0;
const tierDef = SKILL_EVOLUTION_PATHS[skillId]?.tiers.find(t => t.tier === currentTier); const tierDef = SKILL_EVOLUTION_PATHS[skillId]?.tiers.find(t => t.tier === currentTier);
const skillDisplayName = tierDef?.name || def.name; const skillDisplayName = tierDef?.name || def?.name || skillId;
// Check prerequisites // Check prerequisites
let prereqMet = true; let prereqMet = true;
if (def.req) { if (def?.req) {
for (const [r, rl] of Object.entries(def.req)) { for (const [r, rl] of Object.entries(def.req)) {
if ((store.skills[r] || 0) < rl) { if ((skillState.skills[r] || 0) < rl) {
prereqMet = false; prereqMet = false;
break; break;
} }
+12 -13
View File
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useGameStore, fmt, fmtDec } from '@/lib/game/stores'; import { useSkillStore, usePrestigeStore, fmt, fmtDec } from '@/lib/game/stores';
import { ELEMENTS } from '@/lib/game/constants'; import { ELEMENTS } from '@/lib/game/constants';
import { SKILL_EVOLUTION_PATHS, getTierMultiplier } from '@/lib/game/skill-evolution'; import { SKILL_EVOLUTION_PATHS, getTierMultiplier } from '@/lib/game/skill-evolution';
import { useManaStats, useCombatStats, useStudyStats } from '@/lib/game/hooks/useGameDerived'; import { useManaStats, useCombatStats, useStudyStats } from '@/lib/game/hooks/useGameDerived';
@@ -14,31 +14,35 @@ import { LoopStatsSection } from './StatsTab/LoopStatsSection';
import type { SkillUpgradeChoice } from '@/lib/game/types'; import type { SkillUpgradeChoice } from '@/lib/game/types';
export function StatsTab() { export function StatsTab() {
const store = useGameStore(); const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
const skills = useSkillStore((s) => s.skills);
const skillTiers = useSkillStore((s) => s.skillTiers);
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
const manaStats = useManaStats(); const manaStats = useManaStats();
const combatStats = useCombatStats(); const combatStats = useCombatStats();
const studyStats = useStudyStats(); const studyStats = useStudyStats();
// Compute element max // Compute element max
const elemMax = (() => { const elemMax = (() => {
const ea = store.skillTiers?.elemAttune || 1; const ea = skillTiers?.elemAttune || 1;
const tieredSkillId = ea > 1 ? `elemAttune_t${ea}` : 'elemAttune'; const tieredSkillId = ea > 1 ? `elemAttune_t${ea}` : 'elemAttune';
const level = store.skills[tieredSkillId] || store.skills.elemAttune || 0; const level = skills[tieredSkillId] || skills.elemAttune || 0;
const tierMult = getTierMultiplier(tieredSkillId); const tierMult = getTierMultiplier(tieredSkillId);
return 10 + level * 50 * tierMult + (store.prestigeUpgrades.elementalAttune || 0) * 25; return 10 + level * 50 * tierMult + (prestigeUpgrades.elementalAttune || 0) * 25;
})(); })();
// Get all selected skill upgrades // Get all selected skill upgrades
const getAllSelectedUpgrades = (): { skillId: string; upgrade: SkillUpgradeChoice }[] => { const getAllSelectedUpgrades = (): { skillId: string; upgrade: SkillUpgradeChoice }[] => {
const upgrades: { skillId: string; upgrade: SkillUpgradeChoice }[] = []; const upgrades: { skillId: string; upgrade: SkillUpgradeChoice }[] = [];
for (const [skillId, selectedIds] of Object.entries(store.skillUpgrades)) { for (const [skillId, selectedIds] of Object.entries(skillUpgrades)) {
const baseSkillId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId; const baseSkillId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId;
const path = SKILL_EVOLUTION_PATHS[baseSkillId]; const path = SKILL_EVOLUTION_PATHS[baseSkillId];
if (!path) continue; if (!path) continue;
for (const tier of path.tiers) { for (const tier of path.tiers) {
if (tier.skillId === skillId) { if (tier.skillId === skillId) {
for (const upgradeId of selectedIds) { for (const upgradeId of selectedIds) {
const upgrade = (tier as any).upgrades?.find(u => u.id === upgradeId); const upgrade = (tier as any).upgrades?.find((u: any) => u.id === upgradeId);
if (upgrade) { if (upgrade) {
upgrades.push({ skillId, upgrade }); upgrades.push({ skillId, upgrade });
} }
@@ -60,31 +64,26 @@ export function StatsTab() {
clickMana={manaStats.clickMana} clickMana={manaStats.clickMana}
meditationMultiplier={manaStats.meditationMultiplier} meditationMultiplier={manaStats.meditationMultiplier}
upgradeEffects={manaStats.upgradeEffects} upgradeEffects={manaStats.upgradeEffects}
store={store}
elemMax={elemMax} elemMax={elemMax}
selectedUpgrades={selectedUpgrades} selectedUpgrades={selectedUpgrades}
/> />
<CombatStatsSection <CombatStatsSection
store={store}
activeSpellDef={combatStats.activeSpellDef} activeSpellDef={combatStats.activeSpellDef}
pactMultiplier={combatStats.pactMultiplier} pactMultiplier={combatStats.pactMultiplier}
/> />
<PactStatusSection <PactStatusSection
store={store}
pactMultiplier={combatStats.pactMultiplier} pactMultiplier={combatStats.pactMultiplier}
pactInsightMultiplier={combatStats.pactInsightMultiplier} pactInsightMultiplier={combatStats.pactInsightMultiplier}
/> />
<StudyStatsSection <StudyStatsSection
studySpeedMult={studyStats.studySpeedMult} studySpeedMult={studyStats.studySpeedMult}
studyCostMult={studyStats.studyCostMult} studyCostMult={studyStats.studyCostMult}
store={store}
/> />
<ElementStatsSection <ElementStatsSection
store={store}
elemMax={elemMax} elemMax={elemMax}
/> />
<ActiveUpgradesSection selectedUpgrades={selectedUpgrades} /> <ActiveUpgradesSection selectedUpgrades={selectedUpgrades} />
<LoopStatsSection store={store} /> <LoopStatsSection />
</div> </div>
); );
} }
@@ -2,18 +2,26 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Swords } from 'lucide-react'; import { Swords } from 'lucide-react';
import type { GameStore } from '@/lib/game/stores';
import { fmt, fmtDec } from '@/lib/game/stores'; import { fmt, fmtDec } from '@/lib/game/stores';
import { useSkillStore } from '@/lib/game/stores';
import { getUnifiedEffects } from '@/lib/game/effects'; import { getUnifiedEffects } from '@/lib/game/effects';
interface CombatStatsSectionProps { interface CombatStatsSectionProps {
store: GameStore;
activeSpellDef: any; activeSpellDef: any;
pactMultiplier: number; pactMultiplier: number;
} }
export function CombatStatsSection({ store, activeSpellDef, pactMultiplier }: CombatStatsSectionProps) { export function CombatStatsSection({ activeSpellDef, pactMultiplier }: CombatStatsSectionProps) {
const upgradeEffects = getUnifiedEffects(store); const skills = useSkillStore((s) => s.skills);
const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
const skillTiers = useSkillStore((s) => s.skillTiers);
const upgradeEffects = getUnifiedEffects({
skillUpgrades,
skillTiers,
equippedInstances: {},
equipmentInstances: {},
});
return ( return (
<Card className="bg-gray-900/80 border-gray-700"> <Card className="bg-gray-900/80 border-gray-700">
@@ -32,25 +40,25 @@ export function CombatStatsSection({ store, activeSpellDef, pactMultiplier }: Co
</div> </div>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Combat Training Bonus:</span> <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>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Arcane Fury Multiplier:</span> <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>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Elemental Mastery:</span> <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>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Guardian Bane:</span> <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> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Critical Hit Chance:</span> <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>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Critical Multiplier:</span> <span className="text-gray-400">Critical Multiplier:</span>
@@ -58,7 +66,7 @@ export function CombatStatsSection({ store, activeSpellDef, pactMultiplier }: Co
</div> </div>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Spell Echo Chance:</span> <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>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Pact Multiplier:</span> <span className="text-gray-400">Pact Multiplier:</span>
@@ -66,7 +74,7 @@ export function CombatStatsSection({ store, activeSpellDef, pactMultiplier }: Co
</div> </div>
<div className="flex justify-between text-sm font-semibold border-t border-gray-700 pt-2"> <div className="flex justify-between text-sm font-semibold border-t border-gray-700 pt-2">
<span className="text-gray-300">Total Damage:</span> <span className="text-gray-300">Total Damage:</span>
<span className="text-red-400">{fmt(store.activeSpell ? activeSpellDef?.dmg * pactMultiplier : 0)}</span> <span className="text-red-400">{fmt(activeSpellDef ? activeSpellDef.dmg * pactMultiplier : 0)}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -6,17 +6,22 @@ import { FlaskConical } from 'lucide-react';
import { ELEMENTS } from '@/lib/game/constants'; import { ELEMENTS } from '@/lib/game/constants';
import { getTierMultiplier } from '@/lib/game/skill-evolution'; import { getTierMultiplier } from '@/lib/game/skill-evolution';
import { fmt, fmtDec } from '@/lib/game/stores'; import { fmt, fmtDec } from '@/lib/game/stores';
import { useSkillStore, usePrestigeStore, useManaStore } from '@/lib/game/stores';
interface ElementStatsSectionProps { interface ElementStatsSectionProps {
store: any;
elemMax: number; elemMax: number;
} }
export function ElementStatsSection({ store, elemMax }: ElementStatsSectionProps) { export function ElementStatsSection({ elemMax }: ElementStatsSectionProps) {
const skills = useSkillStore((s) => s.skills);
const skillTiers = useSkillStore((s) => s.skillTiers);
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
const elements = useManaStore((s) => s.elements);
const getElemAttunementBonus = () => { const getElemAttunementBonus = () => {
const ea = store.skillTiers?.elemAttune || 1; const ea = skillTiers?.elemAttune || 1;
const tieredSkillId = ea > 1 ? `elemAttune_t${ea}` : 'elemAttune'; const tieredSkillId = ea > 1 ? `elemAttune_t${ea}` : 'elemAttune';
const level = store.skills[tieredSkillId] || store.skills.elemAttune || 0; const level = skills[tieredSkillId] || skills.elemAttune || 0;
const tierMult = getTierMultiplier(tieredSkillId); const tierMult = getTierMultiplier(tieredSkillId);
return level * 50 * tierMult; return level * 50 * tierMult;
}; };
@@ -42,24 +47,24 @@ export function ElementStatsSection({ store, elemMax }: ElementStatsSectionProps
</div> </div>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Prestige Attunement:</span> <span className="text-gray-400">Prestige Attunement:</span>
<span className="text-green-300">+{(store.prestigeUpgrades.elementalAttune || 0) * 25}</span> <span className="text-green-300">+{(prestigeUpgrades.elementalAttune || 0) * 25}</span>
</div> </div>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Unlocked Elements:</span> <span className="text-gray-400">Unlocked Elements:</span>
<span className="text-green-300">{Object.values(store.elements || {}).filter((e: any) => e.unlocked).length} / {Object.keys(ELEMENTS).length}</span> <span className="text-green-300">{Object.values(elements || {}).filter((e: any) => e.unlocked).length} / {Object.keys(ELEMENTS).length}</span>
</div> </div>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Elem. Crafting Bonus:</span> <span className="text-gray-400">Elem. Crafting Bonus:</span>
<span className="text-green-300">×{fmtDec(1 + (store.skills.elemCrafting || 0) * 0.25, 2)}</span> <span className="text-green-300">×{fmtDec(1 + (skills.elemCrafting || 0) * 0.25, 2)}</span>
</div> </div>
</div> </div>
</div> </div>
<Separator className="bg-gray-700 my-3" /> <Separator className="bg-gray-700 my-3" />
<div className="text-xs text-gray-400 mb-2">Elemental Mana Pools:</div> <div className="text-xs text-gray-400 mb-2">Elemental Mana Pools:</div>
<div className="grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 gap-2"> <div className="grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 gap-2">
{Object.entries(store.elements) {Object.entries(elements)
.filter(([, state]: [string, any]) => state.unlocked) .filter(([, state]: [string, any]) => state.unlocked)
.map(([id, state]: [string, any]) => { .map(([id, state]: [string, any]) => {
const def = ELEMENTS[id]; const def = ELEMENTS[id];
@@ -4,14 +4,20 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { RotateCcw } from 'lucide-react'; import { RotateCcw } from 'lucide-react';
import { fmt } from '@/lib/game/stores'; import { fmt } from '@/lib/game/stores';
import { useCombatStore, useSkillStore, usePrestigeStore, useManaStore } from '@/lib/game/stores';
interface LoopStatsSectionProps { export function LoopStatsSection() {
store: any; const spells = useCombatStore((s) => s.spells);
} const skills = useSkillStore((s) => s.skills);
const insight = usePrestigeStore((s) => s.insight);
const totalInsight = usePrestigeStore((s) => s.totalInsight);
const maxFloorReached = useCombatStore((s) => s.maxFloorReached);
const totalManaGathered = useManaStore((s) => s.totalManaGathered);
const loopCount = usePrestigeStore((s) => s.loopCount);
const memorySlots = useSkillStore((s) => s.memorySlots);
export function LoopStatsSection({ store }: LoopStatsSectionProps) { const spellsLearned = Object.values(spells || {}).filter((s: any) => s.learned).length;
const spellsLearned = Object.values(store.spells || {}).filter((s) => s.learned).length; const totalSkillLevels = Object.values(skills || {}).reduce((a: number, b: number) => a + b, 0);
const totalSkillLevels = Object.values(store.skills || {}).reduce((a: number, b: number) => a + b, 0);
return ( return (
<Card className="bg-gray-900/80 border-gray-700"> <Card className="bg-gray-900/80 border-gray-700">
@@ -24,19 +30,19 @@ export function LoopStatsSection({ store }: LoopStatsSectionProps) {
<CardContent> <CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="p-3 bg-gray-800/50 rounded text-center"> <div className="p-3 bg-gray-800/50 rounded text-center">
<div className="text-2xl font-bold text-amber-400 game-mono">{store.loopCount}</div> <div className="text-2xl font-bold text-amber-400 game-mono">{loopCount}</div>
<div className="text-xs text-gray-400">Loops Completed</div> <div className="text-xs text-gray-400">Loops Completed</div>
</div> </div>
<div className="p-3 bg-gray-800/50 rounded text-center"> <div className="p-3 bg-gray-800/50 rounded text-center">
<div className="text-2xl font-bold text-purple-400 game-mono">{fmt(store.insight)}</div> <div className="text-2xl font-bold text-purple-400 game-mono">{fmt(insight)}</div>
<div className="text-xs text-gray-400">Current Insight</div> <div className="text-xs text-gray-400">Current Insight</div>
</div> </div>
<div className="p-3 bg-gray-800/50 rounded text-center"> <div className="p-3 bg-gray-800/50 rounded text-center">
<div className="text-2xl font-bold text-blue-400 game-mono">{fmt(store.totalInsight)}</div> <div className="text-2xl font-bold text-blue-400 game-mono">{fmt(totalInsight)}</div>
<div className="text-xs text-gray-400">Total Insight</div> <div className="text-xs text-gray-400">Total Insight</div>
</div> </div>
<div className="p-3 bg-gray-800/50 rounded text-center"> <div className="p-3 bg-gray-800/50 rounded text-center">
<div className="text-2xl font-bold text-green-400 game-mono">{store.maxFloorReached}</div> <div className="text-2xl font-bold text-green-400 game-mono">{maxFloorReached}</div>
<div className="text-xs text-gray-400">Max Floor</div> <div className="text-xs text-gray-400">Max Floor</div>
</div> </div>
</div> </div>
@@ -51,11 +57,11 @@ export function LoopStatsSection({ store }: LoopStatsSectionProps) {
<div className="text-xs text-gray-400">Total Skill Levels</div> <div className="text-xs text-gray-400">Total Skill Levels</div>
</div> </div>
<div className="p-3 bg-gray-800/50 rounded text-center"> <div className="p-3 bg-gray-800/50 rounded text-center">
<div className="text-xl font-bold text-gray-300 game-mono">{fmt(store.totalManaGathered)}</div> <div className="text-xl font-bold text-gray-300 game-mono">{fmt(totalManaGathered)}</div>
<div className="text-xs text-gray-400">Total Mana Gathered</div> <div className="text-xs text-gray-400">Total Mana Gathered</div>
</div> </div>
<div className="p-3 bg-gray-800/50 rounded text-center"> <div className="p-3 bg-gray-800/50 rounded text-center">
<div className="text-xl font-bold text-gray-300 game-mono">{store.memorySlots}</div> <div className="text-xl font-bold text-gray-300 game-mono">{memorySlots}</div>
<div className="text-xs text-gray-400">Memory Slots</div> <div className="text-xs text-gray-400">Memory Slots</div>
</div> </div>
</div> </div>
@@ -4,6 +4,7 @@ import { fmt, fmtDec } from '@/lib/game/stores';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Droplet } from 'lucide-react'; import { Droplet } from 'lucide-react';
import type { SkillUpgradeChoice } from '@/lib/game/types'; import type { SkillUpgradeChoice } from '@/lib/game/types';
import { useSkillStore, usePrestigeStore } from '@/lib/game/stores';
interface ManaStatsSectionProps { interface ManaStatsSectionProps {
maxMana: number; maxMana: number;
@@ -12,7 +13,6 @@ interface ManaStatsSectionProps {
clickMana: number; clickMana: number;
meditationMultiplier: number; meditationMultiplier: number;
upgradeEffects: any; upgradeEffects: any;
store: any;
elemMax: number; elemMax: number;
selectedUpgrades: { skillId: string; upgrade: SkillUpgradeChoice }[]; selectedUpgrades: { skillId: string; upgrade: SkillUpgradeChoice }[];
} }
@@ -24,13 +24,15 @@ export function ManaStatsSection({
clickMana, clickMana,
meditationMultiplier, meditationMultiplier,
upgradeEffects, upgradeEffects,
store,
elemMax, elemMax,
selectedUpgrades, selectedUpgrades,
}: ManaStatsSectionProps) { }: ManaStatsSectionProps) {
const skills = useSkillStore((s) => s.skills);
const skillTiers = useSkillStore((s) => s.skillTiers);
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
const getTierMultiplier = (skillId: string) => { const getTierMultiplier = (skillId: string) => {
// Simplified - import from skill-evolution in real implementation return 1; // Simplified - import from skill-evolution in real implementation
return 1;
}; };
return ( return (
@@ -52,9 +54,9 @@ export function ManaStatsSection({
<span className="text-gray-400">Mana Well Bonus:</span> <span className="text-gray-400">Mana Well Bonus:</span>
<span className="text-blue-300"> <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 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); const tierMult = getTierMultiplier(tieredSkillId);
return `+${fmt(level * 100 * tierMult)} (${level} lvl × 100 × ${tierMult}x tier)`; return `+${fmt(level * 100 * tierMult)} (${level} lvl × 100 × ${tierMult}x tier)`;
})()} })()}
@@ -62,7 +64,7 @@ export function ManaStatsSection({
</div> </div>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Prestige Mana Well:</span> <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> </div>
{upgradeEffects.maxManaBonus > 0 && ( {upgradeEffects.maxManaBonus > 0 && (
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
@@ -90,25 +92,25 @@ export function ManaStatsSection({
<span className="text-gray-400">Mana Flow Bonus:</span> <span className="text-gray-400">Mana Flow Bonus:</span>
<span className="text-blue-300"> <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 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); const tierMult = getTierMultiplier(tieredSkillId);
return `+${fmtDec(level * 1 * tierMult)}/hr (${level} lvl × 1 × ${tierMult}x tier)`; return `+${fmtDec(level * 1 * tierMult, 2)}/hr (${level} lvl × 1 × ${tierMult}x tier)`;
})()} })()}
</span> </span>
</div> </div>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Mana Spring Bonus:</span> <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>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Prestige Mana Flow:</span> <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>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Temporal Echo:</span> <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>
<div className="flex justify-between text-sm font-semibold border-t border-gray-700 pt-2"> <div className="flex justify-between text-sm font-semibold border-t border-gray-700 pt-2">
<span className="text-gray-300">Base Regen:</span> <span className="text-gray-300">Base Regen:</span>
@@ -142,15 +144,15 @@ export function ManaStatsSection({
</div> </div>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Mana Tap Bonus:</span> <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>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Mana Surge Bonus:</span> <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>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Mana Overflow:</span> <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> </div>
<div className="space-y-2"> <div className="space-y-2">
@@ -4,15 +4,19 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Trophy } from 'lucide-react'; import { Trophy } from 'lucide-react';
import { fmtDec } from '@/lib/game/stores'; import { fmtDec } from '@/lib/game/stores';
import { ELEMENTS } from '@/lib/game/constants'; import { ELEMENTS } from '@/lib/game/constants';
import { usePrestigeStore, useManaStore } from '@/lib/game/stores';
interface PactStatusSectionProps { interface PactStatusSectionProps {
store: any;
pactMultiplier: number; pactMultiplier: number;
pactInsightMultiplier: number; pactInsightMultiplier: number;
} }
export function PactStatusSection({ store, pactMultiplier, pactInsightMultiplier }: PactStatusSectionProps) { export function PactStatusSection({ pactMultiplier, pactInsightMultiplier }: PactStatusSectionProps) {
const pactInterferenceMitigation = store.pactInterferenceMitigation || 0; const signedPacts = usePrestigeStore((s) => s.signedPacts);
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
const elements = useManaStore((s) => s.elements);
const pactInterferenceMitigation = prestigeUpgrades?.pactInterferenceMitigation || 0;
return ( return (
<Card className="bg-gray-900/80 border-gray-700"> <Card className="bg-gray-900/80 border-gray-700">
@@ -27,7 +31,7 @@ export function PactStatusSection({ store, pactMultiplier, pactInsightMultiplier
<div className="space-y-2"> <div className="space-y-2">
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Pact Slots:</span> <span className="text-gray-400">Pact Slots:</span>
<span className="text-amber-300">{store.signedPacts.length} / {1 + (store.prestigeUpgrades.pactCapacity || 0)}</span> <span className="text-amber-300">{signedPacts.length} / {1 + (prestigeUpgrades.pactCapacity || 0)}</span>
</div> </div>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Damage Multiplier:</span> <span className="text-gray-400">Damage Multiplier:</span>
@@ -37,7 +41,7 @@ export function PactStatusSection({ store, pactMultiplier, pactInsightMultiplier
<span className="text-gray-400">Insight Multiplier:</span> <span className="text-gray-400">Insight Multiplier:</span>
<span className="text-purple-300">×{fmtDec(pactInsightMultiplier, 2)}</span> <span className="text-purple-300">×{fmtDec(pactInsightMultiplier, 2)}</span>
</div> </div>
{store.signedPacts.length > 1 && ( {signedPacts.length > 1 && (
<> <>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Interference Mitigation:</span> <span className="text-gray-400">Interference Mitigation:</span>
@@ -55,8 +59,8 @@ export function PactStatusSection({ store, pactMultiplier, pactInsightMultiplier
<div className="space-y-2"> <div className="space-y-2">
<div className="text-sm text-gray-400 mb-2">Unlocked Mana Types:</div> <div className="text-sm text-gray-400 mb-2">Unlocked Mana Types:</div>
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{Object.keys(store.elements).map((id) => { {Object.keys(elements).map((id) => {
const state = store.elements[id]; const state = elements[id];
if (!state.unlocked) return null; if (!state.unlocked) return null;
const elem = ELEMENTS[id]; const elem = ELEMENTS[id];
return ( return (
@@ -3,14 +3,16 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { BookOpen } from 'lucide-react'; import { BookOpen } from 'lucide-react';
import { fmtDec } from '@/lib/game/stores'; import { fmtDec } from '@/lib/game/stores';
import { useSkillStore } from '@/lib/game/stores';
interface StudyStatsSectionProps { interface StudyStatsSectionProps {
studySpeedMult: number; studySpeedMult: number;
studyCostMult: number; studyCostMult: number;
store: any;
} }
export function StudyStatsSection({ studySpeedMult, studyCostMult, store }: StudyStatsSectionProps) { export function StudyStatsSection({ studySpeedMult, studyCostMult }: StudyStatsSectionProps) {
const skills = useSkillStore((s) => s.skills);
return ( return (
<Card className="bg-gray-900/80 border-gray-700"> <Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
@@ -28,7 +30,7 @@ export function StudyStatsSection({ studySpeedMult, studyCostMult, store }: Stud
</div> </div>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Quick Learner Bonus:</span> <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> </div>
<div className="space-y-2"> <div className="space-y-2">
@@ -38,13 +40,13 @@ export function StudyStatsSection({ studySpeedMult, studyCostMult, store }: Stud
</div> </div>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Focused Mind Bonus:</span> <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> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-gray-400">Progress Retention:</span> <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> </div>
</div> </div>
+11 -9
View File
@@ -4,20 +4,22 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Sparkles, Unlock } from 'lucide-react'; import { Sparkles, Unlock } from 'lucide-react';
import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements'; import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements';
import { useGameStore } from '@/lib/game/store'; import { usePrestigeStore } from '@/lib/game/stores';
export function AttunementDebug() { export function AttunementDebug() {
const store = useGameStore((s) => s); const attunements = usePrestigeStore((s) => s.attunements);
const debugUnlockAttunement = usePrestigeStore((s) => s.debugUnlockAttunement);
const debugAddAttunementXP = usePrestigeStore((s) => s.debugAddAttunementXP);
const handleUnlockAttunement = (id: string) => { const handleUnlockAttunement = (id: string) => {
if (useGameStore.getState().debugUnlockAttunement) { if (debugUnlockAttunement) {
useGameStore.getState().debugUnlockAttunement(id); debugUnlockAttunement(id);
} }
}; };
const handleAddAttunementXP = (id: string, amount: number) => { const handleAddAttunementXP = (id: string, amount: number) => {
if (useGameStore.getState().debugAddAttunementXP) { if (debugAddAttunementXP) {
useGameStore.getState().debugAddAttunementXP(id, amount); debugAddAttunementXP(id, amount);
} }
}; };
@@ -31,9 +33,9 @@ export function AttunementDebug() {
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-3">
{Object.entries(ATTUNEMENTS_DEF).map(([id, def]) => { {Object.entries(ATTUNEMENTS_DEF).map(([id, def]) => {
const isActive = store.attunements?.[id]?.active; const isActive = attunements?.[id]?.active;
const level = store.attunements?.[id]?.level || 1; const level = attunements?.[id]?.level || 1;
const xp = store.attunements?.[id]?.experience || 0; const xp = attunements?.[id]?.experience || 0;
return ( return (
<div key={id} className="flex items-center justify-between p-2 bg-gray-800/50 rounded"> <div key={id} className="flex items-center justify-between p-2 bg-gray-800/50 rounded">
+9 -6
View File
@@ -3,18 +3,21 @@
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Star, Lock } from 'lucide-react'; import { Star, Lock } from 'lucide-react';
import { useGameStore } from '@/lib/game/store'; import { useManaStore } from '@/lib/game/stores';
import { ELEMENTS } from '@/lib/game/constants';
export function ElementDebug() { export function ElementDebug() {
const store = useGameStore((s) => s); const elements = useManaStore((s) => s.elements);
const unlockElement = useManaStore((s) => s.unlockElement);
const debugAddElementalMana = useManaStore((s) => s.debugAddElementalMana);
const handleUnlockElement = (element: string) => { const handleUnlockElement = (element: string) => {
useGameStore.getState().unlockElement(element); unlockElement(element, 500);
}; };
const handleAddElementalMana = (element: string, amount: number) => { const handleAddElementalMana = (element: string, amount: number) => {
if (useGameStore.getState().debugAddElementalMana) { if (debugAddElementalMana) {
useGameStore.getState().debugAddElementalMana(element, amount); debugAddElementalMana(element, amount);
} }
}; };
@@ -28,7 +31,7 @@ export function ElementDebug() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 gap-2"> <div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 gap-2">
{Object.entries(store.elements).map(([id, elem]) => { {Object.entries(elements).map(([id, elem]) => {
const def = ELEMENTS[id]; const def = ELEMENTS[id];
return ( return (
<div <div
+42 -40
View File
@@ -10,18 +10,32 @@ import {
RotateCcw, AlertTriangle, Zap, Clock, Settings, Eye, RotateCcw, AlertTriangle, Zap, Clock, Settings, Eye,
} from 'lucide-react'; } from 'lucide-react';
import { useDebug } from '@/lib/game/debug-context'; import { useDebug } from '@/lib/game/debug-context';
import { useGameStore } from '@/lib/game/store'; import { useGameStore, useManaStore } from '@/lib/game/stores';
import { computeMaxMana } from '@/lib/game/stores';
export function GameStateDebug() { export function GameStateDebug() {
const [confirmReset, setConfirmReset] = useState(false); const [confirmReset, setConfirmReset] = useState(false);
const { showComponentNames, toggleComponentNames } = useDebug(); const { showComponentNames, toggleComponentNames } = useDebug();
// Get state from store // Get state from modular stores
const store = useGameStore((s) => s); const rawMana = useManaStore((s) => s.rawMana);
const elements = useManaStore((s) => s.elements);
const unlockElement = useManaStore((s) => s.unlockElement);
const gatherMana = useGameStore((s) => s.gatherMana);
const day = useGameStore((s) => s.day);
const hour = useGameStore((s) => s.hour);
const paused = useGameStore((s) => s.paused);
const togglePause = useGameStore((s) => s.togglePause);
// Get actions from stores
const resetGame = useGameStore((s) => s.resetGame);
const setTime = useGameStore((s) => s.debugSetTime);
const setFloor = useGameStore((s) => s.debugSetFloor);
const resetHP = useGameStore((s) => s.resetFloorHP);
const handleReset = () => { const handleReset = () => {
if (confirmReset) { if (confirmReset) {
useGameStore.getState().resetGame(); resetGame();
setConfirmReset(false); setConfirmReset(false);
} else { } else {
setConfirmReset(true); setConfirmReset(true);
@@ -30,18 +44,16 @@ export function GameStateDebug() {
}; };
const handleAddMana = (amount: number) => { const handleAddMana = (amount: number) => {
// Use gatherMana multiple times to add mana
const state = useGameStore.getState();
for (let i = 0; i < amount; i++) { for (let i = 0; i < amount; i++) {
state.gatherMana(); gatherMana();
} }
}; };
const handleSetTime = (day: number, hour: number) => { const getMaxMana = () => {
const state = useGameStore.getState(); return computeMaxMana(
if (state.debugSetTime) { { skills: {}, prestigeUpgrades: {}, skillUpgrades: {}, skillTiers: {} },
state.debugSetTime(day, hour); { maxManaBonus: 0, maxManaMultiplier: 1 }
} );
}; };
return ( return (
@@ -126,7 +138,7 @@ export function GameStateDebug() {
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-3">
<div className="text-xs text-gray-400 mb-2"> <div className="text-xs text-gray-400 mb-2">
Current: {store.rawMana} / {store.getMaxMana?.() || '?'} Current: {rawMana} / {getMaxMana() || '?'}
</div> </div>
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<Button size="sm" variant="outline" onClick={() => handleAddMana(10)}> <Button size="sm" variant="outline" onClick={() => handleAddMana(10)}>
@@ -143,15 +155,15 @@ export function GameStateDebug() {
</Button> </Button>
</div> </div>
<Separator className="bg-gray-700" /> <Separator className="bg-gray-700" />
<div className="text-xs text-gray-400">Fill to max:</div> <div className="text-xs text-gray-400 mb-2">Fill to max:</div>
<Button <Button
size="sm" size="sm"
className="w-full bg-blue-600 hover:bg-blue-700" className="w-full bg-blue-600 hover:bg-blue-700"
onClick={() => { onClick={() => {
const max = store.getMaxMana?.() || 100; const max = getMaxMana() || 100;
const current = store.rawMana; const current = rawMana;
for (let i = 0; i < Math.floor(max - current); i++) { for (let i = 0; i < Math.floor(max - current); i++) {
store.gatherMana(); gatherMana();
} }
}} }}
> >
@@ -170,19 +182,19 @@ export function GameStateDebug() {
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-3">
<div className="text-xs text-gray-400"> <div className="text-xs text-gray-400">
Current: Day {store.day}, Hour {store.hour} Current: Day {day}, Hour {hour}
</div> </div>
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<Button size="sm" variant="outline" onClick={() => handleSetTime(1, 0)}> <Button size="sm" variant="outline" onClick={() => setTime?.(1, 0)}>
Day 1 Day 1
</Button> </Button>
<Button size="sm" variant="outline" onClick={() => handleSetTime(10, 0)}> <Button size="sm" variant="outline" onClick={() => setTime?.(10, 0)}>
Day 10 Day 10
</Button> </Button>
<Button size="sm" variant="outline" onClick={() => handleSetTime(20, 0)}> <Button size="sm" variant="outline" onClick={() => setTime?.(20, 0)}>
Day 20 Day 20
</Button> </Button>
<Button size="sm" variant="outline" onClick={() => handleSetTime(30, 0)}> <Button size="sm" variant="outline" onClick={() => setTime?.(30, 0)}>
Day 30 Day 30
</Button> </Button>
</div> </div>
@@ -191,9 +203,9 @@ export function GameStateDebug() {
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
onClick={() => useGameStore.getState().togglePause()} onClick={togglePause}
> >
{store.paused ? '▶ Resume' : '⏸ Pause'} {paused ? '▶ Resume' : '⏸ Pause'}
</Button> </Button>
</div> </div>
</CardContent> </CardContent>
@@ -215,8 +227,8 @@ export function GameStateDebug() {
onClick={() => { onClick={() => {
// Unlock all base elements // Unlock all base elements
['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'].forEach(e => { ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'].forEach(e => {
if (!store.elements[e]?.unlocked) { if (!elements[e]?.unlocked) {
useGameStore.getState().unlockElement(e); unlockElement(e, 500);
} }
}); });
}} }}
@@ -229,8 +241,8 @@ export function GameStateDebug() {
onClick={() => { onClick={() => {
// Unlock utility elements // Unlock utility elements
['transference'].forEach(e => { ['transference'].forEach(e => {
if (!store.elements[e]?.unlocked) { if (!elements[e]?.unlocked) {
useGameStore.getState().unlockElement(e); unlockElement(e, 500);
} }
}); });
}} }}
@@ -240,24 +252,14 @@ export function GameStateDebug() {
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
onClick={() => { onClick={() => setFloor?.(100)}
// Max floor
if (store.debugSetFloor) {
useGameStore.getState().debugSetFloor(100);
}
}}
> >
Skip to Floor 100 Skip to Floor 100
</Button> </Button>
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
onClick={() => { onClick={() => resetHP?.()}
// Reset floor HP
if (store.resetFloorHP) {
useGameStore.getState().resetFloorHP();
}
}}
> >
Reset Floor HP Reset Floor HP
</Button> </Button>
+75 -48
View File
@@ -4,10 +4,12 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { BookOpen } from 'lucide-react'; import { BookOpen } from 'lucide-react';
import { useGameStore } from '@/lib/game/store'; import { useSkillStore, useGameStore } from '@/lib/game/stores';
export function SkillDebug() { export function SkillDebug() {
const store = useGameStore((s) => s); const skills = useSkillStore((s) => s.skills);
const setSkills = useSkillStore.setState;
const setGameState = useGameStore.setState;
return ( return (
<Card className="bg-gray-900/80 border-gray-700 md:col-span-2"> <Card className="bg-gray-900/80 border-gray-700 md:col-span-2">
@@ -28,11 +30,13 @@ export function SkillDebug() {
variant="outline" variant="outline"
onClick={() => { onClick={() => {
// Level up all enchanting skills by 1 // Level up all enchanting skills by 1
setSkills((prev) => {
const enchantSkills = ['enchanting', 'efficientEnchant', 'enchantSpeed', 'essenceRefining']; const enchantSkills = ['enchanting', 'efficientEnchant', 'enchantSpeed', 'essenceRefining'];
const newSkills = { ...prev.skills };
enchantSkills.forEach(skillId => { enchantSkills.forEach(skillId => {
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10); newSkills[skillId] = Math.min((newSkills[skillId] || 0) + 1, 10);
// Force update });
useGameStore.setState({ skills: { ...store.skills } }); return { ...prev, skills: newSkills };
}); });
}} }}
> >
@@ -43,11 +47,14 @@ export function SkillDebug() {
variant="outline" variant="outline"
onClick={() => { onClick={() => {
// Max all enchanting skills // Max all enchanting skills
setSkills((prev) => {
const enchantSkills = ['enchanting', 'efficientEnchant', 'enchantSpeed', 'essenceRefining']; const enchantSkills = ['enchanting', 'efficientEnchant', 'enchantSpeed', 'essenceRefining'];
const newSkills = { ...prev.skills };
enchantSkills.forEach(skillId => { enchantSkills.forEach(skillId => {
store.skills[skillId] = 10; newSkills[skillId] = 10;
});
return { ...prev, skills: newSkills };
}); });
useGameStore.setState({ skills: { ...store.skills } });
}} }}
> >
Max All Enchanting Max All Enchanting
@@ -64,10 +71,13 @@ export function SkillDebug() {
variant="outline" variant="outline"
onClick={() => { onClick={() => {
const manaSkills = ['manaWell', 'manaFlow', 'manaOverflow', 'fireManaCap', 'waterManaCap', 'airManaCap', 'earthManaCap', 'lightManaCap', 'darkManaCap', 'deathManaCap', 'metalManaCap', 'sandManaCap', 'lightningManaCap', 'transferenceManaCap']; const manaSkills = ['manaWell', 'manaFlow', 'manaOverflow', 'fireManaCap', 'waterManaCap', 'airManaCap', 'earthManaCap', 'lightManaCap', 'darkManaCap', 'deathManaCap', 'metalManaCap', 'sandManaCap', 'lightningManaCap', 'transferenceManaCap'];
setSkills((prev) => {
const newSkills = { ...prev.skills };
manaSkills.forEach(skillId => { manaSkills.forEach(skillId => {
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10); newSkills[skillId] = Math.min((newSkills[skillId] || 0) + 1, 10);
});
return { ...prev, skills: newSkills };
}); });
useGameStore.setState({ skills: { ...store.skills } });
}} }}
> >
+1 All Mana +1 All Mana
@@ -77,10 +87,13 @@ export function SkillDebug() {
variant="outline" variant="outline"
onClick={() => { onClick={() => {
const manaSkills = ['manaWell', 'manaFlow', 'manaOverflow', 'fireManaCap', 'waterManaCap', 'airManaCap', 'earthManaCap', 'lightManaCap', 'darkManaCap', 'deathManaCap', 'metalManaCap', 'sandManaCap', 'lightningManaCap', 'transferenceManaCap']; const manaSkills = ['manaWell', 'manaFlow', 'manaOverflow', 'fireManaCap', 'waterManaCap', 'airManaCap', 'earthManaCap', 'lightManaCap', 'darkManaCap', 'deathManaCap', 'metalManaCap', 'sandManaCap', 'lightningManaCap', 'transferenceManaCap'];
setSkills((prev) => {
const newSkills = { ...prev.skills };
manaSkills.forEach(skillId => { manaSkills.forEach(skillId => {
store.skills[skillId] = 10; newSkills[skillId] = 10;
});
return { ...prev, skills: newSkills };
}); });
useGameStore.setState({ skills: { ...store.skills } });
}} }}
> >
Max All Mana Max All Mana
@@ -97,10 +110,13 @@ export function SkillDebug() {
variant="outline" variant="outline"
onClick={() => { onClick={() => {
const studySkills = ['quickLearner', 'focusedMind', 'meditation', 'knowledgeRetention']; const studySkills = ['quickLearner', 'focusedMind', 'meditation', 'knowledgeRetention'];
setSkills((prev) => {
const newSkills = { ...prev.skills };
studySkills.forEach(skillId => { studySkills.forEach(skillId => {
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10); newSkills[skillId] = Math.min((newSkills[skillId] || 0) + 1, 10);
});
return { ...prev, skills: newSkills };
}); });
useGameStore.setState({ skills: { ...store.skills } });
}} }}
> >
+1 All Study +1 All Study
@@ -110,10 +126,13 @@ export function SkillDebug() {
variant="outline" variant="outline"
onClick={() => { onClick={() => {
const studySkills = ['quickLearner', 'focusedMind', 'meditation', 'knowledgeRetention']; const studySkills = ['quickLearner', 'focusedMind', 'meditation', 'knowledgeRetention'];
setSkills((prev) => {
const newSkills = { ...prev.skills };
studySkills.forEach(skillId => { studySkills.forEach(skillId => {
store.skills[skillId] = 10; newSkills[skillId] = 10;
});
return { ...prev, skills: newSkills };
}); });
useGameStore.setState({ skills: { ...store.skills } });
}} }}
> >
Max All Study Max All Study
@@ -130,10 +149,13 @@ export function SkillDebug() {
variant="outline" variant="outline"
onClick={() => { onClick={() => {
const craftSkills = ['effCrafting', 'fieldRepair', 'elemCrafting']; const craftSkills = ['effCrafting', 'fieldRepair', 'elemCrafting'];
setSkills((prev) => {
const newSkills = { ...prev.skills };
craftSkills.forEach(skillId => { craftSkills.forEach(skillId => {
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10); newSkills[skillId] = Math.min((newSkills[skillId] || 0) + 1, 10);
});
return { ...prev, skills: newSkills };
}); });
useGameStore.setState({ skills: { ...store.skills } });
}} }}
> >
+1 All Crafting +1 All Crafting
@@ -143,10 +165,13 @@ export function SkillDebug() {
variant="outline" variant="outline"
onClick={() => { onClick={() => {
const craftSkills = ['effCrafting', 'fieldRepair', 'elemCrafting']; const craftSkills = ['effCrafting', 'fieldRepair', 'elemCrafting'];
setSkills((prev) => {
const newSkills = { ...prev.skills };
craftSkills.forEach(skillId => { craftSkills.forEach(skillId => {
store.skills[skillId] = 10; newSkills[skillId] = 10;
});
return { ...prev, skills: newSkills };
}); });
useGameStore.setState({ skills: { ...store.skills } });
}} }}
> >
Max All Crafting Max All Crafting
@@ -162,7 +187,7 @@ export function SkillDebug() {
size="sm" size="sm"
variant="outline" variant="outline"
onClick={() => { onClick={() => {
// Unlock all spell research setSkills((prev) => {
const researchSkills = [ const researchSkills = [
'researchManaSpells', 'researchFireSpells', 'researchWaterSpells', 'researchManaSpells', 'researchFireSpells', 'researchWaterSpells',
'researchAirSpells', 'researchEarthSpells', 'researchLightSpells', 'researchAirSpells', 'researchEarthSpells', 'researchLightSpells',
@@ -173,10 +198,12 @@ export function SkillDebug() {
'researchDamageEffects', 'researchCombatEffects', 'researchManaEffects', 'researchDamageEffects', 'researchCombatEffects', 'researchManaEffects',
'researchAdvancedManaEffects', 'researchUtilityEffects' 'researchAdvancedManaEffects', 'researchUtilityEffects'
]; ];
const newSkills = { ...prev.skills };
researchSkills.forEach(skillId => { researchSkills.forEach(skillId => {
store.skills[skillId] = 1; newSkills[skillId] = 1;
});
return { ...prev, skills: newSkills };
}); });
useGameStore.setState({ skills: { ...store.skills } });
}} }}
> >
Unlock All Research Unlock All Research
@@ -185,47 +212,37 @@ export function SkillDebug() {
size="sm" size="sm"
variant="outline" variant="outline"
onClick={() => { onClick={() => {
// Add all unlocked effects to unlockedEffects setGameState((prev: any) => {
const effectIds = [ const effectIds = [
// Spell effects
'spell_manaBolt', 'spell_manaStrike', 'spell_fireball', 'spell_emberShot', 'spell_manaBolt', 'spell_manaStrike', 'spell_fireball', 'spell_emberShot',
'spell_waterJet', 'spell_iceShard', 'spell_gust', 'spell_windSlash', 'spell_waterJet', 'spell_iceShard', 'spell_gust', 'spell_windSlash',
'spell_stoneBullet', 'spell_rockSpike', 'spell_lightLance', 'spell_radiance', 'spell_stoneBullet', 'spell_rockSpike', 'spell_lightLance', 'spell_radiance',
'spell_shadowBolt', 'spell_darkPulse', 'spell_drain', 'spell_shadowBolt', 'spell_darkPulse', 'spell_drain',
// Tier 2 spells
'spell_inferno', 'spell_flameWave', 'spell_tidalWave', 'spell_iceStorm', 'spell_inferno', 'spell_flameWave', 'spell_tidalWave', 'spell_iceStorm',
'spell_hurricane', 'spell_windBlade', 'spell_earthquake', 'spell_stoneBarrage', 'spell_hurricane', 'spell_windBlade', 'spell_earthquake', 'spell_stoneBarrage',
'spell_solarFlare', 'spell_divineSmite', 'spell_voidRift', 'spell_shadowStorm', 'spell_solarFlare', 'spell_divineSmite', 'spell_voidRift', 'spell_shadowStorm',
// Tier 3 spells
'spell_pyroclasm', 'spell_tsunami', 'spell_meteorStrike', 'spell_pyroclasm', 'spell_tsunami', 'spell_meteorStrike',
// Lightning
'spell_spark', 'spell_lightningBolt', 'spell_chainLightning', 'spell_spark', 'spell_lightningBolt', 'spell_chainLightning',
'spell_stormCall', 'spell_thunderStrike', 'spell_stormCall', 'spell_thunderStrike',
// Metal and Sand
'spell_metalShard', 'spell_ironFist', 'spell_steelTempest', 'spell_furnaceBlast', 'spell_metalShard', 'spell_ironFist', 'spell_steelTempest', 'spell_furnaceBlast',
'spell_sandBlast', 'spell_sandstorm', 'spell_desertWind', 'spell_duneCollapse', 'spell_sandBlast', 'spell_sandstorm', 'spell_desertWind', 'spell_duneCollapse',
// Mana effects
'mana_cap_50', 'mana_cap_100', 'mana_regen_1', 'mana_regen_2', 'mana_regen_5', 'mana_cap_50', 'mana_cap_100', 'mana_regen_1', 'mana_regen_2', 'mana_regen_5',
'click_mana_1', 'click_mana_3', 'click_mana_1', 'click_mana_3',
// Combat effects
'damage_5', 'damage_10', 'damage_pct_10', 'crit_5', 'attack_speed_10', 'damage_5', 'damage_10', 'damage_pct_10', 'crit_5', 'attack_speed_10',
// Utility effects
'meditate_10', 'study_10', 'insight_5', 'meditate_10', 'study_10', 'insight_5',
// Special
'spell_echo_10', 'guardian_dmg_10', 'overpower_80', 'spell_echo_10', 'guardian_dmg_10', 'overpower_80',
// Weapon mana
'weapon_mana_cap_20', 'weapon_mana_cap_50', 'weapon_mana_cap_100', 'weapon_mana_cap_20', 'weapon_mana_cap_50', 'weapon_mana_cap_100',
'weapon_mana_regen_1', 'weapon_mana_regen_2', 'weapon_mana_regen_5', 'weapon_mana_regen_1', 'weapon_mana_regen_2', 'weapon_mana_regen_5',
// Sword enchants
'sword_fire', 'sword_frost', 'sword_lightning', 'sword_void' 'sword_fire', 'sword_frost', 'sword_lightning', 'sword_void'
]; ];
const currentEffects = store.unlockedEffects || []; const currentEffects = prev.unlockedEffects || [];
effectIds.forEach(id => { effectIds.forEach(id => {
if (!currentEffects.includes(id)) { if (!currentEffects.includes(id)) {
currentEffects.push(id); currentEffects.push(id);
} }
}); });
useGameStore.setState({ unlockedEffects: currentEffects }); return { ...prev, unlockedEffects: currentEffects };
});
}} }}
> >
Unlock All Effects Unlock All Effects
@@ -240,14 +257,7 @@ export function SkillDebug() {
size="sm" size="sm"
className="bg-purple-600 hover:bg-purple-700" className="bg-purple-600 hover:bg-purple-700"
onClick={() => { onClick={() => {
// Max all skills setSkills((prev) => {
Object.keys(store.skills).forEach(skillId => {
const current = store.skills[skillId] || 0;
if (current < 10) {
store.skills[skillId] = 10;
}
});
// Unlock all effects
const allEffectIds = [ const allEffectIds = [
'spell_manaBolt', 'spell_manaStrike', 'spell_fireball', 'spell_emberShot', 'spell_manaBolt', 'spell_manaStrike', 'spell_fireball', 'spell_emberShot',
'spell_waterJet', 'spell_iceShard', 'spell_gust', 'spell_windSlash', 'spell_waterJet', 'spell_iceShard', 'spell_gust', 'spell_windSlash',
@@ -258,15 +268,32 @@ export function SkillDebug() {
'crit_5', 'attack_speed_10', 'meditate_10', 'study_10', 'insight_5', 'crit_5', 'attack_speed_10', 'meditate_10', 'study_10', 'insight_5',
'spell_echo_10', 'guardian_dmg_10', 'overpower_80' 'spell_echo_10', 'guardian_dmg_10', 'overpower_80'
]; ];
const currentEffects = store.unlockedEffects || []; const newSkills = { ...prev.skills };
Object.keys(newSkills).forEach(skillId => {
if ((newSkills[skillId] || 0) < 10) {
newSkills[skillId] = 10;
}
});
return { ...prev, skills: newSkills };
});
setGameState((prev: any) => {
const allEffectIds = [
'spell_manaBolt', 'spell_manaStrike', 'spell_fireball', 'spell_emberShot',
'spell_waterJet', 'spell_iceShard', 'spell_gust', 'spell_windSlash',
'spell_stoneBullet', 'spell_rockSpike', 'spell_lightLance', 'spell_radiance',
'spell_shadowBolt', 'spell_darkPulse', 'spell_drain',
'mana_cap_50', 'mana_cap_100', 'mana_regen_1', 'mana_regen_2', 'mana_regen_5',
'click_mana_1', 'click_mana_3', 'damage_5', 'damage_10', 'damage_pct_10',
'crit_5', 'attack_speed_10', 'meditate_10', 'study_10', 'insight_5',
'spell_echo_10', 'guardian_dmg_10', 'overpower_80'
];
const currentEffects = prev.unlockedEffects || [];
allEffectIds.forEach(id => { allEffectIds.forEach(id => {
if (!currentEffects.includes(id)) { if (!currentEffects.includes(id)) {
currentEffects.push(id); currentEffects.push(id);
} }
}); });
useGameStore.setState({ return { ...prev, unlockedEffects: currentEffects };
skills: { ...store.skills },
unlockedEffects: currentEffects
}); });
}} }}
> >
+7 -9
View File
@@ -2,18 +2,16 @@
import { ATTUNEMENTS_DEF, ATTUNEMENT_SLOT_NAMES, getTotalAttunementRegen, getAvailableSkillCategories, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL, getAttunementConversionRate } from '@/lib/game/data/attunements'; import { ATTUNEMENTS_DEF, ATTUNEMENT_SLOT_NAMES, getTotalAttunementRegen, getAvailableSkillCategories, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL, getAttunementConversionRate } from '@/lib/game/data/attunements';
import { ELEMENTS } from '@/lib/game/constants'; import { ELEMENTS } from '@/lib/game/constants';
import type { GameStore, AttunementState } from '@/lib/game/types'; import type { AttunementState } from '@/lib/game/types';
import { usePrestigeStore, useManaStore } from '@/lib/game/stores';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
import { Lock, TrendingUp } from 'lucide-react'; import { Lock, TrendingUp } from 'lucide-react';
export interface AttunementsTabProps { export function AttunementsTab() {
store: GameStore; const attunements = usePrestigeStore((s) => s.attunements) || {};
} const elements = useManaStore((s) => s.elements);
export function AttunementsTab({ store }: AttunementsTabProps) {
const attunements = store.attunements || {};
// Get active attunements // Get active attunements
const activeAttunements = Object.entries(attunements) const activeAttunements = Object.entries(attunements)
@@ -66,8 +64,8 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
const primaryElem = def.primaryManaType ? ELEMENTS[def.primaryManaType] : null; const primaryElem = def.primaryManaType ? ELEMENTS[def.primaryManaType] : null;
// Get current mana for this attunement's type // Get current mana for this attunement's type
const currentMana = def.primaryManaType ? store.elements[def.primaryManaType]?.current || 0 : 0; const currentMana = def.primaryManaType ? elements[def.primaryManaType]?.current || 0 : 0;
const maxMana = def.primaryManaType ? store.elements[def.primaryManaType]?.max || 50 : 50; const maxMana = def.primaryManaType ? elements[def.primaryManaType]?.max || 50 : 50;
// Calculate level-scaled stats // Calculate level-scaled stats
const levelMult = Math.pow(1.5, level - 1); const levelMult = Math.pow(1.5, level - 1);
@@ -1,269 +0,0 @@
'use client';
import { ATTUNEMENTS_DEF, ATTUNEMENT_SLOT_NAMES, getTotalAttunementRegen, getAvailableSkillCategories, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL, getAttunementConversionRate } from '@/lib/game/data/attunements';
import { ELEMENTS } from '@/lib/game/constants';
import type { GameStore, AttunementState } from '@/lib/game/types';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress';
import { Lock, TrendingUp } from 'lucide-react';
export interface AttunementsTabProps {
store: GameStore;
}
export function AttunementsTab({ store }: AttunementsTabProps) {
const attunements = store.attunements || {};
// Get active attunements
const activeAttunements = Object.entries(attunements)
.filter(([, state]) => state.active)
.map(([id]) => ATTUNEMENTS_DEF[id])
.filter(Boolean);
// Calculate total regen from attunements
const totalAttunementRegen = getTotalAttunementRegen(attunements);
// Get available skill categories
const availableCategories = getAvailableSkillCategories(attunements);
return (
<div className="space-y-4">
{/* Overview Card */}
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-amber-400 text-sm">Your Attunements</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-gray-400 mb-3">
Attunements are magical bonds tied to specific body locations. Each attunement grants unique capabilities,
mana regeneration, and access to specialized skills. Level them up to increase their power.
</p>
<div className="flex flex-wrap gap-2">
<Badge className="bg-teal-900/50 text-teal-300">
+{totalAttunementRegen.toFixed(1)} raw mana/hr
</Badge>
<Badge className="bg-purple-900/50 text-purple-300">
{activeAttunements.length} active attunement{activeAttunements.length !== 1 ? 's' : ''}
</Badge>
</div>
</CardContent>
</Card>
{/* Attunement Slots */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{Object.entries(ATTUNEMENTS_DEF).map(([id, def]) => {
const state = attunements[id];
const isActive = state?.active;
const isUnlocked = state?.active || def.unlocked;
const level = state?.level || 1;
const xp = state?.experience || 0;
const xpNeeded = getAttunementXPForLevel(level + 1);
const xpProgress = xpNeeded > 0 ? (xp / xpNeeded) * 100 : 100;
const isMaxLevel = level >= MAX_ATTUNEMENT_LEVEL;
// Get primary mana element info
const primaryElem = def.primaryManaType ? ELEMENTS[def.primaryManaType] : null;
// Get current mana for this attunement's type
const currentMana = def.primaryManaType ? store.elements[def.primaryManaType]?.current || 0 : 0;
const maxMana = def.primaryManaType ? store.elements[def.primaryManaType]?.max || 50 : 50;
// Calculate level-scaled stats
const levelMult = Math.pow(1.5, level - 1);
const scaledRegen = def.rawManaRegen * levelMult;
const scaledConversion = getAttunementConversionRate(id, level);
return (
<Card
key={id}
className={`bg-gray-900/80 transition-all ${
isActive
? 'border-2 shadow-lg'
: isUnlocked
? 'border-gray-600'
: 'border-gray-800 opacity-70'
}`}
style={{
borderColor: isActive ? def.color : undefined,
boxShadow: isActive ? `0 0 20px ${def.color}30` : undefined
}}
>
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-2xl">{def.icon}</span>
<div>
<CardTitle className="text-sm" style={{ color: isActive ? def.color : '#9CA3AF' }}>
{def.name}
</CardTitle>
<div className="text-xs text-gray-500">
{ATTUNEMENT_SLOT_NAMES[def.slot]}
</div>
</div>
</div>
{!isUnlocked && (
<Lock className="w-4 h-4 text-gray-600" />
)}
{isActive && (
<Badge className="text-xs" style={{ backgroundColor: `${def.color}30`, color: def.color }}>
Lv.{level}
</Badge>
)}
</div>
</CardHeader>
<CardContent className="space-y-3">
<p className="text-xs text-gray-400">{def.desc}</p>
{/* Mana Type */}
<div className="space-y-1">
<div className="flex items-center justify-between text-xs">
<span className="text-gray-500">Primary Mana</span>
{primaryElem ? (
<span style={{ color: primaryElem.color }}>
{primaryElem.sym} {primaryElem.name}
</span>
) : (
<span className="text-purple-400">From Pacts</span>
)}
</div>
{/* Mana bar (only for attunements with primary type) */}
{primaryElem && isActive && (
<div className="space-y-1">
<Progress
value={(currentMana / maxMana) * 100}
className="h-2 bg-gray-800"
/>
<div className="flex justify-between text-xs text-gray-500">
<span>{currentMana.toFixed(1)}</span>
<span>/{maxMana}</span>
</div>
</div>
)}
</div>
{/* Stats with level scaling */}
<div className="grid grid-cols-2 gap-2 text-xs">
<div className="p-2 bg-gray-800/50 rounded">
<div className="text-gray-500">Raw Regen</div>
<div className="text-green-400 font-semibold">
+{scaledRegen.toFixed(2)}/hr
{level > 1 && <span className="text-xs ml-1">({((levelMult - 1) * 100).toFixed(0)}% bonus)</span>}
</div>
</div>
<div className="p-2 bg-gray-800/50 rounded">
<div className="text-gray-500">Conversion</div>
<div className="text-cyan-400 font-semibold">
{scaledConversion > 0 ? `${scaledConversion.toFixed(2)}/hr` : '—'}
{level > 1 && scaledConversion > 0 && <span className="text-xs ml-1">({((levelMult - 1) * 100).toFixed(0)}% bonus)</span>}
</div>
</div>
</div>
{/* XP Progress Bar */}
{isUnlocked && state && !isMaxLevel && (
<div className="space-y-1">
<div className="flex items-center justify-between text-xs">
<span className="text-gray-500 flex items-center gap-1">
<TrendingUp className="w-3 h-3" />
XP Progress
</span>
<span className="text-amber-400">{xp} / {xpNeeded}</span>
</div>
<Progress
value={xpProgress}
className="h-2 bg-gray-800"
/>
<div className="text-xs text-gray-500">
{isMaxLevel ? 'Max Level' : `${xpNeeded - xp} XP to Level ${level + 1}`}
</div>
</div>
)}
{/* Max Level Indicator */}
{isMaxLevel && (
<div className="text-xs text-amber-400 text-center font-semibold">
✨ MAX LEVEL ✨
</div>
)}
{/* Capabilities */}
<div className="space-y-1">
<div className="text-xs text-gray-500">Capabilities</div>
<div className="flex flex-wrap gap-1">
{def.capabilities.map(cap => (
<Badge key={cap} variant="outline" className="text-xs">
{cap === 'enchanting' && '✨ Enchanting'}
{cap === 'disenchanting' && '🔄 Disenchant'} // TODO: Remove after bug 13 complete
{cap === 'pacts' && '🤝 Pacts'}
{cap === 'guardianPowers' && '💜 Guardian Powers'}
{cap === 'elementalMastery' && '🌟 Elem. Mastery'}
{cap === 'golemCrafting' && '🗿 Golems'}
{cap === 'gearCrafting' && '⚒️ Gear'}
{cap === 'earthShaping' && '⛰️ Earth Shaping'}
{!['enchanting', 'pacts', 'guardianPowers',
'elementalMastery', 'golemCrafting', 'gearCrafting', 'earthShaping'].includes(cap) && cap}
</Badge>
))}
</div>
</div>
{/* Unlock condition for locked attunements */}
{!isUnlocked && def.unlockCondition && (
<div className="text-xs text-amber-400 italic">
🔒 {def.unlockCondition}
</div>
)}
</CardContent>
</Card>
);
})}
</div>
{/* Available Skills Summary */}
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-amber-400 text-sm">Available Skill Categories</CardTitle>
</CardHeader>
<CardContent>
<p className="text-xs text-gray-400 mb-2">
Your attunements grant access to specialized skill categories:
</p>
<div className="flex flex-wrap gap-2">
{availableCategories.map(cat => {
const attunement = Object.values(ATTUNEMENTS_DEF).find(a =>
a.skillCategories.includes(cat) && attunements[a.id]?.active
);
return (
<Badge
key={cat}
className={attunement ? '' : 'bg-gray-700/50 text-gray-400'}
style={attunement ? {
backgroundColor: `${attunement.color}30`,
color: attunement.color
} : undefined}
>
{cat === 'mana' && '💧 Mana'}
{cat === 'study' && '📚 Study'}
{cat === 'research' && '🔮 Research'} // TODO: Remove after Bug 12 - research moved to mana
{cat === 'ascension' && '⭐ Ascension'}
{cat === 'enchant' && '✨ Enchanting'}
{cat === 'effectResearch' && '🔬 Effect Research'}
{cat === 'invocation' && '💜 Invocation'}
{cat === 'pact' && '🤝 Pact Mastery'}
{cat === 'fabrication' && '⚒️ Fabrication'}
{cat === 'golemancy' && '🗿 Golemancy'}
{!['mana', 'study', 'research', 'ascension', 'enchant', 'effectResearch',
'invocation', 'pact', 'fabrication', 'golemancy'].includes(cat) && cat}
</Badge>
);
})}
</div>
</CardContent>
</Card>
</div>
);
}
AttunementsTab.displayName = "AttunementsTab";
+2 -7
View File
@@ -227,13 +227,8 @@ export function EquipmentTab() {
} }
}; };
// Get unified effects for equipment stats - move hook before conditional // Use already-fetched values for unified effects
const equipmentInstancesForEffects = useCombatStore((s) => s.equipmentInstances); const unifiedEffects = getUnifiedEffects({ equipmentInstances, equippedInstances });
const equippedInstancesForEffects = useCombatStore((s) => s.equippedInstances);
const unifiedEffects = equipmentInstancesForEffects && equippedInstancesForEffects
? getUnifiedEffects({ equipmentInstances, equippedInstances })
: null;
return ( return (
<div className="space-y-4 max-w-full overflow-x-hidden"> <div className="space-y-4 max-w-full overflow-x-hidden">