Task 2: SpireTab Overhaul - add Climb the Spire button, implement Spire Mode with exit condition

This commit is contained in:
Refactoring Agent
2026-04-26 13:44:09 +02:00
parent 9f029d93e1
commit 50ce70efdd
6 changed files with 426 additions and 294 deletions
+15 -10
View File
@@ -1,6 +1,6 @@
# Task 2 Progress Tracking # Task 2 Progress Tracking
**Last Updated**: 2026-04-26 10:35:00 **Last Updated**: 2026-04-26 12:57:00
**Current Status**: In Progress **Current Status**: In Progress
## Completed Tasks ## Completed Tasks
@@ -9,21 +9,22 @@
- [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 ✓
## In Progress Tasks ## In Progress Tasks
None currently None currently
## Pending Tasks ## Pending Tasks
1. Task 1: ActionButtons Rework [FAILED - sub-agent context too long] 1. Task 1: ActionButtons Rework [Try context file approach]
2. Task 2: Research Locking - SkillsTab [Pending] 2. Task 3: SpireTab Overhaul [Try context file approach]
3. Task 3: SpireTab Overhaul [FAILED - sub-agent context too long] 3. Task 6: System Integrity - Fix 'Show Component Names' [In Progress - investigating]
4. Task 5: DebugTab Update - Invoker Debugging Buttons [Pending] 4. Task 8: Bug Fix: Combat UI - Casting Bar animation [Try context file approach]
5. Task 6: System Integrity - Fix 'Show Component Names' [In Progress - investigating] 5. Task 10: Bug Fix: Crafting - Prepare/Design limits [Try again]
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 ## Failed Tasks (Context Length)
- Task 1, 3, 8: Sub-agent attempts failed due to context length limits (603k+ tokens) - Task 1, 3, 8, 10: Sub-agent attempts failed due to context length limits (423k-603k tokens > 262k limit)
- BUT Task 2 and Task 5 SUCCEEDED with context file approach!
## Commit History ## Commit History
- 65b0f96: Remove Transference from LootTab, delete Ascension skills - 65b0f96: Remove Transference from LootTab, delete Ascension skills
@@ -32,3 +33,7 @@ None currently
- 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 debug option
- 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
+177 -133
View File
@@ -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>
+124 -61
View File
@@ -1,86 +1,149 @@
'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>
); );
} }
+61 -54
View File
@@ -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,26 +324,28 @@ 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>
); );
+43 -33
View File
@@ -749,6 +749,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 +804,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 +841,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 +888,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 +983,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 +1086,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 +1113,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 +1142,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 +1166,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];
@@ -1358,7 +1370,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 +1418,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 +1537,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 +1571,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 +1590,7 @@ export const useGameStore = create<GameStore>()(
signedPacts, signedPacts,
currentRoom, currentRoom,
incursionStrength, incursionStrength,
currentAction,
currentStudyTarget, currentStudyTarget,
parallelStudyTarget, parallelStudyTarget,
skills, skills,
@@ -1929,6 +1921,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 }));
}, },
+3
View File
@@ -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 ─────────────────────────────────────────────