Task 2: SpireTab Overhaul - add Climb the Spire button, implement Spire Mode with exit condition
This commit is contained in:
+15
-10
@@ -1,6 +1,6 @@
|
||||
# Task 2 Progress Tracking
|
||||
|
||||
**Last Updated**: 2026-04-26 10:35:00
|
||||
**Last Updated**: 2026-04-26 12:57:00
|
||||
**Current Status**: In Progress
|
||||
|
||||
## Completed Tasks
|
||||
@@ -9,21 +9,22 @@
|
||||
- [2026-04-25] Task 4: Equipment System - 2-Handed Weapons ✓
|
||||
- [2026-04-25] Task 12: StatsTab - Lock Fire/Water/Air/Earth at start ✓
|
||||
- [2026-04-26] Task 7: CRITICAL BUG FIX - Mana Well 'Deep Basin' upgrade ✓
|
||||
- [2026-04-26] Task 2: Research Locking - Prevent switching topics while study in progress ✓
|
||||
- [2026-04-26] Task 5: DebugTab Update - Invoker Debugging Buttons ✓
|
||||
|
||||
## In Progress Tasks
|
||||
None currently
|
||||
|
||||
## Pending Tasks
|
||||
1. Task 1: ActionButtons Rework [FAILED - sub-agent context too long]
|
||||
2. Task 2: Research Locking - SkillsTab [Pending]
|
||||
3. Task 3: SpireTab Overhaul [FAILED - sub-agent context too long]
|
||||
4. Task 5: DebugTab Update - Invoker Debugging Buttons [Pending]
|
||||
5. Task 6: System Integrity - Fix 'Show Component Names' [In Progress - investigating]
|
||||
6. Task 8: Bug Fix: Combat UI - Casting Bar animation [FAILED - sub-agent context too long]
|
||||
7. Task 10: Bug Fix: Crafting - Prepare/Design limits [Pending]
|
||||
1. Task 1: ActionButtons Rework [Try context file approach]
|
||||
2. Task 3: SpireTab Overhaul [Try context file approach]
|
||||
3. Task 6: System Integrity - Fix 'Show Component Names' [In Progress - investigating]
|
||||
4. Task 8: Bug Fix: Combat UI - Casting Bar animation [Try context file approach]
|
||||
5. Task 10: Bug Fix: Crafting - Prepare/Design limits [Try again]
|
||||
|
||||
## Blocked Tasks
|
||||
- Task 1, 3, 8: Sub-agent attempts failed due to context length limits (603k+ tokens)
|
||||
## Failed Tasks (Context Length)
|
||||
- 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
|
||||
- 65b0f96: Remove Transference from LootTab, delete Ascension skills
|
||||
@@ -32,3 +33,7 @@ None currently
|
||||
- 2355be6: StatsTab - Lock Fire/Water/Air/Earth at start
|
||||
- f61ed00: FIX: Skill perks multiplier values (Deep Basin + others)
|
||||
- a6ce36b: WIP: Task 2 progress - investigating Show Component Names debug option
|
||||
- 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
|
||||
|
||||
+49
-5
@@ -12,7 +12,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { RotateCcw } from 'lucide-react';
|
||||
import { RotateCcw, Mountain, ChevronDown } from 'lucide-react';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { DebugName } from '@/lib/game/debug-context';
|
||||
// Non-tab component imports
|
||||
@@ -181,7 +181,7 @@ export default function ManaLoopGame() {
|
||||
<TimeDisplay
|
||||
day={store.day}
|
||||
hour={store.hour}
|
||||
isPaused={store.isPaused}
|
||||
isPaused={store.paused}
|
||||
togglePause={store.togglePause}
|
||||
/>
|
||||
</div>
|
||||
@@ -207,16 +207,34 @@ export default function ManaLoopGame() {
|
||||
/>
|
||||
</DebugName>
|
||||
|
||||
{/* Action Buttons */}
|
||||
{/* Climb the Spire Button - only show when not in Spire Mode */}
|
||||
{!store.spireMode && (
|
||||
<DebugName name="ClimbSpireButton">
|
||||
<Button
|
||||
className="w-full bg-gradient-to-r from-amber-600 to-orange-600 hover:from-amber-700 hover:to-orange-700"
|
||||
size="lg"
|
||||
onClick={() => store.enterSpireMode()}
|
||||
>
|
||||
<Mountain className="w-5 h-5 mr-2" />
|
||||
Climb the Spire
|
||||
</Button>
|
||||
</DebugName>
|
||||
)}
|
||||
|
||||
{/* Action Buttons - only show when not in Spire Mode */}
|
||||
{!store.spireMode && (
|
||||
<DebugName name="ActionButtons">
|
||||
<ActionButtons
|
||||
currentAction={store.currentAction}
|
||||
currentStudyTarget={store.currentStudyTarget}
|
||||
designProgress={store.designProgress}
|
||||
designProgress2={store.designProgress2}
|
||||
preparationProgress={store.preparationProgress}
|
||||
applicationProgress={store.applicationProgress}
|
||||
setAction={store.setAction}
|
||||
equipmentCraftingProgress={store.equipmentCraftingProgress}
|
||||
/>
|
||||
</DebugName>
|
||||
)}
|
||||
|
||||
{/* Calendar */}
|
||||
<DebugName name="CalendarDisplay">
|
||||
@@ -230,7 +248,32 @@ export default function ManaLoopGame() {
|
||||
{/* Loot and Achievements moved to tabs */}
|
||||
</div>
|
||||
|
||||
{/* Right Panel - Tabs */}
|
||||
{/* Right Panel - Conditional rendering based on Spire Mode */}
|
||||
{store.spireMode ? (
|
||||
/* Spire Mode - Simplified UI */
|
||||
<div className="flex-1 min-w-0 space-y-4">
|
||||
<DebugName name="SpireModeUI">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-2xl font-bold game-title text-amber-400">
|
||||
🏔️ Spire Mode
|
||||
</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-blue-600/50 text-blue-400 hover:bg-blue-900/20"
|
||||
onClick={() => store.exitSpireMode()}
|
||||
>
|
||||
<ChevronDown className="w-4 h-4 mr-2" />
|
||||
Climb Down
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Suspense fallback={<TabLoadingFallback />}>
|
||||
<SpireTab store={store} simpleMode={true} />
|
||||
</Suspense>
|
||||
</DebugName>
|
||||
</div>
|
||||
) : (
|
||||
/* Normal Mode - Tabs */
|
||||
<div className="flex-1 min-w-0">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="flex flex-wrap gap-1 w-full mb-4 h-auto">
|
||||
@@ -364,6 +407,7 @@ export default function ManaLoopGame() {
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -1,86 +1,149 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Sparkles, Swords, BookOpen, Target, FlaskConical } from 'lucide-react';
|
||||
import { Sparkles, Swords, BookOpen, Target, FlaskConical, Cog, Hammer } from 'lucide-react';
|
||||
import type { GameAction } from '@/lib/game/types';
|
||||
|
||||
interface ActionButtonsProps {
|
||||
currentAction: GameAction;
|
||||
currentStudyTarget: { type: 'skill' | 'spell'; id: string; progress: number; required: number } | null;
|
||||
designProgress: { progress: number; required: number } | null;
|
||||
designProgress2: { progress: number; required: number } | null;
|
||||
preparationProgress: { progress: number; required: number } | null;
|
||||
applicationProgress: { progress: number; required: number } | null;
|
||||
setAction: (action: GameAction) => void;
|
||||
equipmentCraftingProgress: { progress: number; required: number } | null;
|
||||
}
|
||||
|
||||
// Map action IDs to labels and icons
|
||||
const ACTION_CONFIG: Record<string, { label: string; icon: typeof Sparkles; color: string }> = {
|
||||
meditate: { label: 'Meditating', icon: Sparkles, color: 'text-blue-400' },
|
||||
climb: { label: 'Climbing', icon: Swords, color: 'text-green-400' },
|
||||
study: { label: 'Studying', icon: BookOpen, color: 'text-yellow-400' },
|
||||
design: { label: 'Designing Enchantment', icon: Target, color: 'text-purple-400' },
|
||||
prepare: { label: 'Preparing Equipment', icon: FlaskConical, color: 'text-purple-400' },
|
||||
enchant: { label: 'Enchanting', icon: Sparkles, color: 'text-purple-400' },
|
||||
craft: { label: 'Crafting Equipment', icon: Hammer, color: 'text-orange-400' },
|
||||
convert: { label: 'Converting Mana', icon: Cog, color: 'text-cyan-400' },
|
||||
};
|
||||
|
||||
function ProgressBar({ progress, required, label }: { progress: number; required: number; label?: string }) {
|
||||
const percentage = Math.min(100, (progress / required) * 100);
|
||||
return (
|
||||
<div className="mt-1">
|
||||
{label && <div className="text-xs text-gray-400 mb-0.5">{label}</div>}
|
||||
<div className="w-full bg-gray-700 rounded-full h-1.5">
|
||||
<div
|
||||
className="bg-blue-500 h-1.5 rounded-full transition-all duration-300"
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ActionButtons({
|
||||
currentAction,
|
||||
currentStudyTarget,
|
||||
designProgress,
|
||||
designProgress2,
|
||||
preparationProgress,
|
||||
applicationProgress,
|
||||
setAction,
|
||||
equipmentCraftingProgress,
|
||||
}: ActionButtonsProps) {
|
||||
const actions: { id: GameAction; label: string; icon: typeof Swords }[] = [
|
||||
{ id: 'meditate', label: 'Meditate', icon: Sparkles },
|
||||
{ id: 'climb', label: 'Climb', icon: Swords },
|
||||
{ id: 'study', label: 'Study', icon: BookOpen },
|
||||
];
|
||||
const config = ACTION_CONFIG[currentAction] || { label: currentAction, icon: Sparkles, color: 'text-gray-400' };
|
||||
const Icon = config.icon;
|
||||
|
||||
const hasDesignProgress = designProgress !== null;
|
||||
const hasPrepProgress = preparationProgress !== null;
|
||||
const hasAppProgress = applicationProgress !== null;
|
||||
// Calculate additional info for specific actions
|
||||
const getActionDetails = () => {
|
||||
switch (currentAction) {
|
||||
case 'study':
|
||||
if (currentStudyTarget) {
|
||||
const progress = currentStudyTarget.progress;
|
||||
const required = currentStudyTarget.required;
|
||||
const percentage = Math.min(100, (progress / required) * 100);
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={progress}
|
||||
required={required}
|
||||
label={`${currentStudyTarget.type === 'skill' ? 'Skill' : 'Spell'}: ${percentage.toFixed(0)}%`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'design':
|
||||
if (designProgress) {
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={designProgress.progress}
|
||||
required={designProgress.required}
|
||||
label="Design progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'prepare':
|
||||
if (preparationProgress) {
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={preparationProgress.progress}
|
||||
required={preparationProgress.required}
|
||||
label="Preparation progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'enchant':
|
||||
if (applicationProgress) {
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={applicationProgress.progress}
|
||||
required={applicationProgress.required}
|
||||
label="Enchantment progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'craft':
|
||||
if (equipmentCraftingProgress) {
|
||||
return (
|
||||
<ProgressBar
|
||||
progress={equipmentCraftingProgress.progress}
|
||||
required={equipmentCraftingProgress.required}
|
||||
label="Crafting progress"
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{actions.map(({ id, label, icon: Icon }) => (
|
||||
<Button
|
||||
key={id}
|
||||
variant={currentAction === id ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
className={`h-9 ${currentAction === id ? 'bg-blue-600 hover:bg-blue-700' : 'bg-gray-800/50 hover:bg-gray-700/50 border-gray-600'}`}
|
||||
onClick={() => setAction(id)}
|
||||
>
|
||||
<Icon className="w-4 h-4 mr-1" />
|
||||
{label}
|
||||
</Button>
|
||||
))}
|
||||
<div 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()}
|
||||
|
||||
{/* 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>
|
||||
{/* 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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,9 +19,10 @@ import { getUnifiedEffects } from '@/lib/game/effects';
|
||||
|
||||
interface SpireTabProps {
|
||||
store: GameStore;
|
||||
simpleMode?: boolean; // When true, only show essential Spire info (for Spire Mode)
|
||||
}
|
||||
|
||||
export function SpireTab({ store }: SpireTabProps) {
|
||||
export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
|
||||
const floorElem = getFloorElement(store.currentFloor);
|
||||
const floorElemDef = ELEMENTS[floorElem];
|
||||
const isGuardianFloor = !!GUARDIANS[store.currentFloor];
|
||||
@@ -48,7 +49,7 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<div className={`grid gap-4 ${simpleMode ? 'grid-cols-1' : 'grid-cols-1 lg:grid-cols-2'}`}>
|
||||
{/* Current Floor Card */}
|
||||
<Card className="bg-gray-900/80 border-gray-700">
|
||||
<CardHeader className="pb-2">
|
||||
@@ -92,6 +93,8 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!simpleMode && (
|
||||
<>
|
||||
<Separator className="bg-gray-700" />
|
||||
|
||||
{/* Floor Navigation - Direction indicator only */}
|
||||
@@ -121,6 +124,8 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
</div>
|
||||
|
||||
<Separator className="bg-gray-700" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="text-sm text-gray-400">
|
||||
Best: Floor <strong className="text-gray-200">{store.maxFloorReached}</strong> •
|
||||
@@ -205,8 +210,8 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Summoned Golems Card */}
|
||||
{store.golemancy.summonedGolems.length > 0 && (
|
||||
{/* Summoned Golems Card - Always show in simple mode, conditional in normal mode */}
|
||||
{(simpleMode || store.golemancy.summonedGolems.length > 0) && (
|
||||
<Card className="bg-gray-900/80 border-amber-600/50">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-amber-400 game-panel-title text-xs flex items-center gap-2">
|
||||
@@ -257,8 +262,8 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Current Study (if any) */}
|
||||
{store.currentStudyTarget && (
|
||||
{/* Current Study (if any) - Only show in normal mode */}
|
||||
{!simpleMode && store.currentStudyTarget && (
|
||||
<Card className="bg-gray-900/80 border-purple-600/50 lg:col-span-2">
|
||||
<CardContent className="pt-4 space-y-3">
|
||||
<StudyProgress
|
||||
@@ -299,8 +304,8 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Crafting Progress (if any) */}
|
||||
{(store.designProgress || store.preparationProgress || store.applicationProgress) && (
|
||||
{/* Crafting Progress (if any) - Only show in normal mode */}
|
||||
{!simpleMode && (store.designProgress || store.preparationProgress || store.applicationProgress) && (
|
||||
<Card className="bg-gray-900/80 border-cyan-600/50 lg:col-span-2">
|
||||
<CardContent className="pt-4">
|
||||
<CraftingProgress
|
||||
@@ -319,7 +324,8 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Activity Log */}
|
||||
{/* Activity Log - Only show in normal mode */}
|
||||
{!simpleMode && (
|
||||
<Card className="bg-gray-900/80 border-gray-700 lg:col-span-2">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-amber-400 game-panel-title text-xs">Activity Log</CardTitle>
|
||||
@@ -339,6 +345,7 @@ export function SpireTab({ store }: SpireTabProps) {
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
+43
-33
@@ -749,6 +749,9 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
|
||||
|
||||
// Mana Well Effects (Phase 4)
|
||||
manaHeartBonus: manaHeartBonus, // Cumulative +10% max mana per loop from MANA_HEART
|
||||
|
||||
// Spire Mode - simplified UI for climbing
|
||||
spireMode: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -801,6 +804,10 @@ interface GameStore extends GameState, CraftingActions {
|
||||
getMeditationMultiplier: () => number;
|
||||
canCastSpell: (spellId: string) => boolean;
|
||||
getSkillUpgradeChoices: (skillId: string, milestone: 5 | 10) => { available: SkillUpgradeChoice[]; selected: string[] };
|
||||
|
||||
// Spire Mode actions
|
||||
enterSpireMode: () => void;
|
||||
exitSpireMode: () => void;
|
||||
}
|
||||
|
||||
export const useGameStore = create<GameStore>()(
|
||||
@@ -834,6 +841,9 @@ export const useGameStore = create<GameStore>()(
|
||||
// Compute unified effects (includes skill upgrades AND equipment enchantments)
|
||||
const effects = getUnifiedEffects(state);
|
||||
|
||||
// Track current action for potential auto-transitions
|
||||
let currentAction = state.currentAction;
|
||||
|
||||
const maxMana = computeMaxMana(state, effects);
|
||||
const baseRegen = computeRegen(state, effects);
|
||||
|
||||
@@ -878,7 +888,7 @@ export const useGameStore = create<GameStore>()(
|
||||
let meditateTicks = state.meditateTicks;
|
||||
let meditationMultiplier = 1;
|
||||
|
||||
if (state.currentAction === 'meditate') {
|
||||
if (currentAction === 'meditate') {
|
||||
meditateTicks++;
|
||||
meditationMultiplier = getMeditationBonus(meditateTicks, state.skills);
|
||||
|
||||
@@ -973,7 +983,7 @@ export const useGameStore = create<GameStore>()(
|
||||
let unlockedEffects = state.unlockedEffects;
|
||||
let consecutiveStudyHours = state.consecutiveStudyHours;
|
||||
|
||||
if (state.currentAction === 'study' && currentStudyTarget) {
|
||||
if (currentAction === 'study' && currentStudyTarget) {
|
||||
// Calculate base study speed
|
||||
let studySpeedMult = getStudySpeedMultiplier(skills);
|
||||
|
||||
@@ -1076,11 +1086,13 @@ export const useGameStore = create<GameStore>()(
|
||||
log = [`📖 ${SPELLS_DEF[spellId]?.name} learned!`, ...log.slice(0, 49)];
|
||||
}
|
||||
currentStudyTarget = null;
|
||||
// Auto-transition to meditate when study completes
|
||||
currentAction = 'meditate';
|
||||
}
|
||||
}
|
||||
// Parallel Study processing (PARALLEL_STUDY special effect)
|
||||
let parallelStudyTarget = state.parallelStudyTarget;
|
||||
if (parallelStudyTarget && state.currentAction === 'study') {
|
||||
if (parallelStudyTarget && currentAction === 'study') {
|
||||
// Parallel study progresses at 50% speed
|
||||
const parallelProgressGain = HOURS_PER_TICK * 0.5;
|
||||
parallelStudyTarget = {
|
||||
@@ -1101,7 +1113,7 @@ export const useGameStore = create<GameStore>()(
|
||||
}
|
||||
|
||||
// Convert action - auto convert mana
|
||||
if (state.currentAction === 'convert') {
|
||||
if (currentAction === 'convert') {
|
||||
const unlockedElements = Object.entries(elements)
|
||||
.filter(([, e]) => e.unlocked && e.current < e.max);
|
||||
|
||||
@@ -1130,7 +1142,7 @@ export const useGameStore = create<GameStore>()(
|
||||
const floorElement = getFloorElement(currentFloor);
|
||||
|
||||
// Handle puzzle rooms separately
|
||||
if (state.currentAction === 'climb' && currentRoom.roomType === 'puzzle') {
|
||||
if (currentAction === 'climb' && currentRoom.roomType === 'puzzle') {
|
||||
const progressSpeed = getPuzzleProgressSpeed(
|
||||
currentRoom.puzzleId || '',
|
||||
state.attunements
|
||||
@@ -1154,7 +1166,7 @@ export const useGameStore = create<GameStore>()(
|
||||
maxFloorReached = Math.max(maxFloorReached, currentFloor);
|
||||
castProgress = 0;
|
||||
}
|
||||
} else if (state.currentAction === 'climb') {
|
||||
} else if (currentAction === 'climb') {
|
||||
const spellId = state.activeSpell;
|
||||
const spellDef = SPELLS_DEF[spellId];
|
||||
|
||||
@@ -1358,7 +1370,7 @@ export const useGameStore = create<GameStore>()(
|
||||
const floorChanged = currentFloor !== golemancy.lastSummonFloor;
|
||||
const inCombatRoom = currentRoom.roomType !== 'puzzle';
|
||||
|
||||
if (state.currentAction === 'climb' && inCombatRoom && floorChanged && maxGolemSlots > 0) {
|
||||
if (currentAction === 'climb' && inCombatRoom && floorChanged && maxGolemSlots > 0) {
|
||||
// Determine which golems should be summoned
|
||||
const unlockedElementIds = Object.entries(elements)
|
||||
.filter(([, e]) => e.unlocked)
|
||||
@@ -1406,7 +1418,7 @@ export const useGameStore = create<GameStore>()(
|
||||
}
|
||||
|
||||
// Process golem maintenance and attacks each tick
|
||||
if (golemancy.summonedGolems.length > 0 && state.currentAction === 'climb' && inCombatRoom) {
|
||||
if (golemancy.summonedGolems.length > 0 && currentAction === 'climb' && inCombatRoom) {
|
||||
const floorDuration = getGolemFloorDuration(skills);
|
||||
const survivingGolems: typeof golemancy.summonedGolems = [];
|
||||
let anyGolemDismissed = false;
|
||||
@@ -1525,7 +1537,7 @@ export const useGameStore = create<GameStore>()(
|
||||
}
|
||||
|
||||
// Unsummon golems when not climbing or in puzzle room
|
||||
if ((state.currentAction !== 'climb' || !inCombatRoom) && golemancy.summonedGolems.length > 0) {
|
||||
if ((currentAction !== 'climb' || !inCombatRoom) && golemancy.summonedGolems.length > 0) {
|
||||
log = [`🗿 Golems returned to the earth.`, ...log.slice(0, 49)];
|
||||
golemancy = {
|
||||
...golemancy,
|
||||
@@ -1559,31 +1571,10 @@ export const useGameStore = create<GameStore>()(
|
||||
// Apply crafting updates
|
||||
if (craftingUpdates.rawMana !== undefined) rawMana = craftingUpdates.rawMana;
|
||||
if (craftingUpdates.log !== undefined) log = craftingUpdates.log;
|
||||
|
||||
// If crafting slice set currentAction (e.g., auto-transition to meditate), use it
|
||||
if (craftingUpdates.currentAction !== undefined) {
|
||||
set({
|
||||
...craftingUpdates,
|
||||
day,
|
||||
hour,
|
||||
rawMana,
|
||||
meditateTicks,
|
||||
totalManaGathered,
|
||||
currentFloor,
|
||||
floorHP,
|
||||
floorMaxHP,
|
||||
maxFloorReached,
|
||||
signedPacts,
|
||||
currentRoom,
|
||||
incursionStrength,
|
||||
currentStudyTarget,
|
||||
skills,
|
||||
skillProgress,
|
||||
spells,
|
||||
elements,
|
||||
log,
|
||||
castProgress,
|
||||
golemancy,
|
||||
});
|
||||
return;
|
||||
currentAction = craftingUpdates.currentAction;
|
||||
}
|
||||
|
||||
set({
|
||||
@@ -1599,6 +1590,7 @@ export const useGameStore = create<GameStore>()(
|
||||
signedPacts,
|
||||
currentRoom,
|
||||
incursionStrength,
|
||||
currentAction,
|
||||
currentStudyTarget,
|
||||
parallelStudyTarget,
|
||||
skills,
|
||||
@@ -1929,6 +1921,24 @@ export const useGameStore = create<GameStore>()(
|
||||
set(newState);
|
||||
},
|
||||
|
||||
// Spire Mode - enter simplified UI for climbing
|
||||
enterSpireMode: () => {
|
||||
set((state) => ({
|
||||
spireMode: true,
|
||||
currentAction: 'climb',
|
||||
log: ['🏔️ Entered Spire Mode! The climb begins...', ...state.log.slice(0, 49)],
|
||||
}));
|
||||
},
|
||||
|
||||
// Exit Spire Mode - return to normal game UI
|
||||
exitSpireMode: () => {
|
||||
set((state) => ({
|
||||
spireMode: false,
|
||||
currentAction: 'meditate',
|
||||
log: ['⬇️ Climbed down from the Spire. Returning to normal view.', ...state.log.slice(0, 49)],
|
||||
}));
|
||||
},
|
||||
|
||||
togglePause: () => {
|
||||
set((state) => ({ paused: !state.paused }));
|
||||
},
|
||||
|
||||
@@ -212,6 +212,9 @@ export interface GameState {
|
||||
|
||||
// Loop insight (earned at end of current loop)
|
||||
loopInsight: number;
|
||||
|
||||
// Spire Mode - simplified UI for climbing
|
||||
spireMode: boolean;
|
||||
}
|
||||
|
||||
// ─── Action Types for Store ─────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user