Compare commits
2 Commits
e0a3d82dea
...
4748b81fe6
| Author | SHA1 | Date | |
|---|---|---|---|
| 4748b81fe6 | |||
| a1b15cea74 |
@@ -13,8 +13,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { RotateCcw } from 'lucide-react';
|
import { RotateCcw } from 'lucide-react';
|
||||||
import { CraftingTab, SpireTab, SpellsTab, LabTab, SkillsTab, StatsTab, EquipmentTab, AttunementsTab } from '@/components/game/tabs';
|
import { CraftingTab, SpireTab, SpellsTab, LabTab, SkillsTab, StatsTab, EquipmentTab, AttunementsTab, DebugTab } from '@/components/game/tabs';
|
||||||
import { ComboMeter, ActionButtons, CalendarDisplay, ManaDisplay, TimeDisplay } from '@/components/game';
|
import { ActionButtons, CalendarDisplay, ManaDisplay, TimeDisplay } from '@/components/game';
|
||||||
import { LootInventoryDisplay } from '@/components/game/LootInventory';
|
import { LootInventoryDisplay } from '@/components/game/LootInventory';
|
||||||
import { AchievementsDisplay } from '@/components/game/AchievementsDisplay';
|
import { AchievementsDisplay } from '@/components/game/AchievementsDisplay';
|
||||||
|
|
||||||
@@ -166,8 +166,6 @@ export default function ManaLoopGame() {
|
|||||||
isPaused={store.isPaused}
|
isPaused={store.isPaused}
|
||||||
togglePause={store.togglePause}
|
togglePause={store.togglePause}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ComboMeter combo={store.combo} isClimbing={store.currentAction === 'climb'} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -181,9 +179,12 @@ export default function ManaLoopGame() {
|
|||||||
rawMana={store.rawMana}
|
rawMana={store.rawMana}
|
||||||
maxMana={maxMana}
|
maxMana={maxMana}
|
||||||
effectiveRegen={effectiveRegen}
|
effectiveRegen={effectiveRegen}
|
||||||
|
meditationMultiplier={meditationMultiplier}
|
||||||
|
clickMana={clickMana}
|
||||||
isGathering={isGathering}
|
isGathering={isGathering}
|
||||||
onGatherStart={handleGatherStart}
|
onGatherStart={handleGatherStart}
|
||||||
onGatherEnd={handleGatherEnd}
|
onGatherEnd={handleGatherEnd}
|
||||||
|
elements={store.elements}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Buttons */}
|
||||||
@@ -238,6 +239,7 @@ export default function ManaLoopGame() {
|
|||||||
<TabsTrigger value="crafting" className="text-xs px-2 py-1">🔧 Craft</TabsTrigger>
|
<TabsTrigger value="crafting" className="text-xs px-2 py-1">🔧 Craft</TabsTrigger>
|
||||||
<TabsTrigger value="lab" className="text-xs px-2 py-1">🔬 Lab</TabsTrigger>
|
<TabsTrigger value="lab" className="text-xs px-2 py-1">🔬 Lab</TabsTrigger>
|
||||||
<TabsTrigger value="stats" className="text-xs px-2 py-1">📊 Stats</TabsTrigger>
|
<TabsTrigger value="stats" className="text-xs px-2 py-1">📊 Stats</TabsTrigger>
|
||||||
|
<TabsTrigger value="debug" className="text-xs px-2 py-1">🔧 Debug</TabsTrigger>
|
||||||
<TabsTrigger value="grimoire" className="text-xs px-2 py-1">📖 Grimoire</TabsTrigger>
|
<TabsTrigger value="grimoire" className="text-xs px-2 py-1">📖 Grimoire</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
@@ -288,6 +290,10 @@ export default function ManaLoopGame() {
|
|||||||
<TabsContent value="grimoire">
|
<TabsContent value="grimoire">
|
||||||
{renderGrimoireTab()}
|
{renderGrimoireTab()}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="debug">
|
||||||
|
<DebugTab store={store} />
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Progress } from '@/components/ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Zap } from 'lucide-react';
|
import { Zap, ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
import { fmt, fmtDec } from '@/lib/game/store';
|
import { fmt, fmtDec } from '@/lib/game/store';
|
||||||
|
import { ELEMENTS } from '@/lib/game/constants';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
interface ManaDisplayProps {
|
interface ManaDisplayProps {
|
||||||
rawMana: number;
|
rawMana: number;
|
||||||
@@ -15,6 +17,7 @@ interface ManaDisplayProps {
|
|||||||
isGathering: boolean;
|
isGathering: boolean;
|
||||||
onGatherStart: () => void;
|
onGatherStart: () => void;
|
||||||
onGatherEnd: () => void;
|
onGatherEnd: () => void;
|
||||||
|
elements: Record<string, { current: number; max: number; unlocked: boolean }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ManaDisplay({
|
export function ManaDisplay({
|
||||||
@@ -26,10 +29,19 @@ export function ManaDisplay({
|
|||||||
isGathering,
|
isGathering,
|
||||||
onGatherStart,
|
onGatherStart,
|
||||||
onGatherEnd,
|
onGatherEnd,
|
||||||
|
elements,
|
||||||
}: ManaDisplayProps) {
|
}: ManaDisplayProps) {
|
||||||
|
const [expanded, setExpanded] = useState(true);
|
||||||
|
|
||||||
|
// Get unlocked elements sorted by current amount
|
||||||
|
const unlockedElements = Object.entries(elements)
|
||||||
|
.filter(([, state]) => state.unlocked)
|
||||||
|
.sort((a, b) => b[1].current - a[1].current);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
<CardContent className="pt-4 space-y-3">
|
<CardContent className="pt-4 space-y-3">
|
||||||
|
{/* Raw Mana - Main Display */}
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-baseline gap-1">
|
<div className="flex items-baseline gap-1">
|
||||||
<span className="text-3xl font-bold game-mono text-blue-400">{fmt(rawMana)}</span>
|
<span className="text-3xl font-bold game-mono text-blue-400">{fmt(rawMana)}</span>
|
||||||
@@ -57,6 +69,54 @@ export function ManaDisplay({
|
|||||||
Gather +{clickMana} Mana
|
Gather +{clickMana} Mana
|
||||||
{isGathering && <span className="ml-2 text-xs">(Holding...)</span>}
|
{isGathering && <span className="ml-2 text-xs">(Holding...)</span>}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{/* Elemental Mana Pools */}
|
||||||
|
{unlockedElements.length > 0 && (
|
||||||
|
<div className="border-t border-gray-700 pt-3 mt-3">
|
||||||
|
<button
|
||||||
|
onClick={() => setExpanded(!expanded)}
|
||||||
|
className="flex items-center justify-between w-full text-xs text-gray-400 hover:text-gray-300 mb-2"
|
||||||
|
>
|
||||||
|
<span>Elemental Mana ({unlockedElements.length})</span>
|
||||||
|
{expanded ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{expanded && (
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
{unlockedElements.map(([id, state]) => {
|
||||||
|
const elem = ELEMENTS[id];
|
||||||
|
if (!elem) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={id}
|
||||||
|
className="p-2 rounded bg-gray-800/50 border border-gray-700"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-1 mb-1">
|
||||||
|
<span style={{ color: elem.color }}>{elem.sym}</span>
|
||||||
|
<span className="text-xs font-medium" style={{ color: elem.color }}>
|
||||||
|
{elem.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-1.5 bg-gray-700 rounded-full overflow-hidden mb-1">
|
||||||
|
<div
|
||||||
|
className="h-full rounded-full transition-all"
|
||||||
|
style={{
|
||||||
|
width: `${Math.min(100, (state.current / state.max) * 100)}%`,
|
||||||
|
backgroundColor: elem.color
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400 game-mono">
|
||||||
|
{fmt(state.current)}/{fmt(state.max)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ATTUNEMENTS_DEF, ATTUNEMENT_SLOT_NAMES, getTotalAttunementRegen, getAvailableSkillCategories } from '@/lib/game/data/attunements';
|
import { ATTUNEMENTS_DEF, ATTUNEMENT_SLOT_NAMES, getTotalAttunementRegen, getAvailableSkillCategories, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL, getAttunementConversionRate } from '@/lib/game/data/attunements';
|
||||||
import { ELEMENTS } from '@/lib/game/constants';
|
import { ELEMENTS } from '@/lib/game/constants';
|
||||||
import type { GameStore, AttunementState } from '@/lib/game/types';
|
import type { GameStore, AttunementState } from '@/lib/game/types';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Progress } from '@/components/ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { Lock, Sparkles } from 'lucide-react';
|
import { Lock, Sparkles, TrendingUp } from 'lucide-react';
|
||||||
|
|
||||||
export interface AttunementsTabProps {
|
export interface AttunementsTabProps {
|
||||||
store: GameStore;
|
store: GameStore;
|
||||||
@@ -38,7 +38,7 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<p className="text-sm text-gray-400 mb-3">
|
<p className="text-sm text-gray-400 mb-3">
|
||||||
Attunements are magical bonds tied to specific body locations. Each attunement grants unique capabilities,
|
Attunements are magical bonds tied to specific body locations. Each attunement grants unique capabilities,
|
||||||
mana regeneration, and access to specialized skills.
|
mana regeneration, and access to specialized skills. Level them up to increase their power.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<Badge className="bg-teal-900/50 text-teal-300">
|
<Badge className="bg-teal-900/50 text-teal-300">
|
||||||
@@ -57,6 +57,11 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
|
|||||||
const state = attunements[id];
|
const state = attunements[id];
|
||||||
const isActive = state?.active;
|
const isActive = state?.active;
|
||||||
const isUnlocked = state?.active || def.unlocked;
|
const isUnlocked = state?.active || def.unlocked;
|
||||||
|
const level = state?.level || 1;
|
||||||
|
const xp = state?.experience || 0;
|
||||||
|
const xpNeeded = getAttunementXPForLevel(level + 1);
|
||||||
|
const xpProgress = xpNeeded > 0 ? (xp / xpNeeded) * 100 : 100;
|
||||||
|
const isMaxLevel = level >= MAX_ATTUNEMENT_LEVEL;
|
||||||
|
|
||||||
// Get primary mana element info
|
// Get primary mana element info
|
||||||
const primaryElem = def.primaryManaType ? ELEMENTS[def.primaryManaType] : null;
|
const primaryElem = def.primaryManaType ? ELEMENTS[def.primaryManaType] : null;
|
||||||
@@ -65,6 +70,11 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
|
|||||||
const currentMana = def.primaryManaType ? store.elements[def.primaryManaType]?.current || 0 : 0;
|
const currentMana = def.primaryManaType ? store.elements[def.primaryManaType]?.current || 0 : 0;
|
||||||
const maxMana = def.primaryManaType ? store.elements[def.primaryManaType]?.max || 50 : 50;
|
const maxMana = def.primaryManaType ? store.elements[def.primaryManaType]?.max || 50 : 50;
|
||||||
|
|
||||||
|
// Calculate level-scaled stats
|
||||||
|
const levelMult = Math.pow(1.5, level - 1);
|
||||||
|
const scaledRegen = def.rawManaRegen * levelMult;
|
||||||
|
const scaledConversion = getAttunementConversionRate(id, level);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={id}
|
key={id}
|
||||||
@@ -98,7 +108,7 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
|
|||||||
)}
|
)}
|
||||||
{isActive && (
|
{isActive && (
|
||||||
<Badge className="text-xs" style={{ backgroundColor: `${def.color}30`, color: def.color }}>
|
<Badge className="text-xs" style={{ backgroundColor: `${def.color}30`, color: def.color }}>
|
||||||
Active
|
Lv.{level}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -134,20 +144,51 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats */}
|
{/* Stats with level scaling */}
|
||||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||||
<div className="p-2 bg-gray-800/50 rounded">
|
<div className="p-2 bg-gray-800/50 rounded">
|
||||||
<div className="text-gray-500">Raw Regen</div>
|
<div className="text-gray-500">Raw Regen</div>
|
||||||
<div className="text-green-400 font-semibold">+{def.rawManaRegen}/hr</div>
|
<div className="text-green-400 font-semibold">
|
||||||
|
+{scaledRegen.toFixed(2)}/hr
|
||||||
|
{level > 1 && <span className="text-xs ml-1">({((levelMult - 1) * 100).toFixed(0)}% bonus)</span>}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-2 bg-gray-800/50 rounded">
|
<div className="p-2 bg-gray-800/50 rounded">
|
||||||
<div className="text-gray-500">Conversion</div>
|
<div className="text-gray-500">Conversion</div>
|
||||||
<div className="text-cyan-400 font-semibold">
|
<div className="text-cyan-400 font-semibold">
|
||||||
{def.conversionRate > 0 ? `${def.conversionRate}/hr` : '—'}
|
{scaledConversion > 0 ? `${scaledConversion.toFixed(2)}/hr` : '—'}
|
||||||
|
{level > 1 && scaledConversion > 0 && <span className="text-xs ml-1">({((levelMult - 1) * 100).toFixed(0)}% bonus)</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* XP Progress Bar */}
|
||||||
|
{isUnlocked && state && !isMaxLevel && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center justify-between text-xs">
|
||||||
|
<span className="text-gray-500 flex items-center gap-1">
|
||||||
|
<TrendingUp className="w-3 h-3" />
|
||||||
|
XP Progress
|
||||||
|
</span>
|
||||||
|
<span className="text-amber-400">{xp} / {xpNeeded}</span>
|
||||||
|
</div>
|
||||||
|
<Progress
|
||||||
|
value={xpProgress}
|
||||||
|
className="h-2 bg-gray-800"
|
||||||
|
/>
|
||||||
|
<div className="text-xs text-gray-500">
|
||||||
|
{isMaxLevel ? 'Max Level' : `${xpNeeded - xp} XP to Level ${level + 1}`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Max Level Indicator */}
|
||||||
|
{isMaxLevel && (
|
||||||
|
<div className="text-xs text-amber-400 text-center font-semibold">
|
||||||
|
✨ MAX LEVEL ✨
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Capabilities */}
|
{/* Capabilities */}
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="text-xs text-gray-500">Capabilities</div>
|
<div className="text-xs text-gray-500">Capabilities</div>
|
||||||
@@ -156,14 +197,13 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
|
|||||||
<Badge key={cap} variant="outline" className="text-xs">
|
<Badge key={cap} variant="outline" className="text-xs">
|
||||||
{cap === 'enchanting' && '✨ Enchanting'}
|
{cap === 'enchanting' && '✨ Enchanting'}
|
||||||
{cap === 'disenchanting' && '🔄 Disenchant'}
|
{cap === 'disenchanting' && '🔄 Disenchant'}
|
||||||
{cap === 'scrollCrafting' && '📜 Scrolls'}
|
|
||||||
{cap === 'pacts' && '🤝 Pacts'}
|
{cap === 'pacts' && '🤝 Pacts'}
|
||||||
{cap === 'guardianPowers' && '💜 Guardian Powers'}
|
{cap === 'guardianPowers' && '💜 Guardian Powers'}
|
||||||
{cap === 'elementalMastery' && '🌟 Elem. Mastery'}
|
{cap === 'elementalMastery' && '🌟 Elem. Mastery'}
|
||||||
{cap === 'golemCrafting' && '🗿 Golems'}
|
{cap === 'golemCrafting' && '🗿 Golems'}
|
||||||
{cap === 'gearCrafting' && '⚒️ Gear'}
|
{cap === 'gearCrafting' && '⚒️ Gear'}
|
||||||
{cap === 'earthShaping' && '⛰️ Earth Shaping'}
|
{cap === 'earthShaping' && '⛰️ Earth Shaping'}
|
||||||
{!['enchanting', 'disenchanting', 'scrollCrafting', 'pacts', 'guardianPowers',
|
{!['enchanting', 'disenchanting', 'pacts', 'guardianPowers',
|
||||||
'elementalMastery', 'golemCrafting', 'gearCrafting', 'earthShaping'].includes(cap) && cap}
|
'elementalMastery', 'golemCrafting', 'gearCrafting', 'earthShaping'].includes(cap) && cap}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
@@ -176,13 +216,6 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
|
|||||||
🔒 {def.unlockCondition}
|
🔒 {def.unlockCondition}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Level for unlocked attunements */}
|
|
||||||
{isUnlocked && state && (
|
|
||||||
<div className="text-xs text-gray-400">
|
|
||||||
Level {state.level} • {state.experience} XP
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
380
src/components/game/tabs/DebugTab.tsx
Normal file
380
src/components/game/tabs/DebugTab.tsx
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import {
|
||||||
|
RotateCcw, Bug, Plus, Minus, Lock, Unlock, Zap,
|
||||||
|
Clock, Star, AlertTriangle, Sparkles, Settings
|
||||||
|
} from 'lucide-react';
|
||||||
|
import type { GameStore } from '@/lib/game/types';
|
||||||
|
import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements';
|
||||||
|
import { ELEMENTS } from '@/lib/game/constants';
|
||||||
|
import { fmt } from '@/lib/game/store';
|
||||||
|
|
||||||
|
interface DebugTabProps {
|
||||||
|
store: GameStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DebugTab({ store }: DebugTabProps) {
|
||||||
|
const [confirmReset, setConfirmReset] = useState(false);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* 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', 'life', '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
|
||||||
|
['mental', 'transference', 'force'].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>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ import { ELEMENTS, GUARDIANS, SPELLS_DEF, SKILLS_DEF } from '@/lib/game/constant
|
|||||||
import { fmt, fmtDec, getFloorElement, canAffordSpellCost } from '@/lib/game/store';
|
import { fmt, fmtDec, getFloorElement, canAffordSpellCost } from '@/lib/game/store';
|
||||||
import { getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/computed-stats';
|
import { getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/computed-stats';
|
||||||
import { formatSpellCost, getSpellCostColor, formatStudyTime } from '@/lib/game/formatting';
|
import { formatSpellCost, getSpellCostColor, formatStudyTime } from '@/lib/game/formatting';
|
||||||
import { ComboMeter, CraftingProgress, StudyProgress } from '@/components/game';
|
import { CraftingProgress, StudyProgress } from '@/components/game';
|
||||||
import { getUnifiedEffects } from '@/lib/game/effects';
|
import { getUnifiedEffects } from '@/lib/game/effects';
|
||||||
|
|
||||||
interface SpireTabProps {
|
interface SpireTabProps {
|
||||||
@@ -200,9 +200,6 @@ export function SpireTab({ store }: SpireTabProps) {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Combo Meter - Shows when climbing or has active combo */}
|
|
||||||
<ComboMeter combo={store.combo} isClimbing={store.currentAction === 'climb'} />
|
|
||||||
|
|
||||||
{/* Current Study (if any) */}
|
{/* Current Study (if any) */}
|
||||||
{store.currentStudyTarget && (
|
{store.currentStudyTarget && (
|
||||||
<Card className="bg-gray-900/80 border-purple-600/50 lg:col-span-2">
|
<Card className="bg-gray-900/80 border-purple-600/50 lg:col-span-2">
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ 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';
|
||||||
|
export { DebugTab } from './DebugTab';
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const ATTUNEMENTS_DEF: Record<string, AttunementDef> = {
|
|||||||
rawManaRegen: 0.5,
|
rawManaRegen: 0.5,
|
||||||
conversionRate: 0.2, // Converts 0.2 raw mana to transference per hour
|
conversionRate: 0.2, // Converts 0.2 raw mana to transference per hour
|
||||||
unlocked: true, // Starting attunement
|
unlocked: true, // Starting attunement
|
||||||
capabilities: ['enchanting', 'disenchanting', 'scrollCrafting'],
|
capabilities: ['enchanting', 'disenchanting'],
|
||||||
skillCategories: ['enchant', 'effectResearch'],
|
skillCategories: ['enchant', 'effectResearch'],
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -88,13 +88,38 @@ export function getUnlockedAttunements(attunements: Record<string, { active: boo
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to calculate total raw mana regen from attunements
|
// Helper function to calculate total raw mana regen from attunements (with level scaling)
|
||||||
export function getTotalAttunementRegen(attunements: Record<string, { active: boolean; level: number; experience: number }>): number {
|
export function getTotalAttunementRegen(attunements: Record<string, { active: boolean; level: number; experience: number }>): number {
|
||||||
return Object.entries(attunements)
|
return Object.entries(attunements)
|
||||||
.filter(([id, state]) => state.active)
|
.filter(([, state]) => state.active)
|
||||||
.reduce((total, [id]) => total + (ATTUNEMENTS_DEF[id]?.rawManaRegen || 0), 0);
|
.reduce((total, [id, state]) => {
|
||||||
|
const def = ATTUNEMENTS_DEF[id];
|
||||||
|
if (!def) return total;
|
||||||
|
// Exponential scaling: base * (1.5 ^ (level - 1))
|
||||||
|
const levelMult = Math.pow(1.5, (state.level || 1) - 1);
|
||||||
|
return total + def.rawManaRegen * levelMult;
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get conversion rate with level scaling
|
||||||
|
export function getAttunementConversionRate(attunementId: string, level: number): number {
|
||||||
|
const def = ATTUNEMENTS_DEF[attunementId];
|
||||||
|
if (!def || def.conversionRate <= 0) return 0;
|
||||||
|
// Exponential scaling: base * (1.5 ^ (level - 1))
|
||||||
|
return def.conversionRate * Math.pow(1.5, (level || 1) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XP required for attunement level
|
||||||
|
export function getAttunementXPForLevel(level: number): number {
|
||||||
|
// Level 2: 100 XP, Level 3: 300 XP, Level 4: 900 XP, etc.
|
||||||
|
// Exponential: 100 * (3 ^ (level - 2))
|
||||||
|
if (level <= 1) return 0;
|
||||||
|
return Math.floor(100 * Math.pow(3, level - 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max attunement level
|
||||||
|
export const MAX_ATTUNEMENT_LEVEL = 10;
|
||||||
|
|
||||||
// Helper function to get mana types from active attunements and pacts
|
// Helper function to get mana types from active attunements and pacts
|
||||||
export function getAttunementManaTypes(
|
export function getAttunementManaTypes(
|
||||||
attunements: Record<string, { active: boolean; level: number; experience: number }>,
|
attunements: Record<string, { active: boolean; level: number; experience: number }>,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import {
|
|||||||
} from './crafting-slice';
|
} from './crafting-slice';
|
||||||
import { EQUIPMENT_TYPES } from './data/equipment';
|
import { EQUIPMENT_TYPES } from './data/equipment';
|
||||||
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
||||||
import { ATTUNEMENTS_DEF, getTotalAttunementRegen } from './data/attunements';
|
import { ATTUNEMENTS_DEF, getTotalAttunementRegen, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
||||||
|
|
||||||
// Default empty effects for when effects aren't provided
|
// Default empty effects for when effects aren't provided
|
||||||
const DEFAULT_EFFECTS: ComputedEffects = {
|
const DEFAULT_EFFECTS: ComputedEffects = {
|
||||||
@@ -568,6 +568,16 @@ interface GameStore extends GameState, CraftingActions {
|
|||||||
commitSkillUpgrades: (skillId: string, upgradeIds: string[]) => void;
|
commitSkillUpgrades: (skillId: string, upgradeIds: string[]) => void;
|
||||||
tierUpSkill: (skillId: string) => void;
|
tierUpSkill: (skillId: string) => void;
|
||||||
|
|
||||||
|
// Attunement XP and leveling
|
||||||
|
addAttunementXP: (attunementId: string, amount: number) => void;
|
||||||
|
|
||||||
|
// Debug functions
|
||||||
|
debugUnlockAttunement: (attunementId: string) => void;
|
||||||
|
debugAddElementalMana: (element: string, amount: number) => void;
|
||||||
|
debugSetTime: (day: number, hour: number) => void;
|
||||||
|
debugAddAttunementXP: (attunementId: string, amount: number) => void;
|
||||||
|
debugSetFloor: (floor: number) => void;
|
||||||
|
|
||||||
// Computed getters
|
// Computed getters
|
||||||
getMaxMana: () => number;
|
getMaxMana: () => number;
|
||||||
getRegen: () => number;
|
getRegen: () => number;
|
||||||
@@ -679,8 +689,11 @@ export const useGameStore = create<GameStore>()(
|
|||||||
const elem = elements[attDef.primaryManaType];
|
const elem = elements[attDef.primaryManaType];
|
||||||
if (!elem || !elem.unlocked) return;
|
if (!elem || !elem.unlocked) return;
|
||||||
|
|
||||||
|
// Get level-scaled conversion rate
|
||||||
|
const scaledConversionRate = getAttunementConversionRate(attId, attState.level || 1);
|
||||||
|
|
||||||
// Convert raw mana to primary type
|
// Convert raw mana to primary type
|
||||||
const conversionAmount = attDef.conversionRate * HOURS_PER_TICK;
|
const conversionAmount = scaledConversionRate * HOURS_PER_TICK;
|
||||||
const actualConversion = Math.min(conversionAmount, rawMana, elem.max - elem.current);
|
const actualConversion = Math.min(conversionAmount, rawMana, elem.max - elem.current);
|
||||||
|
|
||||||
if (actualConversion > 0) {
|
if (actualConversion > 0) {
|
||||||
@@ -1668,6 +1681,140 @@ export const useGameStore = create<GameStore>()(
|
|||||||
if (!instance) return 0;
|
if (!instance) return 0;
|
||||||
return instance.totalCapacity - instance.usedCapacity;
|
return instance.totalCapacity - instance.usedCapacity;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Attunement XP and leveling
|
||||||
|
addAttunementXP: (attunementId: string, amount: number) => {
|
||||||
|
const state = get();
|
||||||
|
const attState = state.attunements[attunementId];
|
||||||
|
if (!attState?.active) return;
|
||||||
|
|
||||||
|
let newXP = attState.experience + amount;
|
||||||
|
let newLevel = attState.level;
|
||||||
|
|
||||||
|
// Check for level ups
|
||||||
|
while (newLevel < MAX_ATTUNEMENT_LEVEL) {
|
||||||
|
const xpNeeded = getAttunementXPForLevel(newLevel + 1);
|
||||||
|
if (newXP >= xpNeeded) {
|
||||||
|
newXP -= xpNeeded;
|
||||||
|
newLevel++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap XP at max level
|
||||||
|
if (newLevel >= MAX_ATTUNEMENT_LEVEL) {
|
||||||
|
newXP = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
set({
|
||||||
|
attunements: {
|
||||||
|
...state.attunements,
|
||||||
|
[attunementId]: {
|
||||||
|
...attState,
|
||||||
|
level: newLevel,
|
||||||
|
experience: newXP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
log: newLevel > attState.level
|
||||||
|
? [`🌟 ${attunementId} attunement reached Level ${newLevel}!`, ...state.log.slice(0, 49)]
|
||||||
|
: state.log,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Debug functions
|
||||||
|
debugUnlockAttunement: (attunementId: string) => {
|
||||||
|
const state = get();
|
||||||
|
const def = ATTUNEMENTS_DEF[attunementId];
|
||||||
|
if (!def) return;
|
||||||
|
|
||||||
|
set({
|
||||||
|
attunements: {
|
||||||
|
...state.attunements,
|
||||||
|
[attunementId]: {
|
||||||
|
id: attunementId,
|
||||||
|
active: true,
|
||||||
|
level: 1,
|
||||||
|
experience: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Unlock the primary mana type if applicable
|
||||||
|
elements: def.primaryManaType && state.elements[def.primaryManaType]
|
||||||
|
? {
|
||||||
|
...state.elements,
|
||||||
|
[def.primaryManaType]: {
|
||||||
|
...state.elements[def.primaryManaType],
|
||||||
|
unlocked: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: state.elements,
|
||||||
|
log: [`🔓 Debug: Unlocked ${def.name} attunement!`, ...state.log.slice(0, 49)],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
debugAddElementalMana: (element: string, amount: number) => {
|
||||||
|
const state = get();
|
||||||
|
const elem = state.elements[element];
|
||||||
|
if (!elem?.unlocked) return;
|
||||||
|
|
||||||
|
set({
|
||||||
|
elements: {
|
||||||
|
...state.elements,
|
||||||
|
[element]: {
|
||||||
|
...elem,
|
||||||
|
current: Math.min(elem.current + amount, elem.max * 10), // Allow overflow
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
debugSetTime: (day: number, hour: number) => {
|
||||||
|
set({
|
||||||
|
day,
|
||||||
|
hour,
|
||||||
|
incursionStrength: getIncursionStrength(day, hour),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
debugAddAttunementXP: (attunementId: string, amount: number) => {
|
||||||
|
const state = get();
|
||||||
|
const attState = state.attunements[attunementId];
|
||||||
|
if (!attState) return;
|
||||||
|
|
||||||
|
let newXP = attState.experience + amount;
|
||||||
|
let newLevel = attState.level;
|
||||||
|
|
||||||
|
while (newLevel < MAX_ATTUNEMENT_LEVEL) {
|
||||||
|
const xpNeeded = getAttunementXPForLevel(newLevel + 1);
|
||||||
|
if (newXP >= xpNeeded) {
|
||||||
|
newXP -= xpNeeded;
|
||||||
|
newLevel++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set({
|
||||||
|
attunements: {
|
||||||
|
...state.attunements,
|
||||||
|
[attunementId]: {
|
||||||
|
...attState,
|
||||||
|
level: newLevel,
|
||||||
|
experience: newXP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
debugSetFloor: (floor: number) => {
|
||||||
|
const state = get();
|
||||||
|
set({
|
||||||
|
currentFloor: floor,
|
||||||
|
floorHP: getFloorMaxHP(floor),
|
||||||
|
floorMaxHP: getFloorMaxHP(floor),
|
||||||
|
maxFloorReached: Math.max(state.maxFloorReached, floor),
|
||||||
|
});
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'mana-loop-storage',
|
name: 'mana-loop-storage',
|
||||||
|
|||||||
Reference in New Issue
Block a user