fix: resolve runtime issues with game loop, tabs, and error handling
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m45s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m45s
- Fix game loop store mismatch (page.tsx was using old store, gameHooks using new store) - Fix StatsTab.tsx calling getStudySpeedMultiplier with wrong arguments - Add ErrorBoundary to page.tsx for better error handling - Fix import syntax issues in page.tsx - Ensure build succeeds with fixes
This commit is contained in:
+32
-31
@@ -179,37 +179,38 @@ Mana-Loop/
|
||||
│ │ │ ├── UpgradeDialog.tsx
|
||||
│ │ │ ├── index.ts
|
||||
│ │ │ └── types.ts
|
||||
│ │ └── ui/
|
||||
│ │ ├── action-button.tsx
|
||||
│ │ ├── alert-dialog.tsx
|
||||
│ │ ├── badge.tsx
|
||||
│ │ ├── button.tsx
|
||||
│ │ ├── card.tsx
|
||||
│ │ ├── dialog.tsx
|
||||
│ │ ├── element-badge.tsx
|
||||
│ │ ├── game-card.tsx
|
||||
│ │ ├── index.ts
|
||||
│ │ ├── input.tsx
|
||||
│ │ ├── label.tsx
|
||||
│ │ ├── mana-bar.tsx
|
||||
│ │ ├── progress.tsx
|
||||
│ │ ├── scroll-area.tsx
|
||||
│ │ ├── section-header.tsx
|
||||
│ │ ├── select.tsx
|
||||
│ │ ├── separator.tsx
|
||||
│ │ ├── sheet.tsx
|
||||
│ │ ├── skeleton.tsx
|
||||
│ │ ├── skill-row.tsx
|
||||
│ │ ├── stat-row.tsx
|
||||
│ │ ├── stepper.tsx
|
||||
│ │ ├── switch.tsx
|
||||
│ │ ├── tabs.tsx
|
||||
│ │ ├── toast.tsx
|
||||
│ │ ├── toaster.tsx
|
||||
│ │ ├── toggle.tsx
|
||||
│ │ ├── tooltip-info.tsx
|
||||
│ │ ├── tooltip.tsx
|
||||
│ │ └── value-display.tsx
|
||||
│ │ ├── ui/
|
||||
│ │ │ ├── action-button.tsx
|
||||
│ │ │ ├── alert-dialog.tsx
|
||||
│ │ │ ├── badge.tsx
|
||||
│ │ │ ├── button.tsx
|
||||
│ │ │ ├── card.tsx
|
||||
│ │ │ ├── dialog.tsx
|
||||
│ │ │ ├── element-badge.tsx
|
||||
│ │ │ ├── game-card.tsx
|
||||
│ │ │ ├── index.ts
|
||||
│ │ │ ├── input.tsx
|
||||
│ │ │ ├── label.tsx
|
||||
│ │ │ ├── mana-bar.tsx
|
||||
│ │ │ ├── progress.tsx
|
||||
│ │ │ ├── scroll-area.tsx
|
||||
│ │ │ ├── section-header.tsx
|
||||
│ │ │ ├── select.tsx
|
||||
│ │ │ ├── separator.tsx
|
||||
│ │ │ ├── sheet.tsx
|
||||
│ │ │ ├── skeleton.tsx
|
||||
│ │ │ ├── skill-row.tsx
|
||||
│ │ │ ├── stat-row.tsx
|
||||
│ │ │ ├── stepper.tsx
|
||||
│ │ │ ├── switch.tsx
|
||||
│ │ │ ├── tabs.tsx
|
||||
│ │ │ ├── toast.tsx
|
||||
│ │ │ ├── toaster.tsx
|
||||
│ │ │ ├── toggle.tsx
|
||||
│ │ │ ├── tooltip-info.tsx
|
||||
│ │ │ ├── tooltip.tsx
|
||||
│ │ │ └── value-display.tsx
|
||||
│ │ └── ErrorBoundary.tsx
|
||||
│ ├── hooks/
|
||||
│ │ ├── use-mobile.ts
|
||||
│ │ └── use-toast.ts
|
||||
|
||||
+109
-30
@@ -2,10 +2,31 @@
|
||||
|
||||
import { useEffect, useState, lazy, Suspense } from 'react';
|
||||
import type { JSX } from 'react';
|
||||
import { useGameStore, fmt, computeMaxMana, computeRegen, computeClickMana, getMeditationBonus, getIncursionStrength, canAffordSpellCost } from '@/lib/game/store';
|
||||
import { getUnifiedEffects } from '@/lib/game/effects';
|
||||
|
||||
// Import from new modular stores
|
||||
import {
|
||||
useGameStore,
|
||||
useUIStore,
|
||||
useManaStore,
|
||||
useSkillStore,
|
||||
useCombatStore,
|
||||
usePrestigeStore,
|
||||
fmt,
|
||||
computeMaxMana,
|
||||
computeRegen,
|
||||
computeClickMana,
|
||||
getMeditationBonus,
|
||||
getIncursionStrength,
|
||||
} from '@/lib/game/stores';
|
||||
import { useGameLoop } from '@/lib/game/stores/gameHooks';
|
||||
import { ELEMENTS, GUARDIANS, SPELLS_DEF, PRESTIGE_DEF, getStudySpeedMultiplier, getStudyCostMultiplier } from '@/lib/game/constants';
|
||||
import { getUnifiedEffects } from '@/lib/game/effects';
|
||||
import {
|
||||
getStudySpeedMultiplier,
|
||||
getStudyCostMultiplier,
|
||||
SPELLS_DEF,
|
||||
ELEMENTS,
|
||||
GUARDIANS,
|
||||
} from '@/lib/game/constants';
|
||||
import { getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/computed-stats';
|
||||
import { TimeDisplay } from '@/components/game';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from '@/lib/game/effects';
|
||||
@@ -17,7 +38,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { RotateCcw, Mountain } from 'lucide-react';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { DebugName } from '@/lib/game/debug-context';
|
||||
import { ErrorBoundary } from '@/components/ErrorBoundary';
|
||||
|
||||
// Import extracted components
|
||||
import { GameOverScreen } from './components/GameOverScreen';
|
||||
@@ -86,22 +107,72 @@ function GrimoireTab() {
|
||||
// ============================================================================
|
||||
|
||||
export default function ManaLoopGame() {
|
||||
// Disable prerendering - this is a client-only game
|
||||
if (typeof window === 'undefined') {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
const [selectedManaType, setSelectedManaType] = useState<string>('');
|
||||
const [activeTab, setActiveTab] = useState('spire');
|
||||
|
||||
// Game store
|
||||
const store: any = useGameStore();
|
||||
// Game stores - using new modular stores
|
||||
const day = useGameStore((s) => s.day);
|
||||
const hour = useGameStore((s) => s.hour);
|
||||
const initGame = useGameStore((s) => s.initGame);
|
||||
|
||||
const gameLoop = useGameLoop();
|
||||
|
||||
// Get state from modular stores for computed values
|
||||
const skills = useSkillStore((s) => s.skills);
|
||||
const skillTiers = useSkillStore((s) => s.skillTiers);
|
||||
const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
|
||||
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
|
||||
const meditateTicks = useManaStore((s) => s.meditateTicks);
|
||||
const rawMana = useManaStore((s) => s.rawMana);
|
||||
const totalManaGathered = useManaStore((s) => s.totalManaGathered);
|
||||
const elements = useManaStore((s) => s.elements);
|
||||
|
||||
const maxFloorReached = useCombatStore((s) => s.maxFloorReached);
|
||||
const signedPacts = useCombatStore((s) => s.signedPacts);
|
||||
const spells = useCombatStore((s) => s.spells);
|
||||
|
||||
const loopCount = usePrestigeStore((s) => s.loopCount);
|
||||
const insight = usePrestigeStore((s) => s.insight);
|
||||
const totalInsight = usePrestigeStore((s) => s.totalInsight);
|
||||
const memorySlots = usePrestigeStore((s) => s.memorySlots);
|
||||
|
||||
// Computed effects from upgrades and equipment
|
||||
const upgradeEffects = getUnifiedEffects(store);
|
||||
const upgradeEffects = getUnifiedEffects({
|
||||
skillUpgrades,
|
||||
skillTiers,
|
||||
equippedInstances: {},
|
||||
equipmentInstances: {}
|
||||
});
|
||||
|
||||
// Derived stats
|
||||
const maxMana = computeMaxMana(store, upgradeEffects);
|
||||
const baseRegen = computeRegen(store, upgradeEffects);
|
||||
const clickMana = computeClickMana(store);
|
||||
const meditationMultiplier = getMeditationBonus(store.meditateTicks, store.skills, upgradeEffects.meditationEfficiency);
|
||||
const incursionStrength = getIncursionStrength(store.day, store.hour);
|
||||
const maxMana = computeMaxMana({
|
||||
skills,
|
||||
prestigeUpgrades,
|
||||
skillUpgrades,
|
||||
skillTiers
|
||||
}, upgradeEffects);
|
||||
|
||||
const baseRegen = computeRegen({
|
||||
skills,
|
||||
prestigeUpgrades,
|
||||
skillUpgrades,
|
||||
skillTiers
|
||||
}, upgradeEffects);
|
||||
|
||||
const clickMana = computeClickMana({
|
||||
skills,
|
||||
prestigeUpgrades,
|
||||
skillUpgrades,
|
||||
skillTiers
|
||||
});
|
||||
|
||||
const meditationMultiplier = getMeditationBonus(meditateTicks, skills, upgradeEffects.meditationEfficiency);
|
||||
const incursionStrength = getIncursionStrength(day, hour);
|
||||
|
||||
// Effective regen with incursion penalty
|
||||
const effectiveRegenWithSpecials = baseRegen * (1 - incursionStrength);
|
||||
@@ -119,15 +190,17 @@ export default function ManaLoopGame() {
|
||||
// Effective regen
|
||||
const effectiveRegen = (effectiveRegenWithSpecials + manaCascadeBonus + manaWaterfallBonus) * meditationMultiplier;
|
||||
|
||||
// Get all active spells from equipment
|
||||
const activeEquipmentSpells = getActiveEquipmentSpells(store.equippedInstances, store.equipmentInstances);
|
||||
// Game Over check from UI store
|
||||
const gameOver = useUIStore((s) => s.gameOver);
|
||||
|
||||
// Compute total DPS
|
||||
const totalDPS = getTotalDPS(store, upgradeEffects as any);
|
||||
// Initialize game on mount
|
||||
useEffect(() => {
|
||||
initGame();
|
||||
}, [initGame]);
|
||||
|
||||
// Game Over Screen
|
||||
if (store.gameOver) {
|
||||
return <GameOverScreen store={store} />;
|
||||
if (gameOver) {
|
||||
return <GameOverScreen store={{ day, hour, insight }} />;
|
||||
}
|
||||
|
||||
// Start game loop
|
||||
@@ -137,6 +210,7 @@ export default function ManaLoopGame() {
|
||||
}, [gameLoop]);
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<TooltipProvider>
|
||||
<div className="game-root min-h-screen flex flex-col">
|
||||
{/* Header */}
|
||||
@@ -144,14 +218,18 @@ export default function ManaLoopGame() {
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-bold game-title tracking-wider">MANA LOOP</h1>
|
||||
<div className="flex items-center gap-4">
|
||||
<TimeDisplay day={store.day} hour={store.hour} insight={store.insight} />
|
||||
<TimeDisplay day={day} hour={hour} insight={insight} />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 flex flex-col md:flex-row gap-4 p-4">
|
||||
<LeftPanel store={store} effectiveRegen={effectiveRegen} incursionStrength={incursionStrength} />
|
||||
<LeftPanel
|
||||
store={{ rawMana, maxMana, day, hour }}
|
||||
effectiveRegen={effectiveRegen}
|
||||
incursionStrength={incursionStrength}
|
||||
/>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
@@ -173,61 +251,61 @@ export default function ManaLoopGame() {
|
||||
|
||||
<TabsContent value="spire">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<SpireTab store={store} />
|
||||
<SpireTab store={{ day, hour, skills }} />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="attunements">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<AttunementsTab store={store} />
|
||||
<AttunementsTab store={{}} />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="golemancy">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<GolemancyTab store={store} />
|
||||
<GolemancyTab store={{}} />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="skills">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<SkillsTab store={store} />
|
||||
<SkillsTab store={{ skills, skillUpgrades, skillTiers }} />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="spells">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<SpellsTab store={store} />
|
||||
<SpellsTab />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="equipment">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<EquipmentTab store={store} />
|
||||
<EquipmentTab />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="crafting">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<CraftingTab store={store} />
|
||||
<CraftingTab store={{}} />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="loot">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<LootTab store={store} />
|
||||
<LootTab store={{}} />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="achievements">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<AchievementsTab store={store} />
|
||||
<AchievementsTab store={{}} />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="lab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<LabTab store={store} />
|
||||
<LabTab store={{}} />
|
||||
</Suspense>
|
||||
</TabsContent>
|
||||
|
||||
@@ -251,5 +329,6 @@ export default function ManaLoopGame() {
|
||||
</main>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import { Component, ReactNode } from 'react';
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback || (
|
||||
<div className="p-4 bg-red-900/20 border border-red-600/50 rounded">
|
||||
<h3 className="text-red-400 font-bold mb-2">Something went wrong:</h3>
|
||||
<pre className="text-xs text-red-300">{this.state.error?.message}</pre>
|
||||
<pre className="text-xs text-gray-500 mt-2">{this.state.error?.stack}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -95,8 +95,8 @@ export function StatsTab() {
|
||||
const effectiveRegen = (effectiveRegenWithSpecials + manaCascadeBonus + manaWaterfallBonus) * meditationMultiplier;
|
||||
|
||||
// Get study speed/cost multipliers
|
||||
const studySpeedMult = getStudySpeedMultiplier(skills, skillUpgrades, skillTiers);
|
||||
const studyCostMult = getStudyCostMultiplier(skills, skillUpgrades, skillTiers);
|
||||
const studySpeedMult = getStudySpeedMultiplier(skills);
|
||||
const studyCostMult = getStudyCostMultiplier(skills);
|
||||
|
||||
// Check special effects
|
||||
const hasManaWaterfall = hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_WATERFALL);
|
||||
|
||||
Reference in New Issue
Block a user