'use client'; import { Button } from '@/components/ui/button'; import { Progress } from '@/components/ui/progress'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Separator } from '@/components/ui/separator'; import { TooltipProvider, Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { BookOpen, ChevronUp, ChevronDown, RotateCcw, X, Mountain, Skull, Zap, Wind, Shield } from 'lucide-react'; import type { ActivityLogEntry } from '@/lib/game/types'; import type { GameStore } from '@/lib/game/store'; import { ELEMENTS, GUARDIANS, SPELLS_DEF, SKILLS_DEF, ROOM_TYPE_LABELS } from '@/lib/game/constants'; import { GOLEMS_DEF, getGolemDamage, getGolemAttackSpeed } from '@/lib/game/data/golems'; import { fmt, fmtDec, getFloorElement, canAffordSpellCost, getEnemyName } from '@/lib/game/store'; import { getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/computed-stats'; import { formatSpellCost, getSpellCostColor, formatStudyTime } from '@/lib/game/formatting'; import { CraftingProgress, StudyProgress } from '@/components/game'; import { getUnifiedEffects } from '@/lib/game/effects'; interface SpireTabProps { store: GameStore; simpleMode?: boolean; // When true, only show essential Spire info (for Spire Mode) } // Helper to check if player can enter spire mode const canEnterSpireMode = (store: GameStore): boolean => { return !store.spireMode; // Can enter if not already in Spire Mode }; // Room type configurations for display const ROOM_TYPE_CONFIG: Record = { combat: { label: 'Combat', icon: '⚔️', color: '#EF4444' }, swarm: { label: 'Swarm', icon: '🐝', color: '#F59E0B' }, speed: { label: 'Speed', icon: '💨', color: '#3B82F6' }, guardian: { label: 'Guardian', icon: '🛡️', color: '#EF4444' }, puzzle: { label: 'Puzzle', icon: '🧩', color: '#8B5CF6' }, }; export function SpireTab({ store, simpleMode = false }: SpireTabProps) { const floorElem = getFloorElement(store.currentFloor); const floorElemDef = ELEMENTS[floorElem]; const isGuardianFloor = !!GUARDIANS[store.currentFloor]; const currentGuardian = GUARDIANS[store.currentFloor]; const climbDirection = store.climbDirection || 'up'; const clearedFloors = store.clearedFloors || {}; const currentRoom = store.currentRoom; // Check if current floor is cleared (for respawn indicator) const isFloorCleared = clearedFloors[store.currentFloor]; // Get room type info const roomType = currentRoom?.roomType || 'combat'; const roomConfig = ROOM_TYPE_CONFIG[roomType] || ROOM_TYPE_CONFIG.combat; // Get active equipment spells const activeEquipmentSpells = getActiveEquipmentSpells(store.equippedInstances, store.equipmentInstances); // Get upgrade effects and DPS const upgradeEffects = getUnifiedEffects(store); const totalDPS = getTotalDPS(store, upgradeEffects, floorElem); const studySpeedMult = 1; // Base study speed const canCastSpell = (spellId: string): boolean => { const spell = SPELLS_DEF[spellId]; if (!spell) return false; return canAffordSpellCost(spell.cost, store.rawMana, store.elements); }; // Get enemy display info const getEnemyDisplayInfo = () => { if (!currentRoom || !currentRoom.enemies || currentRoom.enemies.length === 0) { return { primaryEnemy: null, swarmEnemies: [] }; } const enemies = currentRoom.enemies; const primaryEnemy = enemies[0]; // For swarm rooms, return all enemies if (roomType === 'swarm') { return { primaryEnemy: null, swarmEnemies: enemies }; } return { primaryEnemy, swarmEnemies: [] }; }; const { primaryEnemy, swarmEnemies } = getEnemyDisplayInfo(); return (
{/* Spire Stats View - Only show in normal mode (not simpleMode) */} {!simpleMode && ( <> {/* Enter Spire Mode Button */}
Climb the Spire to face guardians and earn pacts
{/* Spire Stats Card - Replaces Current Floor stat */} Spire Stats
{store.maxFloorReached}
Best Floor
{store.signedPacts.length}
Pacts Signed
Current Floor: {store.currentFloor}
)} {/* Current Floor Card - Only show in Spire Mode (simpleMode) */} {simpleMode && ( Current Floor {roomConfig.icon} {roomConfig.label}
{store.currentFloor} / 100 {floorElemDef?.sym} {floorElemDef?.name} {isGuardianFloor && ( GUARDIAN )}
{/* Guardian Name */} {isGuardianFloor && currentGuardian && (
⚔️ {currentGuardian.name}
)} {/* Single Enemy Display (Combat/Speed/Guardian) */} {!isGuardianFloor && primaryEnemy && roomType !== 'swarm' && (
{primaryEnemy.name || 'Unknown Enemy'}
{ELEMENTS[primaryEnemy.element]?.sym} {ELEMENTS[primaryEnemy.element]?.name}
{/* Enemy HP Bar */}
{fmt(primaryEnemy.hp)} / {fmt(primaryEnemy.maxHP)} HP
{/* Enemy Properties */}
{primaryEnemy.armor > 0 && ( {(primaryEnemy.armor * 100).toFixed(0)}% Armor

Reduces incoming damage by {(primaryEnemy.armor * 100).toFixed(0)}%

)} {primaryEnemy.dodgeChance > 0 && ( {(primaryEnemy.dodgeChance * 100).toFixed(0)}% Dodge

Chance to dodge attacks and reduce progress

)}
)} {/* Swarm Enemies Display */} {roomType === 'swarm' && swarmEnemies.length > 0 && (
Swarm Enemies ({swarmEnemies.length})
{swarmEnemies.map((enemy, index) => (
{enemy.name || `Enemy ${index + 1}`}
{ELEMENTS[enemy.element]?.sym} {fmt(enemy.hp)}/{fmt(enemy.maxHP)} HP
))}
)} {/* Puzzle Room Display */} {roomType === 'puzzle' && (
🧩 {currentRoom.puzzleId ? currentRoom.puzzleId.replace(/_/g, ' ').toUpperCase() : 'Puzzle Room'}
Progress {((currentRoom.puzzleProgress || 0) * 100).toFixed(0)}%
)} {/* Floor HP Bar (for non-swarm, non-puzzle) */} {roomType !== 'swarm' && roomType !== 'puzzle' && (
{fmt(store.floorHP)} / {fmt(store.floorMaxHP)} HP DPS: {store.currentAction === 'climb' && activeEquipmentSpells.length > 0 ? fmtDec(totalDPS) : '—'}
)}
Best: Floor {store.maxFloorReached} • Pacts: {store.signedPacts.length}
)} {/* Active Spells Card - Only show in Spire Mode (simpleMode) */} {simpleMode && ( Active Spells ({activeEquipmentSpells.length}) {activeEquipmentSpells.length > 0 ? (
{activeEquipmentSpells.map(({ spellId, equipmentId }) => { const spellDef = SPELLS_DEF[spellId]; if (!spellDef) return null; const spellState = store.equipmentSpellStates?.find( s => s.spellId === spellId && s.sourceEquipment === equipmentId ); const progress = spellState?.castProgress || 0; const canCast = canAffordSpellCost(spellDef.cost, store.rawMana, store.elements); return (
{spellDef.name} {spellDef.tier === 0 && Basic} {spellDef.tier >= 4 && Legendary}
{canCast ? '✓' : '✗'}
⚔️ {fmt(calcDamage(store, spellId))} dmg/cast • {' '}{formatSpellCost(spellDef.cost)} {' '}• ⚡ {fmt(Math.floor(calcDamage(store, spellId) * (spellDef.castSpeed || 1)))} dmg/hr
{/* Cast progress bar when climbing */} {store.currentAction === 'climb' && (
Cast {(progress * 100).toFixed(0)}%
)} {spellDef.effects && spellDef.effects.length > 0 && (
{spellDef.effects.map((eff, i) => ( {eff.type === 'burn' && `🔥 Burn`} {eff.type === 'freeze' && `❄️ Freeze`} {eff.type === 'stun' && `⚡ Stun`} {eff.type === 'armor_pierce' && `🗡️ Pierce`} {eff.type === 'buff' && `⬆ Buff`} {eff.type === 'chain' && `⛓️ Chain`} {eff.type === 'aoe' && `💥 AOE`} ))}
)}
); })}
) : (
No spells on equipped weapons. Enchant a staff with spell effects in the Crafting tab.
)}
)} {/* Summoned Golems Card - Show in Spire Mode (simpleMode) or if have golems */} {simpleMode && store.golemancy.summonedGolems.length > 0 && ( Active Golems ({store.golemancy.summonedGolems.length}) {store.golemancy.summonedGolems.map((summoned) => { const golemDef = GOLEMS_DEF[summoned.golemId]; if (!golemDef) return null; const elemColor = ELEMENTS[golemDef.baseManaType]?.color || '#888'; const damage = getGolemDamage(summoned.golemId, store.skills); const attackSpeed = getGolemAttackSpeed(summoned.golemId, store.skills); return (
{golemDef.name}
{golemDef.isAoe && ( AOE {golemDef.aoeTargets} )}
⚔️ {damage} DMG • ⚡ {attackSpeed.toFixed(1)}/hr • 🗡️ {Math.floor(golemDef.armorPierce * 100)}% Pierce
{/* Attack progress bar when climbing */} {store.currentAction === 'climb' && summoned.attackProgress > 0 && (
Attack {Math.min(100, (summoned.attackProgress * 100)).toFixed(0)}%
)}
); })}
)} {/* Current Study (if any) - Only show in normal mode */} {!simpleMode && store.currentStudyTarget && ( {/* Parallel Study Progress */} {store.parallelStudyTarget && (
Parallel: {SKILLS_DEF[store.parallelStudyTarget.id]?.name} {store.parallelStudyTarget.type === 'skill' && ` Lv.${(store.skills[store.parallelStudyTarget.id] || 0) + 1}`}
{formatStudyTime(store.parallelStudyTarget.progress)} / {formatStudyTime(store.parallelStudyTarget.required)} 50% speed (Parallel Study)
)}
)} {/* Activity Log - Show in Spire Mode (simpleMode) */} {simpleMode && ( Activity Log
{(store.activityLog || []).slice(0, 50).map((entry: ActivityLogEntry, i) => { // Style based on event type const getEventStyle = (eventType: string) => { switch (eventType) { case 'enemy_defeated': case 'floor_cleared': return 'text-green-400'; case 'damage_dealt': return 'text-red-400'; case 'dodge': return 'text-yellow-400'; case 'armor_proc': return 'text-blue-400'; case 'special_effect': return 'text-purple-400'; case 'floor_transition': return 'text-cyan-400'; case 'spell_cast': return 'text-amber-400'; case 'golem_attack': return 'text-orange-400'; case 'puzzle_solved': return 'text-pink-400'; default: return 'text-gray-300'; } }; return (
{entry.message}
); })} {(store.activityLog || []).length === 0 && (
No activity yet...
)}
)} {/* Crafting Progress (if any) - Only show in normal mode */} {!simpleMode && (store.designProgress || store.preparationProgress || store.applicationProgress) && ( )}
); } SpireTab.displayName = "SpireTab";