Compare commits
7 Commits
fc9e4c8327
...
563e41dbe3
| Author | SHA1 | Date | |
|---|---|---|---|
| 563e41dbe3 | |||
| c2dd846f63 | |||
| c8baea4346 | |||
| 9bf6e911f4 | |||
| 50ce70efdd | |||
| 9f029d93e1 | |||
| 229cb16c5d |
+26
-13
@@ -1,7 +1,7 @@
|
||||
# Task 2 Progress Tracking
|
||||
|
||||
**Last Updated**: 2026-04-26 10:35:00
|
||||
**Current Status**: In Progress
|
||||
**Last Updated**: 2026-04-26 16:00:00
|
||||
**Current Status**: Nearly Complete (11/12 tasks done)
|
||||
|
||||
## Completed Tasks
|
||||
- [2026-04-25] Task 9: Remove 'Transference' mana from LootTab essence list ✓
|
||||
@@ -9,26 +9,39 @@
|
||||
- [2026-04-25] Task 4: Equipment System - 2-Handed Weapons ✓
|
||||
- [2026-04-25] Task 12: StatsTab - Lock Fire/Water/Air/Earth at start ✓
|
||||
- [2026-04-26] Task 7: CRITICAL BUG FIX - Mana Well 'Deep Basin' upgrade ✓
|
||||
- [2026-04-26] Task 2: Research Locking - Prevent switching topics while study in progress ✓
|
||||
- [2026-04-26] Task 5: DebugTab Update - Invoker Debugging Buttons ✓
|
||||
- [2026-04-26] Task 3: SpireTab Overhaul - Implement Spire Mode with exit condition ✓
|
||||
- [2026-04-26] Task 8: Bug Fix: Combat UI - Casting Bar progress animation ✓
|
||||
- [2026-04-26] Task 10: Bug Fix: Crafting - Disable Prepare for enchanted items; limit Design to owned gear ✓
|
||||
- [2026-04-26] Task 6: System Integrity - Fix 'Show Component Names' ✓
|
||||
|
||||
## In Progress Tasks
|
||||
None currently
|
||||
|
||||
## Pending Tasks
|
||||
1. Task 1: ActionButtons Rework [FAILED - sub-agent context too long]
|
||||
2. Task 2: Research Locking - SkillsTab [Pending]
|
||||
3. Task 3: SpireTab Overhaul [FAILED - sub-agent context too long]
|
||||
4. Task 5: DebugTab Update - Invoker Debugging Buttons [Pending]
|
||||
5. Task 6: System Integrity - Fix 'Show Component Names' [In Progress - investigating]
|
||||
6. Task 8: Bug Fix: Combat UI - Casting Bar animation [FAILED - sub-agent context too long]
|
||||
7. Task 10: Bug Fix: Crafting - Prepare/Design limits [Pending]
|
||||
## Remaining Tasks (1/12)
|
||||
1. Task 1: ActionButtons Rework - **BLOCKED** (sub-agent context length error: 2.4M tokens > 262k limit due to conversation history)
|
||||
|
||||
## Blocked Tasks
|
||||
- Task 1, 3, 8: Sub-agent attempts failed due to context length limits (603k+ tokens)
|
||||
- Task 1: Sub-agent fails due to framework bug (entire 2.4M token conversation history is passed to sub-agent, exceeding 262k limit)
|
||||
|
||||
## Commit History
|
||||
## Commit History (Task 2)
|
||||
- 65b0f96: Remove Transference from LootTab, delete Ascension skills
|
||||
- 7c05bea: Update task2 progress
|
||||
- 5e0bee8: Equipment System - 2-handed weapons, staves block offhand
|
||||
- 2355be6: StatsTab - Lock Fire/Water/Air/Earth at start
|
||||
- f61ed00: FIX: Skill perks multiplier values (Deep Basin + others)
|
||||
- a6ce36b: WIP: Task 2 progress - investigating Show Component Names debug option
|
||||
- a6ce36b: WIP: Task 2 progress - investigating Show Component Names
|
||||
- 4193718: WIP: Task 2 - completed 5/12 tasks, investigating remaining
|
||||
- fc9e4c8: Add context files for Task 2 sub-agents
|
||||
- 229cb16: Task 2: Research Locking - prevent switching topics while study in progress
|
||||
- 9f029d9: Task 2: DebugTab Update - add Invoker Debugging Buttons for Pacts
|
||||
- 50ce70e: Task 2: SpireTab Overhaul - add Climb the Spire button, implement Spire Mode
|
||||
- 9bf6e91: Task 2: Fix Combat UI Casting Bar progress animation
|
||||
- c8baea4: Task 2: Crafting - disable Prepare for enchanted items, limit Design to owned gear types
|
||||
- c2dd846: Task 2: System Integrity - Fix Show Component Names for all components
|
||||
|
||||
## Summary
|
||||
**11/12 tasks completed successfully!** Only Task 1 (ActionButtons Rework) remains but is blocked due to a framework bug where the entire 2.4M token conversation history is passed to sub-agents, exceeding the 262k token limit.
|
||||
|
||||
**All completed work has been committed and pushed to gitea (master branch).**
|
||||
|
||||
+178
-134
@@ -12,7 +12,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { RotateCcw } from 'lucide-react';
|
||||
import { RotateCcw, Mountain, ChevronDown } from 'lucide-react';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { DebugName } from '@/lib/game/debug-context';
|
||||
// Non-tab component imports
|
||||
@@ -181,7 +181,7 @@ export default function ManaLoopGame() {
|
||||
<TimeDisplay
|
||||
day={store.day}
|
||||
hour={store.hour}
|
||||
isPaused={store.isPaused}
|
||||
isPaused={store.paused}
|
||||
togglePause={store.togglePause}
|
||||
/>
|
||||
</div>
|
||||
@@ -207,16 +207,34 @@ export default function ManaLoopGame() {
|
||||
/>
|
||||
</DebugName>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<DebugName name="ActionButtons">
|
||||
<ActionButtons
|
||||
currentAction={store.currentAction}
|
||||
designProgress={store.designProgress}
|
||||
preparationProgress={store.preparationProgress}
|
||||
applicationProgress={store.applicationProgress}
|
||||
setAction={store.setAction}
|
||||
/>
|
||||
</DebugName>
|
||||
{/* Climb the Spire Button - only show when not in Spire Mode */}
|
||||
{!store.spireMode && (
|
||||
<DebugName name="ClimbSpireButton">
|
||||
<Button
|
||||
className="w-full bg-gradient-to-r from-amber-600 to-orange-600 hover:from-amber-700 hover:to-orange-700"
|
||||
size="lg"
|
||||
onClick={() => store.enterSpireMode()}
|
||||
>
|
||||
<Mountain className="w-5 h-5 mr-2" />
|
||||
Climb the Spire
|
||||
</Button>
|
||||
</DebugName>
|
||||
)}
|
||||
|
||||
{/* Action Buttons - only show when not in Spire Mode */}
|
||||
{!store.spireMode && (
|
||||
<DebugName name="ActionButtons">
|
||||
<ActionButtons
|
||||
currentAction={store.currentAction}
|
||||
currentStudyTarget={store.currentStudyTarget}
|
||||
designProgress={store.designProgress}
|
||||
designProgress2={store.designProgress2}
|
||||
preparationProgress={store.preparationProgress}
|
||||
applicationProgress={store.applicationProgress}
|
||||
equipmentCraftingProgress={store.equipmentCraftingProgress}
|
||||
/>
|
||||
</DebugName>
|
||||
)}
|
||||
|
||||
{/* Calendar */}
|
||||
<DebugName name="CalendarDisplay">
|
||||
@@ -230,140 +248,166 @@ export default function ManaLoopGame() {
|
||||
{/* Loot and Achievements moved to tabs */}
|
||||
</div>
|
||||
|
||||
{/* Right Panel - Tabs */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="flex flex-wrap gap-1 w-full mb-4 h-auto">
|
||||
<TabsTrigger value="spire" className="text-xs px-2 py-1">⚔️ Spire</TabsTrigger>
|
||||
<TabsTrigger value="attunements" className="text-xs px-2 py-1">✨ Attune</TabsTrigger>
|
||||
<TabsTrigger value="golemancy" className="text-xs px-2 py-1">🗿 Golems</TabsTrigger>
|
||||
<TabsTrigger value="skills" className="text-xs px-2 py-1">📚 Skills</TabsTrigger>
|
||||
<TabsTrigger value="spells" className="text-xs px-2 py-1">🔮 Spells</TabsTrigger>
|
||||
<TabsTrigger value="equipment" className="text-xs px-2 py-1">🛡️ Gear</TabsTrigger>
|
||||
<TabsTrigger value="crafting" className="text-xs px-2 py-1">🔧 Craft</TabsTrigger>
|
||||
<TabsTrigger value="loot" className="text-xs px-2 py-1">💎 Loot</TabsTrigger>
|
||||
<TabsTrigger value="achievements" className="text-xs px-2 py-1">🏆 Achieve</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="debug" className="text-xs px-2 py-1">🔧 Debug</TabsTrigger>
|
||||
<TabsTrigger value="grimoire" className="text-xs px-2 py-1">📖 Grimoire</TabsTrigger>
|
||||
</TabsList>
|
||||
{/* Right Panel - Conditional rendering based on Spire Mode */}
|
||||
{store.spireMode ? (
|
||||
/* Spire Mode - Simplified UI */
|
||||
<div className="flex-1 min-w-0 space-y-4">
|
||||
<DebugName name="SpireModeUI">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-2xl font-bold game-title text-amber-400">
|
||||
🏔️ Spire Mode
|
||||
</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-blue-600/50 text-blue-400 hover:bg-blue-900/20"
|
||||
onClick={() => store.exitSpireMode()}
|
||||
>
|
||||
<ChevronDown className="w-4 h-4 mr-2" />
|
||||
Climb Down
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<SpireTab store={store} simpleMode={true} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</div>
|
||||
) : (
|
||||
/* Normal Mode - Tabs */
|
||||
<div className="flex-1 min-w-0">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="flex flex-wrap gap-1 w-full mb-4 h-auto">
|
||||
<TabsTrigger value="spire" className="text-xs px-2 py-1">⚔️ Spire</TabsTrigger>
|
||||
<TabsTrigger value="attunements" className="text-xs px-2 py-1">✨ Attune</TabsTrigger>
|
||||
<TabsTrigger value="golemancy" className="text-xs px-2 py-1">🗿 Golems</TabsTrigger>
|
||||
<TabsTrigger value="skills" className="text-xs px-2 py-1">📚 Skills</TabsTrigger>
|
||||
<TabsTrigger value="spells" className="text-xs px-2 py-1">🔮 Spells</TabsTrigger>
|
||||
<TabsTrigger value="equipment" className="text-xs px-2 py-1">🛡️ Gear</TabsTrigger>
|
||||
<TabsTrigger value="crafting" className="text-xs px-2 py-1">🔧 Craft</TabsTrigger>
|
||||
<TabsTrigger value="loot" className="text-xs px-2 py-1">💎 Loot</TabsTrigger>
|
||||
<TabsTrigger value="achievements" className="text-xs px-2 py-1">🏆 Achieve</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="debug" className="text-xs px-2 py-1">🔧 Debug</TabsTrigger>
|
||||
<TabsTrigger value="grimoire" className="text-xs px-2 py-1">📖 Grimoire</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="spire">
|
||||
<DebugName name="SpireTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<SpireTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
<TabsContent value="spire">
|
||||
<DebugName name="SpireTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<SpireTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="attunements">
|
||||
<DebugName name="AttunementsTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<AttunementsTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
<TabsContent value="attunements">
|
||||
<DebugName name="AttunementsTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<AttunementsTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="golemancy">
|
||||
<DebugName name="GolemancyTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<GolemancyTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
<TabsContent value="golemancy">
|
||||
<DebugName name="GolemancyTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<GolemancyTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="skills">
|
||||
<DebugName name="SkillsTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<SkillsTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
<TabsContent value="skills">
|
||||
<DebugName name="SkillsTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<SkillsTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="spells">
|
||||
<DebugName name="SpellsTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<SpellsTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
<TabsContent value="spells">
|
||||
<DebugName name="SpellsTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<SpellsTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="equipment">
|
||||
<DebugName name="EquipmentTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<EquipmentTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
<TabsContent value="equipment">
|
||||
<DebugName name="EquipmentTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<EquipmentTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="crafting">
|
||||
<DebugName name="CraftingTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<CraftingTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="loot">
|
||||
<DebugName name="LootTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<LootTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
<TabsContent value="crafting">
|
||||
<DebugName name="CraftingTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<CraftingTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="loot">
|
||||
<DebugName name="LootTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<LootTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="achievements">
|
||||
<DebugName name="AchievementsTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<AchievementsTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
<TabsContent value="achievements">
|
||||
<DebugName name="AchievementsTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<AchievementsTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="lab">
|
||||
<DebugName name="LabTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<LabTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
<TabsContent value="lab">
|
||||
<DebugName name="LabTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<LabTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="stats">
|
||||
<DebugName name="StatsTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<StatsTab
|
||||
store={store}
|
||||
upgradeEffects={upgradeEffects}
|
||||
maxMana={maxMana}
|
||||
baseRegen={baseRegen}
|
||||
clickMana={clickMana}
|
||||
meditationMultiplier={meditationMultiplier}
|
||||
effectiveRegen={effectiveRegen}
|
||||
incursionStrength={incursionStrength}
|
||||
manaCascadeBonus={manaCascadeBonus}
|
||||
studySpeedMult={studySpeedMult}
|
||||
studyCostMult={studyCostMult}
|
||||
/>
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
<TabsContent value="stats">
|
||||
<DebugName name="StatsTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<StatsTab
|
||||
store={store}
|
||||
upgradeEffects={upgradeEffects}
|
||||
maxMana={maxMana}
|
||||
baseRegen={baseRegen}
|
||||
clickMana={clickMana}
|
||||
meditationMultiplier={meditationMultiplier}
|
||||
effectiveRegen={effectiveRegen}
|
||||
incursionStrength={incursionStrength}
|
||||
manaCascadeBonus={manaCascadeBonus}
|
||||
studySpeedMult={studySpeedMult}
|
||||
studyCostMult={studyCostMult}
|
||||
/>
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="grimoire">
|
||||
<DebugName name="GrimoireTab">
|
||||
{renderGrimoireTab()}
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
<TabsContent value="grimoire">
|
||||
<DebugName name="GrimoireTab">
|
||||
{renderGrimoireTab()}
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="debug">
|
||||
<DebugName name="DebugTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<DebugTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
<TabsContent value="debug">
|
||||
<DebugName name="DebugTab">
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<DebugTab store={store} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -171,3 +171,5 @@ export function AchievementsDisplay({ achievements, gameState }: AchievementsPro
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
AchievementsDisplay.displayName = "AchievementsDisplay";
|
||||
|
||||
@@ -1,86 +1,152 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Sparkles, Swords, BookOpen, Target, FlaskConical } from 'lucide-react';
|
||||
import { Sparkles, Swords, BookOpen, Target, FlaskConical, Cog, Hammer } from 'lucide-react';
|
||||
import type { GameAction } from '@/lib/game/types';
|
||||
|
||||
interface ActionButtonsProps {
|
||||
currentAction: GameAction;
|
||||
currentStudyTarget: { type: 'skill' | 'spell'; id: string; progress: number; required: number } | null;
|
||||
designProgress: { progress: number; required: number } | null;
|
||||
designProgress2: { progress: number; required: number } | null;
|
||||
preparationProgress: { progress: number; required: number } | null;
|
||||
applicationProgress: { progress: number; required: number } | null;
|
||||
setAction: (action: GameAction) => void;
|
||||
equipmentCraftingProgress: { progress: number; required: number } | null;
|
||||
}
|
||||
|
||||
// Map action IDs to labels and icons
|
||||
const ACTION_CONFIG: Record<string, { label: string; icon: typeof Sparkles; color: string }> = {
|
||||
meditate: { label: 'Meditating', icon: Sparkles, color: 'text-blue-400' },
|
||||
climb: { label: 'Climbing', icon: Swords, color: 'text-green-400' },
|
||||
study: { label: 'Studying', icon: BookOpen, color: 'text-yellow-400' },
|
||||
design: { label: 'Designing Enchantment', icon: Target, color: 'text-purple-400' },
|
||||
prepare: { label: 'Preparing Equipment', icon: FlaskConical, color: 'text-purple-400' },
|
||||
enchant: { label: 'Enchanting', icon: Sparkles, color: 'text-purple-400' },
|
||||
craft: { label: 'Crafting Equipment', icon: Hammer, color: 'text-orange-400' },
|
||||
convert: { label: 'Converting Mana', icon: Cog, color: 'text-cyan-400' },
|
||||
};
|
||||
|
||||
function ProgressBar({ progress, required, label }: { progress: number; required: number; label?: string }) {
|
||||
const percentage = Math.min(100, (progress / required) * 100);
|
||||
return (
|
||||
<div className="mt-1">
|
||||
{label && <div className="text-xs text-gray-400 mb-0.5">{label}</div>}
|
||||
<div className="w-full bg-gray-700 rounded-full h-1.5">
|
||||
<div
|
||||
className="bg-blue-500 h-1.5 rounded-full transition-all duration-300"
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ActionButtons({
|
||||
currentAction,
|
||||
currentStudyTarget,
|
||||
designProgress,
|
||||
designProgress2,
|
||||
preparationProgress,
|
||||
applicationProgress,
|
||||
setAction,
|
||||
equipmentCraftingProgress,
|
||||
}: ActionButtonsProps) {
|
||||
const actions: { id: GameAction; label: string; icon: typeof Swords }[] = [
|
||||
{ id: 'meditate', label: 'Meditate', icon: Sparkles },
|
||||
{ id: 'climb', label: 'Climb', icon: Swords },
|
||||
{ id: 'study', label: 'Study', icon: BookOpen },
|
||||
];
|
||||
const config = ACTION_CONFIG[currentAction] || { label: currentAction, icon: Sparkles, color: 'text-gray-400' };
|
||||
const Icon = config.icon;
|
||||
|
||||
const hasDesignProgress = designProgress !== null;
|
||||
const hasPrepProgress = preparationProgress !== null;
|
||||
const hasAppProgress = applicationProgress !== null;
|
||||
// Calculate additional info for specific actions
|
||||
const getActionDetails = () => {
|
||||
switch (currentAction) {
|
||||
case 'study':
|
||||
if (currentStudyTarget) {
|
||||
const progress = currentStudyTarget.progress;
|
||||
const required = currentStudyTarget.required;
|
||||
const percentage = Math.min(100, (progress / required) * 100);
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={progress}
|
||||
required={required}
|
||||
label={`${currentStudyTarget.type === 'skill' ? 'Skill' : 'Spell'}: ${percentage.toFixed(0)}%`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'design':
|
||||
if (designProgress) {
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={designProgress.progress}
|
||||
required={designProgress.required}
|
||||
label="Design progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'prepare':
|
||||
if (preparationProgress) {
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={preparationProgress.progress}
|
||||
required={preparationProgress.required}
|
||||
label="Preparation progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'enchant':
|
||||
if (applicationProgress) {
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={applicationProgress.progress}
|
||||
required={applicationProgress.required}
|
||||
label="Enchantment progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'craft':
|
||||
if (equipmentCraftingProgress) {
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={equipmentCraftingProgress.progress}
|
||||
required={equipmentCraftingProgress.required}
|
||||
label="Crafting progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{actions.map(({ id, label, icon: Icon }) => (
|
||||
<Button
|
||||
key={id}
|
||||
variant={currentAction === id ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
className={`h-9 ${currentAction === id ? 'bg-blue-600 hover:bg-blue-700' : 'bg-gray-800/50 hover:bg-gray-700/50 border-gray-600'}`}
|
||||
onClick={() => setAction(id)}
|
||||
>
|
||||
<Icon className="w-4 h-4 mr-1" />
|
||||
{label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Crafting actions row - shown when there's active crafting progress */}
|
||||
{(hasDesignProgress || hasPrepProgress || hasAppProgress) && (
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<Button
|
||||
variant={currentAction === 'design' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
disabled={!hasDesignProgress}
|
||||
className={`h-9 ${currentAction === 'design' ? 'bg-purple-600 hover:bg-purple-700' : 'bg-gray-800/50 hover:bg-gray-700/50 border-gray-600'}`}
|
||||
onClick={() => hasDesignProgress && setAction('design')}
|
||||
>
|
||||
<Target className="w-4 h-4 mr-1" />
|
||||
Design
|
||||
</Button>
|
||||
<Button
|
||||
variant={currentAction === 'prepare' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
disabled={!hasPrepProgress}
|
||||
className={`h-9 ${currentAction === 'prepare' ? 'bg-purple-600 hover:bg-purple-700' : 'bg-gray-800/50 hover:bg-gray-700/50 border-gray-600'}`}
|
||||
onClick={() => hasPrepProgress && setAction('prepare')}
|
||||
>
|
||||
<FlaskConical className="w-4 h-4 mr-1" />
|
||||
Prepare
|
||||
</Button>
|
||||
<Button
|
||||
variant={currentAction === 'enchant' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
disabled={!hasAppProgress}
|
||||
className={`h-9 ${currentAction === 'enchant' ? 'bg-purple-600 hover:bg-purple-700' : 'bg-gray-800/50 hover:bg-gray-700/50 border-gray-600'}`}
|
||||
onClick={() => hasAppProgress && setAction('enchant')}
|
||||
>
|
||||
<Sparkles className="w-4 h-4 mr-1" />
|
||||
Enchant
|
||||
</Button>
|
||||
<div className="bg-gray-800/50 rounded-lg p-3 border border-gray-700">
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className={`w-4 h-4 ${config.color}`} />
|
||||
<span className="text-sm font-medium text-gray-200">Current Activity</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={`text-lg font-semibold mt-1 ${config.color}`}>
|
||||
{config.label}
|
||||
</div>
|
||||
{getActionDetails()}
|
||||
|
||||
{/* Show second design slot if active */}
|
||||
{designProgress2 && (
|
||||
<div className="mt-2 pt-2 border-t border-gray-700">
|
||||
<div className="flex items-center gap-2">
|
||||
<Target className="w-3 h-3 text-purple-400" />
|
||||
<span className="text-xs text-gray-400">Second Design Slot</span>
|
||||
</div>
|
||||
<ProgressBar
|
||||
progress={designProgress2.progress}
|
||||
required={designProgress2.required}
|
||||
label="Design progress"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ActionButtons.displayName = "ActionButtons";
|
||||
ProgressBar.displayName = "ProgressBar";
|
||||
|
||||
Executable
+152
@@ -0,0 +1,152 @@
|
||||
'use client';
|
||||
|
||||
import { Sparkles, Swords, BookOpen, Target, FlaskConical, Cog, Hammer } from 'lucide-react';
|
||||
import type { GameAction } from '@/lib/game/types';
|
||||
|
||||
interface ActionButtonsProps {
|
||||
currentAction: GameAction;
|
||||
currentStudyTarget: { type: 'skill' | 'spell'; id: string; progress: number; required: number } | null;
|
||||
designProgress: { progress: number; required: number } | null;
|
||||
designProgress2: { progress: number; required: number } | null;
|
||||
preparationProgress: { progress: number; required: number } | null;
|
||||
applicationProgress: { progress: number; required: number } | null;
|
||||
equipmentCraftingProgress: { progress: number; required: number } | null;
|
||||
}
|
||||
|
||||
// Map action IDs to labels and icons
|
||||
const ACTION_CONFIG: Record<string, { label: string; icon: typeof Sparkles; color: string }> = {
|
||||
meditate: { label: 'Meditating', icon: Sparkles, color: 'text-blue-400' },
|
||||
climb: { label: 'Climbing', icon: Swords, color: 'text-green-400' },
|
||||
study: { label: 'Studying', icon: BookOpen, color: 'text-yellow-400' },
|
||||
design: { label: 'Designing Enchantment', icon: Target, color: 'text-purple-400' },
|
||||
prepare: { label: 'Preparing Equipment', icon: FlaskConical, color: 'text-purple-400' },
|
||||
enchant: { label: 'Enchanting', icon: Sparkles, color: 'text-purple-400' },
|
||||
craft: { label: 'Crafting Equipment', icon: Hammer, color: 'text-orange-400' },
|
||||
convert: { label: 'Converting Mana', icon: Cog, color: 'text-cyan-400' },
|
||||
};
|
||||
|
||||
function ProgressBar({ progress, required, label }: { progress: number; required: number; label?: string }) {
|
||||
const percentage = Math.min(100, (progress / required) * 100);
|
||||
return (
|
||||
<div className="mt-1">
|
||||
{label && <div className="text-xs text-gray-400 mb-0.5">{label}</div>}
|
||||
<div className="w-full bg-gray-700 rounded-full h-1.5">
|
||||
<div
|
||||
className="bg-blue-500 h-1.5 rounded-full transition-all duration-300"
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ActionButtons({
|
||||
currentAction,
|
||||
currentStudyTarget,
|
||||
designProgress,
|
||||
designProgress2,
|
||||
preparationProgress,
|
||||
applicationProgress,
|
||||
equipmentCraftingProgress,
|
||||
}: ActionButtonsProps) {
|
||||
const config = ACTION_CONFIG[currentAction] || { label: currentAction, icon: Sparkles, color: 'text-gray-400' };
|
||||
const Icon = config.icon;
|
||||
|
||||
// Calculate additional info for specific actions
|
||||
const getActionDetails = () => {
|
||||
switch (currentAction) {
|
||||
case 'study':
|
||||
if (currentStudyTarget) {
|
||||
const progress = currentStudyTarget.progress;
|
||||
const required = currentStudyTarget.required;
|
||||
const percentage = Math.min(100, (progress / required) * 100);
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={progress}
|
||||
required={required}
|
||||
label={`${currentStudyTarget.type === 'skill' ? 'Skill' : 'Spell'}: ${percentage.toFixed(0)}%`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'design':
|
||||
if (designProgress) {
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={designProgress.progress}
|
||||
required={designProgress.required}
|
||||
label="Design progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'prepare':
|
||||
if (preparationProgress) {
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={preparationProgress.progress}
|
||||
required={preparationProgress.required}
|
||||
label="Preparation progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'enchant':
|
||||
if (applicationProgress) {
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={applicationProgress.progress}
|
||||
required={applicationProgress.required}
|
||||
label="Enchantment progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'craft':
|
||||
if (equipmentCraftingProgress) {
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={equipmentCraftingProgress.progress}
|
||||
required={equipmentCraftingProgress.required}
|
||||
label="Crafting progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="bg-gray-800/50 rounded-lg p-3 border border-gray-700">
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className={`w-4 h-4 ${config.color}`} />
|
||||
<span className="text-sm font-medium text-gray-200">Current Activity</span>
|
||||
</div>
|
||||
<div className={`text-lg font-semibold mt-1 ${config.color}`}>
|
||||
{config.label}
|
||||
</div>
|
||||
{getActionDetails()}
|
||||
|
||||
{/* Show second design slot if active */}
|
||||
{designProgress2 && (
|
||||
<div className="mt-2 pt-2 border-t border-gray-700">
|
||||
<div className="flex items-center gap-2">
|
||||
<Target className="w-3 h-3 text-purple-400" />
|
||||
<span className="text-xs text-gray-400">Second Design Slot</span>
|
||||
</div>
|
||||
<ProgressBar
|
||||
progress={designProgress2.progress}
|
||||
required={designProgress2.required}
|
||||
label="Design progress"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ActionButtons.displayName = "ActionButtons";
|
||||
ProgressBar.displayName = "ProgressBar";
|
||||
@@ -48,3 +48,6 @@ export function CalendarDisplay({ day }: CalendarDisplayProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CalendarDisplay.displayName = "CalendarDisplay";
|
||||
CalendarDisplay.displayName = "CalendarDisplay";
|
||||
|
||||
@@ -159,3 +159,5 @@ export function CraftingProgress({
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
CraftingProgress.displayName = "CraftingProgress";
|
||||
|
||||
@@ -422,5 +422,7 @@ export function useGameContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
GameProvider.displayName = "GameProvider";
|
||||
|
||||
// Re-export useGameLoop for convenience
|
||||
export { useGameLoop };
|
||||
|
||||
@@ -169,3 +169,5 @@ export function LabTab() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LabTab.displayName = "LabTab";
|
||||
|
||||
@@ -459,3 +459,5 @@ export function LootInventoryDisplay({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
LootInventoryDisplay.displayName = "LootInventoryDisplay";
|
||||
|
||||
@@ -121,3 +121,5 @@ export function ManaDisplay({
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
ManaDisplay.displayName = "ManaDisplay";
|
||||
|
||||
@@ -267,7 +267,10 @@ export function SkillsTab() {
|
||||
const baseCost = def.base * (level + 1) * currentTier;
|
||||
const cost = Math.floor(baseCost * studyCostMult);
|
||||
|
||||
const canStudy = !maxed && prereqMet && store.rawMana >= cost && !isStudying;
|
||||
// Check if any study is in progress (prevent switching topics)
|
||||
const isAnyStudyInProgress = store.currentAction === 'study' && store.currentStudyTarget;
|
||||
// Can only study if: not maxed, prereqs met, has mana, and either no study in progress or already studying this skill
|
||||
const canStudy = !maxed && prereqMet && store.rawMana >= cost && (!isAnyStudyInProgress || isStudying);
|
||||
|
||||
const milestoneInfo = hasMilestoneUpgrade(tieredSkillId, level);
|
||||
const nextTierSkill = getNextTierSkill(tieredSkillId);
|
||||
@@ -369,15 +372,26 @@ export function SkillsTab() {
|
||||
<Badge className="bg-green-900/50 text-green-300">Maxed</Badge>
|
||||
) : (
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={canStudy ? 'default' : 'outline'}
|
||||
disabled={!canStudy}
|
||||
className={canStudy ? 'bg-purple-600 hover:bg-purple-700' : 'opacity-50'}
|
||||
onClick={() => store.startStudyingSkill(tieredSkillId)}
|
||||
>
|
||||
Study ({fmt(cost)})
|
||||
</Button>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={canStudy ? 'default' : 'outline'}
|
||||
disabled={!canStudy}
|
||||
className={canStudy ? 'bg-purple-600 hover:bg-purple-700' : 'opacity-50'}
|
||||
onClick={() => store.startStudyingSkill(tieredSkillId)}
|
||||
>
|
||||
Study ({fmt(cost)})
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
{!canStudy && isAnyStudyInProgress && !isStudying && (
|
||||
<TooltipContent>
|
||||
<p>Cannot switch topics while studying {SKILLS_DEF[store.currentStudyTarget?.id || '']?.name || 'another skill'}</p>
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{/* Parallel Study button */}
|
||||
{hasParallelStudy &&
|
||||
store.currentStudyTarget &&
|
||||
@@ -416,3 +430,5 @@ export function SkillsTab() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SkillsTab.displayName = "SkillsTab";
|
||||
|
||||
@@ -164,3 +164,5 @@ export function SpellsTab() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SpellsTab.displayName = "SpellsTab";
|
||||
|
||||
@@ -318,3 +318,5 @@ export function SpireTab() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SpireTab.displayName = "SpireTab";
|
||||
|
||||
@@ -580,3 +580,5 @@ export function StatsTab() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
StatsTab.displayName = "StatsTab";
|
||||
|
||||
@@ -55,3 +55,5 @@ export function StudyProgress({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
StudyProgress.displayName = "StudyProgress";
|
||||
|
||||
@@ -49,3 +49,5 @@ export function TimeDisplay({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TimeDisplay.displayName = "TimeDisplay";
|
||||
|
||||
@@ -113,3 +113,5 @@ export function UpgradeDialog({
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
UpgradeDialog.displayName = "UpgradeDialog";
|
||||
|
||||
@@ -209,3 +209,5 @@ export function EnchantmentApplier({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
EnchantmentApplier.displayName = "EnchantmentApplier";
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Wand2, Scroll, Trash2, Plus, Minus } from 'lucide-react';
|
||||
import { EQUIPMENT_TYPES } from '@/lib/game/data/equipment';
|
||||
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from '@/lib/game/data/enchantment-effects';
|
||||
import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops';
|
||||
import { CRAFTING_RECIPES } from '@/lib/game/data/crafting-recipes';
|
||||
import type { EquipmentInstance, EnchantmentDesign, DesignEffect, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
|
||||
import { fmt, type GameStore } from '@/lib/game/store';
|
||||
|
||||
@@ -138,6 +139,34 @@ export function EnchantmentDesigner({
|
||||
);
|
||||
};
|
||||
|
||||
// Get equipment types that the player has blueprints for
|
||||
const getOwnedEquipmentTypes = () => {
|
||||
const ownedBlueprints = store.lootInventory.blueprints || [];
|
||||
|
||||
// Map blueprint IDs to equipment type IDs
|
||||
const ownedEquipmentTypeIds = new Set<string>();
|
||||
for (const blueprintId of ownedBlueprints) {
|
||||
const recipe = CRAFTING_RECIPES[blueprintId];
|
||||
if (recipe) {
|
||||
ownedEquipmentTypeIds.add(recipe.equipmentTypeId);
|
||||
}
|
||||
}
|
||||
|
||||
// Also include the starting equipment types (basicStaff, civilianShirt, civilianShoes)
|
||||
// These are the types the player starts with, so they should be able to design for them
|
||||
ownedEquipmentTypeIds.add('basicStaff');
|
||||
ownedEquipmentTypeIds.add('civilianShirt');
|
||||
ownedEquipmentTypeIds.add('civilianShoes');
|
||||
ownedEquipmentTypeIds.add('apprenticeWand');
|
||||
ownedEquipmentTypeIds.add('clothHood');
|
||||
ownedEquipmentTypeIds.add('civilianGloves');
|
||||
ownedEquipmentTypeIds.add('copperRing');
|
||||
|
||||
return Object.values(EQUIPMENT_TYPES).filter(type => ownedEquipmentTypeIds.has(type.id));
|
||||
};
|
||||
|
||||
const ownedEquipmentTypes = getOwnedEquipmentTypes();
|
||||
|
||||
// Render design stage
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
@@ -162,7 +191,7 @@ export function EnchantmentDesigner({
|
||||
) : (
|
||||
<ScrollArea className="h-64">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{Object.values(EQUIPMENT_TYPES).map(type => (
|
||||
{ownedEquipmentTypes.map(type => (
|
||||
<div
|
||||
key={type.id}
|
||||
className={`p-2 rounded border cursor-pointer transition-all ${
|
||||
@@ -177,6 +206,11 @@ export function EnchantmentDesigner({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{ownedEquipmentTypes.length === 0 && (
|
||||
<div className="text-center text-gray-400 py-4 text-sm">
|
||||
No equipment blueprints owned. Craft or find equipment blueprints first.
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
)}
|
||||
</CardContent>
|
||||
@@ -354,3 +388,5 @@ export function EnchantmentDesigner({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
EnchantmentDesigner.displayName = "EnchantmentDesigner";
|
||||
|
||||
@@ -202,3 +202,5 @@ export function EnchantmentPreparer({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
EnchantmentPreparer.displayName = "EnchantmentPreparer";
|
||||
|
||||
@@ -198,3 +198,5 @@ export function EquipmentCrafter({ store }: EquipmentCrafterProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
EquipmentCrafter.displayName = "EquipmentCrafter";
|
||||
|
||||
@@ -85,3 +85,5 @@ export function AttunementDebug({ store }: AttunementDebugProps) {
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
AttunementDebug.displayName = "AttunementDebug";
|
||||
|
||||
@@ -86,3 +86,5 @@ export function ElementDebug({ store }: ElementDebugProps) {
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
ElementDebug.displayName = "ElementDebug";
|
||||
|
||||
@@ -269,3 +269,5 @@ export function GameStateDebug({ store }: GameStateDebugProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
GameStateDebug.displayName = "GameStateDebug";
|
||||
|
||||
@@ -25,3 +25,5 @@ export function GolemDebug({ store }: GolemDebugProps) {
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
GolemDebug.displayName = "GolemDebug";
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
'use client';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Bug } from 'lucide-react';
|
||||
import { useGameStore } from '@/lib/game/stores';
|
||||
import { GUARDIANS, ELEMENTS } from '@/lib/game/constants';
|
||||
|
||||
export function PactDebug() {
|
||||
// Get state from the main game store where pacts are stored
|
||||
const store = useGameStore();
|
||||
const signedPacts = useGameStore((s) => s.signedPacts);
|
||||
const signedPactDetails = useGameStore((s) => s.signedPactDetails);
|
||||
const elements = useGameStore((s) => s.elements);
|
||||
const prestigeUpgrades = useGameStore((s) => s.prestigeUpgrades);
|
||||
|
||||
// Get all guardian floors
|
||||
const guardianFloors = Object.keys(GUARDIANS).map(Number).sort((a, b) => a - b);
|
||||
|
||||
// Helper to add log messages
|
||||
const addLog = (message: string) => {
|
||||
store.log.unshift(message);
|
||||
};
|
||||
|
||||
// Force sign a pact with a guardian (bypass costs and time)
|
||||
const forcePact = (floor: number) => {
|
||||
const guardian = GUARDIANS[floor];
|
||||
if (!guardian) return;
|
||||
|
||||
// Check if already signed
|
||||
if (signedPacts.includes(floor)) {
|
||||
addLog(`⚠️ Already signed pact with ${guardian.name}!`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check max pacts
|
||||
const maxPacts = 1 + (prestigeUpgrades?.pactCapacity || 0);
|
||||
if (signedPacts.length >= maxPacts) {
|
||||
addLog(`⚠️ Cannot sign more pacts! Maximum: ${maxPacts}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Force sign the pact
|
||||
const newSignedPacts = [...signedPacts, floor];
|
||||
|
||||
// Add pact details
|
||||
const newSignedPactDetails = {
|
||||
...signedPactDetails,
|
||||
[floor]: {
|
||||
floor,
|
||||
guardianId: guardian.element,
|
||||
signedAt: { day: store.day, hour: store.hour },
|
||||
skillLevels: {} as Record<string, number>,
|
||||
},
|
||||
};
|
||||
|
||||
// Unlock mana types
|
||||
let newElements = { ...elements };
|
||||
for (const elemId of guardian.unlocksMana) {
|
||||
if (newElements[elemId]) {
|
||||
newElements = {
|
||||
...newElements,
|
||||
[elemId]: { ...newElements[elemId], unlocked: true },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Check for compound element unlocks
|
||||
const unlockedSet = new Set(
|
||||
Object.entries(newElements)
|
||||
.filter(([, e]) => e.unlocked)
|
||||
.map(([id]) => id)
|
||||
);
|
||||
|
||||
for (const [elemId, elemDef] of Object.entries(ELEMENTS)) {
|
||||
if (elemDef.recipe && !newElements[elemId]?.unlocked) {
|
||||
const canUnlock = elemDef.recipe.every((comp: string) => unlockedSet.has(comp));
|
||||
if (canUnlock) {
|
||||
newElements = {
|
||||
...newElements,
|
||||
[elemId]: { ...newElements[elemId], unlocked: true },
|
||||
};
|
||||
addLog(`🔮 ${elemDef.name} mana unlocked through component synergy!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addLog(`📜 DEBUG: Pact with ${guardian.name} force-signed! ${guardian.unlocksMana.map(e => ELEMENTS[e]?.name || e).join(', ')} mana unlocked!`);
|
||||
|
||||
// Update store
|
||||
store.setState({
|
||||
signedPacts: newSignedPacts,
|
||||
signedPactDetails: newSignedPactDetails,
|
||||
elements: newElements,
|
||||
});
|
||||
};
|
||||
|
||||
// Remove a pact
|
||||
const removePact = (floor: number) => {
|
||||
const guardian = GUARDIANS[floor];
|
||||
const newSignedPacts = signedPacts.filter(f => f !== floor);
|
||||
const newSignedPactDetails = { ...signedPactDetails };
|
||||
delete newSignedPactDetails[floor];
|
||||
|
||||
addLog(`📜 DEBUG: Removed pact with ${guardian?.name || 'Unknown'}!`);
|
||||
|
||||
store.setState({
|
||||
signedPacts: newSignedPacts,
|
||||
signedPactDetails: newSignedPactDetails,
|
||||
});
|
||||
};
|
||||
|
||||
// Clear all pacts
|
||||
const clearAllPacts = () => {
|
||||
addLog(`📜 DEBUG: Cleared all pacts!`);
|
||||
store.setState({
|
||||
signedPacts: [],
|
||||
signedPactDetails: {},
|
||||
});
|
||||
};
|
||||
|
||||
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" />
|
||||
Pact Debug
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs text-gray-400 mb-2">
|
||||
Force sign pacts with guardians (bypasses mana costs and signing time)
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||
{guardianFloors.map((floor) => {
|
||||
const guardian = GUARDIANS[floor];
|
||||
const isSigned = signedPacts.includes(floor);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={floor}
|
||||
className={`p-2 rounded border flex items-center justify-between ${
|
||||
isSigned ? 'border-green-600/50 bg-green-900/20' : 'border-gray-700'
|
||||
}`}
|
||||
style={{ borderColor: isSigned ? undefined : guardian.color, borderWidth: '1px' }}
|
||||
>
|
||||
<div>
|
||||
<div className="text-sm font-semibold" style={{ color: guardian.color }}>
|
||||
{guardian.name}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">
|
||||
Floor {floor} | {guardian.pact}x multiplier
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
Unlocks: {guardian.unlocksMana.map(e => ELEMENTS[e]?.name || e).join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
{isSigned ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={() => removePact(floor)}
|
||||
className="text-xs"
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
onClick={() => forcePact(floor)}
|
||||
className="text-xs bg-amber-600 hover:bg-amber-700"
|
||||
>
|
||||
Force Sign
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Clear All Button */}
|
||||
{signedPacts.length > 0 && (
|
||||
<div className="pt-2 border-t border-gray-700">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={clearAllPacts}
|
||||
className="w-full text-xs"
|
||||
>
|
||||
Clear All Pacts ({signedPacts.length})
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Status */}
|
||||
<div className="text-xs text-gray-400 pt-2 border-t border-gray-700">
|
||||
Signed Pacts: {signedPacts.length} |
|
||||
Max Pacts: {1 + (prestigeUpgrades?.pactCapacity || 0)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
PactDebug.displayName = "PactDebug";
|
||||
@@ -291,3 +291,5 @@ export function SkillDebug({ store }: SkillDebugProps) {
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
SkillDebug.displayName = "SkillDebug";
|
||||
|
||||
@@ -3,3 +3,4 @@ export { SkillDebug } from './SkillDebug';
|
||||
export { ElementDebug } from './ElementDebug';
|
||||
export { AttunementDebug } from './AttunementDebug';
|
||||
export { GolemDebug } from './GolemDebug';
|
||||
export { PactDebug } from './PactDebug';
|
||||
|
||||
@@ -204,3 +204,5 @@ export function MemorySlotPicker({ onConfirm }: MemorySlotPickerProps) {
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
MemorySlotPicker.displayName = "MemorySlotPicker";
|
||||
|
||||
@@ -58,3 +58,5 @@ export function StudyProgress({ target, showCancel = true, speedLabel }: StudyPr
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
StudyProgress.displayName = "StudyProgress";
|
||||
|
||||
@@ -124,3 +124,5 @@ export function UpgradeDialog({ skillId, milestone, onClose }: UpgradeDialogProp
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
UpgradeDialog.displayName = "UpgradeDialog";
|
||||
|
||||
@@ -62,3 +62,5 @@ export function CombatStatsSection({ store }: CombatStatsSectionProps) {
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
CombatStatsSection.displayName = "CombatStatsSection";
|
||||
|
||||
@@ -255,3 +255,5 @@ export function ManaStatsSection({
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
ManaStatsSection.displayName = "ManaStatsSection";
|
||||
|
||||
@@ -53,3 +53,5 @@ export function StudyStatsSection({ store, studySpeedMult, studyCostMult }: Stud
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
StudyStatsSection.displayName = "StudyStatsSection";
|
||||
|
||||
@@ -80,3 +80,5 @@ export function UpgradeEffectsSection({ store }: UpgradeEffectsSectionProps) {
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
UpgradeEffectsSection.displayName = "UpgradeEffectsSection";
|
||||
|
||||
@@ -41,3 +41,5 @@ export function AchievementsTab({ store }: AchievementsTabProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
AchievementsTab.displayName = "AchievementsTab";
|
||||
|
||||
@@ -265,3 +265,5 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
AttunementsTab.displayName = "AttunementsTab";
|
||||
|
||||
@@ -160,3 +160,5 @@ export function CraftingTab({ store }: CraftingTabProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CraftingTab.displayName = "CraftingTab";
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
SkillDebug,
|
||||
ElementDebug,
|
||||
AttunementDebug,
|
||||
GolemDebug
|
||||
GolemDebug,
|
||||
PactDebug
|
||||
} from '@/components/game/debug';
|
||||
|
||||
interface DebugTabProps {
|
||||
@@ -25,6 +26,9 @@ export function DebugTab({ store }: DebugTabProps) {
|
||||
|
||||
<SkillDebug store={store} />
|
||||
<GolemDebug store={store} />
|
||||
<PactDebug />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
DebugTab.displayName = "DebugTab";
|
||||
|
||||
@@ -431,3 +431,5 @@ export function EquipmentTab({ store }: EquipmentTabProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
EquipmentTab.displayName = "EquipmentTab";
|
||||
|
||||
@@ -336,3 +336,5 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
GolemancyTab.displayName = "GolemancyTab";
|
||||
|
||||
@@ -114,3 +114,5 @@ export function LabTab({ store }: LabTabProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LabTab.displayName = "LabTab";
|
||||
|
||||
@@ -44,3 +44,5 @@ export function LootTab({ store }: LootTabProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LootTab.displayName = "LootTab";
|
||||
|
||||
@@ -367,3 +367,5 @@ export function SkillsTab({ store }: SkillsTabProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SkillsTab.displayName = "SkillsTab";
|
||||
|
||||
@@ -178,3 +178,5 @@ export function SpellsTab({ store }: SpellsTabProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SpellsTab.displayName = "SpellsTab";
|
||||
|
||||
@@ -19,9 +19,10 @@ import { getUnifiedEffects } from '@/lib/game/effects';
|
||||
|
||||
interface SpireTabProps {
|
||||
store: GameStore;
|
||||
simpleMode?: boolean; // When true, only show essential Spire info (for Spire Mode)
|
||||
}
|
||||
|
||||
export function SpireTab({ store }: SpireTabProps) {
|
||||
export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
|
||||
const floorElem = getFloorElement(store.currentFloor);
|
||||
const floorElemDef = ELEMENTS[floorElem];
|
||||
const isGuardianFloor = !!GUARDIANS[store.currentFloor];
|
||||
@@ -48,7 +49,7 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<div className={`grid gap-4 ${simpleMode ? 'grid-cols-1' : 'grid-cols-1 lg:grid-cols-2'}`}>
|
||||
{/* Current Floor Card */}
|
||||
<Card className="bg-gray-900/80 border-gray-700">
|
||||
<CardHeader className="pb-2">
|
||||
@@ -92,35 +93,39 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator className="bg-gray-700" />
|
||||
|
||||
{/* Floor Navigation - Direction indicator only */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-gray-400">Direction</span>
|
||||
<div className="flex gap-1">
|
||||
<Badge variant={climbDirection === 'up' ? 'default' : 'outline'}
|
||||
className={climbDirection === 'up' ? 'bg-green-600' : ''}>
|
||||
<ChevronUp className="w-3 h-3 mr-1" />
|
||||
Up
|
||||
</Badge>
|
||||
<Badge variant={climbDirection === 'down' ? 'default' : 'outline'}
|
||||
className={climbDirection === 'down' ? 'bg-blue-600' : ''}>
|
||||
<ChevronDown className="w-3 h-3 mr-1" />
|
||||
Down
|
||||
</Badge>
|
||||
{!simpleMode && (
|
||||
<>
|
||||
<Separator className="bg-gray-700" />
|
||||
|
||||
{/* Floor Navigation - Direction indicator only */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-gray-400">Direction</span>
|
||||
<div className="flex gap-1">
|
||||
<Badge variant={climbDirection === 'up' ? 'default' : 'outline'}
|
||||
className={climbDirection === 'up' ? 'bg-green-600' : ''}>
|
||||
<ChevronUp className="w-3 h-3 mr-1" />
|
||||
Up
|
||||
</Badge>
|
||||
<Badge variant={climbDirection === 'down' ? 'default' : 'outline'}
|
||||
className={climbDirection === 'down' ? 'bg-blue-600' : ''}>
|
||||
<ChevronDown className="w-3 h-3 mr-1" />
|
||||
Down
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isFloorCleared && (
|
||||
<div className="text-xs text-amber-400 text-center flex items-center justify-center gap-1">
|
||||
<RotateCcw className="w-3 h-3" />
|
||||
Floor cleared! Advancing...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isFloorCleared && (
|
||||
<div className="text-xs text-amber-400 text-center flex items-center justify-center gap-1">
|
||||
<RotateCcw className="w-3 h-3" />
|
||||
Floor cleared! Advancing...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Separator className="bg-gray-700" />
|
||||
|
||||
<Separator className="bg-gray-700" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="text-sm text-gray-400">
|
||||
Best: Floor <strong className="text-gray-200">{store.maxFloorReached}</strong> •
|
||||
@@ -205,8 +210,8 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Summoned Golems Card */}
|
||||
{store.golemancy.summonedGolems.length > 0 && (
|
||||
{/* Summoned Golems Card - Always show in simple mode, conditional in normal mode */}
|
||||
{(simpleMode || store.golemancy.summonedGolems.length > 0) && (
|
||||
<Card className="bg-gray-900/80 border-amber-600/50">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-amber-400 game-panel-title text-xs flex items-center gap-2">
|
||||
@@ -257,8 +262,8 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Current Study (if any) */}
|
||||
{store.currentStudyTarget && (
|
||||
{/* Current Study (if any) - Only show in normal mode */}
|
||||
{!simpleMode && store.currentStudyTarget && (
|
||||
<Card className="bg-gray-900/80 border-purple-600/50 lg:col-span-2">
|
||||
<CardContent className="pt-4 space-y-3">
|
||||
<StudyProgress
|
||||
@@ -299,8 +304,8 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Crafting Progress (if any) */}
|
||||
{(store.designProgress || store.preparationProgress || store.applicationProgress) && (
|
||||
{/* Crafting Progress (if any) - Only show in normal mode */}
|
||||
{!simpleMode && (store.designProgress || store.preparationProgress || store.applicationProgress) && (
|
||||
<Card className="bg-gray-900/80 border-cyan-600/50 lg:col-span-2">
|
||||
<CardContent className="pt-4">
|
||||
<CraftingProgress
|
||||
@@ -319,27 +324,31 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Activity Log */}
|
||||
<Card className="bg-gray-900/80 border-gray-700 lg:col-span-2">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-amber-400 game-panel-title text-xs">Activity Log</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ScrollArea className="h-32">
|
||||
<div className="space-y-1">
|
||||
{store.log.slice(0, 20).map((entry, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`text-sm ${i === 0 ? 'text-gray-200' : 'text-gray-500'} italic`}
|
||||
>
|
||||
{entry}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Activity Log - Only show in normal mode */}
|
||||
{!simpleMode && (
|
||||
<Card className="bg-gray-900/80 border-gray-700 lg:col-span-2">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-amber-400 game-panel-title text-xs">Activity Log</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ScrollArea className="h-32">
|
||||
<div className="space-y-1">
|
||||
{store.log.slice(0, 20).map((entry, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`text-sm ${i === 0 ? 'text-gray-200' : 'text-gray-500'} italic`}
|
||||
>
|
||||
{entry}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
SpireTab.displayName = "SpireTab";
|
||||
|
||||
@@ -247,3 +247,5 @@ export function StatsTab({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
StatsTab.displayName = "StatsTab";
|
||||
|
||||
@@ -71,3 +71,5 @@ export function StudyProgress({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
StudyProgress.displayName = "StudyProgress";
|
||||
|
||||
@@ -113,3 +113,5 @@ export function UpgradeDialog({
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
UpgradeDialog.displayName = "UpgradeDialog";
|
||||
|
||||
@@ -422,6 +422,9 @@ export function createCraftingSlice(
|
||||
const instance = state.equipmentInstances[equipmentInstanceId];
|
||||
if (!instance) return false;
|
||||
|
||||
// Don't allow preparing enchanted items - they need to be disenchanted first
|
||||
if (instance.enchantments.length > 0) return false;
|
||||
|
||||
const prepTime = calculatePrepTime(instance.totalCapacity);
|
||||
const manaCost = calculatePrepManaCost(instance.totalCapacity);
|
||||
|
||||
|
||||
@@ -65,3 +65,5 @@ export function DebugName({ name, children }: DebugNameProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
DebugName.displayName = "DebugName";
|
||||
|
||||
+127
-34
@@ -2,7 +2,7 @@
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { GameState, GameAction, StudyTarget, SpellCost, SkillUpgradeChoice, EquipmentSlot, EquipmentInstance, EnchantmentDesign, DesignEffect, AttunementState, FloorState, EnemyState, RoomType } from './types';
|
||||
import type { GameState, GameAction, StudyTarget, SpellCost, SkillUpgradeChoice, EquipmentSlot, EquipmentInstance, EnchantmentDesign, DesignEffect, AttunementState, FloorState, EnemyState, RoomType, EquipmentSpellState } from './types';
|
||||
import {
|
||||
ELEMENTS,
|
||||
GUARDIANS,
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
getSpellsFromEquipment,
|
||||
type CraftingActions
|
||||
} from './crafting-slice';
|
||||
import { getActiveEquipmentSpells, type ActiveEquipmentSpell } from './utils/combat-utils';
|
||||
import { EQUIPMENT_TYPES } from './data/equipment';
|
||||
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
||||
import { ATTUNEMENTS_DEF, getTotalAttunementRegen, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
||||
@@ -749,6 +750,9 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
|
||||
|
||||
// Mana Well Effects (Phase 4)
|
||||
manaHeartBonus: manaHeartBonus, // Cumulative +10% max mana per loop from MANA_HEART
|
||||
|
||||
// Spire Mode - simplified UI for climbing
|
||||
spireMode: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -801,6 +805,10 @@ interface GameStore extends GameState, CraftingActions {
|
||||
getMeditationMultiplier: () => number;
|
||||
canCastSpell: (spellId: string) => boolean;
|
||||
getSkillUpgradeChoices: (skillId: string, milestone: 5 | 10) => { available: SkillUpgradeChoice[]; selected: string[] };
|
||||
|
||||
// Spire Mode actions
|
||||
enterSpireMode: () => void;
|
||||
exitSpireMode: () => void;
|
||||
}
|
||||
|
||||
export const useGameStore = create<GameStore>()(
|
||||
@@ -833,6 +841,9 @@ export const useGameStore = create<GameStore>()(
|
||||
|
||||
// Compute unified effects (includes skill upgrades AND equipment enchantments)
|
||||
const effects = getUnifiedEffects(state);
|
||||
|
||||
// Track current action for potential auto-transitions
|
||||
let currentAction = state.currentAction;
|
||||
|
||||
const maxMana = computeMaxMana(state, effects);
|
||||
const baseRegen = computeRegen(state, effects);
|
||||
@@ -878,7 +889,7 @@ export const useGameStore = create<GameStore>()(
|
||||
let meditateTicks = state.meditateTicks;
|
||||
let meditationMultiplier = 1;
|
||||
|
||||
if (state.currentAction === 'meditate') {
|
||||
if (currentAction === 'meditate') {
|
||||
meditateTicks++;
|
||||
meditationMultiplier = getMeditationBonus(meditateTicks, state.skills);
|
||||
|
||||
@@ -973,7 +984,7 @@ export const useGameStore = create<GameStore>()(
|
||||
let unlockedEffects = state.unlockedEffects;
|
||||
let consecutiveStudyHours = state.consecutiveStudyHours;
|
||||
|
||||
if (state.currentAction === 'study' && currentStudyTarget) {
|
||||
if (currentAction === 'study' && currentStudyTarget) {
|
||||
// Calculate base study speed
|
||||
let studySpeedMult = getStudySpeedMultiplier(skills);
|
||||
|
||||
@@ -1076,11 +1087,13 @@ export const useGameStore = create<GameStore>()(
|
||||
log = [`📖 ${SPELLS_DEF[spellId]?.name} learned!`, ...log.slice(0, 49)];
|
||||
}
|
||||
currentStudyTarget = null;
|
||||
// Auto-transition to meditate when study completes
|
||||
currentAction = 'meditate';
|
||||
}
|
||||
}
|
||||
// Parallel Study processing (PARALLEL_STUDY special effect)
|
||||
let parallelStudyTarget = state.parallelStudyTarget;
|
||||
if (parallelStudyTarget && state.currentAction === 'study') {
|
||||
if (parallelStudyTarget && currentAction === 'study') {
|
||||
// Parallel study progresses at 50% speed
|
||||
const parallelProgressGain = HOURS_PER_TICK * 0.5;
|
||||
parallelStudyTarget = {
|
||||
@@ -1101,7 +1114,7 @@ export const useGameStore = create<GameStore>()(
|
||||
}
|
||||
|
||||
// Convert action - auto convert mana
|
||||
if (state.currentAction === 'convert') {
|
||||
if (currentAction === 'convert') {
|
||||
const unlockedElements = Object.entries(elements)
|
||||
.filter(([, e]) => e.unlocked && e.current < e.max);
|
||||
|
||||
@@ -1130,7 +1143,7 @@ export const useGameStore = create<GameStore>()(
|
||||
const floorElement = getFloorElement(currentFloor);
|
||||
|
||||
// Handle puzzle rooms separately
|
||||
if (state.currentAction === 'climb' && currentRoom.roomType === 'puzzle') {
|
||||
if (currentAction === 'climb' && currentRoom.roomType === 'puzzle') {
|
||||
const progressSpeed = getPuzzleProgressSpeed(
|
||||
currentRoom.puzzleId || '',
|
||||
state.attunements
|
||||
@@ -1154,7 +1167,7 @@ export const useGameStore = create<GameStore>()(
|
||||
maxFloorReached = Math.max(maxFloorReached, currentFloor);
|
||||
castProgress = 0;
|
||||
}
|
||||
} else if (state.currentAction === 'climb') {
|
||||
} else if (currentAction === 'climb') {
|
||||
const spellId = state.activeSpell;
|
||||
const spellDef = SPELLS_DEF[spellId];
|
||||
|
||||
@@ -1349,6 +1362,87 @@ export const useGameStore = create<GameStore>()(
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Equipment Spell Processing ────────────────────────────────────────
|
||||
// Process casting for spells from equipped weapons
|
||||
const activeEquipSpells = getActiveEquipmentSpells(state.equippedInstances, state.equipmentInstances);
|
||||
|
||||
// Update equipmentSpellStates with current progress and process casts
|
||||
const updatedSpellStates: EquipmentSpellState[] = [];
|
||||
|
||||
for (const { spellId, equipmentId } of activeEquipSpells) {
|
||||
const spellDef = SPELLS_DEF[spellId];
|
||||
if (!spellDef) continue;
|
||||
|
||||
// Find or create spell state for this spell + equipment combo
|
||||
let spellState = state.equipmentSpellStates.find(
|
||||
s => s.spellId === spellId && s.sourceEquipment === equipmentId
|
||||
);
|
||||
|
||||
if (!spellState) {
|
||||
spellState = {
|
||||
spellId,
|
||||
sourceEquipment: equipmentId,
|
||||
castProgress: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Only process if climbing
|
||||
if (currentAction === 'climb') {
|
||||
// Calculate progress per tick for this spell
|
||||
const baseAttackSpeed = 1 + (state.skills.quickCast || 0) * 0.05;
|
||||
const totalAttackSpeed = baseAttackSpeed * effects.attackSpeedMultiplier;
|
||||
const spellCastSpeed = spellDef.castSpeed || 1;
|
||||
const lightningBonus = spellDef.elem === 'lightning' ? 0.3 : 0;
|
||||
const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed * (1 + lightningBonus);
|
||||
|
||||
// Accumulate progress
|
||||
let equipCastProgress = (spellState.castProgress || 0) + progressPerTick;
|
||||
|
||||
// Process complete casts
|
||||
while (equipCastProgress >= 1 && canAffordSpellCost(spellDef.cost, rawMana, elements)) {
|
||||
// Deduct cost
|
||||
const afterCost = deductSpellCost(spellDef.cost, rawMana, elements);
|
||||
rawMana = afterCost.rawMana;
|
||||
elements = afterCost.elements;
|
||||
totalManaGathered += spellDef.cost.amount;
|
||||
|
||||
// Calculate damage
|
||||
const floorElement = getFloorElement(currentFloor);
|
||||
let baseDmg = calcDamage(
|
||||
{ ...state, skills: state.skills, signedPacts: state.signedPacts },
|
||||
spellId,
|
||||
floorElement
|
||||
);
|
||||
baseDmg = baseDmg * effects.baseDamageMultiplier + effects.baseDamageBonus;
|
||||
|
||||
// Apply damage to enemies
|
||||
const aliveEnemies = currentRoom.enemies.filter(e => e.hp > 0);
|
||||
if (aliveEnemies.length > 0) {
|
||||
const target = aliveEnemies[0]; // Simple: target first alive enemy
|
||||
const armorPierceEffect = spellDef.effects?.find(e => e.type === 'armor_pierce');
|
||||
const armorPierce = armorPierceEffect?.value || 0;
|
||||
const effectiveArmor = Math.max(0, target.armor - armorPierce);
|
||||
const dmg = Math.floor(baseDmg * (1 - effectiveArmor));
|
||||
target.hp = Math.max(0, target.hp - dmg);
|
||||
}
|
||||
|
||||
equipCastProgress -= 1;
|
||||
}
|
||||
|
||||
// Update spell state with new progress
|
||||
spellState = { ...spellState, castProgress: equipCastProgress };
|
||||
}
|
||||
|
||||
updatedSpellStates.push(spellState);
|
||||
}
|
||||
|
||||
// Keep spell states for equipment that's no longer active (they'll be cleaned up elsewhere)
|
||||
const inactiveStates = state.equipmentSpellStates.filter(
|
||||
s => !activeEquipSpells.some(es => es.spellId === s.spellId && es.equipmentId === s.sourceEquipment)
|
||||
);
|
||||
|
||||
const equipmentSpellStates = [...updatedSpellStates, ...inactiveStates];
|
||||
|
||||
// ─── Golemancy Processing ─────────────────────────────────────────────────
|
||||
let golemancy = state.golemancy;
|
||||
const fabricatorLevel = state.attunements.fabricator?.level || 0;
|
||||
@@ -1358,7 +1452,7 @@ export const useGameStore = create<GameStore>()(
|
||||
const floorChanged = currentFloor !== golemancy.lastSummonFloor;
|
||||
const inCombatRoom = currentRoom.roomType !== 'puzzle';
|
||||
|
||||
if (state.currentAction === 'climb' && inCombatRoom && floorChanged && maxGolemSlots > 0) {
|
||||
if (currentAction === 'climb' && inCombatRoom && floorChanged && maxGolemSlots > 0) {
|
||||
// Determine which golems should be summoned
|
||||
const unlockedElementIds = Object.entries(elements)
|
||||
.filter(([, e]) => e.unlocked)
|
||||
@@ -1406,7 +1500,7 @@ export const useGameStore = create<GameStore>()(
|
||||
}
|
||||
|
||||
// Process golem maintenance and attacks each tick
|
||||
if (golemancy.summonedGolems.length > 0 && state.currentAction === 'climb' && inCombatRoom) {
|
||||
if (golemancy.summonedGolems.length > 0 && currentAction === 'climb' && inCombatRoom) {
|
||||
const floorDuration = getGolemFloorDuration(skills);
|
||||
const survivingGolems: typeof golemancy.summonedGolems = [];
|
||||
let anyGolemDismissed = false;
|
||||
@@ -1525,7 +1619,7 @@ export const useGameStore = create<GameStore>()(
|
||||
}
|
||||
|
||||
// Unsummon golems when not climbing or in puzzle room
|
||||
if ((state.currentAction !== 'climb' || !inCombatRoom) && golemancy.summonedGolems.length > 0) {
|
||||
if ((currentAction !== 'climb' || !inCombatRoom) && golemancy.summonedGolems.length > 0) {
|
||||
log = [`🗿 Golems returned to the earth.`, ...log.slice(0, 49)];
|
||||
golemancy = {
|
||||
...golemancy,
|
||||
@@ -1559,31 +1653,10 @@ export const useGameStore = create<GameStore>()(
|
||||
// Apply crafting updates
|
||||
if (craftingUpdates.rawMana !== undefined) rawMana = craftingUpdates.rawMana;
|
||||
if (craftingUpdates.log !== undefined) log = craftingUpdates.log;
|
||||
|
||||
// If crafting slice set currentAction (e.g., auto-transition to meditate), use it
|
||||
if (craftingUpdates.currentAction !== undefined) {
|
||||
set({
|
||||
...craftingUpdates,
|
||||
day,
|
||||
hour,
|
||||
rawMana,
|
||||
meditateTicks,
|
||||
totalManaGathered,
|
||||
currentFloor,
|
||||
floorHP,
|
||||
floorMaxHP,
|
||||
maxFloorReached,
|
||||
signedPacts,
|
||||
currentRoom,
|
||||
incursionStrength,
|
||||
currentStudyTarget,
|
||||
skills,
|
||||
skillProgress,
|
||||
spells,
|
||||
elements,
|
||||
log,
|
||||
castProgress,
|
||||
golemancy,
|
||||
});
|
||||
return;
|
||||
currentAction = craftingUpdates.currentAction;
|
||||
}
|
||||
|
||||
set({
|
||||
@@ -1599,6 +1672,7 @@ export const useGameStore = create<GameStore>()(
|
||||
signedPacts,
|
||||
currentRoom,
|
||||
incursionStrength,
|
||||
currentAction,
|
||||
currentStudyTarget,
|
||||
parallelStudyTarget,
|
||||
skills,
|
||||
@@ -1608,6 +1682,7 @@ export const useGameStore = create<GameStore>()(
|
||||
unlockedEffects,
|
||||
log,
|
||||
castProgress,
|
||||
equipmentSpellStates,
|
||||
golemancy,
|
||||
flowSurgeEndTime,
|
||||
comboHitCount,
|
||||
@@ -1929,6 +2004,24 @@ export const useGameStore = create<GameStore>()(
|
||||
set(newState);
|
||||
},
|
||||
|
||||
// Spire Mode - enter simplified UI for climbing
|
||||
enterSpireMode: () => {
|
||||
set((state) => ({
|
||||
spireMode: true,
|
||||
currentAction: 'climb',
|
||||
log: ['🏔️ Entered Spire Mode! The climb begins...', ...state.log.slice(0, 49)],
|
||||
}));
|
||||
},
|
||||
|
||||
// Exit Spire Mode - return to normal game UI
|
||||
exitSpireMode: () => {
|
||||
set((state) => ({
|
||||
spireMode: false,
|
||||
currentAction: 'meditate',
|
||||
log: ['⬇️ Climbed down from the Spire. Returning to normal view.', ...state.log.slice(0, 49)],
|
||||
}));
|
||||
},
|
||||
|
||||
togglePause: () => {
|
||||
set((state) => ({ paused: !state.paused }));
|
||||
},
|
||||
|
||||
@@ -212,6 +212,9 @@ export interface GameState {
|
||||
|
||||
// Loop insight (earned at end of current loop)
|
||||
loopInsight: number;
|
||||
|
||||
// Spire Mode - simplified UI for climbing
|
||||
spireMode: boolean;
|
||||
}
|
||||
|
||||
// ─── Action Types for Store ─────────────────────────────────────────────
|
||||
|
||||
@@ -221,13 +221,19 @@ export function deductSpellCost(
|
||||
|
||||
// ─── Equipment Spell Helpers ──────────────────────────────────────────────────
|
||||
|
||||
// Return type for active equipment spells with source equipment
|
||||
export interface ActiveEquipmentSpell {
|
||||
spellId: string;
|
||||
equipmentId: string;
|
||||
}
|
||||
|
||||
// Get active spells from equipped equipment
|
||||
export function getActiveEquipmentSpells(
|
||||
equippedInstances: Record<string, string | null>,
|
||||
equipmentInstances: Record<string, EquipmentInstance>
|
||||
): string[] {
|
||||
): ActiveEquipmentSpell[] {
|
||||
const equippedIds = Object.values(equippedInstances).filter((id): id is string => id !== null);
|
||||
const spells: string[] = [];
|
||||
const spells: ActiveEquipmentSpell[] = [];
|
||||
|
||||
for (const id of equippedIds) {
|
||||
const instance = equipmentInstances[id];
|
||||
@@ -236,12 +242,16 @@ export function getActiveEquipmentSpells(
|
||||
for (const ench of instance.enchantments) {
|
||||
const effectDef = ENCHANTMENT_EFFECTS[ench.effectId];
|
||||
if (effectDef?.effect.type === 'spell' && effectDef.effect.spellId) {
|
||||
spells.push(effectDef.effect.spellId);
|
||||
// Check if we already have this spell from this equipment
|
||||
const exists = spells.some(s => s.spellId === effectDef.effect.spellId && s.equipmentId === id);
|
||||
if (!exists) {
|
||||
spells.push({ spellId: effectDef.effect.spellId, equipmentId: id });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...new Set(spells)];
|
||||
return spells;
|
||||
}
|
||||
|
||||
// ─── DPS Calculation ──────────────────────────────────────────────────────────
|
||||
@@ -258,7 +268,7 @@ export function getTotalDPS(
|
||||
const activeSpells = getActiveEquipmentSpells(state.equippedInstances, state.equipmentInstances);
|
||||
|
||||
// Calculate DPS for each active spell
|
||||
for (const spellId of activeSpells) {
|
||||
for (const { spellId } of activeSpells) {
|
||||
const spellDef = SPELLS_DEF[spellId];
|
||||
if (!spellDef) continue;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user