fix: split SpireTab.tsx to 395 lines, remove require() imports, import from data modules; complete store migration
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 30m15s

This commit is contained in:
Refactoring Agent
2026-05-04 13:36:10 +02:00
parent 0eabd604b0
commit 837d963b63
41 changed files with 727 additions and 3935 deletions
+110 -88
View File
@@ -1,17 +1,19 @@
'use client';
import { useMemo } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Mountain } from 'lucide-react';
import type { ActivityLogEntry } from '@/lib/game/types';
import type { GameStore } from '@/lib/game/store';
import { ELEMENTS, GUARDIANS, SPELLS_DEF } from '@/lib/game/constants';
import { calcDamage } from '@/lib/game/store';
import { ELEMENTS, GUARDIANS, SPELLS_DEF, SKILLS_DEF } from '@/lib/game/constants';
import { calcDamage } from '@/lib/game/stores';
import { getEnemyName } from '@/lib/game/store-modules/enemy-utils';
import { getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/computed-stats';
import { getUnifiedEffects } from '@/lib/game/effects';
import { formatSpellCost, getSpellCostColor } from '@/lib/game/formatting';
import { canAffordSpellCost, getFloorElement } from '@/lib/game/store';
import { canAffordSpellCost, getFloorElement } from '@/lib/game/stores';
import { useGameStore, useManaStore, useSkillStore, useCombatStore, usePrestigeStore } from '@/lib/game/stores';
import { GOLEMS_DEF, getGolemDamage, getGolemAttackSpeed } from '@/lib/game/data/golems';
// Extracted components
import { SpireHeader } from './SpireHeader';
@@ -31,31 +33,77 @@ const ROOM_TYPE_CONFIG: Record<string, { label: string; icon: string; color: str
};
interface SpireTabProps {
store: GameStore;
simpleMode?: boolean;
}
// Check if player can enter spire mode
const canEnterSpireMode = (store: GameStore): boolean => {
return !store.spireMode;
const canEnterSpireMode = (spireMode: boolean): boolean => {
return !spireMode;
};
export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
export function SpireTab({ simpleMode = false }: SpireTabProps) {
// Get state from modular stores
const currentFloor = useCombatStore((s) => s.currentFloor);
const floorHP = useCombatStore((s) => s.floorHP);
const floorMaxHP = useCombatStore((s) => s.floorMaxHP);
const maxFloorReached = useCombatStore((s) => s.maxFloorReached);
const currentAction = useCombatStore((s) => s.currentAction);
const castProgress = useCombatStore((s) => s.castProgress);
const activeSpell = useCombatStore((s) => s.activeSpell);
const startClimbUp = useCombatStore((s) => s.startClimbUp);
const startClimbDown = useCombatStore((s) => s.startClimbDown);
const enterSpireMode = useGameStore((s) => s.enterSpireMode);
const spireMode = useGameStore((s) => s.spireMode);
const climbDirection = useGameStore((s) => s.climbDirection) || 'up';
const clearedFloors = useGameStore((s) => s.clearedFloors || {});
const currentRoom = useGameStore((s) => s.currentRoom);
const equipmentSpellStates = useGameStore((s) => s.equipmentSpellStates);
const golemancy = useGameStore((s) => s.golemancy);
const activityLog = useGameStore((s) => s.activityLog);
const currentStudyTarget = useGameStore((s) => s.currentStudyTarget);
const parallelStudyTarget = useGameStore((s) => s.parallelStudyTarget);
const signedPacts = usePrestigeStore((s) => s.signedPacts);
const skills = useSkillStore((s) => s.skills);
const skillUpgrades = useSkillStore((s) => s.skillUpgrades);
const skillTiers = useSkillStore((s) => s.skillTiers);
const rawMana = useManaStore((s) => s.rawMana);
const elements = useManaStore((s) => s.elements);
const equippedInstances = useGameStore((s) => s.equippedInstances);
const equipmentInstances = useGameStore((s) => s.equipmentInstances);
const designProgress = useGameStore((s) => s.designProgress);
const designProgress2 = useGameStore((s) => s.designProgress2);
const preparationProgress = useGameStore((s) => s.preparationProgress);
const applicationProgress = useGameStore((s) => s.applicationProgress);
const equipmentCraftingProgress = useGameStore((s) => s.equipmentCraftingProgress);
// Derived data
const floorElem = getFloorElement(store.currentFloor);
const floorElem = getFloorElement(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;
const isFloorCleared = clearedFloors[store.currentFloor];
const isGuardianFloor = !!GUARDIANS[currentFloor];
const currentGuardian = GUARDIANS[currentFloor];
const isFloorCleared = clearedFloors[currentFloor];
const roomType = currentRoom?.roomType || 'combat';
const roomConfig = ROOM_TYPE_CONFIG[roomType] || ROOM_TYPE_CONFIG.combat;
const activeEquipmentSpells = getActiveEquipmentSpells(store.equippedInstances, store.equipmentInstances);
const upgradeEffects = getUnifiedEffects(store);
const totalDPS = getTotalDPS(store, upgradeEffects, floorElem);
const studySpeedMult = 1;
const activeEquipmentSpells = useMemo(
() => getActiveEquipmentSpells(equippedInstances, equipmentInstances),
[equippedInstances, equipmentInstances]
);
const upgradeEffects = useMemo(
() => getUnifiedEffects({
skillUpgrades,
skillTiers,
equippedInstances,
equipmentInstances,
}),
[skillUpgrades, skillTiers, equippedInstances, equipmentInstances]
);
const totalDPS = useMemo(
() => getTotalDPS({ skills, signedPacts, skillUpgrades, skillTiers }, upgradeEffects, floorElem),
[skills, signedPacts, skillUpgrades, skillTiers, upgradeEffects, floorElem]
);
// Enemy display info
const primaryEnemy = currentRoom?.enemies?.[0] || null;
@@ -65,18 +113,22 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
const canCastSpell = (spellId: string): boolean => {
const spell = SPELLS_DEF[spellId];
if (!spell) return false;
return canAffordSpellCost(spell.cost, store.rawMana, store.elements);
return canAffordSpellCost(spell.cost, rawMana, elements);
};
// Climb handler
const handleClimb = (direction: 'up' | 'down') => {
if (direction === 'up') {
store.startClimbUp();
startClimbUp();
} else {
store.startClimbDown();
startClimbDown();
}
};
const getSkillName = (skillId: string): string => {
return SKILLS_DEF[skillId]?.name || skillId;
};
return (
<div className="grid gap-4">
{/* Enter Spire Mode - Normal mode only */}
@@ -86,8 +138,8 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
<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()}
disabled={!canEnterSpireMode(store)}
onClick={enterSpireMode}
disabled={!canEnterSpireMode(spireMode)}
>
<Mountain className="w-5 h-5 mr-2" />
Enter Spire Mode
@@ -101,9 +153,9 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
{/* Spire Header */}
<SpireHeader
currentFloor={store.currentFloor}
maxFloorReached={store.maxFloorReached}
signedPacts={store.signedPacts.length}
currentFloor={currentFloor}
maxFloorReached={maxFloorReached}
signedPacts={signedPacts.length}
isGuardianFloor={isGuardianFloor}
roomType={roomType}
roomLabel={roomConfig.label}
@@ -126,7 +178,7 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
{activeEquipmentSpells.map(({ spellId, equipmentId }) => {
const spellDef = SPELLS_DEF[spellId];
if (!spellDef) return null;
const spellState = store.equipmentSpellStates?.find(
const spellState = equipmentSpellStates?.find(
s => s.spellId === spellId && s.sourceEquipment === equipmentId
);
const progress = spellState?.castProgress || 0;
@@ -142,9 +194,9 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
<span className={`text-xs ${canCast ? 'text-green-400' : 'text-red-400'}`}>{canCast ? '✓' : '✗'}</span>
</div>
<div className="text-xs text-gray-400 mb-1">
{fmt(calcDamage(store, spellId))} dmg <span style={{ color: getSpellCostColor(spellDef.cost) }}>{formatSpellCost(spellDef.cost)}</span> {' '} {fmt(Math.floor(calcDamage(store, spellId) * (spellDef.castSpeed || 1)))} dmg/hr
{calcDamage({ skills, signedPacts, skillUpgrades, skillTiers }, spellId, floorElem)} dmg <span style={{ color: getSpellCostColor(spellDef.cost) }}>{formatSpellCost(spellDef.cost)}</span> {' '} {Math.floor(calcDamage({ skills, signedPacts, skillUpgrades, skillTiers }, spellId, floorElem) * (spellDef.castSpeed || 1))} dmg/hr
</div>
{store.currentAction === 'climb' && (
{currentAction === 'climb' && (
<div className="space-y-0.5">
<div className="flex justify-between text-xs text-gray-500">
<span>Cast</span>
@@ -167,20 +219,20 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
)}
{/* Summoned Golems */}
{simpleMode && store.golemancy.summonedGolems.length > 0 && (
{simpleMode && golemancy.summonedGolems.length > 0 && (
<Card className="bg-gray-900/80 border-amber-600/50">
<CardContent className="pt-4 pb-4">
<div className="text-amber-400 text-xs font-semibold mb-3 uppercase tracking-wider flex items-center gap-2">
<Mountain className="w-4 h-4" />
Active Golems ({store.golemancy.summonedGolems.length})
Active Golems ({golemancy.summonedGolems.length})
</div>
<div className="space-y-2">
{store.golemancy.summonedGolems.map((summoned) => {
const golemDef = getGolemDef(summoned.golemId);
{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);
const damage = getGolemDamage(summoned.golemId, skills);
const attackSpeed = getGolemAttackSpeed(summoned.golemId, skills);
return (
<div key={summoned.golemId} className="p-2 rounded bg-gray-800/30 border border-gray-700">
@@ -192,7 +244,7 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
{golemDef.isAoe && <span className="text-xs border border-gray-600 px-1 rounded">AOE {golemDef.aoeTargets}</span>}
</div>
<div className="text-xs text-gray-400"> {damage} DMG {attackSpeed.toFixed(1)}/hr</div>
{store.currentAction === 'climb' && summoned.attackProgress > 0 && (
{currentAction === 'climb' && summoned.attackProgress > 0 && (
<div className="mt-1">
<div className="flex justify-between text-xs text-gray-500 mb-0.5">
<span>Attack</span>
@@ -213,7 +265,7 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
{/* Guardian Panel */}
{isGuardianFloor && simpleMode && (
<GuardianPanel currentFloor={store.currentFloor} floorElemDef={floorElemDef} />
<GuardianPanel currentFloor={currentFloor} floorElemDef={floorElemDef} />
)}
{/* Room Display */}
@@ -227,10 +279,10 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
puzzleProgress={currentRoom?.puzzleProgress}
simpleMode={true}
floorElemDef={floorElemDef}
floorHP={store.floorHP}
floorMaxHP={store.floorMaxHP}
floorHP={floorHP}
floorMaxHP={floorMaxHP}
totalDPS={totalDPS}
currentAction={store.currentAction}
currentAction={currentAction}
activeEquipmentSpells={activeEquipmentSpells}
/>
)}
@@ -238,7 +290,7 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
{/* Floor Controls */}
{simpleMode && (
<FloorControls
store={store}
storeCurrentAction={currentAction}
climbDirection={climbDirection}
isGuardianFloor={isGuardianFloor}
currentRoom={currentRoom}
@@ -255,7 +307,6 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
calcDamage={calcDamage}
SPELLS_DEF={SPELLS_DEF}
canCastSpell={canCastSpell}
storeCurrentAction={store.currentAction}
handleClimb={handleClimb}
formatSpellCost={formatSpellCost}
getSpellCostColor={getSpellCostColor}
@@ -266,7 +317,7 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
{simpleMode && (
<CombatStatsPanel
activeEquipmentSpells={activeEquipmentSpells}
store={store}
storeCurrentAction={currentAction}
totalDPS={totalDPS}
calcDamage={calcDamage}
formatSpellCost={formatSpellCost}
@@ -274,27 +325,26 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
SPELLS_DEF={SPELLS_DEF}
upgradeEffects={upgradeEffects}
canCastSpell={canCastSpell}
studySpeedMult={studySpeedMult}
storeCurrentAction={store.currentAction}
studySpeedMult={1}
/>
)}
{/* Activity Log - Spire Mode only */}
{simpleMode && <ActivityLog activityLog={store.activityLog} />}
{simpleMode && <ActivityLog activityLog={activityLog} />}
{/* Study Progress - Normal mode only */}
{!simpleMode && store.currentStudyTarget && (
{!simpleMode && currentStudyTarget && (
<Card className="bg-gray-900/80 border-purple-600/50">
<CardContent className="pt-4 pb-4">
<div className="text-xs text-gray-400 mb-2">Study: {getSkillName(store.currentStudyTarget.id)}</div>
<div className="text-xs text-gray-400 mb-2">Study: {getSkillName(currentStudyTarget.id)}</div>
<div className="h-2 bg-gray-800 rounded-full overflow-hidden">
<div className="h-full rounded-full transition-all duration-300 bg-purple-500" style={{ width: `${Math.min(100, (store.currentStudyTarget.progress / store.currentStudyTarget.required) * 100)}%` }} />
<div className="h-full rounded-full transition-all duration-300 bg-purple-500" style={{ width: `${Math.min(100, (currentStudyTarget.progress / currentStudyTarget.required) * 100)}%` }} />
</div>
{store.parallelStudyTarget && (
{parallelStudyTarget && (
<div className="mt-3 p-2 rounded border border-cyan-600/50 bg-cyan-900/20">
<div className="text-xs text-cyan-300 mb-1">Parallel: {getSkillName(store.parallelStudyTarget.id)} (50% speed)</div>
<div className="text-xs text-cyan-300 mb-1">Parallel: {getSkillName(parallelStudyTarget.id)} (50% speed)</div>
<div className="h-1.5 bg-gray-800 rounded-full overflow-hidden">
<div className="h-full rounded-full transition-all duration-300 bg-cyan-500" style={{ width: `${Math.min(100, (store.parallelStudyTarget.progress / store.parallelStudyTarget.required) * 100)}%` }} />
<div className="h-full rounded-full transition-all duration-300 bg-cyan-500" style={{ width: `${Math.min(100, (parallelStudyTarget.progress / parallelStudyTarget.required) * 100)}%` }} />
</div>
</div>
)}
@@ -303,30 +353,30 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
)}
{/* Crafting Progress - Normal mode only */}
{!simpleMode && (store.designProgress || store.preparationProgress || store.applicationProgress) && (
{!simpleMode && (designProgress || preparationProgress || applicationProgress) && (
<Card className="bg-gray-900/80 border-cyan-600/50">
<CardContent className="pt-4 pb-4">
{store.designProgress && (
{designProgress && (
<div className="mb-3">
<div className="text-xs text-gray-400 mb-1">Design Progress</div>
<div className="h-2 bg-gray-800 rounded-full overflow-hidden">
<div className="h-full rounded-full transition-all duration-300 bg-cyan-500" style={{ width: `${Math.min(100, (store.designProgress.progress / store.designProgress.required) * 100)}%` }} />
<div className="h-full rounded-full transition-all duration-300 bg-cyan-500" style={{ width: `${Math.min(100, (designProgress.progress / designProgress.required) * 100)}%` }} />
</div>
</div>
)}
{store.preparationProgress && (
{preparationProgress && (
<div className="mb-3">
<div className="text-xs text-gray-400 mb-1">Preparation Progress</div>
<div className="h-2 bg-gray-800 rounded-full overflow-hidden">
<div className="h-full rounded-full transition-all duration-300 bg-cyan-500" style={{ width: `${Math.min(100, (store.preparationProgress.progress / store.preparationProgress.required) * 100)}%` }} />
<div className="h-full rounded-full transition-all duration-300 bg-cyan-500" style={{ width: `${Math.min(100, (preparationProgress.progress / preparationProgress.required) * 100)}%` }} />
</div>
</div>
)}
{store.applicationProgress && (
{applicationProgress && (
<div>
<div className="text-xs text-gray-400 mb-1">Application Progress</div>
<div className="h-2 bg-gray-800 rounded-full overflow-hidden">
<div className="h-full rounded-full transition-all duration-300 bg-cyan-500" style={{ width: `${Math.min(100, (store.applicationProgress.progress / store.applicationProgress.required) * 100)}%` }} />
<div className="h-full rounded-full transition-all duration-300 bg-cyan-500" style={{ width: `${Math.min(100, (applicationProgress.progress / applicationProgress.required) * 100)}%` }} />
</div>
</div>
)}
@@ -338,31 +388,3 @@ export function SpireTab({ store, simpleMode = false }: SpireTabProps) {
}
SpireTab.displayName = "SpireTab";
function getSkillName(skillId: string): string {
const { SKILLS_DEF } = require('@/lib/game/constants');
return SKILLS_DEF[skillId]?.name || skillId;
}
function fmt(value: number): string {
if (value >= 1e12) return (value / 1e12).toFixed(2) + 't';
if (value >= 1e9) return (value / 1e9).toFixed(2) + 'b';
if (value >= 1e6) return (value / 1e6).toFixed(2) + 'm';
if (value >= 1e3) return (value / 1e3).toFixed(2) + 'k';
return value.toFixed(0);
}
function getGolemDef(golemId: string) {
const { GOLEMS_DEF } = require('@/lib/game/data/golems');
return GOLEMS_DEF[golemId];
}
function getGolemDamage(golemId: string, skills: any) {
const { getGolemDamage } = require('@/lib/game/data/golems');
return getGolemDamage(golemId, skills);
}
function getGolemAttackSpeed(golemId: string, skills: any) {
const { getGolemAttackSpeed } = require('@/lib/game/data/golems');
return getGolemAttackSpeed(golemId, skills);
}