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
|
# Task 2 Progress Tracking
|
||||||
|
|
||||||
**Last Updated**: 2026-04-26 10:35:00
|
**Last Updated**: 2026-04-26 16:00:00
|
||||||
**Current Status**: In Progress
|
**Current Status**: Nearly Complete (11/12 tasks done)
|
||||||
|
|
||||||
## Completed Tasks
|
## Completed Tasks
|
||||||
- [2026-04-25] Task 9: Remove 'Transference' mana from LootTab essence list ✓
|
- [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 4: Equipment System - 2-Handed Weapons ✓
|
||||||
- [2026-04-25] Task 12: StatsTab - Lock Fire/Water/Air/Earth at start ✓
|
- [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 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
|
## In Progress Tasks
|
||||||
None currently
|
None currently
|
||||||
|
|
||||||
## Pending Tasks
|
## Remaining Tasks (1/12)
|
||||||
1. Task 1: ActionButtons Rework [FAILED - sub-agent context too long]
|
1. Task 1: ActionButtons Rework - **BLOCKED** (sub-agent context length error: 2.4M tokens > 262k limit due to conversation history)
|
||||||
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]
|
|
||||||
|
|
||||||
## Blocked Tasks
|
## 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
|
- 65b0f96: Remove Transference from LootTab, delete Ascension skills
|
||||||
- 7c05bea: Update task2 progress
|
- 7c05bea: Update task2 progress
|
||||||
- 5e0bee8: Equipment System - 2-handed weapons, staves block offhand
|
- 5e0bee8: Equipment System - 2-handed weapons, staves block offhand
|
||||||
- 2355be6: StatsTab - Lock Fire/Water/Air/Earth at start
|
- 2355be6: StatsTab - Lock Fire/Water/Air/Earth at start
|
||||||
- f61ed00: FIX: Skill perks multiplier values (Deep Basin + others)
|
- 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).**
|
||||||
|
|||||||
+177
-133
@@ -12,7 +12,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
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, Mountain, ChevronDown } from 'lucide-react';
|
||||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||||
import { DebugName } from '@/lib/game/debug-context';
|
import { DebugName } from '@/lib/game/debug-context';
|
||||||
// Non-tab component imports
|
// Non-tab component imports
|
||||||
@@ -181,7 +181,7 @@ export default function ManaLoopGame() {
|
|||||||
<TimeDisplay
|
<TimeDisplay
|
||||||
day={store.day}
|
day={store.day}
|
||||||
hour={store.hour}
|
hour={store.hour}
|
||||||
isPaused={store.isPaused}
|
isPaused={store.paused}
|
||||||
togglePause={store.togglePause}
|
togglePause={store.togglePause}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -207,16 +207,34 @@ export default function ManaLoopGame() {
|
|||||||
/>
|
/>
|
||||||
</DebugName>
|
</DebugName>
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Climb the Spire Button - only show when not in Spire Mode */}
|
||||||
<DebugName name="ActionButtons">
|
{!store.spireMode && (
|
||||||
<ActionButtons
|
<DebugName name="ClimbSpireButton">
|
||||||
currentAction={store.currentAction}
|
<Button
|
||||||
designProgress={store.designProgress}
|
className="w-full bg-gradient-to-r from-amber-600 to-orange-600 hover:from-amber-700 hover:to-orange-700"
|
||||||
preparationProgress={store.preparationProgress}
|
size="lg"
|
||||||
applicationProgress={store.applicationProgress}
|
onClick={() => store.enterSpireMode()}
|
||||||
setAction={store.setAction}
|
>
|
||||||
/>
|
<Mountain className="w-5 h-5 mr-2" />
|
||||||
</DebugName>
|
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 */}
|
{/* Calendar */}
|
||||||
<DebugName name="CalendarDisplay">
|
<DebugName name="CalendarDisplay">
|
||||||
@@ -230,140 +248,166 @@ export default function ManaLoopGame() {
|
|||||||
{/* Loot and Achievements moved to tabs */}
|
{/* Loot and Achievements moved to tabs */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Panel - Tabs */}
|
{/* Right Panel - Conditional rendering based on Spire Mode */}
|
||||||
<div className="flex-1 min-w-0">
|
{store.spireMode ? (
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
/* Spire Mode - Simplified UI */
|
||||||
<TabsList className="flex flex-wrap gap-1 w-full mb-4 h-auto">
|
<div className="flex-1 min-w-0 space-y-4">
|
||||||
<TabsTrigger value="spire" className="text-xs px-2 py-1">⚔️ Spire</TabsTrigger>
|
<DebugName name="SpireModeUI">
|
||||||
<TabsTrigger value="attunements" className="text-xs px-2 py-1">✨ Attune</TabsTrigger>
|
<div className="flex items-center justify-between mb-4">
|
||||||
<TabsTrigger value="golemancy" className="text-xs px-2 py-1">🗿 Golems</TabsTrigger>
|
<h2 className="text-2xl font-bold game-title text-amber-400">
|
||||||
<TabsTrigger value="skills" className="text-xs px-2 py-1">📚 Skills</TabsTrigger>
|
🏔️ Spire Mode
|
||||||
<TabsTrigger value="spells" className="text-xs px-2 py-1">🔮 Spells</TabsTrigger>
|
</h2>
|
||||||
<TabsTrigger value="equipment" className="text-xs px-2 py-1">🛡️ Gear</TabsTrigger>
|
<Button
|
||||||
<TabsTrigger value="crafting" className="text-xs px-2 py-1">🔧 Craft</TabsTrigger>
|
variant="outline"
|
||||||
<TabsTrigger value="loot" className="text-xs px-2 py-1">💎 Loot</TabsTrigger>
|
className="border-blue-600/50 text-blue-400 hover:bg-blue-900/20"
|
||||||
<TabsTrigger value="achievements" className="text-xs px-2 py-1">🏆 Achieve</TabsTrigger>
|
onClick={() => store.exitSpireMode()}
|
||||||
<TabsTrigger value="lab" className="text-xs px-2 py-1">🔬 Lab</TabsTrigger>
|
>
|
||||||
<TabsTrigger value="stats" className="text-xs px-2 py-1">📊 Stats</TabsTrigger>
|
<ChevronDown className="w-4 h-4 mr-2" />
|
||||||
<TabsTrigger value="debug" className="text-xs px-2 py-1">🔧 Debug</TabsTrigger>
|
Climb Down
|
||||||
<TabsTrigger value="grimoire" className="text-xs px-2 py-1">📖 Grimoire</TabsTrigger>
|
</Button>
|
||||||
</TabsList>
|
</div>
|
||||||
|
|
||||||
<TabsContent value="spire">
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
<DebugName name="SpireTab">
|
<SpireTab store={store} simpleMode={true} />
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
</Suspense>
|
||||||
<SpireTab store={store} />
|
</DebugName>
|
||||||
</Suspense>
|
</div>
|
||||||
</DebugName>
|
) : (
|
||||||
</TabsContent>
|
/* 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="attunements">
|
<TabsContent value="spire">
|
||||||
<DebugName name="AttunementsTab">
|
<DebugName name="SpireTab">
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
<AttunementsTab store={store} />
|
<SpireTab store={store} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</DebugName>
|
</DebugName>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="golemancy">
|
<TabsContent value="attunements">
|
||||||
<DebugName name="GolemancyTab">
|
<DebugName name="AttunementsTab">
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
<GolemancyTab store={store} />
|
<AttunementsTab store={store} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</DebugName>
|
</DebugName>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="skills">
|
<TabsContent value="golemancy">
|
||||||
<DebugName name="SkillsTab">
|
<DebugName name="GolemancyTab">
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
<SkillsTab store={store} />
|
<GolemancyTab store={store} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</DebugName>
|
</DebugName>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="spells">
|
<TabsContent value="skills">
|
||||||
<DebugName name="SpellsTab">
|
<DebugName name="SkillsTab">
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
<SpellsTab store={store} />
|
<SkillsTab store={store} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</DebugName>
|
</DebugName>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="equipment">
|
<TabsContent value="spells">
|
||||||
<DebugName name="EquipmentTab">
|
<DebugName name="SpellsTab">
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
<EquipmentTab store={store} />
|
<SpellsTab store={store} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</DebugName>
|
</DebugName>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="crafting">
|
<TabsContent value="equipment">
|
||||||
<DebugName name="CraftingTab">
|
<DebugName name="EquipmentTab">
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
<CraftingTab store={store} />
|
<EquipmentTab store={store} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</DebugName>
|
</DebugName>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="loot">
|
<TabsContent value="crafting">
|
||||||
<DebugName name="LootTab">
|
<DebugName name="CraftingTab">
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
<LootTab store={store} />
|
<CraftingTab store={store} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</DebugName>
|
</DebugName>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="achievements">
|
<TabsContent value="loot">
|
||||||
<DebugName name="AchievementsTab">
|
<DebugName name="LootTab">
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
<AchievementsTab store={store} />
|
<LootTab store={store} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</DebugName>
|
</DebugName>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="lab">
|
<TabsContent value="achievements">
|
||||||
<DebugName name="LabTab">
|
<DebugName name="AchievementsTab">
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
<LabTab store={store} />
|
<AchievementsTab store={store} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</DebugName>
|
</DebugName>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="stats">
|
<TabsContent value="lab">
|
||||||
<DebugName name="StatsTab">
|
<DebugName name="LabTab">
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
<StatsTab
|
<LabTab store={store} />
|
||||||
store={store}
|
</Suspense>
|
||||||
upgradeEffects={upgradeEffects}
|
</DebugName>
|
||||||
maxMana={maxMana}
|
</TabsContent>
|
||||||
baseRegen={baseRegen}
|
|
||||||
clickMana={clickMana}
|
|
||||||
meditationMultiplier={meditationMultiplier}
|
|
||||||
effectiveRegen={effectiveRegen}
|
|
||||||
incursionStrength={incursionStrength}
|
|
||||||
manaCascadeBonus={manaCascadeBonus}
|
|
||||||
studySpeedMult={studySpeedMult}
|
|
||||||
studyCostMult={studyCostMult}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
</DebugName>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="grimoire">
|
<TabsContent value="stats">
|
||||||
<DebugName name="GrimoireTab">
|
<DebugName name="StatsTab">
|
||||||
{renderGrimoireTab()}
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
</DebugName>
|
<StatsTab
|
||||||
</TabsContent>
|
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="debug">
|
<TabsContent value="grimoire">
|
||||||
<DebugName name="DebugTab">
|
<DebugName name="GrimoireTab">
|
||||||
<Suspense fallback={<TabLoadingFallback />}>
|
{renderGrimoireTab()}
|
||||||
<DebugTab store={store} />
|
</DebugName>
|
||||||
</Suspense>
|
</TabsContent>
|
||||||
</DebugName>
|
|
||||||
</TabsContent>
|
<TabsContent value="debug">
|
||||||
</Tabs>
|
<DebugName name="DebugTab">
|
||||||
</div>
|
<Suspense fallback={<TabLoadingFallback />}>
|
||||||
|
<DebugTab store={store} />
|
||||||
|
</Suspense>
|
||||||
|
</DebugName>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|||||||
@@ -171,3 +171,5 @@ export function AchievementsDisplay({ achievements, gameState }: AchievementsPro
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AchievementsDisplay.displayName = "AchievementsDisplay";
|
||||||
|
|||||||
@@ -1,86 +1,152 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Sparkles, Swords, BookOpen, Target, FlaskConical, Cog, Hammer } from 'lucide-react';
|
||||||
import { Sparkles, Swords, BookOpen, Target, FlaskConical } from 'lucide-react';
|
|
||||||
import type { GameAction } from '@/lib/game/types';
|
import type { GameAction } from '@/lib/game/types';
|
||||||
|
|
||||||
interface ActionButtonsProps {
|
interface ActionButtonsProps {
|
||||||
currentAction: GameAction;
|
currentAction: GameAction;
|
||||||
|
currentStudyTarget: { type: 'skill' | 'spell'; id: string; progress: number; required: number } | null;
|
||||||
designProgress: { progress: number; required: number } | null;
|
designProgress: { progress: number; required: number } | null;
|
||||||
|
designProgress2: { progress: number; required: number } | null;
|
||||||
preparationProgress: { progress: number; required: number } | null;
|
preparationProgress: { progress: number; required: number } | null;
|
||||||
applicationProgress: { 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({
|
export function ActionButtons({
|
||||||
currentAction,
|
currentAction,
|
||||||
|
currentStudyTarget,
|
||||||
designProgress,
|
designProgress,
|
||||||
|
designProgress2,
|
||||||
preparationProgress,
|
preparationProgress,
|
||||||
applicationProgress,
|
applicationProgress,
|
||||||
setAction,
|
equipmentCraftingProgress,
|
||||||
}: ActionButtonsProps) {
|
}: ActionButtonsProps) {
|
||||||
const actions: { id: GameAction; label: string; icon: typeof Swords }[] = [
|
const config = ACTION_CONFIG[currentAction] || { label: currentAction, icon: Sparkles, color: 'text-gray-400' };
|
||||||
{ id: 'meditate', label: 'Meditate', icon: Sparkles },
|
const Icon = config.icon;
|
||||||
{ id: 'climb', label: 'Climb', icon: Swords },
|
|
||||||
{ id: 'study', label: 'Study', icon: BookOpen },
|
|
||||||
];
|
|
||||||
|
|
||||||
const hasDesignProgress = designProgress !== null;
|
// Calculate additional info for specific actions
|
||||||
const hasPrepProgress = preparationProgress !== null;
|
const getActionDetails = () => {
|
||||||
const hasAppProgress = applicationProgress !== null;
|
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 (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="bg-gray-800/50 rounded-lg p-3 border border-gray-700">
|
||||||
{actions.map(({ id, label, icon: Icon }) => (
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Icon className={`w-4 h-4 ${config.color}`} />
|
||||||
key={id}
|
<span className="text-sm font-medium text-gray-200">Current Activity</span>
|
||||||
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>
|
</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>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CalendarDisplay.displayName = "CalendarDisplay";
|
||||||
|
CalendarDisplay.displayName = "CalendarDisplay";
|
||||||
|
|||||||
@@ -159,3 +159,5 @@ export function CraftingProgress({
|
|||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CraftingProgress.displayName = "CraftingProgress";
|
||||||
|
|||||||
@@ -422,5 +422,7 @@ export function useGameContext() {
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GameProvider.displayName = "GameProvider";
|
||||||
|
|
||||||
// Re-export useGameLoop for convenience
|
// Re-export useGameLoop for convenience
|
||||||
export { useGameLoop };
|
export { useGameLoop };
|
||||||
|
|||||||
@@ -169,3 +169,5 @@ export function LabTab() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LabTab.displayName = "LabTab";
|
||||||
|
|||||||
@@ -459,3 +459,5 @@ export function LootInventoryDisplay({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LootInventoryDisplay.displayName = "LootInventoryDisplay";
|
||||||
|
|||||||
@@ -121,3 +121,5 @@ export function ManaDisplay({
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ManaDisplay.displayName = "ManaDisplay";
|
||||||
|
|||||||
@@ -267,7 +267,10 @@ export function SkillsTab() {
|
|||||||
const baseCost = def.base * (level + 1) * currentTier;
|
const baseCost = def.base * (level + 1) * currentTier;
|
||||||
const cost = Math.floor(baseCost * studyCostMult);
|
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 milestoneInfo = hasMilestoneUpgrade(tieredSkillId, level);
|
||||||
const nextTierSkill = getNextTierSkill(tieredSkillId);
|
const nextTierSkill = getNextTierSkill(tieredSkillId);
|
||||||
@@ -369,15 +372,26 @@ export function SkillsTab() {
|
|||||||
<Badge className="bg-green-900/50 text-green-300">Maxed</Badge>
|
<Badge className="bg-green-900/50 text-green-300">Maxed</Badge>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<Button
|
<TooltipProvider>
|
||||||
size="sm"
|
<Tooltip>
|
||||||
variant={canStudy ? 'default' : 'outline'}
|
<TooltipTrigger asChild>
|
||||||
disabled={!canStudy}
|
<Button
|
||||||
className={canStudy ? 'bg-purple-600 hover:bg-purple-700' : 'opacity-50'}
|
size="sm"
|
||||||
onClick={() => store.startStudyingSkill(tieredSkillId)}
|
variant={canStudy ? 'default' : 'outline'}
|
||||||
>
|
disabled={!canStudy}
|
||||||
Study ({fmt(cost)})
|
className={canStudy ? 'bg-purple-600 hover:bg-purple-700' : 'opacity-50'}
|
||||||
</Button>
|
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 */}
|
{/* Parallel Study button */}
|
||||||
{hasParallelStudy &&
|
{hasParallelStudy &&
|
||||||
store.currentStudyTarget &&
|
store.currentStudyTarget &&
|
||||||
@@ -416,3 +430,5 @@ export function SkillsTab() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SkillsTab.displayName = "SkillsTab";
|
||||||
|
|||||||
@@ -164,3 +164,5 @@ export function SpellsTab() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpellsTab.displayName = "SpellsTab";
|
||||||
|
|||||||
@@ -318,3 +318,5 @@ export function SpireTab() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpireTab.displayName = "SpireTab";
|
||||||
|
|||||||
@@ -580,3 +580,5 @@ export function StatsTab() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatsTab.displayName = "StatsTab";
|
||||||
|
|||||||
@@ -55,3 +55,5 @@ export function StudyProgress({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StudyProgress.displayName = "StudyProgress";
|
||||||
|
|||||||
@@ -49,3 +49,5 @@ export function TimeDisplay({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimeDisplay.displayName = "TimeDisplay";
|
||||||
|
|||||||
@@ -113,3 +113,5 @@ export function UpgradeDialog({
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpgradeDialog.displayName = "UpgradeDialog";
|
||||||
|
|||||||
@@ -209,3 +209,5 @@ export function EnchantmentApplier({
|
|||||||
</div>
|
</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 { EQUIPMENT_TYPES } from '@/lib/game/data/equipment';
|
||||||
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from '@/lib/game/data/enchantment-effects';
|
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from '@/lib/game/data/enchantment-effects';
|
||||||
import { LOOT_DROPS, RARITY_COLORS } from '@/lib/game/data/loot-drops';
|
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 type { EquipmentInstance, EnchantmentDesign, DesignEffect, LootInventory, EquipmentCraftingProgress } from '@/lib/game/types';
|
||||||
import { fmt, type GameStore } from '@/lib/game/store';
|
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
|
// Render design stage
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
@@ -162,7 +191,7 @@ export function EnchantmentDesigner({
|
|||||||
) : (
|
) : (
|
||||||
<ScrollArea className="h-64">
|
<ScrollArea className="h-64">
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{Object.values(EQUIPMENT_TYPES).map(type => (
|
{ownedEquipmentTypes.map(type => (
|
||||||
<div
|
<div
|
||||||
key={type.id}
|
key={type.id}
|
||||||
className={`p-2 rounded border cursor-pointer transition-all ${
|
className={`p-2 rounded border cursor-pointer transition-all ${
|
||||||
@@ -177,6 +206,11 @@ export function EnchantmentDesigner({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</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>
|
</ScrollArea>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -354,3 +388,5 @@ export function EnchantmentDesigner({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnchantmentDesigner.displayName = "EnchantmentDesigner";
|
||||||
|
|||||||
@@ -202,3 +202,5 @@ export function EnchantmentPreparer({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnchantmentPreparer.displayName = "EnchantmentPreparer";
|
||||||
|
|||||||
@@ -198,3 +198,5 @@ export function EquipmentCrafter({ store }: EquipmentCrafterProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EquipmentCrafter.displayName = "EquipmentCrafter";
|
||||||
|
|||||||
@@ -85,3 +85,5 @@ export function AttunementDebug({ store }: AttunementDebugProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AttunementDebug.displayName = "AttunementDebug";
|
||||||
|
|||||||
@@ -86,3 +86,5 @@ export function ElementDebug({ store }: ElementDebugProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ElementDebug.displayName = "ElementDebug";
|
||||||
|
|||||||
@@ -269,3 +269,5 @@ export function GameStateDebug({ store }: GameStateDebugProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GameStateDebug.displayName = "GameStateDebug";
|
||||||
|
|||||||
@@ -25,3 +25,5 @@ export function GolemDebug({ store }: GolemDebugProps) {
|
|||||||
</Card>
|
</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>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SkillDebug.displayName = "SkillDebug";
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ export { SkillDebug } from './SkillDebug';
|
|||||||
export { ElementDebug } from './ElementDebug';
|
export { ElementDebug } from './ElementDebug';
|
||||||
export { AttunementDebug } from './AttunementDebug';
|
export { AttunementDebug } from './AttunementDebug';
|
||||||
export { GolemDebug } from './GolemDebug';
|
export { GolemDebug } from './GolemDebug';
|
||||||
|
export { PactDebug } from './PactDebug';
|
||||||
|
|||||||
@@ -204,3 +204,5 @@ export function MemorySlotPicker({ onConfirm }: MemorySlotPickerProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MemorySlotPicker.displayName = "MemorySlotPicker";
|
||||||
|
|||||||
@@ -58,3 +58,5 @@ export function StudyProgress({ target, showCancel = true, speedLabel }: StudyPr
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StudyProgress.displayName = "StudyProgress";
|
||||||
|
|||||||
@@ -124,3 +124,5 @@ export function UpgradeDialog({ skillId, milestone, onClose }: UpgradeDialogProp
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpgradeDialog.displayName = "UpgradeDialog";
|
||||||
|
|||||||
@@ -62,3 +62,5 @@ export function CombatStatsSection({ store }: CombatStatsSectionProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CombatStatsSection.displayName = "CombatStatsSection";
|
||||||
|
|||||||
@@ -255,3 +255,5 @@ export function ManaStatsSection({
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ManaStatsSection.displayName = "ManaStatsSection";
|
||||||
|
|||||||
@@ -53,3 +53,5 @@ export function StudyStatsSection({ store, studySpeedMult, studyCostMult }: Stud
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StudyStatsSection.displayName = "StudyStatsSection";
|
||||||
|
|||||||
@@ -80,3 +80,5 @@ export function UpgradeEffectsSection({ store }: UpgradeEffectsSectionProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpgradeEffectsSection.displayName = "UpgradeEffectsSection";
|
||||||
|
|||||||
@@ -41,3 +41,5 @@ export function AchievementsTab({ store }: AchievementsTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AchievementsTab.displayName = "AchievementsTab";
|
||||||
|
|||||||
@@ -265,3 +265,5 @@ export function AttunementsTab({ store }: AttunementsTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AttunementsTab.displayName = "AttunementsTab";
|
||||||
|
|||||||
@@ -160,3 +160,5 @@ export function CraftingTab({ store }: CraftingTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CraftingTab.displayName = "CraftingTab";
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import {
|
|||||||
SkillDebug,
|
SkillDebug,
|
||||||
ElementDebug,
|
ElementDebug,
|
||||||
AttunementDebug,
|
AttunementDebug,
|
||||||
GolemDebug
|
GolemDebug,
|
||||||
|
PactDebug
|
||||||
} from '@/components/game/debug';
|
} from '@/components/game/debug';
|
||||||
|
|
||||||
interface DebugTabProps {
|
interface DebugTabProps {
|
||||||
@@ -25,6 +26,9 @@ export function DebugTab({ store }: DebugTabProps) {
|
|||||||
|
|
||||||
<SkillDebug store={store} />
|
<SkillDebug store={store} />
|
||||||
<GolemDebug store={store} />
|
<GolemDebug store={store} />
|
||||||
|
<PactDebug />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DebugTab.displayName = "DebugTab";
|
||||||
|
|||||||
@@ -431,3 +431,5 @@ export function EquipmentTab({ store }: EquipmentTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EquipmentTab.displayName = "EquipmentTab";
|
||||||
|
|||||||
@@ -336,3 +336,5 @@ export function GolemancyTab({ store }: GolemancyTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GolemancyTab.displayName = "GolemancyTab";
|
||||||
|
|||||||
@@ -114,3 +114,5 @@ export function LabTab({ store }: LabTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LabTab.displayName = "LabTab";
|
||||||
|
|||||||
@@ -44,3 +44,5 @@ export function LootTab({ store }: LootTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LootTab.displayName = "LootTab";
|
||||||
|
|||||||
@@ -367,3 +367,5 @@ export function SkillsTab({ store }: SkillsTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SkillsTab.displayName = "SkillsTab";
|
||||||
|
|||||||
@@ -178,3 +178,5 @@ export function SpellsTab({ store }: SpellsTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpellsTab.displayName = "SpellsTab";
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ import { getUnifiedEffects } from '@/lib/game/effects';
|
|||||||
|
|
||||||
interface SpireTabProps {
|
interface SpireTabProps {
|
||||||
store: GameStore;
|
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 floorElem = getFloorElement(store.currentFloor);
|
||||||
const floorElemDef = ELEMENTS[floorElem];
|
const floorElemDef = ELEMENTS[floorElem];
|
||||||
const isGuardianFloor = !!GUARDIANS[store.currentFloor];
|
const isGuardianFloor = !!GUARDIANS[store.currentFloor];
|
||||||
@@ -48,7 +49,7 @@ export function SpireTab({ store }: SpireTabProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<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 */}
|
{/* Current Floor Card */}
|
||||||
<Card className="bg-gray-900/80 border-gray-700">
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
@@ -92,35 +93,39 @@ export function SpireTab({ store }: SpireTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator className="bg-gray-700" />
|
{!simpleMode && (
|
||||||
|
<>
|
||||||
|
<Separator className="bg-gray-700" />
|
||||||
|
|
||||||
{/* Floor Navigation - Direction indicator only */}
|
{/* Floor Navigation - Direction indicator only */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-xs text-gray-400">Direction</span>
|
<span className="text-xs text-gray-400">Direction</span>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<Badge variant={climbDirection === 'up' ? 'default' : 'outline'}
|
<Badge variant={climbDirection === 'up' ? 'default' : 'outline'}
|
||||||
className={climbDirection === 'up' ? 'bg-green-600' : ''}>
|
className={climbDirection === 'up' ? 'bg-green-600' : ''}>
|
||||||
<ChevronUp className="w-3 h-3 mr-1" />
|
<ChevronUp className="w-3 h-3 mr-1" />
|
||||||
Up
|
Up
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant={climbDirection === 'down' ? 'default' : 'outline'}
|
<Badge variant={climbDirection === 'down' ? 'default' : 'outline'}
|
||||||
className={climbDirection === 'down' ? 'bg-blue-600' : ''}>
|
className={climbDirection === 'down' ? 'bg-blue-600' : ''}>
|
||||||
<ChevronDown className="w-3 h-3 mr-1" />
|
<ChevronDown className="w-3 h-3 mr-1" />
|
||||||
Down
|
Down
|
||||||
</Badge>
|
</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>
|
||||||
</div>
|
|
||||||
|
|
||||||
{isFloorCleared && (
|
<Separator className="bg-gray-700" />
|
||||||
<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" />
|
|
||||||
|
|
||||||
<div className="text-sm text-gray-400">
|
<div className="text-sm text-gray-400">
|
||||||
Best: Floor <strong className="text-gray-200">{store.maxFloorReached}</strong> •
|
Best: Floor <strong className="text-gray-200">{store.maxFloorReached}</strong> •
|
||||||
@@ -205,8 +210,8 @@ export function SpireTab({ store }: SpireTabProps) {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Summoned Golems Card */}
|
{/* Summoned Golems Card - Always show in simple mode, conditional in normal mode */}
|
||||||
{store.golemancy.summonedGolems.length > 0 && (
|
{(simpleMode || store.golemancy.summonedGolems.length > 0) && (
|
||||||
<Card className="bg-gray-900/80 border-amber-600/50">
|
<Card className="bg-gray-900/80 border-amber-600/50">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="text-amber-400 game-panel-title text-xs flex items-center gap-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>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Current Study (if any) */}
|
{/* Current Study (if any) - Only show in normal mode */}
|
||||||
{store.currentStudyTarget && (
|
{!simpleMode && 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">
|
||||||
<CardContent className="pt-4 space-y-3">
|
<CardContent className="pt-4 space-y-3">
|
||||||
<StudyProgress
|
<StudyProgress
|
||||||
@@ -299,8 +304,8 @@ export function SpireTab({ store }: SpireTabProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Crafting Progress (if any) */}
|
{/* Crafting Progress (if any) - Only show in normal mode */}
|
||||||
{(store.designProgress || store.preparationProgress || store.applicationProgress) && (
|
{!simpleMode && (store.designProgress || store.preparationProgress || store.applicationProgress) && (
|
||||||
<Card className="bg-gray-900/80 border-cyan-600/50 lg:col-span-2">
|
<Card className="bg-gray-900/80 border-cyan-600/50 lg:col-span-2">
|
||||||
<CardContent className="pt-4">
|
<CardContent className="pt-4">
|
||||||
<CraftingProgress
|
<CraftingProgress
|
||||||
@@ -319,27 +324,31 @@ export function SpireTab({ store }: SpireTabProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Activity Log */}
|
{/* Activity Log - Only show in normal mode */}
|
||||||
<Card className="bg-gray-900/80 border-gray-700 lg:col-span-2">
|
{!simpleMode && (
|
||||||
<CardHeader className="pb-2">
|
<Card className="bg-gray-900/80 border-gray-700 lg:col-span-2">
|
||||||
<CardTitle className="text-amber-400 game-panel-title text-xs">Activity Log</CardTitle>
|
<CardHeader className="pb-2">
|
||||||
</CardHeader>
|
<CardTitle className="text-amber-400 game-panel-title text-xs">Activity Log</CardTitle>
|
||||||
<CardContent>
|
</CardHeader>
|
||||||
<ScrollArea className="h-32">
|
<CardContent>
|
||||||
<div className="space-y-1">
|
<ScrollArea className="h-32">
|
||||||
{store.log.slice(0, 20).map((entry, i) => (
|
<div className="space-y-1">
|
||||||
<div
|
{store.log.slice(0, 20).map((entry, i) => (
|
||||||
key={i}
|
<div
|
||||||
className={`text-sm ${i === 0 ? 'text-gray-200' : 'text-gray-500'} italic`}
|
key={i}
|
||||||
>
|
className={`text-sm ${i === 0 ? 'text-gray-200' : 'text-gray-500'} italic`}
|
||||||
{entry}
|
>
|
||||||
</div>
|
{entry}
|
||||||
))}
|
</div>
|
||||||
</div>
|
))}
|
||||||
</ScrollArea>
|
</div>
|
||||||
</CardContent>
|
</ScrollArea>
|
||||||
</Card>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpireTab.displayName = "SpireTab";
|
||||||
|
|||||||
@@ -247,3 +247,5 @@ export function StatsTab({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatsTab.displayName = "StatsTab";
|
||||||
|
|||||||
@@ -71,3 +71,5 @@ export function StudyProgress({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StudyProgress.displayName = "StudyProgress";
|
||||||
|
|||||||
@@ -113,3 +113,5 @@ export function UpgradeDialog({
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpgradeDialog.displayName = "UpgradeDialog";
|
||||||
|
|||||||
@@ -422,6 +422,9 @@ export function createCraftingSlice(
|
|||||||
const instance = state.equipmentInstances[equipmentInstanceId];
|
const instance = state.equipmentInstances[equipmentInstanceId];
|
||||||
if (!instance) return false;
|
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 prepTime = calculatePrepTime(instance.totalCapacity);
|
||||||
const manaCost = calculatePrepManaCost(instance.totalCapacity);
|
const manaCost = calculatePrepManaCost(instance.totalCapacity);
|
||||||
|
|
||||||
|
|||||||
@@ -65,3 +65,5 @@ export function DebugName({ name, children }: DebugNameProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DebugName.displayName = "DebugName";
|
||||||
|
|||||||
+127
-34
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
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 {
|
import {
|
||||||
ELEMENTS,
|
ELEMENTS,
|
||||||
GUARDIANS,
|
GUARDIANS,
|
||||||
@@ -45,6 +45,7 @@ import {
|
|||||||
getSpellsFromEquipment,
|
getSpellsFromEquipment,
|
||||||
type CraftingActions
|
type CraftingActions
|
||||||
} from './crafting-slice';
|
} from './crafting-slice';
|
||||||
|
import { getActiveEquipmentSpells, type ActiveEquipmentSpell } from './utils/combat-utils';
|
||||||
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, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
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)
|
// Mana Well Effects (Phase 4)
|
||||||
manaHeartBonus: manaHeartBonus, // Cumulative +10% max mana per loop from MANA_HEART
|
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;
|
getMeditationMultiplier: () => number;
|
||||||
canCastSpell: (spellId: string) => boolean;
|
canCastSpell: (spellId: string) => boolean;
|
||||||
getSkillUpgradeChoices: (skillId: string, milestone: 5 | 10) => { available: SkillUpgradeChoice[]; selected: string[] };
|
getSkillUpgradeChoices: (skillId: string, milestone: 5 | 10) => { available: SkillUpgradeChoice[]; selected: string[] };
|
||||||
|
|
||||||
|
// Spire Mode actions
|
||||||
|
enterSpireMode: () => void;
|
||||||
|
exitSpireMode: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGameStore = create<GameStore>()(
|
export const useGameStore = create<GameStore>()(
|
||||||
@@ -834,6 +842,9 @@ export const useGameStore = create<GameStore>()(
|
|||||||
// Compute unified effects (includes skill upgrades AND equipment enchantments)
|
// Compute unified effects (includes skill upgrades AND equipment enchantments)
|
||||||
const effects = getUnifiedEffects(state);
|
const effects = getUnifiedEffects(state);
|
||||||
|
|
||||||
|
// Track current action for potential auto-transitions
|
||||||
|
let currentAction = state.currentAction;
|
||||||
|
|
||||||
const maxMana = computeMaxMana(state, effects);
|
const maxMana = computeMaxMana(state, effects);
|
||||||
const baseRegen = computeRegen(state, effects);
|
const baseRegen = computeRegen(state, effects);
|
||||||
|
|
||||||
@@ -878,7 +889,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
let meditateTicks = state.meditateTicks;
|
let meditateTicks = state.meditateTicks;
|
||||||
let meditationMultiplier = 1;
|
let meditationMultiplier = 1;
|
||||||
|
|
||||||
if (state.currentAction === 'meditate') {
|
if (currentAction === 'meditate') {
|
||||||
meditateTicks++;
|
meditateTicks++;
|
||||||
meditationMultiplier = getMeditationBonus(meditateTicks, state.skills);
|
meditationMultiplier = getMeditationBonus(meditateTicks, state.skills);
|
||||||
|
|
||||||
@@ -973,7 +984,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
let unlockedEffects = state.unlockedEffects;
|
let unlockedEffects = state.unlockedEffects;
|
||||||
let consecutiveStudyHours = state.consecutiveStudyHours;
|
let consecutiveStudyHours = state.consecutiveStudyHours;
|
||||||
|
|
||||||
if (state.currentAction === 'study' && currentStudyTarget) {
|
if (currentAction === 'study' && currentStudyTarget) {
|
||||||
// Calculate base study speed
|
// Calculate base study speed
|
||||||
let studySpeedMult = getStudySpeedMultiplier(skills);
|
let studySpeedMult = getStudySpeedMultiplier(skills);
|
||||||
|
|
||||||
@@ -1076,11 +1087,13 @@ export const useGameStore = create<GameStore>()(
|
|||||||
log = [`📖 ${SPELLS_DEF[spellId]?.name} learned!`, ...log.slice(0, 49)];
|
log = [`📖 ${SPELLS_DEF[spellId]?.name} learned!`, ...log.slice(0, 49)];
|
||||||
}
|
}
|
||||||
currentStudyTarget = null;
|
currentStudyTarget = null;
|
||||||
|
// Auto-transition to meditate when study completes
|
||||||
|
currentAction = 'meditate';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Parallel Study processing (PARALLEL_STUDY special effect)
|
// Parallel Study processing (PARALLEL_STUDY special effect)
|
||||||
let parallelStudyTarget = state.parallelStudyTarget;
|
let parallelStudyTarget = state.parallelStudyTarget;
|
||||||
if (parallelStudyTarget && state.currentAction === 'study') {
|
if (parallelStudyTarget && currentAction === 'study') {
|
||||||
// Parallel study progresses at 50% speed
|
// Parallel study progresses at 50% speed
|
||||||
const parallelProgressGain = HOURS_PER_TICK * 0.5;
|
const parallelProgressGain = HOURS_PER_TICK * 0.5;
|
||||||
parallelStudyTarget = {
|
parallelStudyTarget = {
|
||||||
@@ -1101,7 +1114,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert action - auto convert mana
|
// Convert action - auto convert mana
|
||||||
if (state.currentAction === 'convert') {
|
if (currentAction === 'convert') {
|
||||||
const unlockedElements = Object.entries(elements)
|
const unlockedElements = Object.entries(elements)
|
||||||
.filter(([, e]) => e.unlocked && e.current < e.max);
|
.filter(([, e]) => e.unlocked && e.current < e.max);
|
||||||
|
|
||||||
@@ -1130,7 +1143,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
const floorElement = getFloorElement(currentFloor);
|
const floorElement = getFloorElement(currentFloor);
|
||||||
|
|
||||||
// Handle puzzle rooms separately
|
// Handle puzzle rooms separately
|
||||||
if (state.currentAction === 'climb' && currentRoom.roomType === 'puzzle') {
|
if (currentAction === 'climb' && currentRoom.roomType === 'puzzle') {
|
||||||
const progressSpeed = getPuzzleProgressSpeed(
|
const progressSpeed = getPuzzleProgressSpeed(
|
||||||
currentRoom.puzzleId || '',
|
currentRoom.puzzleId || '',
|
||||||
state.attunements
|
state.attunements
|
||||||
@@ -1154,7 +1167,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
maxFloorReached = Math.max(maxFloorReached, currentFloor);
|
maxFloorReached = Math.max(maxFloorReached, currentFloor);
|
||||||
castProgress = 0;
|
castProgress = 0;
|
||||||
}
|
}
|
||||||
} else if (state.currentAction === 'climb') {
|
} else if (currentAction === 'climb') {
|
||||||
const spellId = state.activeSpell;
|
const spellId = state.activeSpell;
|
||||||
const spellDef = SPELLS_DEF[spellId];
|
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 ─────────────────────────────────────────────────
|
// ─── Golemancy Processing ─────────────────────────────────────────────────
|
||||||
let golemancy = state.golemancy;
|
let golemancy = state.golemancy;
|
||||||
const fabricatorLevel = state.attunements.fabricator?.level || 0;
|
const fabricatorLevel = state.attunements.fabricator?.level || 0;
|
||||||
@@ -1358,7 +1452,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
const floorChanged = currentFloor !== golemancy.lastSummonFloor;
|
const floorChanged = currentFloor !== golemancy.lastSummonFloor;
|
||||||
const inCombatRoom = currentRoom.roomType !== 'puzzle';
|
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
|
// Determine which golems should be summoned
|
||||||
const unlockedElementIds = Object.entries(elements)
|
const unlockedElementIds = Object.entries(elements)
|
||||||
.filter(([, e]) => e.unlocked)
|
.filter(([, e]) => e.unlocked)
|
||||||
@@ -1406,7 +1500,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process golem maintenance and attacks each tick
|
// 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 floorDuration = getGolemFloorDuration(skills);
|
||||||
const survivingGolems: typeof golemancy.summonedGolems = [];
|
const survivingGolems: typeof golemancy.summonedGolems = [];
|
||||||
let anyGolemDismissed = false;
|
let anyGolemDismissed = false;
|
||||||
@@ -1525,7 +1619,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unsummon golems when not climbing or in puzzle room
|
// 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)];
|
log = [`🗿 Golems returned to the earth.`, ...log.slice(0, 49)];
|
||||||
golemancy = {
|
golemancy = {
|
||||||
...golemancy,
|
...golemancy,
|
||||||
@@ -1559,31 +1653,10 @@ export const useGameStore = create<GameStore>()(
|
|||||||
// Apply crafting updates
|
// Apply crafting updates
|
||||||
if (craftingUpdates.rawMana !== undefined) rawMana = craftingUpdates.rawMana;
|
if (craftingUpdates.rawMana !== undefined) rawMana = craftingUpdates.rawMana;
|
||||||
if (craftingUpdates.log !== undefined) log = craftingUpdates.log;
|
if (craftingUpdates.log !== undefined) log = craftingUpdates.log;
|
||||||
|
|
||||||
|
// If crafting slice set currentAction (e.g., auto-transition to meditate), use it
|
||||||
if (craftingUpdates.currentAction !== undefined) {
|
if (craftingUpdates.currentAction !== undefined) {
|
||||||
set({
|
currentAction = craftingUpdates.currentAction;
|
||||||
...craftingUpdates,
|
|
||||||
day,
|
|
||||||
hour,
|
|
||||||
rawMana,
|
|
||||||
meditateTicks,
|
|
||||||
totalManaGathered,
|
|
||||||
currentFloor,
|
|
||||||
floorHP,
|
|
||||||
floorMaxHP,
|
|
||||||
maxFloorReached,
|
|
||||||
signedPacts,
|
|
||||||
currentRoom,
|
|
||||||
incursionStrength,
|
|
||||||
currentStudyTarget,
|
|
||||||
skills,
|
|
||||||
skillProgress,
|
|
||||||
spells,
|
|
||||||
elements,
|
|
||||||
log,
|
|
||||||
castProgress,
|
|
||||||
golemancy,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set({
|
set({
|
||||||
@@ -1599,6 +1672,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
signedPacts,
|
signedPacts,
|
||||||
currentRoom,
|
currentRoom,
|
||||||
incursionStrength,
|
incursionStrength,
|
||||||
|
currentAction,
|
||||||
currentStudyTarget,
|
currentStudyTarget,
|
||||||
parallelStudyTarget,
|
parallelStudyTarget,
|
||||||
skills,
|
skills,
|
||||||
@@ -1608,6 +1682,7 @@ export const useGameStore = create<GameStore>()(
|
|||||||
unlockedEffects,
|
unlockedEffects,
|
||||||
log,
|
log,
|
||||||
castProgress,
|
castProgress,
|
||||||
|
equipmentSpellStates,
|
||||||
golemancy,
|
golemancy,
|
||||||
flowSurgeEndTime,
|
flowSurgeEndTime,
|
||||||
comboHitCount,
|
comboHitCount,
|
||||||
@@ -1929,6 +2004,24 @@ export const useGameStore = create<GameStore>()(
|
|||||||
set(newState);
|
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: () => {
|
togglePause: () => {
|
||||||
set((state) => ({ paused: !state.paused }));
|
set((state) => ({ paused: !state.paused }));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -212,6 +212,9 @@ export interface GameState {
|
|||||||
|
|
||||||
// Loop insight (earned at end of current loop)
|
// Loop insight (earned at end of current loop)
|
||||||
loopInsight: number;
|
loopInsight: number;
|
||||||
|
|
||||||
|
// Spire Mode - simplified UI for climbing
|
||||||
|
spireMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Action Types for Store ─────────────────────────────────────────────
|
// ─── Action Types for Store ─────────────────────────────────────────────
|
||||||
|
|||||||
@@ -221,13 +221,19 @@ export function deductSpellCost(
|
|||||||
|
|
||||||
// ─── Equipment Spell Helpers ──────────────────────────────────────────────────
|
// ─── 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
|
// Get active spells from equipped equipment
|
||||||
export function getActiveEquipmentSpells(
|
export function getActiveEquipmentSpells(
|
||||||
equippedInstances: Record<string, string | null>,
|
equippedInstances: Record<string, string | null>,
|
||||||
equipmentInstances: Record<string, EquipmentInstance>
|
equipmentInstances: Record<string, EquipmentInstance>
|
||||||
): string[] {
|
): ActiveEquipmentSpell[] {
|
||||||
const equippedIds = Object.values(equippedInstances).filter((id): id is string => id !== null);
|
const equippedIds = Object.values(equippedInstances).filter((id): id is string => id !== null);
|
||||||
const spells: string[] = [];
|
const spells: ActiveEquipmentSpell[] = [];
|
||||||
|
|
||||||
for (const id of equippedIds) {
|
for (const id of equippedIds) {
|
||||||
const instance = equipmentInstances[id];
|
const instance = equipmentInstances[id];
|
||||||
@@ -236,12 +242,16 @@ export function getActiveEquipmentSpells(
|
|||||||
for (const ench of instance.enchantments) {
|
for (const ench of instance.enchantments) {
|
||||||
const effectDef = ENCHANTMENT_EFFECTS[ench.effectId];
|
const effectDef = ENCHANTMENT_EFFECTS[ench.effectId];
|
||||||
if (effectDef?.effect.type === 'spell' && effectDef.effect.spellId) {
|
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 ──────────────────────────────────────────────────────────
|
// ─── DPS Calculation ──────────────────────────────────────────────────────────
|
||||||
@@ -258,7 +268,7 @@ export function getTotalDPS(
|
|||||||
const activeSpells = getActiveEquipmentSpells(state.equippedInstances, state.equipmentInstances);
|
const activeSpells = getActiveEquipmentSpells(state.equippedInstances, state.equipmentInstances);
|
||||||
|
|
||||||
// Calculate DPS for each active spell
|
// Calculate DPS for each active spell
|
||||||
for (const spellId of activeSpells) {
|
for (const { spellId } of activeSpells) {
|
||||||
const spellDef = SPELLS_DEF[spellId];
|
const spellDef = SPELLS_DEF[spellId];
|
||||||
if (!spellDef) continue;
|
if (!spellDef) continue;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user