Phase 3: Split DebugTab.tsx into functional components
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 4m17s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 4m17s
This commit is contained in:
+28
-10
@@ -17,6 +17,21 @@
|
|||||||
- **Result**: Split into `data/enchantments/spell-effects.ts`, `mana-effects.ts`, `combat-effects.ts`, `elemental-effects.ts`, `defense-effects.ts`, `utility-effects.ts`, `special-effects.ts`, `enchantment-types.ts`, `index.ts`
|
- **Result**: Split into `data/enchantments/spell-effects.ts`, `mana-effects.ts`, `combat-effects.ts`, `elemental-effects.ts`, `defense-effects.ts`, `utility-effects.ts`, `special-effects.ts`, `enchantment-types.ts`, `index.ts`
|
||||||
- **Build**: ✅ Passes
|
- **Build**: ✅ Passes
|
||||||
|
|
||||||
|
### 4. `CraftingTab.tsx` (965 lines) ✅
|
||||||
|
- **Commit**: `ra528feb Phase 3: Split CraftingTab.tsx into crafting stage components`
|
||||||
|
- **Result**: Split into `crafting/EnchantmentDesigner.tsx`, `EnchantmentPreparer.tsx`, `EnchantmentApplier.tsx`, `EquipmentCrafter.tsx`, `index.tsx`
|
||||||
|
- **Build**: ✅ Passes
|
||||||
|
|
||||||
|
### 5. `computed-stats.ts` (492 lines) ✅
|
||||||
|
- **Commit**: `b3291c3 Phase 3: Split computed-stats.ts by responsibility`
|
||||||
|
- **Result**: Split into `utils/formatting.ts`, `floor-utils.ts`, `mana-utils.ts`, `combat-utils.ts`, `index.ts`
|
||||||
|
- **Build**: ✅ Passes
|
||||||
|
|
||||||
|
### 6. `utils.ts` (372 lines) ✅
|
||||||
|
- **Commit**: `23d0a12 Phase 3: Split utils.ts by responsibility`
|
||||||
|
- **Result**: Split into `utils/formatting.ts`, `floor-utils.ts`, `mana-utils.ts`, `combat-utils.ts`, `index.ts` (some overlap with computed-stats, but consistent)
|
||||||
|
- **Build**: ✅ Passes
|
||||||
|
|
||||||
## Failed Refactorings
|
## Failed Refactorings
|
||||||
|
|
||||||
### 1. `store.ts` (2464 lines) ❌
|
### 1. `store.ts` (2464 lines) ❌
|
||||||
@@ -28,23 +43,26 @@
|
|||||||
- **Issue**: Larger than `store.ts` which failed
|
- **Issue**: Larger than `store.ts` which failed
|
||||||
- **Status**: Flagged as "too large for current sub-agent setup"
|
- **Status**: Flagged as "too large for current sub-agent setup"
|
||||||
|
|
||||||
|
### 3. `gameStore.ts` (509 lines) ❌
|
||||||
|
- **Issue**: Sub-agent returned empty result (context limits or other issue)
|
||||||
|
- **Status**: Will try again with simpler prompt, or flag as "sub-agent unstable for this file"
|
||||||
|
|
||||||
## Next Files to Refactor
|
## Next Files to Refactor
|
||||||
|
|
||||||
### High Priority (Smaller, Likely to Work)
|
### High Priority (Smaller, Likely to Work)
|
||||||
1. `src/components/game/tabs/CraftingTab.tsx` (965 lines) - Split by crafting stage
|
1. `src/components/game/tabs/DebugTab.tsx` (700 lines) - Split by functional area
|
||||||
2. `src/lib/game/computed-stats.ts` (492 lines) - Split by responsibility
|
2. `src/app/page.tsx` (465 lines) - Lazy load tabs
|
||||||
3. `src/lib/game/utils.ts` (372 lines) - Split by responsibility
|
|
||||||
|
|
||||||
### Medium Priority
|
### Medium Priority
|
||||||
4. `src/components/game/tabs/DebugTab.tsx` (700 lines) - Split by functional area
|
3. `src/components/game/StatsTab.tsx` (551 lines) - Extract sub-components
|
||||||
5. `src/lib/game/stores/gameStore.ts` (509 lines) - Clean up coordinator
|
4. `src/lib/game/stores/index.test.ts` (maybe not needed)
|
||||||
6. `src/app/page.tsx` (465 lines) - Lazy load tabs
|
|
||||||
|
|
||||||
## Build Status
|
## Build Status
|
||||||
✅ Build passes after each successful refactoring
|
✅ Build passes after each successful refactoring
|
||||||
✅ All commits pushed to remote
|
✅ All commits pushed to remote (`git push origin master` successful)
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
- Sub-agents work best with files under ~1500 lines
|
- Sub-agents work best with files under ~1500 lines with focused prompts
|
||||||
- Focused prompts yield better results
|
- Files over 2000 lines consistently fail (context limits)
|
||||||
- Larger files (2000+ lines) tend to break builds or fail silently
|
- Some files around 500 lines also fail occasionally (unstable sub-agent behavior)
|
||||||
|
- When in doubt, flag it and move on (per user instructions)
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements';
|
||||||
|
import { Sparkles, Unlock } from 'lucide-react';
|
||||||
|
import type { GameStore } from '@/lib/game/types';
|
||||||
|
|
||||||
|
interface AttunementDebugProps {
|
||||||
|
store: GameStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AttunementDebug({ store }: AttunementDebugProps) {
|
||||||
|
const handleUnlockAttunement = (id: string) => {
|
||||||
|
// Debug action to unlock attunements
|
||||||
|
if (store.debugUnlockAttunement) {
|
||||||
|
store.debugUnlockAttunement(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddAttunementXP = (id: string, amount: number) => {
|
||||||
|
if (store.debugAddAttunementXP) {
|
||||||
|
store.debugAddAttunementXP(id, amount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-purple-400 text-sm flex items-center gap-2">
|
||||||
|
<Sparkles className="w-4 h-4" />
|
||||||
|
Attunements
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
{Object.entries(ATTUNEMENTS_DEF).map(([id, def]) => {
|
||||||
|
const isActive = store.attunements?.[id]?.active;
|
||||||
|
const level = store.attunements?.[id]?.level || 1;
|
||||||
|
const xp = store.attunements?.[id]?.experience || 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={id} className="flex items-center justify-between p-2 bg-gray-800/50 rounded">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>{def.icon}</span>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-medium">{def.name}</div>
|
||||||
|
{isActive && (
|
||||||
|
<div className="text-xs text-gray-400">Lv.{level} • {xp} XP</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
{!isActive && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleUnlockAttunement(id)}
|
||||||
|
>
|
||||||
|
<Unlock className="w-3 h-3" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{isActive && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleAddAttunementXP(id, 50)}
|
||||||
|
>
|
||||||
|
+50 XP
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleAddAttunementXP(id, 500)}
|
||||||
|
>
|
||||||
|
+500 XP
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { ELEMENTS } from '@/lib/game/constants';
|
||||||
|
import { Star, Lock } from 'lucide-react';
|
||||||
|
import type { GameStore } from '@/lib/game/types';
|
||||||
|
|
||||||
|
interface ElementDebugProps {
|
||||||
|
store: GameStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ElementDebug({ store }: ElementDebugProps) {
|
||||||
|
const handleUnlockElement = (element: string) => {
|
||||||
|
store.unlockElement(element);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddElementalMana = (element: string, amount: number) => {
|
||||||
|
const elem = store.elements[element];
|
||||||
|
if (elem?.unlocked) {
|
||||||
|
// Add directly to element pool - need to implement in store
|
||||||
|
if (store.debugAddElementalMana) {
|
||||||
|
store.debugAddElementalMana(element, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700 md:col-span-2">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-green-400 text-sm flex items-center gap-2">
|
||||||
|
<Star className="w-4 h-4" />
|
||||||
|
Elemental Mana
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 gap-2">
|
||||||
|
{Object.entries(ELEMENTS).map(([id, def]) => {
|
||||||
|
const elem = store.elements[id];
|
||||||
|
const isUnlocked = elem?.unlocked;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={id}
|
||||||
|
className={`p-2 rounded border ${
|
||||||
|
isUnlocked ? 'border-gray-600' : 'border-gray-800 opacity-60'
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
borderColor: isUnlocked ? def.color : undefined
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between mb-1">
|
||||||
|
<span style={{ color: def.color }}>{def.sym}</span>
|
||||||
|
{!isUnlocked && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="h-5 w-5 p-0"
|
||||||
|
onClick={() => handleUnlockElement(id)}
|
||||||
|
>
|
||||||
|
<Lock className="w-3 h-3" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs" style={{ color: def.color }}>{def.name}</div>
|
||||||
|
{isUnlocked && (
|
||||||
|
<div className="text-xs text-gray-400 mt-1">
|
||||||
|
{elem.current.toFixed(0)}/{elem.max}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isUnlocked && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="h-5 w-full mt-1 text-xs"
|
||||||
|
onClick={() => handleAddElementalMana(id, 100)}
|
||||||
|
>
|
||||||
|
+100
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import {
|
||||||
|
RotateCcw, AlertTriangle, Zap, Clock, Settings, Eye,
|
||||||
|
Plus
|
||||||
|
} from 'lucide-react';
|
||||||
|
import type { GameStore } from '@/lib/game/types';
|
||||||
|
import { fmt } from '@/lib/game/store';
|
||||||
|
import { useDebug } from '@/lib/game/debug-context';
|
||||||
|
|
||||||
|
interface GameStateDebugProps {
|
||||||
|
store: GameStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GameStateDebug({ store }: GameStateDebugProps) {
|
||||||
|
const [confirmReset, setConfirmReset] = useState(false);
|
||||||
|
const { showComponentNames, toggleComponentNames } = useDebug();
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
if (confirmReset) {
|
||||||
|
store.resetGame();
|
||||||
|
setConfirmReset(false);
|
||||||
|
} else {
|
||||||
|
setConfirmReset(true);
|
||||||
|
setTimeout(() => setConfirmReset(false), 3000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddMana = (amount: number) => {
|
||||||
|
// Use gatherMana multiple times to add mana
|
||||||
|
for (let i = 0; i < amount; i++) {
|
||||||
|
store.gatherMana();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetTime = (day: number, hour: number) => {
|
||||||
|
if (store.debugSetTime) {
|
||||||
|
store.debugSetTime(day, hour);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Warning Banner */}
|
||||||
|
<Card className="bg-amber-900/20 border-amber-600/50">
|
||||||
|
<CardContent className="pt-4">
|
||||||
|
<div className="flex items-center gap-2 text-amber-400">
|
||||||
|
<AlertTriangle className="w-5 h-5" />
|
||||||
|
<span className="font-semibold">Debug Mode</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-amber-300/70 mt-1">
|
||||||
|
These tools are for development and testing. Using them may break game balance or save data.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Display Options */}
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-cyan-400 text-sm flex items-center gap-2">
|
||||||
|
<Eye className="w-4 h-4" />
|
||||||
|
Display Options
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label htmlFor="show-component-names" className="text-sm">Show Component Names</Label>
|
||||||
|
<p className="text-xs text-gray-400">
|
||||||
|
Display component names at the top of each component for debugging
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
id="show-component-names"
|
||||||
|
checked={showComponentNames}
|
||||||
|
onCheckedChange={toggleComponentNames}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{/* Game Reset */}
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-red-400 text-sm flex items-center gap-2">
|
||||||
|
<RotateCcw className="w-4 h-4" />
|
||||||
|
Game Reset
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
<p className="text-xs text-gray-400">
|
||||||
|
Reset all game progress and start fresh. This cannot be undone.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
className={`w-full ${confirmReset ? 'bg-red-600 hover:bg-red-700' : 'bg-gray-700 hover:bg-gray-600'}`}
|
||||||
|
onClick={handleReset}
|
||||||
|
>
|
||||||
|
{confirmReset ? (
|
||||||
|
<>
|
||||||
|
<AlertTriangle className="w-4 h-4 mr-2" />
|
||||||
|
Click Again to Confirm Reset
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<RotateCcw className="w-4 h-4 mr-2" />
|
||||||
|
Reset Game
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Mana Debug */}
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-blue-400 text-sm flex items-center gap-2">
|
||||||
|
<Zap className="w-4 h-4" />
|
||||||
|
Mana Debug
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
<div className="text-xs text-gray-400 mb-2">
|
||||||
|
Current: {fmt(store.rawMana)} / {fmt(store.getMaxMana())}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button size="sm" variant="outline" onClick={() => handleAddMana(10)}>
|
||||||
|
<Plus className="w-3 h-3 mr-1" /> +10
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" variant="outline" onClick={() => handleAddMana(100)}>
|
||||||
|
<Plus className="w-3 h-3 mr-1" /> +100
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" variant="outline" onClick={() => handleAddMana(1000)}>
|
||||||
|
<Plus className="w-3 h-3 mr-1" /> +1K
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" variant="outline" onClick={() => handleAddMana(10000)}>
|
||||||
|
<Plus className="w-3 h-3 mr-1" /> +10K
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Separator className="bg-gray-700" />
|
||||||
|
<div className="text-xs text-gray-400">Fill to max:</div>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="w-full bg-blue-600 hover:bg-blue-700"
|
||||||
|
onClick={() => {
|
||||||
|
const max = store.getMaxMana();
|
||||||
|
const current = store.rawMana;
|
||||||
|
for (let i = 0; i < Math.floor(max - current); i++) {
|
||||||
|
store.gatherMana();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Fill Mana
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Time Control */}
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-amber-400 text-sm flex items-center gap-2">
|
||||||
|
<Clock className="w-4 h-4" />
|
||||||
|
Time Control
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
<div className="text-xs text-gray-400">
|
||||||
|
Current: Day {store.day}, Hour {store.hour}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button size="sm" variant="outline" onClick={() => handleSetTime(1, 0)}>
|
||||||
|
Day 1
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" variant="outline" onClick={() => handleSetTime(10, 0)}>
|
||||||
|
Day 10
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" variant="outline" onClick={() => handleSetTime(20, 0)}>
|
||||||
|
Day 20
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" variant="outline" onClick={() => handleSetTime(30, 0)}>
|
||||||
|
Day 30
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Separator className="bg-gray-700" />
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => store.togglePause()}
|
||||||
|
>
|
||||||
|
{store.paused ? '▶ Resume' : '⏸ Pause'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Skills Debug - Quick Actions */}
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700 md:col-span-2">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-cyan-400 text-sm flex items-center gap-2">
|
||||||
|
<Settings className="w-4 h-4" />
|
||||||
|
Quick Actions
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// Unlock all base elements
|
||||||
|
['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'].forEach(e => {
|
||||||
|
if (!store.elements[e]?.unlocked) {
|
||||||
|
store.unlockElement(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Unlock All Base Elements
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// Unlock utility elements (only transference remains)
|
||||||
|
['transference'].forEach(e => {
|
||||||
|
if (!store.elements[e]?.unlocked) {
|
||||||
|
store.unlockElement(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Unlock Utility Elements
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// Max floor
|
||||||
|
if (store.debugSetFloor) {
|
||||||
|
store.debugSetFloor(100);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Skip to Floor 100
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// Reset floor HP
|
||||||
|
if (store.resetFloorHP) {
|
||||||
|
store.resetFloorHP();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset Floor HP
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Bug } from 'lucide-react';
|
||||||
|
import type { GameStore } from '@/lib/game/types';
|
||||||
|
|
||||||
|
interface GolemDebugProps {
|
||||||
|
store: GameStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GolemDebug({ store }: GolemDebugProps) {
|
||||||
|
return (
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700 md:col-span-2">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-orange-400 text-sm flex items-center gap-2">
|
||||||
|
<Bug className="w-4 h-4" />
|
||||||
|
Golem Debug
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-xs text-gray-400">
|
||||||
|
Golem debugging tools will be added here.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { BookOpen } from 'lucide-react';
|
||||||
|
import type { GameStore } from '@/lib/game/types';
|
||||||
|
|
||||||
|
interface SkillDebugProps {
|
||||||
|
store: GameStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SkillDebug({ store }: SkillDebugProps) {
|
||||||
|
return (
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700 md:col-span-2">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-purple-400 text-sm flex items-center gap-2">
|
||||||
|
<BookOpen className="w-4 h-4" />
|
||||||
|
Skill Research Debug
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{/* Enchanting Skills */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-2">Enchanting Skills:</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// Level up all enchanting skills by 1
|
||||||
|
const enchantSkills = ['enchanting', 'efficientEnchant', 'disenchanting', 'enchantSpeed','essenceRefining'];
|
||||||
|
enchantSkills.forEach(skillId => {
|
||||||
|
if (store.skills[skillId] !== undefined) {
|
||||||
|
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10);
|
||||||
|
} else {
|
||||||
|
store.skills[skillId] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+1 All Enchanting
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// Max all enchanting skills
|
||||||
|
const enchantSkills = ['enchanting', 'efficientEnchant', 'disenchanting', 'enchantSpeed','essenceRefining'];
|
||||||
|
enchantSkills.forEach(skillId => {
|
||||||
|
store.skills[skillId] = 10;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Max All Enchanting
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mana Skills */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-2">Mana Skills:</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const manaSkills = ['manaWell', 'manaFlow', 'elemAttune', 'manaOverflow'];
|
||||||
|
manaSkills.forEach(skillId => {
|
||||||
|
if (store.skills[skillId] !== undefined) {
|
||||||
|
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10);
|
||||||
|
} else {
|
||||||
|
store.skills[skillId] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+1 All Mana
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const manaSkills = ['manaWell', 'manaFlow', 'elemAttune', 'manaOverflow'];
|
||||||
|
manaSkills.forEach(skillId => {
|
||||||
|
store.skills[skillId] = 10;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Max All Mana
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Study Skills */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-2">Study Skills:</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const studySkills = ['quickLearner', 'focusedMind', 'meditation', 'knowledgeRetention'];
|
||||||
|
studySkills.forEach(skillId => {
|
||||||
|
if (store.skills[skillId] !== undefined) {
|
||||||
|
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10);
|
||||||
|
} else {
|
||||||
|
store.skills[skillId] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+1 All Study
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const studySkills = ['quickLearner', 'focusedMind', 'meditation', 'knowledgeRetention'];
|
||||||
|
studySkills.forEach(skillId => {
|
||||||
|
store.skills[skillId] = 10;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Max All Study
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Crafting Skills */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-2">Crafting Skills:</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const craftSkills = ['effCrafting', 'fieldRepair', 'elemCrafting'];
|
||||||
|
craftSkills.forEach(skillId => {
|
||||||
|
if (store.skills[skillId] !== undefined) {
|
||||||
|
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10);
|
||||||
|
} else {
|
||||||
|
store.skills[skillId] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+1 All Crafting
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const craftSkills = ['effCrafting', 'fieldRepair', 'elemCrafting'];
|
||||||
|
craftSkills.forEach(skillId => {
|
||||||
|
store.skills[skillId] = 10;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Max All Crafting
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Research Effects */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-2">Research Effects:</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// Unlock all spell research
|
||||||
|
const researchSkills = [
|
||||||
|
'researchManaSpells', 'researchFireSpells', 'researchWaterSpells',
|
||||||
|
'researchAirSpells', 'researchEarthSpells', 'researchLightSpells',
|
||||||
|
'researchDarkSpells', 'researchLifeDeathSpells',
|
||||||
|
'researchAdvancedFire', 'researchAdvancedWater', 'researchAdvancedAir',
|
||||||
|
'researchAdvancedEarth', 'researchAdvancedLight', 'researchAdvancedDark',
|
||||||
|
'researchMasterFire', 'researchMasterWater', 'researchMasterEarth',
|
||||||
|
'researchDamageEffects', 'researchCombatEffects',
|
||||||
|
'researchManaEffects', 'researchAdvancedManaEffects', 'researchUtilityEffects'
|
||||||
|
];
|
||||||
|
researchSkills.forEach(skillId => {
|
||||||
|
store.skills[skillId] = 1;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Unlock All Research
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
// Add all unlocked effects to unlockedEffects
|
||||||
|
const effectIds = Object.keys(store.unlockedEffects || {});
|
||||||
|
// Add spell effects, mana effects, combat effects, utility effects
|
||||||
|
const allEffectIds = [
|
||||||
|
// Spell effects
|
||||||
|
'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',
|
||||||
|
// Tier 2 spells
|
||||||
|
'spell_inferno', 'spell_flameWave', 'spell_tidalWave', 'spell_iceStorm',
|
||||||
|
'spell_hurricane', 'spell_windBlade', 'spell_earthquake', 'spell_stoneBarrage',
|
||||||
|
'spell_solarFlare', 'spell_divineSmite', 'spell_voidRift', 'spell_shadowStorm',
|
||||||
|
// Tier 3 spells
|
||||||
|
'spell_pyroclasm', 'spell_tsunami', 'spell_meteorStrike',
|
||||||
|
// Lightning
|
||||||
|
'spell_spark', 'spell_lightningBolt', 'spell_chainLightning',
|
||||||
|
'spell_stormCall', 'spell_thunderStrike',
|
||||||
|
// Metal and Sand
|
||||||
|
'spell_metalShard', 'spell_ironFist', 'spell_steelTempest', 'spell_furnaceBlast',
|
||||||
|
'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',
|
||||||
|
'click_mana_1', 'click_mana_3',
|
||||||
|
// Combat effects
|
||||||
|
'damage_5', 'damage_10', 'damage_pct_10', 'crit_5', 'attack_speed_10',
|
||||||
|
// Utility effects
|
||||||
|
'meditate_10', 'study_10', 'insight_5',
|
||||||
|
// Special
|
||||||
|
'spell_echo_10', 'guardian_dmg_10', 'overpower_80',
|
||||||
|
// Weapon mana
|
||||||
|
'weapon_mana_cap_20', 'weapon_mana_cap_50', 'weapon_mana_cap_100',
|
||||||
|
'weapon_mana_regen_1', 'weapon_mana_regen_2', 'weapon_mana_regen_5',
|
||||||
|
// Sword enchants
|
||||||
|
'sword_fire', 'sword_frost', 'sword_lightning', 'sword_void'
|
||||||
|
];
|
||||||
|
allEffectIds.forEach(id => {
|
||||||
|
if (!store.unlockedEffects.includes(id)) {
|
||||||
|
store.unlockedEffects.push(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Unlock All Effects
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Max All */}
|
||||||
|
<Separator className="bg-gray-700" />
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="bg-purple-600 hover:bg-purple-700"
|
||||||
|
onClick={() => {
|
||||||
|
// Max all skills
|
||||||
|
Object.keys(store.skills).forEach(skillId => {
|
||||||
|
const current = store.skills[skillId] || 0;
|
||||||
|
if (current < 10) {
|
||||||
|
store.skills[skillId] = 10;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Unlock all effects
|
||||||
|
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', 'spell_inferno',
|
||||||
|
'spell_flameWave', 'spell_tidalWave', 'spell_iceStorm', 'spell_hurricane',
|
||||||
|
'spell_windBlade', 'spell_earthquake', 'spell_stoneBarrage', 'spell_solarFlare',
|
||||||
|
'spell_divineSmite', 'spell_voidRift', 'spell_shadowStorm', 'spell_pyroclasm',
|
||||||
|
'spell_tsunami', 'spell_meteorStrike', 'spell_spark', 'spell_lightningBolt',
|
||||||
|
'spell_chainLightning', 'spell_stormCall', 'spell_thunderStrike', 'spell_metalShard',
|
||||||
|
'spell_ironFist', 'spell_steelTempest', 'spell_furnaceBlast', 'spell_sandBlast',
|
||||||
|
'spell_sandstorm', 'spell_desertWind', 'spell_duneCollapse',
|
||||||
|
'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',
|
||||||
|
'weapon_mana_cap_20', 'weapon_mana_cap_50', 'weapon_mana_cap_100',
|
||||||
|
'weapon_mana_regen_1', 'weapon_mana_regen_2', 'weapon_mana_regen_5',
|
||||||
|
'sword_fire', 'sword_frost', 'sword_lightning', 'sword_void'
|
||||||
|
];
|
||||||
|
allEffectIds.forEach(id => {
|
||||||
|
if (!store.unlockedEffects.includes(id)) {
|
||||||
|
store.unlockedEffects.push(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🚀 Max Everything
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export { GameStateDebug } from './GameStateDebug';
|
||||||
|
export { SkillDebug } from './SkillDebug';
|
||||||
|
export { ElementDebug } from './ElementDebug';
|
||||||
|
export { AttunementDebug } from './AttunementDebug';
|
||||||
|
export { GolemDebug } from './GolemDebug';
|
||||||
@@ -1,699 +1,30 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
||||||
import { Separator } from '@/components/ui/separator';
|
|
||||||
import { Switch } from '@/components/ui/switch';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import {
|
|
||||||
RotateCcw, Bug, Plus, Minus, Lock, Unlock, Zap,
|
|
||||||
Clock, Star, AlertTriangle, Sparkles, Settings, Eye, BookOpen
|
|
||||||
} from 'lucide-react';
|
|
||||||
import type { GameStore } from '@/lib/game/types';
|
import type { GameStore } from '@/lib/game/types';
|
||||||
import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements';
|
import {
|
||||||
import { ELEMENTS } from '@/lib/game/constants';
|
GameStateDebug,
|
||||||
import { fmt } from '@/lib/game/store';
|
SkillDebug,
|
||||||
import { useDebug } from '@/lib/game/debug-context';
|
ElementDebug,
|
||||||
|
AttunementDebug,
|
||||||
|
GolemDebug
|
||||||
|
} from '@/components/game/debug';
|
||||||
|
|
||||||
interface DebugTabProps {
|
interface DebugTabProps {
|
||||||
store: GameStore;
|
store: GameStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DebugTab({ store }: DebugTabProps) {
|
export function DebugTab({ store }: DebugTabProps) {
|
||||||
const [confirmReset, setConfirmReset] = useState(false);
|
|
||||||
const { showComponentNames, toggleComponentNames } = useDebug();
|
|
||||||
|
|
||||||
const handleReset = () => {
|
|
||||||
if (confirmReset) {
|
|
||||||
store.resetGame();
|
|
||||||
setConfirmReset(false);
|
|
||||||
} else {
|
|
||||||
setConfirmReset(true);
|
|
||||||
setTimeout(() => setConfirmReset(false), 3000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddMana = (amount: number) => {
|
|
||||||
// Use gatherMana multiple times to add mana
|
|
||||||
for (let i = 0; i < amount; i++) {
|
|
||||||
store.gatherMana();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUnlockAttunement = (id: string) => {
|
|
||||||
// Debug action to unlock attunements
|
|
||||||
if (store.debugUnlockAttunement) {
|
|
||||||
store.debugUnlockAttunement(id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUnlockElement = (element: string) => {
|
|
||||||
store.unlockElement(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddElementalMana = (element: string, amount: number) => {
|
|
||||||
const elem = store.elements[element];
|
|
||||||
if (elem?.unlocked) {
|
|
||||||
// Add directly to element pool - need to implement in store
|
|
||||||
if (store.debugAddElementalMana) {
|
|
||||||
store.debugAddElementalMana(element, amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSetTime = (day: number, hour: number) => {
|
|
||||||
if (store.debugSetTime) {
|
|
||||||
store.debugSetTime(day, hour);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddAttunementXP = (id: string, amount: number) => {
|
|
||||||
if (store.debugAddAttunementXP) {
|
|
||||||
store.debugAddAttunementXP(id, amount);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Warning Banner */}
|
<GameStateDebug store={store} />
|
||||||
<Card className="bg-amber-900/20 border-amber-600/50">
|
|
||||||
<CardContent className="pt-4">
|
|
||||||
<div className="flex items-center gap-2 text-amber-400">
|
|
||||||
<AlertTriangle className="w-5 h-5" />
|
|
||||||
<span className="font-semibold">Debug Mode</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-amber-300/70 mt-1">
|
|
||||||
These tools are for development and testing. Using them may break game balance or save data.
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Display Options */}
|
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle className="text-cyan-400 text-sm flex items-center gap-2">
|
|
||||||
<Eye className="w-4 h-4" />
|
|
||||||
Display Options
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<Label htmlFor="show-component-names" className="text-sm">Show Component Names</Label>
|
|
||||||
<p className="text-xs text-gray-400">
|
|
||||||
Display component names at the top of each component for debugging
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
id="show-component-names"
|
|
||||||
checked={showComponentNames}
|
|
||||||
onCheckedChange={toggleComponentNames}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{/* Game Reset */}
|
<AttunementDebug store={store} />
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
<ElementDebug store={store} />
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle className="text-red-400 text-sm flex items-center gap-2">
|
|
||||||
<RotateCcw className="w-4 h-4" />
|
|
||||||
Game Reset
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-3">
|
|
||||||
<p className="text-xs text-gray-400">
|
|
||||||
Reset all game progress and start fresh. This cannot be undone.
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
className={`w-full ${confirmReset ? 'bg-red-600 hover:bg-red-700' : 'bg-gray-700 hover:bg-gray-600'}`}
|
|
||||||
onClick={handleReset}
|
|
||||||
>
|
|
||||||
{confirmReset ? (
|
|
||||||
<>
|
|
||||||
<AlertTriangle className="w-4 h-4 mr-2" />
|
|
||||||
Click Again to Confirm Reset
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<RotateCcw className="w-4 h-4 mr-2" />
|
|
||||||
Reset Game
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Mana Debug */}
|
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle className="text-blue-400 text-sm flex items-center gap-2">
|
|
||||||
<Zap className="w-4 h-4" />
|
|
||||||
Mana Debug
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-3">
|
|
||||||
<div className="text-xs text-gray-400 mb-2">
|
|
||||||
Current: {fmt(store.rawMana)} / {fmt(store.getMaxMana())}
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
|
||||||
<Button size="sm" variant="outline" onClick={() => handleAddMana(10)}>
|
|
||||||
<Plus className="w-3 h-3 mr-1" /> +10
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="outline" onClick={() => handleAddMana(100)}>
|
|
||||||
<Plus className="w-3 h-3 mr-1" /> +100
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="outline" onClick={() => handleAddMana(1000)}>
|
|
||||||
<Plus className="w-3 h-3 mr-1" /> +1K
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="outline" onClick={() => handleAddMana(10000)}>
|
|
||||||
<Plus className="w-3 h-3 mr-1" /> +10K
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Separator className="bg-gray-700" />
|
|
||||||
<div className="text-xs text-gray-400">Fill to max:</div>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className="w-full bg-blue-600 hover:bg-blue-700"
|
|
||||||
onClick={() => {
|
|
||||||
const max = store.getMaxMana();
|
|
||||||
const current = store.rawMana;
|
|
||||||
for (let i = 0; i < Math.floor(max - current); i++) {
|
|
||||||
store.gatherMana();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Fill Mana
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Time Control */}
|
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle className="text-amber-400 text-sm flex items-center gap-2">
|
|
||||||
<Clock className="w-4 h-4" />
|
|
||||||
Time Control
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-3">
|
|
||||||
<div className="text-xs text-gray-400">
|
|
||||||
Current: Day {store.day}, Hour {store.hour}
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
|
||||||
<Button size="sm" variant="outline" onClick={() => handleSetTime(1, 0)}>
|
|
||||||
Day 1
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="outline" onClick={() => handleSetTime(10, 0)}>
|
|
||||||
Day 10
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="outline" onClick={() => handleSetTime(20, 0)}>
|
|
||||||
Day 20
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="outline" onClick={() => handleSetTime(30, 0)}>
|
|
||||||
Day 30
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Separator className="bg-gray-700" />
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => store.togglePause()}
|
|
||||||
>
|
|
||||||
{store.paused ? '▶ Resume' : '⏸ Pause'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Attunement Unlock */}
|
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle className="text-purple-400 text-sm flex items-center gap-2">
|
|
||||||
<Sparkles className="w-4 h-4" />
|
|
||||||
Attunements
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-3">
|
|
||||||
{Object.entries(ATTUNEMENTS_DEF).map(([id, def]) => {
|
|
||||||
const isActive = store.attunements?.[id]?.active;
|
|
||||||
const level = store.attunements?.[id]?.level || 1;
|
|
||||||
const xp = store.attunements?.[id]?.experience || 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={id} className="flex items-center justify-between p-2 bg-gray-800/50 rounded">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span>{def.icon}</span>
|
|
||||||
<div>
|
|
||||||
<div className="text-sm font-medium">{def.name}</div>
|
|
||||||
{isActive && (
|
|
||||||
<div className="text-xs text-gray-400">Lv.{level} • {xp} XP</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-1">
|
|
||||||
{!isActive && (
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => handleUnlockAttunement(id)}
|
|
||||||
>
|
|
||||||
<Unlock className="w-3 h-3" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{isActive && (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => handleAddAttunementXP(id, 50)}
|
|
||||||
>
|
|
||||||
+50 XP
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => handleAddAttunementXP(id, 500)}
|
|
||||||
>
|
|
||||||
+500 XP
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Element Unlock */}
|
|
||||||
<Card className="bg-gray-900/80 border-gray-700 md:col-span-2">
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle className="text-green-400 text-sm flex items-center gap-2">
|
|
||||||
<Star className="w-4 h-4" />
|
|
||||||
Elemental Mana
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 gap-2">
|
|
||||||
{Object.entries(ELEMENTS).map(([id, def]) => {
|
|
||||||
const elem = store.elements[id];
|
|
||||||
const isUnlocked = elem?.unlocked;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={id}
|
|
||||||
className={`p-2 rounded border ${
|
|
||||||
isUnlocked ? 'border-gray-600' : 'border-gray-800 opacity-60'
|
|
||||||
}`}
|
|
||||||
style={{
|
|
||||||
borderColor: isUnlocked ? def.color : undefined
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between mb-1">
|
|
||||||
<span style={{ color: def.color }}>{def.sym}</span>
|
|
||||||
{!isUnlocked && (
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
className="h-5 w-5 p-0"
|
|
||||||
onClick={() => handleUnlockElement(id)}
|
|
||||||
>
|
|
||||||
<Lock className="w-3 h-3" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs" style={{ color: def.color }}>{def.name}</div>
|
|
||||||
{isUnlocked && (
|
|
||||||
<div className="text-xs text-gray-400 mt-1">
|
|
||||||
{elem.current.toFixed(0)}/{elem.max}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isUnlocked && (
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
className="h-5 w-full mt-1 text-xs"
|
|
||||||
onClick={() => handleAddElementalMana(id, 100)}
|
|
||||||
>
|
|
||||||
+100
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Skills Debug */}
|
|
||||||
<Card className="bg-gray-900/80 border-gray-700 md:col-span-2">
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle className="text-cyan-400 text-sm flex items-center gap-2">
|
|
||||||
<Settings className="w-4 h-4" />
|
|
||||||
Quick Actions
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
// Unlock all base elements
|
|
||||||
['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'].forEach(e => {
|
|
||||||
if (!store.elements[e]?.unlocked) {
|
|
||||||
store.unlockElement(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Unlock All Base Elements
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
// Unlock utility elements (only transference remains)
|
|
||||||
['transference'].forEach(e => {
|
|
||||||
if (!store.elements[e]?.unlocked) {
|
|
||||||
store.unlockElement(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Unlock Utility Elements
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
// Max floor
|
|
||||||
if (store.debugSetFloor) {
|
|
||||||
store.debugSetFloor(100);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Skip to Floor 100
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
// Reset floor HP
|
|
||||||
if (store.resetFloorHP) {
|
|
||||||
store.resetFloorHP();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reset Floor HP
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Skill Research Debug */}
|
|
||||||
<Card className="bg-gray-900/80 border-gray-700 md:col-span-2">
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<CardTitle className="text-purple-400 text-sm flex items-center gap-2">
|
|
||||||
<BookOpen className="w-4 h-4" />
|
|
||||||
Skill Research Debug
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{/* Enchanting Skills */}
|
|
||||||
<div>
|
|
||||||
<div className="text-xs text-gray-400 mb-2">Enchanting Skills:</div>
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
// Level up all enchanting skills by 1
|
|
||||||
const enchantSkills = ['enchanting', 'efficientEnchant', 'disenchanting', 'enchantSpeed','essenceRefining'];
|
|
||||||
enchantSkills.forEach(skillId => {
|
|
||||||
if (store.skills[skillId] !== undefined) {
|
|
||||||
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10);
|
|
||||||
} else {
|
|
||||||
store.skills[skillId] = 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
+1 All Enchanting
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
// Max all enchanting skills
|
|
||||||
const enchantSkills = ['enchanting', 'efficientEnchant', 'disenchanting', 'enchantSpeed','essenceRefining'];
|
|
||||||
enchantSkills.forEach(skillId => {
|
|
||||||
store.skills[skillId] = 10;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Max All Enchanting
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mana Skills */}
|
<SkillDebug store={store} />
|
||||||
<div>
|
<GolemDebug store={store} />
|
||||||
<div className="text-xs text-gray-400 mb-2">Mana Skills:</div>
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
const manaSkills = ['manaWell', 'manaFlow', 'elemAttune', 'manaOverflow'];
|
|
||||||
manaSkills.forEach(skillId => {
|
|
||||||
if (store.skills[skillId] !== undefined) {
|
|
||||||
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10);
|
|
||||||
} else {
|
|
||||||
store.skills[skillId] = 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
+1 All Mana
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
const manaSkills = ['manaWell', 'manaFlow', 'elemAttune', 'manaOverflow'];
|
|
||||||
manaSkills.forEach(skillId => {
|
|
||||||
store.skills[skillId] = 10;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Max All Mana
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Study Skills */}
|
|
||||||
<div>
|
|
||||||
<div className="text-xs text-gray-400 mb-2">Study Skills:</div>
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
const studySkills = ['quickLearner', 'focusedMind', 'meditation', 'knowledgeRetention'];
|
|
||||||
studySkills.forEach(skillId => {
|
|
||||||
if (store.skills[skillId] !== undefined) {
|
|
||||||
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10);
|
|
||||||
} else {
|
|
||||||
store.skills[skillId] = 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
+1 All Study
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
const studySkills = ['quickLearner', 'focusedMind', 'meditation', 'knowledgeRetention'];
|
|
||||||
studySkills.forEach(skillId => {
|
|
||||||
store.skills[skillId] = 10;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Max All Study
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Crafting Skills */}
|
|
||||||
<div>
|
|
||||||
<div className="text-xs text-gray-400 mb-2">Crafting Skills:</div>
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
const craftSkills = ['effCrafting', 'fieldRepair', 'elemCrafting'];
|
|
||||||
craftSkills.forEach(skillId => {
|
|
||||||
if (store.skills[skillId] !== undefined) {
|
|
||||||
store.skills[skillId] = Math.min((store.skills[skillId] || 0) + 1, 10);
|
|
||||||
} else {
|
|
||||||
store.skills[skillId] = 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
+1 All Crafting
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
const craftSkills = ['effCrafting', 'fieldRepair', 'elemCrafting'];
|
|
||||||
craftSkills.forEach(skillId => {
|
|
||||||
store.skills[skillId] = 10;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Max All Crafting
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Research Effects */}
|
|
||||||
<div>
|
|
||||||
<div className="text-xs text-gray-400 mb-2">Research Effects:</div>
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
// Unlock all spell research
|
|
||||||
const researchSkills = [
|
|
||||||
'researchManaSpells', 'researchFireSpells', 'researchWaterSpells',
|
|
||||||
'researchAirSpells', 'researchEarthSpells', 'researchLightSpells',
|
|
||||||
'researchDarkSpells', 'researchLifeDeathSpells',
|
|
||||||
'researchAdvancedFire', 'researchAdvancedWater', 'researchAdvancedAir',
|
|
||||||
'researchAdvancedEarth', 'researchAdvancedLight', 'researchAdvancedDark',
|
|
||||||
'researchMasterFire', 'researchMasterWater', 'researchMasterEarth',
|
|
||||||
'researchDamageEffects', 'researchCombatEffects',
|
|
||||||
'researchManaEffects', 'researchAdvancedManaEffects', 'researchUtilityEffects'
|
|
||||||
];
|
|
||||||
researchSkills.forEach(skillId => {
|
|
||||||
store.skills[skillId] = 1;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Unlock All Research
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
// Add all unlocked effects to unlockedEffects
|
|
||||||
const effectIds = Object.keys(store.unlockedEffects || {});
|
|
||||||
// Add spell effects, mana effects, combat effects, utility effects
|
|
||||||
const allEffectIds = [
|
|
||||||
// Spell effects
|
|
||||||
'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',
|
|
||||||
// Tier 2 spells
|
|
||||||
'spell_inferno', 'spell_flameWave', 'spell_tidalWave', 'spell_iceStorm',
|
|
||||||
'spell_hurricane', 'spell_windBlade', 'spell_earthquake', 'spell_stoneBarrage',
|
|
||||||
'spell_solarFlare', 'spell_divineSmite', 'spell_voidRift', 'spell_shadowStorm',
|
|
||||||
// Tier 3 spells
|
|
||||||
'spell_pyroclasm', 'spell_tsunami', 'spell_meteorStrike',
|
|
||||||
// Lightning
|
|
||||||
'spell_spark', 'spell_lightningBolt', 'spell_chainLightning',
|
|
||||||
'spell_stormCall', 'spell_thunderStrike',
|
|
||||||
// Metal and Sand
|
|
||||||
'spell_metalShard', 'spell_ironFist', 'spell_steelTempest', 'spell_furnaceBlast',
|
|
||||||
'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',
|
|
||||||
'click_mana_1', 'click_mana_3',
|
|
||||||
// Combat effects
|
|
||||||
'damage_5', 'damage_10', 'damage_pct_10', 'crit_5', 'attack_speed_10',
|
|
||||||
// Utility effects
|
|
||||||
'meditate_10', 'study_10', 'insight_5',
|
|
||||||
// Special
|
|
||||||
'spell_echo_10', 'guardian_dmg_10', 'overpower_80',
|
|
||||||
// Weapon mana
|
|
||||||
'weapon_mana_cap_20', 'weapon_mana_cap_50', 'weapon_mana_cap_100',
|
|
||||||
'weapon_mana_regen_1', 'weapon_mana_regen_2', 'weapon_mana_regen_5',
|
|
||||||
// Sword enchants
|
|
||||||
'sword_fire', 'sword_frost', 'sword_lightning', 'sword_void'
|
|
||||||
];
|
|
||||||
allEffectIds.forEach(id => {
|
|
||||||
if (!store.unlockedEffects.includes(id)) {
|
|
||||||
store.unlockedEffects.push(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Unlock All Effects
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Max All */}
|
|
||||||
<Separator className="bg-gray-700" />
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className="bg-purple-600 hover:bg-purple-700"
|
|
||||||
onClick={() => {
|
|
||||||
// Max all skills
|
|
||||||
Object.keys(store.skills).forEach(skillId => {
|
|
||||||
const current = store.skills[skillId] || 0;
|
|
||||||
if (current < 10) {
|
|
||||||
store.skills[skillId] = 10;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Unlock all effects
|
|
||||||
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', 'spell_inferno',
|
|
||||||
'spell_flameWave', 'spell_tidalWave', 'spell_iceStorm', 'spell_hurricane',
|
|
||||||
'spell_windBlade', 'spell_earthquake', 'spell_stoneBarrage', 'spell_solarFlare',
|
|
||||||
'spell_divineSmite', 'spell_voidRift', 'spell_shadowStorm', 'spell_pyroclasm',
|
|
||||||
'spell_tsunami', 'spell_meteorStrike', 'spell_spark', 'spell_lightningBolt',
|
|
||||||
'spell_chainLightning', 'spell_stormCall', 'spell_thunderStrike', 'spell_metalShard',
|
|
||||||
'spell_ironFist', 'spell_steelTempest', 'spell_furnaceBlast', 'spell_sandBlast',
|
|
||||||
'spell_sandstorm', 'spell_desertWind', 'spell_duneCollapse',
|
|
||||||
'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',
|
|
||||||
'weapon_mana_cap_20', 'weapon_mana_cap_50', 'weapon_mana_cap_100',
|
|
||||||
'weapon_mana_regen_1', 'weapon_mana_regen_2', 'weapon_mana_regen_5',
|
|
||||||
'sword_fire', 'sword_frost', 'sword_lightning', 'sword_void'
|
|
||||||
];
|
|
||||||
allEffectIds.forEach(id => {
|
|
||||||
if (!store.unlockedEffects.includes(id)) {
|
|
||||||
store.unlockedEffects.push(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
🚀 Max Everything
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { create } from 'zustand';
|
|||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
import { SPELLS_DEF, GUARDIANS, HOURS_PER_TICK } from '../constants';
|
import { SPELLS_DEF, GUARDIANS, HOURS_PER_TICK } from '../constants';
|
||||||
import type { GameAction, SpellState } from '../types';
|
import type { GameAction, SpellState } from '../types';
|
||||||
import { getFloorMaxHP, getFloorElement, calcDamage } from '../utils';
|
import { getFloorMaxHP, getFloorElement, calcDamage, canAffordSpellCost, deductSpellCost } from '../utils';
|
||||||
import { usePrestigeStore } from './prestigeStore';
|
import { usePrestigeStore } from './prestigeStore';
|
||||||
|
|
||||||
export interface CombatState {
|
export interface CombatState {
|
||||||
@@ -141,15 +141,16 @@ export const useCombatStore = create<CombatState>()(
|
|||||||
) => {
|
) => {
|
||||||
const state = get();
|
const state = get();
|
||||||
const logMessages: string[] = [];
|
const logMessages: string[] = [];
|
||||||
|
let totalManaGathered = 0;
|
||||||
|
|
||||||
if (state.currentAction !== 'climb') {
|
if (state.currentAction !== 'climb') {
|
||||||
return { rawMana, elements, logMessages };
|
return { rawMana, elements, logMessages, totalManaGathered };
|
||||||
}
|
}
|
||||||
|
|
||||||
const spellId = state.activeSpell;
|
const spellId = state.activeSpell;
|
||||||
const spellDef = SPELLS_DEF[spellId];
|
const spellDef = SPELLS_DEF[spellId];
|
||||||
if (!spellDef) {
|
if (!spellDef) {
|
||||||
return { rawMana, elements, logMessages };
|
return { rawMana, elements, logMessages, totalManaGathered };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate cast speed
|
// Calculate cast speed
|
||||||
@@ -164,28 +165,14 @@ export const useCombatStore = create<CombatState>()(
|
|||||||
let floorMaxHP = state.floorMaxHP;
|
let floorMaxHP = state.floorMaxHP;
|
||||||
|
|
||||||
// Process complete casts
|
// Process complete casts
|
||||||
while (castProgress >= 1) {
|
while (castProgress >= 1 && canAffordSpellCost(spellDef.cost, rawMana, elements)) {
|
||||||
// Check if we can afford the spell
|
// Deduct spell cost
|
||||||
const cost = spellDef.cost;
|
const afterCost = deductSpellCost(spellDef.cost, rawMana, elements);
|
||||||
let canCast = false;
|
rawMana = afterCost.rawMana;
|
||||||
|
elements = afterCost.elements;
|
||||||
|
totalManaGathered += spellDef.cost.amount;
|
||||||
|
|
||||||
if (cost.type === 'raw') {
|
// Calculate base damage
|
||||||
canCast = rawMana >= cost.amount;
|
|
||||||
if (canCast) rawMana -= cost.amount;
|
|
||||||
} else if (cost.element) {
|
|
||||||
const elem = elements[cost.element];
|
|
||||||
canCast = elem && elem.unlocked && elem.current >= cost.amount;
|
|
||||||
if (canCast) {
|
|
||||||
elements = {
|
|
||||||
...elements,
|
|
||||||
[cost.element]: { ...elem, current: elem.current - cost.amount },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canCast) break;
|
|
||||||
|
|
||||||
// Calculate damage
|
|
||||||
const floorElement = getFloorElement(currentFloor);
|
const floorElement = getFloorElement(currentFloor);
|
||||||
const damage = calcDamage(
|
const damage = calcDamage(
|
||||||
{ skills, signedPacts: usePrestigeStore.getState().signedPacts },
|
{ skills, signedPacts: usePrestigeStore.getState().signedPacts },
|
||||||
@@ -193,8 +180,14 @@ export const useCombatStore = create<CombatState>()(
|
|||||||
floorElement
|
floorElement
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Let gameStore apply damage modifiers (executioner, berserker, spell echo)
|
||||||
|
const result = onDamageDealt(damage);
|
||||||
|
rawMana = result.rawMana;
|
||||||
|
elements = result.elements;
|
||||||
|
const finalDamage = result.modifiedDamage || damage;
|
||||||
|
|
||||||
// Apply damage
|
// Apply damage
|
||||||
floorHP = Math.max(0, floorHP - damage);
|
floorHP = Math.max(0, floorHP - finalDamage);
|
||||||
castProgress -= 1;
|
castProgress -= 1;
|
||||||
|
|
||||||
// Check if floor is cleared
|
// Check if floor is cleared
|
||||||
@@ -223,7 +216,7 @@ export const useCombatStore = create<CombatState>()(
|
|||||||
castProgress,
|
castProgress,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { rawMana, elements, logMessages };
|
return { rawMana, elements, logMessages, totalManaGathered };
|
||||||
},
|
},
|
||||||
|
|
||||||
resetCombat: (startFloor: number, spellsToKeep: string[] = []) => {
|
resetCombat: (startFloor: number, spellsToKeep: string[] = []) => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
import { TICK_MS, HOURS_PER_TICK, MAX_DAY, SPELLS_DEF, GUARDIANS, ELEMENTS, BASE_UNLOCKED_ELEMENTS, getStudySpeedMultiplier } from '../constants';
|
import { TICK_MS, HOURS_PER_TICK, MAX_DAY, SPELLS_DEF, GUARDIANS, getStudySpeedMultiplier } from '../constants';
|
||||||
import { computeEffects, hasSpecial, SPECIAL_EFFECTS } from '../upgrade-effects';
|
import { computeEffects, hasSpecial, SPECIAL_EFFECTS } from '../upgrade-effects';
|
||||||
import {
|
import {
|
||||||
computeMaxMana,
|
computeMaxMana,
|
||||||
@@ -16,13 +16,13 @@ import {
|
|||||||
calcInsight,
|
calcInsight,
|
||||||
calcDamage,
|
calcDamage,
|
||||||
deductSpellCost,
|
deductSpellCost,
|
||||||
|
canAffordSpellCost,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { useUIStore } from './uiStore';
|
import { useUIStore } from './uiStore';
|
||||||
import { usePrestigeStore } from './prestigeStore';
|
import { usePrestigeStore } from './prestigeStore';
|
||||||
import { useManaStore } from './manaStore';
|
import { useManaStore } from './manaStore';
|
||||||
import { useSkillStore } from './skillStore';
|
import { useSkillStore } from './skillStore';
|
||||||
import { useCombatStore, makeInitialSpells } from './combatStore';
|
import { useCombatStore, makeInitialSpells } from './combatStore';
|
||||||
import type { Memory } from '../types';
|
|
||||||
|
|
||||||
export interface GameCoordinatorState {
|
export interface GameCoordinatorState {
|
||||||
day: number;
|
day: number;
|
||||||
@@ -37,7 +37,6 @@ export interface GameCoordinatorStore extends GameCoordinatorState {
|
|||||||
resetGame: () => void;
|
resetGame: () => void;
|
||||||
togglePause: () => void;
|
togglePause: () => void;
|
||||||
startNewLoop: () => void;
|
startNewLoop: () => void;
|
||||||
gatherMana: () => void;
|
|
||||||
initGame: () => void;
|
initGame: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,21 +48,6 @@ const initialState: GameCoordinatorState = {
|
|||||||
initialized: false,
|
initialized: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function for checking spell cost affordability
|
|
||||||
function canAffordSpell(
|
|
||||||
cost: { type: string; element?: string; amount: number },
|
|
||||||
rawMana: number,
|
|
||||||
elements: Record<string, { current: number; max: number; unlocked: boolean }>
|
|
||||||
): boolean {
|
|
||||||
if (cost.type === 'raw') {
|
|
||||||
return rawMana >= cost.amount;
|
|
||||||
} else if (cost.element) {
|
|
||||||
const elem = elements[cost.element];
|
|
||||||
return elem && elem.unlocked && elem.current >= cost.amount;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useGameStore = create<GameCoordinatorStore>()(
|
export const useGameStore = create<GameCoordinatorStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
@@ -187,25 +171,12 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert action - auto convert mana
|
// Convert action - delegate to manaStore
|
||||||
if (combatState.currentAction === 'convert') {
|
if (combatState.currentAction === 'convert') {
|
||||||
const unlockedElements = Object.entries(elements)
|
const convertResult = useManaStore.getState().processConvertAction(rawMana);
|
||||||
.filter(([, e]) => e.unlocked && e.current < e.max);
|
if (convertResult) {
|
||||||
|
rawMana = convertResult.rawMana;
|
||||||
if (unlockedElements.length > 0 && rawMana >= 100) {
|
elements = convertResult.elements;
|
||||||
unlockedElements.sort((a, b) => (b[1].max - b[1].current) - (a[1].max - a[1].current));
|
|
||||||
const [targetId, targetState] = unlockedElements[0];
|
|
||||||
const canConvert = Math.min(
|
|
||||||
Math.floor(rawMana / 100),
|
|
||||||
targetState.max - targetState.current
|
|
||||||
);
|
|
||||||
if (canConvert > 0) {
|
|
||||||
rawMana -= canConvert * 100;
|
|
||||||
elements = {
|
|
||||||
...elements,
|
|
||||||
[targetId]: { ...targetState, current: targetState.current + canConvert }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,41 +199,27 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combat
|
// Combat - delegate to combatStore
|
||||||
let { currentFloor, floorHP, floorMaxHP, maxFloorReached, castProgress } = combatState;
|
|
||||||
const floorElement = getFloorElement(currentFloor);
|
|
||||||
|
|
||||||
if (combatState.currentAction === 'climb') {
|
if (combatState.currentAction === 'climb') {
|
||||||
const spellId = combatState.activeSpell;
|
const combatResult = useCombatStore.getState().processCombatTick(
|
||||||
const spellDef = SPELLS_DEF[spellId];
|
skillState.skills,
|
||||||
|
rawMana,
|
||||||
if (spellDef) {
|
elements,
|
||||||
const baseAttackSpeed = 1 + (skillState.skills.quickCast || 0) * 0.05;
|
maxMana,
|
||||||
const totalAttackSpeed = baseAttackSpeed * effects.attackSpeedMultiplier;
|
effects.attackSpeedMultiplier,
|
||||||
const spellCastSpeed = spellDef.castSpeed || 1;
|
(floor, wasGuardian) => {
|
||||||
const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed;
|
if (wasGuardian) {
|
||||||
|
addLog(`⚔️ ${GUARDIANS[floor]?.name || 'Guardian'} defeated! Visit the Grimoire to sign a pact.`);
|
||||||
castProgress = (castProgress || 0) + progressPerTick;
|
} else if (floor % 5 === 0) {
|
||||||
|
addLog(`🏰 Floor ${floor} cleared!`);
|
||||||
// Process complete casts
|
}
|
||||||
while (castProgress >= 1 && canAffordSpell(spellDef.cost, rawMana, elements)) {
|
},
|
||||||
const afterCost = deductSpellCost(spellDef.cost, rawMana, elements);
|
(damage) => {
|
||||||
rawMana = afterCost.rawMana;
|
|
||||||
elements = afterCost.elements;
|
|
||||||
totalManaGathered += spellDef.cost.amount;
|
|
||||||
|
|
||||||
// Calculate damage
|
|
||||||
let dmg = calcDamage(
|
|
||||||
{ skills: skillState.skills, signedPacts: prestigeState.signedPacts },
|
|
||||||
spellId,
|
|
||||||
floorElement
|
|
||||||
);
|
|
||||||
|
|
||||||
// Apply upgrade damage multipliers and bonuses
|
// Apply upgrade damage multipliers and bonuses
|
||||||
dmg = dmg * effects.baseDamageMultiplier + effects.baseDamageBonus;
|
let dmg = damage * effects.baseDamageMultiplier + effects.baseDamageBonus;
|
||||||
|
|
||||||
// Executioner: +100% damage to enemies below 25% HP
|
// Executioner: +100% damage to enemies below 25% HP
|
||||||
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && floorHP / floorMaxHP < 0.25) {
|
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && combatState.floorHP / combatState.floorMaxHP < 0.25) {
|
||||||
dmg *= 2;
|
dmg *= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,34 +235,17 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
|||||||
addLog(`✨ Spell Echo! Double damage!`);
|
addLog(`✨ Spell Echo! Double damage!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply damage
|
return { rawMana, elements, modifiedDamage: dmg };
|
||||||
floorHP = Math.max(0, floorHP - dmg);
|
}
|
||||||
castProgress -= 1;
|
);
|
||||||
|
|
||||||
if (floorHP <= 0) {
|
rawMana = combatResult.rawMana;
|
||||||
// Floor cleared
|
elements = combatResult.elements;
|
||||||
const wasGuardian = GUARDIANS[currentFloor];
|
totalManaGathered += combatResult.totalManaGathered || 0;
|
||||||
if (wasGuardian && !prestigeState.defeatedGuardians.includes(currentFloor) && !prestigeState.signedPacts.includes(currentFloor)) {
|
|
||||||
usePrestigeStore.getState().addDefeatedGuardian(currentFloor);
|
|
||||||
addLog(`⚔️ ${wasGuardian.name} defeated! Visit the Grimoire to sign a pact.`);
|
|
||||||
} else if (!wasGuardian) {
|
|
||||||
if (currentFloor % 5 === 0) {
|
|
||||||
addLog(`🏰 Floor ${currentFloor} cleared!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentFloor = currentFloor + 1;
|
// Log any messages from combat
|
||||||
if (currentFloor > 100) {
|
if (combatResult.logMessages) {
|
||||||
currentFloor = 100;
|
combatResult.logMessages.forEach(msg => addLog(msg));
|
||||||
}
|
|
||||||
floorMaxHP = getFloorMaxHP(currentFloor);
|
|
||||||
floorHP = floorMaxHP;
|
|
||||||
maxFloorReached = Math.max(maxFloorReached, currentFloor);
|
|
||||||
castProgress = 0;
|
|
||||||
|
|
||||||
useCombatStore.getState().advanceFloor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,13 +257,6 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
|||||||
elements,
|
elements,
|
||||||
});
|
});
|
||||||
|
|
||||||
useCombatStore.setState({
|
|
||||||
floorHP,
|
|
||||||
floorMaxHP,
|
|
||||||
maxFloorReached,
|
|
||||||
castProgress,
|
|
||||||
});
|
|
||||||
|
|
||||||
set({
|
set({
|
||||||
day,
|
day,
|
||||||
hour,
|
hour,
|
||||||
@@ -331,6 +264,33 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resetGame: () => {
|
||||||
|
// Clear all persisted state
|
||||||
|
localStorage.removeItem('mana-loop-ui-storage');
|
||||||
|
localStorage.removeItem('mana-loop-prestige-storage');
|
||||||
|
localStorage.removeItem('mana-loop-mana-storage');
|
||||||
|
localStorage.removeItem('mana-loop-skill-storage');
|
||||||
|
localStorage.removeItem('mana-loop-combat-storage');
|
||||||
|
localStorage.removeItem('mana-loop-game-storage');
|
||||||
|
|
||||||
|
const startFloor = 1;
|
||||||
|
|
||||||
|
useUIStore.getState().resetUI();
|
||||||
|
usePrestigeStore.getState().resetPrestige();
|
||||||
|
useManaStore.getState().resetMana({}, {}, {}, {});
|
||||||
|
useSkillStore.getState().resetSkills();
|
||||||
|
useCombatStore.getState().resetCombat(startFloor);
|
||||||
|
|
||||||
|
set({
|
||||||
|
...initialState,
|
||||||
|
initialized: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
togglePause: () => {
|
||||||
|
useUIStore.getState().togglePause();
|
||||||
|
},
|
||||||
|
|
||||||
gatherMana: () => {
|
gatherMana: () => {
|
||||||
const skillState = useSkillStore.getState();
|
const skillState = useSkillStore.getState();
|
||||||
const manaState = useManaStore.getState();
|
const manaState = useManaStore.getState();
|
||||||
@@ -351,47 +311,7 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
|||||||
effects
|
effects
|
||||||
);
|
);
|
||||||
|
|
||||||
useManaStore.setState({
|
useManaStore.getState().gatherMana(cm, max);
|
||||||
rawMana: Math.min(manaState.rawMana + cm, max),
|
|
||||||
totalManaGathered: manaState.totalManaGathered + cm,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
resetGame: () => {
|
|
||||||
// Clear all persisted state
|
|
||||||
localStorage.removeItem('mana-loop-ui-storage');
|
|
||||||
localStorage.removeItem('mana-loop-prestige-storage');
|
|
||||||
localStorage.removeItem('mana-loop-mana-storage');
|
|
||||||
localStorage.removeItem('mana-loop-skill-storage');
|
|
||||||
localStorage.removeItem('mana-loop-combat-storage');
|
|
||||||
localStorage.removeItem('mana-loop-game-storage');
|
|
||||||
|
|
||||||
const startFloor = 1;
|
|
||||||
const elemMax = 10;
|
|
||||||
|
|
||||||
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
|
|
||||||
Object.keys(ELEMENTS).forEach((k) => {
|
|
||||||
elements[k] = {
|
|
||||||
current: 0,
|
|
||||||
max: elemMax,
|
|
||||||
unlocked: BASE_UNLOCKED_ELEMENTS.includes(k),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
useUIStore.getState().resetUI();
|
|
||||||
usePrestigeStore.getState().resetPrestige();
|
|
||||||
useManaStore.getState().resetMana({}, {}, {}, {});
|
|
||||||
useSkillStore.getState().resetSkills();
|
|
||||||
useCombatStore.getState().resetCombat(startFloor);
|
|
||||||
|
|
||||||
set({
|
|
||||||
...initialState,
|
|
||||||
initialized: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
togglePause: () => {
|
|
||||||
useUIStore.getState().togglePause();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
startNewLoop: () => {
|
startNewLoop: () => {
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ export interface ManaState {
|
|||||||
setElementMax: (max: number) => void;
|
setElementMax: (max: number) => void;
|
||||||
craftComposite: (target: string, recipe: string[]) => boolean;
|
craftComposite: (target: string, recipe: string[]) => boolean;
|
||||||
|
|
||||||
|
// Helper for gameStore coordination
|
||||||
|
processConvertAction: (rawMana: number) => { rawMana: number; elements: Record<string, ElementState> } | null;
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
resetMana: (
|
resetMana: (
|
||||||
prestigeUpgrades: Record<string, number>,
|
prestigeUpgrades: Record<string, number>,
|
||||||
@@ -214,6 +217,33 @@ export const useManaStore = create<ManaState>()(
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
processConvertAction: (rawMana: number) => {
|
||||||
|
const state = get();
|
||||||
|
const elements = { ...state.elements };
|
||||||
|
|
||||||
|
const unlockedElements = Object.entries(elements)
|
||||||
|
.filter(([, e]) => e.unlocked && e.current < e.max);
|
||||||
|
|
||||||
|
if (unlockedElements.length === 0 || rawMana < 100) return null;
|
||||||
|
|
||||||
|
unlockedElements.sort((a, b) => (b[1].max - b[1].current) - (a[1].max - a[1].current));
|
||||||
|
const [targetId, targetState] = unlockedElements[0];
|
||||||
|
const canConvert = Math.min(
|
||||||
|
Math.floor(rawMana / 100),
|
||||||
|
targetState.max - targetState.current
|
||||||
|
);
|
||||||
|
|
||||||
|
if (canConvert <= 0) return null;
|
||||||
|
|
||||||
|
rawMana -= canConvert * 100;
|
||||||
|
const updatedElements = {
|
||||||
|
...elements,
|
||||||
|
[targetId]: { ...targetState, current: targetState.current + canConvert }
|
||||||
|
};
|
||||||
|
|
||||||
|
return { rawMana, elements: updatedElements };
|
||||||
|
},
|
||||||
|
|
||||||
resetMana: (
|
resetMana: (
|
||||||
prestigeUpgrades: Record<string, number>,
|
prestigeUpgrades: Record<string, number>,
|
||||||
skills: Record<string, number> = {},
|
skills: Record<string, number> = {},
|
||||||
|
|||||||
Reference in New Issue
Block a user