fix: hydration mismatch, production Dockerfile, SSR localStorage guard, SpellsTab/SkillsTab/debug store migrations
Build and Publish Mana Loop Docker Image / build-and-publish (push) Has been cancelled
Build and Publish Mana Loop Docker Image / build-and-publish (push) Has been cancelled
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useGameStore, canAffordSpellCost, fmt } from '@/lib/game/stores';
|
import { canAffordSpellCost, fmt } from '@/lib/game/stores';
|
||||||
|
import { useCombatStore, useSkillStore, useManaStore } from '@/lib/game/stores';
|
||||||
import { ELEMENTS, SPELLS_DEF } from '@/lib/game/constants';
|
import { ELEMENTS, SPELLS_DEF } from '@/lib/game/constants';
|
||||||
import { useStudyStats } from '@/lib/game/hooks/useGameDerived';
|
import { useStudyStats } from '@/lib/game/hooks/useGameDerived';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@@ -32,7 +33,13 @@ function formatStudyTime(hours: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SpellsTab() {
|
export function SpellsTab() {
|
||||||
const store = useGameStore();
|
const spells = useCombatStore((s) => s.spells);
|
||||||
|
const activeSpell = useCombatStore((s) => s.activeSpell);
|
||||||
|
const setSpell = useCombatStore((s) => s.setSpell);
|
||||||
|
const currentStudyTarget = useSkillStore((s) => s.currentStudyTarget);
|
||||||
|
const setCurrentStudyTarget = useSkillStore((s) => s.setCurrentStudyTarget);
|
||||||
|
const rawMana = useManaStore((s) => s.rawMana);
|
||||||
|
const elements = useManaStore((s) => s.elements);
|
||||||
const { studySpeedMult, studyCostMult } = useStudyStats();
|
const { studySpeedMult, studyCostMult } = useStudyStats();
|
||||||
|
|
||||||
const spellTiers = [0, 1, 2, 3, 4];
|
const spellTiers = [0, 1, 2, 3, 4];
|
||||||
@@ -51,13 +58,13 @@ export function SpellsTab() {
|
|||||||
<h3 className={`text-lg font-semibold mb-3 ${tierColors[tier]}`}>{tierNames[tier]}</h3>
|
<h3 className={`text-lg font-semibold mb-3 ${tierColors[tier]}`}>{tierNames[tier]}</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{spellsInTier.map(([id, def]) => {
|
{spellsInTier.map(([id, def]) => {
|
||||||
const state = store.spells?.[id];
|
const state = spells?.[id];
|
||||||
const learned = state?.learned;
|
const learned = state?.learned;
|
||||||
const isStudying = store.currentStudyTarget?.id === id;
|
const isStudying = currentStudyTarget?.id === id;
|
||||||
const elemDef = def.elem === 'raw' ? null : ELEMENTS[def.elem];
|
const elemDef = def.elem === 'raw' ? null : ELEMENTS[def.elem];
|
||||||
const baseStudyTime = def.studyTime || (def.tier * 4);
|
const baseStudyTime = def.studyTime || (def.tier * 4);
|
||||||
const isActive = store.activeSpell === id;
|
const isActive = activeSpell === id;
|
||||||
const canCast = learned && canAffordSpellCost(def.cost, store.rawMana, store.elements);
|
const canCast = learned && canAffordSpellCost(def.cost, rawMana, elements);
|
||||||
|
|
||||||
// Apply skill modifiers
|
// Apply skill modifiers
|
||||||
const studyTime = baseStudyTime / studySpeedMult;
|
const studyTime = baseStudyTime / studySpeedMult;
|
||||||
@@ -116,7 +123,7 @@ export function SpellsTab() {
|
|||||||
<Badge className="bg-green-900/50 text-green-300">Learned</Badge>
|
<Badge className="bg-green-900/50 text-green-300">Learned</Badge>
|
||||||
{isActive && <Badge className="bg-amber-900/50 text-amber-300">Active</Badge>}
|
{isActive && <Badge className="bg-amber-900/50 text-amber-300">Active</Badge>}
|
||||||
{!isActive && (
|
{!isActive && (
|
||||||
<Button size="sm" variant="outline" onClick={() => store.setSpell(id)}>
|
<Button size="sm" variant="outline" onClick={() => setSpell(id)}>
|
||||||
Set Active
|
Set Active
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -147,7 +154,7 @@ export function SpellsTab() {
|
|||||||
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.setCurrentStudy?.(id, 'spell')}
|
onClick={() => setCurrentStudyTarget({ type: 'spell', id, progress: 0, required: studyTime })}
|
||||||
>
|
>
|
||||||
Start Study ({fmt(unlockCost)} mana)
|
Start Study ({fmt(unlockCost)} mana)
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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';
|
import { useCombatStore, usePrestigeStore, useManaStore } from '@/lib/game/stores';
|
||||||
|
|
||||||
export function LoopStatsSection() {
|
export function LoopStatsSection() {
|
||||||
const spells = useCombatStore((s) => s.spells);
|
const spells = useCombatStore((s) => s.spells);
|
||||||
@@ -14,7 +14,7 @@ export function LoopStatsSection() {
|
|||||||
const maxFloorReached = useCombatStore((s) => s.maxFloorReached);
|
const maxFloorReached = useCombatStore((s) => s.maxFloorReached);
|
||||||
const totalManaGathered = useManaStore((s) => s.totalManaGathered);
|
const totalManaGathered = useManaStore((s) => s.totalManaGathered);
|
||||||
const loopCount = usePrestigeStore((s) => s.loopCount);
|
const loopCount = usePrestigeStore((s) => s.loopCount);
|
||||||
const memorySlots = useSkillStore((s) => s.memorySlots);
|
const memorySlots = usePrestigeStore((s) => s.memorySlots);
|
||||||
|
|
||||||
const spellsLearned = Object.values(spells || {}).filter((s: any) => s.learned).length;
|
const spellsLearned = Object.values(spells || {}).filter((s: any) => s.learned).length;
|
||||||
const totalSkillLevels = Object.values(skills || {}).reduce((a: number, b: number) => a + b, 0);
|
const totalSkillLevels = Object.values(skills || {}).reduce((a: number, b: number) => a + b, 0);
|
||||||
|
|||||||
@@ -4,12 +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 { 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 { usePrestigeStore } from '@/lib/game/stores';
|
import { useAttunementStore } from '@/lib/game/stores';
|
||||||
|
|
||||||
export function AttunementDebug() {
|
export function AttunementDebug() {
|
||||||
const attunements = usePrestigeStore((s) => s.attunements);
|
const attunements = useAttunementStore((s) => s.attunements);
|
||||||
const debugUnlockAttunement = usePrestigeStore((s) => s.debugUnlockAttunement);
|
const debugUnlockAttunement = useAttunementStore((s) => s.debugUnlockAttunement);
|
||||||
const debugAddAttunementXP = usePrestigeStore((s) => s.debugAddAttunementXP);
|
const debugAddAttunementXP = useAttunementStore((s) => s.debugAddAttunementXP);
|
||||||
|
|
||||||
const handleUnlockAttunement = (id: string) => {
|
const handleUnlockAttunement = (id: string) => {
|
||||||
if (debugUnlockAttunement) {
|
if (debugUnlockAttunement) {
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ import { ELEMENTS } from '@/lib/game/constants';
|
|||||||
export function ElementDebug() {
|
export function ElementDebug() {
|
||||||
const elements = useManaStore((s) => s.elements);
|
const elements = useManaStore((s) => s.elements);
|
||||||
const unlockElement = useManaStore((s) => s.unlockElement);
|
const unlockElement = useManaStore((s) => s.unlockElement);
|
||||||
const debugAddElementalMana = useManaStore((s) => s.debugAddElementalMana);
|
const addElementMana = useManaStore((s) => s.addElementMana);
|
||||||
|
|
||||||
const handleUnlockElement = (element: string) => {
|
const handleUnlockElement = (element: string) => {
|
||||||
unlockElement(element, 500);
|
unlockElement(element, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddElementalMana = (element: string, amount: number) => {
|
const handleAddElementalMana = (element: string, amount: number) => {
|
||||||
if (debugAddElementalMana) {
|
if (addElementMana) {
|
||||||
debugAddElementalMana(element, amount);
|
addElementMana(element, amount);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
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';
|
||||||
import { Bug } from 'lucide-react';
|
import { Bug } from 'lucide-react';
|
||||||
import { usePrestigeStore, useManaStore, useUIStore } from '@/lib/game/stores';
|
import { usePrestigeStore, useManaStore, useUIStore, useGameStore } from '@/lib/game/stores';
|
||||||
import { GUARDIANS, ELEMENTS } from '@/lib/game/constants';
|
import { GUARDIANS, ELEMENTS } from '@/lib/game/constants';
|
||||||
|
|
||||||
export function PactDebug() {
|
export function PactDebug() {
|
||||||
@@ -59,32 +59,7 @@ export function PactDebug() {
|
|||||||
};
|
};
|
||||||
debugSetPactDetails(newSignedPactDetails);
|
debugSetPactDetails(newSignedPactDetails);
|
||||||
|
|
||||||
// Unlock mana types
|
addLog(`📜 DEBUG: Pact with ${guardian.name} force-signed!`);
|
||||||
for (const elemId of guardian.unlocksMana) {
|
|
||||||
if (!elements[elemId]?.unlocked) {
|
|
||||||
unlockElement(elemId, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for compound element unlocks
|
|
||||||
const currentElements = useManaStore.getState().elements;
|
|
||||||
const unlockedSet = new Set(
|
|
||||||
Object.entries(currentElements)
|
|
||||||
.filter(([, e]) => e.unlocked)
|
|
||||||
.map(([id]) => id)
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const [elemId, elemDef] of Object.entries(ELEMENTS)) {
|
|
||||||
if (elemDef.recipe && !currentElements[elemId]?.unlocked) {
|
|
||||||
const canUnlock = elemDef.recipe.every((comp: string) => unlockedSet.has(comp));
|
|
||||||
if (canUnlock) {
|
|
||||||
unlockElement(elemId, 500);
|
|
||||||
addLog(`🔮 ${elemDef.name} mana unlocked through component synergy!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addLog(`📜 DEBUG: Pact with ${guardian.name} force-signed! ${guardian.unlocksMana.map(e => ELEMENTS[e]?.name || e).join(', ')} mana unlocked!`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remove a pact
|
// Remove a pact
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ export { CraftingTab } from './CraftingTab';
|
|||||||
export { SpireTab } from './SpireTab';
|
export { SpireTab } from './SpireTab';
|
||||||
export { SpellsTab } from './SpellsTab';
|
export { SpellsTab } from './SpellsTab';
|
||||||
export { LabTab } from './LabTab';
|
export { LabTab } from './LabTab';
|
||||||
export { SkillsTab } from './SkillsTab';
|
// SkillsTab is now exported from src/components/game/index.ts
|
||||||
|
// export { SkillsTab } from './SkillsTab';
|
||||||
export { StatsTab } from './StatsTab';
|
export { StatsTab } from './StatsTab';
|
||||||
export { EquipmentTab } from './EquipmentTab';
|
export { EquipmentTab } from './EquipmentTab';
|
||||||
export { AttunementsTab } from './AttunementsTab';
|
export { AttunementsTab } from './AttunementsTab';
|
||||||
|
|||||||
Reference in New Issue
Block a user