Phase 3: Split DebugTab.tsx into functional components
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 4m17s

This commit is contained in:
Refactoring Agent
2026-04-24 14:12:52 +02:00
parent 23d0a129c1
commit d6b85d6367
11 changed files with 924 additions and 861 deletions
+62 -142
View File
@@ -4,7 +4,7 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { TICK_MS, HOURS_PER_TICK, MAX_DAY, SPELLS_DEF, GUARDIANS, ELEMENTS, BASE_UNLOCKED_ELEMENTS, getStudySpeedMultiplier } from '../constants';
import { TICK_MS, HOURS_PER_TICK, MAX_DAY, SPELLS_DEF, GUARDIANS, getStudySpeedMultiplier } from '../constants';
import { computeEffects, hasSpecial, SPECIAL_EFFECTS } from '../upgrade-effects';
import {
computeMaxMana,
@@ -16,13 +16,13 @@ import {
calcInsight,
calcDamage,
deductSpellCost,
canAffordSpellCost,
} from '../utils';
import { useUIStore } from './uiStore';
import { usePrestigeStore } from './prestigeStore';
import { useManaStore } from './manaStore';
import { useSkillStore } from './skillStore';
import { useCombatStore, makeInitialSpells } from './combatStore';
import type { Memory } from '../types';
export interface GameCoordinatorState {
day: number;
@@ -37,7 +37,6 @@ export interface GameCoordinatorStore extends GameCoordinatorState {
resetGame: () => void;
togglePause: () => void;
startNewLoop: () => void;
gatherMana: () => void;
initGame: () => void;
}
@@ -49,21 +48,6 @@ const initialState: GameCoordinatorState = {
initialized: false,
};
// Helper function for checking spell cost affordability
function canAffordSpell(
cost: { type: string; element?: string; amount: number },
rawMana: number,
elements: Record<string, { current: number; max: number; unlocked: boolean }>
): boolean {
if (cost.type === 'raw') {
return rawMana >= cost.amount;
} else if (cost.element) {
const elem = elements[cost.element];
return elem && elem.unlocked && elem.current >= cost.amount;
}
return false;
}
export const useGameStore = create<GameCoordinatorStore>()(
persist(
(set, get) => ({
@@ -187,25 +171,12 @@ export const useGameStore = create<GameCoordinatorStore>()(
}
}
// Convert action - auto convert mana
// Convert action - delegate to manaStore
if (combatState.currentAction === 'convert') {
const unlockedElements = Object.entries(elements)
.filter(([, e]) => e.unlocked && e.current < e.max);
if (unlockedElements.length > 0 && rawMana >= 100) {
unlockedElements.sort((a, b) => (b[1].max - b[1].current) - (a[1].max - a[1].current));
const [targetId, targetState] = unlockedElements[0];
const canConvert = Math.min(
Math.floor(rawMana / 100),
targetState.max - targetState.current
);
if (canConvert > 0) {
rawMana -= canConvert * 100;
elements = {
...elements,
[targetId]: { ...targetState, current: targetState.current + canConvert }
};
}
const convertResult = useManaStore.getState().processConvertAction(rawMana);
if (convertResult) {
rawMana = convertResult.rawMana;
elements = convertResult.elements;
}
}
@@ -228,41 +199,27 @@ export const useGameStore = create<GameCoordinatorStore>()(
}
}
// Combat
let { currentFloor, floorHP, floorMaxHP, maxFloorReached, castProgress } = combatState;
const floorElement = getFloorElement(currentFloor);
// Combat - delegate to combatStore
if (combatState.currentAction === 'climb') {
const spellId = combatState.activeSpell;
const spellDef = SPELLS_DEF[spellId];
if (spellDef) {
const baseAttackSpeed = 1 + (skillState.skills.quickCast || 0) * 0.05;
const totalAttackSpeed = baseAttackSpeed * effects.attackSpeedMultiplier;
const spellCastSpeed = spellDef.castSpeed || 1;
const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed;
castProgress = (castProgress || 0) + progressPerTick;
// Process complete casts
while (castProgress >= 1 && canAffordSpell(spellDef.cost, rawMana, elements)) {
const afterCost = deductSpellCost(spellDef.cost, rawMana, elements);
rawMana = afterCost.rawMana;
elements = afterCost.elements;
totalManaGathered += spellDef.cost.amount;
// Calculate damage
let dmg = calcDamage(
{ skills: skillState.skills, signedPacts: prestigeState.signedPacts },
spellId,
floorElement
);
const combatResult = useCombatStore.getState().processCombatTick(
skillState.skills,
rawMana,
elements,
maxMana,
effects.attackSpeedMultiplier,
(floor, wasGuardian) => {
if (wasGuardian) {
addLog(`⚔️ ${GUARDIANS[floor]?.name || 'Guardian'} defeated! Visit the Grimoire to sign a pact.`);
} else if (floor % 5 === 0) {
addLog(`🏰 Floor ${floor} cleared!`);
}
},
(damage) => {
// Apply upgrade damage multipliers and bonuses
dmg = dmg * effects.baseDamageMultiplier + effects.baseDamageBonus;
let dmg = damage * effects.baseDamageMultiplier + effects.baseDamageBonus;
// Executioner: +100% damage to enemies below 25% HP
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && floorHP / floorMaxHP < 0.25) {
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && combatState.floorHP / combatState.floorMaxHP < 0.25) {
dmg *= 2;
}
@@ -278,34 +235,17 @@ export const useGameStore = create<GameCoordinatorStore>()(
addLog(`✨ Spell Echo! Double damage!`);
}
// Apply damage
floorHP = Math.max(0, floorHP - dmg);
castProgress -= 1;
if (floorHP <= 0) {
// Floor cleared
const wasGuardian = GUARDIANS[currentFloor];
if (wasGuardian && !prestigeState.defeatedGuardians.includes(currentFloor) && !prestigeState.signedPacts.includes(currentFloor)) {
usePrestigeStore.getState().addDefeatedGuardian(currentFloor);
addLog(`⚔️ ${wasGuardian.name} defeated! Visit the Grimoire to sign a pact.`);
} else if (!wasGuardian) {
if (currentFloor % 5 === 0) {
addLog(`🏰 Floor ${currentFloor} cleared!`);
}
}
currentFloor = currentFloor + 1;
if (currentFloor > 100) {
currentFloor = 100;
}
floorMaxHP = getFloorMaxHP(currentFloor);
floorHP = floorMaxHP;
maxFloorReached = Math.max(maxFloorReached, currentFloor);
castProgress = 0;
useCombatStore.getState().advanceFloor();
}
return { rawMana, elements, modifiedDamage: dmg };
}
);
rawMana = combatResult.rawMana;
elements = combatResult.elements;
totalManaGathered += combatResult.totalManaGathered || 0;
// Log any messages from combat
if (combatResult.logMessages) {
combatResult.logMessages.forEach(msg => addLog(msg));
}
}
@@ -317,13 +257,6 @@ export const useGameStore = create<GameCoordinatorStore>()(
elements,
});
useCombatStore.setState({
floorHP,
floorMaxHP,
maxFloorReached,
castProgress,
});
set({
day,
hour,
@@ -331,6 +264,33 @@ export const useGameStore = create<GameCoordinatorStore>()(
});
},
resetGame: () => {
// Clear all persisted state
localStorage.removeItem('mana-loop-ui-storage');
localStorage.removeItem('mana-loop-prestige-storage');
localStorage.removeItem('mana-loop-mana-storage');
localStorage.removeItem('mana-loop-skill-storage');
localStorage.removeItem('mana-loop-combat-storage');
localStorage.removeItem('mana-loop-game-storage');
const startFloor = 1;
useUIStore.getState().resetUI();
usePrestigeStore.getState().resetPrestige();
useManaStore.getState().resetMana({}, {}, {}, {});
useSkillStore.getState().resetSkills();
useCombatStore.getState().resetCombat(startFloor);
set({
...initialState,
initialized: true,
});
},
togglePause: () => {
useUIStore.getState().togglePause();
},
gatherMana: () => {
const skillState = useSkillStore.getState();
const manaState = useManaStore.getState();
@@ -351,47 +311,7 @@ export const useGameStore = create<GameCoordinatorStore>()(
effects
);
useManaStore.setState({
rawMana: Math.min(manaState.rawMana + cm, max),
totalManaGathered: manaState.totalManaGathered + cm,
});
},
resetGame: () => {
// Clear all persisted state
localStorage.removeItem('mana-loop-ui-storage');
localStorage.removeItem('mana-loop-prestige-storage');
localStorage.removeItem('mana-loop-mana-storage');
localStorage.removeItem('mana-loop-skill-storage');
localStorage.removeItem('mana-loop-combat-storage');
localStorage.removeItem('mana-loop-game-storage');
const startFloor = 1;
const elemMax = 10;
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
Object.keys(ELEMENTS).forEach((k) => {
elements[k] = {
current: 0,
max: elemMax,
unlocked: BASE_UNLOCKED_ELEMENTS.includes(k),
};
});
useUIStore.getState().resetUI();
usePrestigeStore.getState().resetPrestige();
useManaStore.getState().resetMana({}, {}, {}, {});
useSkillStore.getState().resetSkills();
useCombatStore.getState().resetCombat(startFloor);
set({
...initialState,
initialized: true,
});
},
togglePause: () => {
useUIStore.getState().togglePause();
useManaStore.getState().gatherMana(cm, max);
},
startNewLoop: () => {