Files
Mana-Loop/src/lib/game/store.ts
T
n8n-gitea ca86b6268c
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 55s
refactor: resolve structural inconsistencies and dead code
- Fix broken barrel exports in components/game/index.ts
- Remove skill system from stores (gameStore, gameActions, gameLoopActions, gameHooks, craftingStore, combat)
- Remove skill system from components (page.tsx, LeftPanel, StatsTab, SpellsTab, EnchantmentDesigner, EnchantmentPreparer, GameContext/Provider)
- Delete dead code: stats/ directory, attunements/ directory, layout/ Header+TabBar, shared/ StudyProgress+UpgradeDialog duplicates, effects.ts.fix, study-slice.ts, navigation-slice.ts
- Delete legacy store/ and store-modules/ directories, redirect remaining callers
- Merge root formatting.ts into utils/formatting.ts
- Move effects files (dynamic-compute, upgrade-effects, special-effects, upgrade-effects.types) into effects/ directory
- Move debug-context.tsx into components/game/debug/
- Create tabs/index.ts barrel for tab components
- Fix page.tsx lazy imports to use tabs barrel
- Fix all broken import paths across codebase
- Remove SKILLS_DEF and skill-evolution references
- Trim store.ts to under 400 lines by removing dead skill actions
2026-05-18 14:21:59 +02:00

356 lines
11 KiB
TypeScript
Executable File

// ─── Game Store (Refactored) ──────────────────────────────────────────────
// Main entry point - imports from modular store components
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { GameState, GameAction, ActivityLogEntry } from './types';
import { addActivityLogEntry } from './utils/activity-log';
import {
computeMaxMana, computeRegen, computeClickMana,
getMeditationBonus,
} from './utils/mana-utils';
import {
calcDamage, calcInsight, getIncursionStrength, canAffordSpellCost, deductSpellCost,
} from './utils/combat-utils';
import { generateFloorState } from './utils/room-utils';
// Re-export formatting functions for backward compatibility
export { fmt, fmtDec } from './utils/formatting';
export { getFloorMaxHP, getFloorElement } from './utils/floor-utils';
// Re-export computed stats functions for backward compatibility and tests
export {
computeMaxMana, computeRegen, computeClickMana,
getMeditationBonus,
} from './utils/mana-utils';
export {
calcDamage, calcInsight, getIncursionStrength, canAffordSpellCost, deductSpellCost,
} from './utils/combat-utils';
// ─── Initial State ───────────────────────────────────────────────────────
interface MakeInitialOptions {
loopCount?: number;
totalInsight?: number;
insight?: number;
prestigeUpgrades?: Record<string, number>;
}
export function makeInitial(opts?: MakeInitialOptions): GameState {
return {
day: 1,
hour: 0,
rawMana: 100,
maxMana: 100,
elements: {},
skills: {},
skillUpgrades: {},
skillTiers: {},
spells: {},
currentAction: 'meditate' as GameAction,
currentStudyTarget: null,
parallelStudyTarget: null,
activeSpell: null,
currentFloor: 100,
floorHP: 1000,
floorMaxHP: 1000,
currentRoom: generateFloorState(100),
maxFloorReached: 100,
paused: false,
gameOver: false,
victory: false,
loopCount: opts?.loopCount ?? 0,
totalInsight: opts?.totalInsight ?? 0,
insight: opts?.insight ?? 0,
loopInsight: 0,
prestigeUpgrades: opts?.prestigeUpgrades ?? {},
signedPacts: [],
attunements: {},
golemancy: { enabledGolems: [] },
memories: [],
memorySlots: 0,
log: [],
activityLog: [],
meditateTicks: 0,
totalManaGathered: 0,
equippedInstances: {},
equipmentInstances: {},
lootInventory: {},
blueprints: {},
spireMode: false,
};
}
// ─── Game Store Interface ─────────────────────────────────────────────────
export interface GameStore extends GameState {
tick: () => void;
gatherMana: () => void;
setAction: (action: GameAction) => void;
addActivityLog: (eventType: string, message: string, details?: ActivityLogEntry['details']) => void;
setSpell: (spellId: string) => void;
cancelStudy: () => void;
convertMana: (element: string, amount: number) => void;
unlockElement: (element: string) => void;
doPrestige: (id: string, selectedManaType?: string) => void;
startNewLoop: () => void;
togglePause: () => void;
resetGame: () => void;
addLog: (message: string) => void;
addAttunementXP: (attunementId: string, amount: number) => void;
toggleGolem: (golemId: string) => void;
setEnabledGolems: (golemIds: string[]) => void;
debugUnlockAttunement: (attunementId: string) => void;
debugAddElementalMana: (element: string, amount: number) => void;
debugSetTime: (day: number, hour: number) => void;
debugAddAttunementXP: (attunementId: string, amount: number) => void;
debugSetFloor: (floor: number) => void;
resetFloorHP: () => void;
getMaxMana: () => number;
getRegen: () => number;
getClickMana: () => number;
getDamage: (spellId: string) => number;
getMeditationMultiplier: () => number;
canCastSpell: (spellId: string) => boolean;
enterSpireMode: () => void;
climbDownFloor: () => void;
exitSpireMode: () => void;
}
// ─── Store Implementation ────────────────────────────────────────────────
export const useGameStore = create<GameStore>()(
persist(
(set, get) => ({
...makeInitial(),
getMaxMana: () => computeMaxMana(get()),
getRegen: () => computeRegen(get()),
getClickMana: () => computeClickMana(get()),
getDamage: (spellId: string) => calcDamage(get(), spellId),
getMeditationMultiplier: () => getMeditationBonus(get().meditateTicks, {}),
canCastSpell: (spellId: string) => {
const state = get();
const spell = state.spells?.[spellId];
if (!spell) return false;
return true;
},
addLog: (message: string) => {
set((state) => ({
log: [message, ...(state.log || []).slice(0, 49)],
}));
},
addActivityLog: (eventType: string, message: string, details?: ActivityLogEntry['details']) => {
set((state) => ({
activityLog: addActivityLogEntry(state, eventType, message, details),
}));
},
tick: () => {
const state = get();
if (state.gameOver || state.paused) return;
const maxMana = computeMaxMana(state);
const baseRegen = computeRegen(state);
let hour = state.hour + 1;
let day = state.day;
if (hour >= 24) { hour -= 24; day += 1; }
if (day > 100) {
const insightGained = calcInsight(state);
set({
day, hour, gameOver: true, victory: false, loopInsight: insightGained,
log: [`⏰ The loop ends. Gained ${insightGained} Insight.`, ...state.log.slice(0, 49)],
});
return;
}
let rawMana = state.rawMana + baseRegen;
rawMana = Math.min(rawMana, maxMana);
set({
day, hour, rawMana,
meditateTicks: state.currentAction === 'meditate' ? state.meditateTicks + 1 : 0,
});
},
gatherMana: () => {
const state = get();
const clickMana = computeClickMana(state);
const maxMana = computeMaxMana(state);
set((s) => ({
rawMana: Math.min(s.rawMana + clickMana, maxMana),
totalManaGathered: s.totalManaGathered + clickMana,
}));
},
setAction: (action: GameAction) => {
set({ currentAction: action });
},
setSpell: (spellId: string) => {
set({ activeSpell: spellId });
},
cancelStudy: () => {
set({ currentStudyTarget: null, currentAction: 'meditate' });
},
convertMana: (element: string, amount: number) => {
set((s) => {
const elem = s.elements?.[element];
if (!elem || !elem.unlocked) return s;
const canConvert = Math.min(amount, Math.floor(s.rawMana / 10), elem.max - elem.current);
if (canConvert > 0) {
return {
rawMana: s.rawMana - canConvert * 10,
elements: { ...s.elements, [element]: { ...elem, current: elem.current + canConvert } },
};
}
return s;
});
},
unlockElement: (element: string) => {
set((s) => ({
elements: { ...s.elements, [element]: { ...s.elements[element], unlocked: true } },
}));
},
doPrestige: (id: string, selectedManaType?: string) => {
set((s) => ({
prestigeUpgrades: { ...s.prestigeUpgrades, [id]: (s.prestigeUpgrades[id] || 0) + 1 },
}));
},
startNewLoop: () => {
const state = get();
const insightGained = state.loopInsight || 0;
set({
...makeInitial({
loopCount: state.loopCount + 1,
totalInsight: (state.totalInsight || 0) + insightGained,
insight: (state.insight || 0) + insightGained,
prestigeUpgrades: state.prestigeUpgrades,
}),
});
},
togglePause: () => {
set((s) => ({ paused: !s.paused }));
},
resetGame: () => {
set(makeInitial());
},
addAttunementXP: (attunementId: string, amount: number) => {
set((s) => {
const attState = s.attunements?.[attunementId];
if (!attState) return s;
return {
attunements: { ...s.attunements, [attunementId]: { ...attState, experience: attState.experience + amount } },
};
});
},
toggleGolem: (golemId: string) => {
set((s) => {
const enabledGolems = s.golemancy?.enabledGolems || [];
const isEnabled = enabledGolems.includes(golemId);
return {
golemancy: { ...s.golemancy, enabledGolems: isEnabled ? enabledGolems.filter(id => id !== golemId) : [...enabledGolems, golemId] },
};
});
},
setEnabledGolems: (golemIds: string[]) => {
set((s) => ({
golemancy: { ...s.golemancy, enabledGolems: golemIds },
}));
},
debugUnlockAttunement: (attunementId: string) => {
set((s) => ({
attunements: { ...s.attunements, [attunementId]: { id: attunementId, active: true, level: 1, experience: 0 } },
}));
},
debugAddElementalMana: (element: string, amount: number) => {
set((s) => {
const elem = s.elements?.[element];
if (!elem) return s;
return {
elements: { ...s.elements, [element]: { ...elem, current: Math.min(elem.current + amount, elem.max) } },
};
});
},
debugSetTime: (day: number, hour: number) => {
set({ day, hour });
},
debugAddAttunementXP: (attunementId: string, amount: number) => {
get().addAttunementXP(attunementId, amount);
},
debugSetFloor: (floor: number) => {
set((s) => ({
currentFloor: floor,
currentRoom: generateFloorState(floor),
floorMaxHP: 100 + floor * 50,
floorHP: 100 + floor * 50,
}));
},
resetFloorHP: () => {
set((s) => ({
floorHP: s.floorMaxHP,
currentRoom: generateFloorState(s.currentFloor),
}));
},
enterSpireMode: () => {
set({ spireMode: true });
},
climbDownFloor: () => {
set((s) => {
if (s.currentFloor <= 1) return s;
const newFloor = s.currentFloor - 1;
return {
currentFloor: newFloor,
currentRoom: generateFloorState(newFloor),
floorMaxHP: 100 + newFloor * 50,
floorHP: 100 + newFloor * 50,
};
});
},
exitSpireMode: () => {
set({ spireMode: false });
},
}),
{
name: 'mana-loop-game-store',
}
)
);
// ─── Game Loop Hook ───────────────────────────────────────────────────────────
export function useGameLoop() {
const tick = useGameStore((s) => s.tick);
return {
start: () => {
const interval = setInterval(tick, 1000);
return () => clearInterval(interval);
},
};
}