refactor: split bloated state types into State + Actions interfaces (issue #102)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s
- CombatState: split into CombatState (data) + CombatActions + CombatStore - PrestigeState: split into PrestigeState (data) + PrestigeActions + PrestigeStore - ManaState: split into ManaState (data) + ManaActions + ManaStore - GameState: deprecated, removed from barrel exports - crafting-actions: updated to use CraftingState instead of GameState - combat-utils/mana-utils: replaced Pick<GameState,...> with focused interfaces - DisciplineCardProps: split into Definition + Runtime + Callbacks - stores/index.ts: now exports both State and Actions types
This commit is contained in:
@@ -6,23 +6,28 @@ import { persist } from 'zustand/middleware';
|
||||
import { ELEMENTS, MANA_PER_ELEMENT, BASE_UNLOCKED_ELEMENTS } from '../constants';
|
||||
import type { ElementState } from '../types';
|
||||
|
||||
// ─── Mana State (data only) ─────────────────────────────────────────────────
|
||||
|
||||
export interface ManaState {
|
||||
rawMana: number;
|
||||
meditateTicks: number;
|
||||
totalManaGathered: number;
|
||||
elements: Record<string, ElementState>;
|
||||
|
||||
// Actions
|
||||
}
|
||||
|
||||
// ─── Mana Actions ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ManaActions {
|
||||
setRawMana: (amount: number) => void;
|
||||
addRawMana: (amount: number, maxMana: number) => void;
|
||||
spendRawMana: (amount: number) => boolean;
|
||||
gatherMana: (amount: number, maxMana: number) => void;
|
||||
|
||||
|
||||
// Meditation
|
||||
setMeditateTicks: (ticks: number) => void;
|
||||
incrementMeditateTicks: () => void;
|
||||
resetMeditateTicks: () => void;
|
||||
|
||||
|
||||
// Elements
|
||||
convertMana: (element: string, amount: number) => boolean;
|
||||
unlockElement: (element: string, cost: number) => boolean;
|
||||
@@ -30,10 +35,10 @@ export interface ManaState {
|
||||
spendElementMana: (element: string, amount: number) => boolean;
|
||||
setElementMax: (max: number) => void;
|
||||
craftComposite: (target: string, recipe: string[]) => boolean;
|
||||
|
||||
|
||||
// Helper for gameStore coordination
|
||||
processConvertAction: (rawMana: number) => { rawMana: number; elements: Record<string, ElementState> } | null;
|
||||
|
||||
|
||||
// Reset
|
||||
resetMana: (
|
||||
prestigeUpgrades: Record<string, number>,
|
||||
@@ -43,7 +48,11 @@ export interface ManaState {
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const useManaStore = create<ManaState>()(
|
||||
// ─── Combined Mana Store Type ────────────────────────────────────────────────
|
||||
|
||||
export type ManaStore = ManaState & ManaActions;
|
||||
|
||||
export const useManaStore = create<ManaStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
rawMana: 10,
|
||||
@@ -59,62 +68,62 @@ export const useManaStore = create<ManaState>()(
|
||||
}
|
||||
])
|
||||
) as Record<string, ElementState>,
|
||||
|
||||
|
||||
setRawMana: (amount: number) => {
|
||||
set({ rawMana: Math.max(0, amount) });
|
||||
},
|
||||
|
||||
|
||||
addRawMana: (amount: number, maxMana: number) => {
|
||||
set((state) => ({
|
||||
rawMana: Math.min(state.rawMana + amount, maxMana),
|
||||
totalManaGathered: state.totalManaGathered + amount,
|
||||
}));
|
||||
},
|
||||
|
||||
|
||||
spendRawMana: (amount: number) => {
|
||||
const state = get();
|
||||
if (state.rawMana < amount) return false;
|
||||
|
||||
|
||||
set({ rawMana: state.rawMana - amount });
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
gatherMana: (amount: number, maxMana: number) => {
|
||||
set((state) => ({
|
||||
rawMana: Math.min(state.rawMana + amount, maxMana),
|
||||
totalManaGathered: state.totalManaGathered + amount,
|
||||
}));
|
||||
},
|
||||
|
||||
|
||||
setMeditateTicks: (ticks: number) => {
|
||||
set({ meditateTicks: ticks });
|
||||
},
|
||||
|
||||
|
||||
incrementMeditateTicks: () => {
|
||||
set((state) => ({ meditateTicks: state.meditateTicks + 1 }));
|
||||
},
|
||||
|
||||
|
||||
resetMeditateTicks: () => {
|
||||
set({ meditateTicks: 0 });
|
||||
},
|
||||
|
||||
|
||||
convertMana: (element: string, amount: number) => {
|
||||
const state = get();
|
||||
const elem = state.elements[element];
|
||||
if (!elem?.unlocked) return false;
|
||||
|
||||
|
||||
const cost = MANA_PER_ELEMENT * amount;
|
||||
if (state.rawMana < cost) return false;
|
||||
if (elem.current >= elem.max) return false;
|
||||
|
||||
|
||||
const canConvert = Math.min(
|
||||
amount,
|
||||
Math.floor(state.rawMana / MANA_PER_ELEMENT),
|
||||
elem.max - elem.current
|
||||
);
|
||||
|
||||
|
||||
if (canConvert <= 0) return false;
|
||||
|
||||
|
||||
set({
|
||||
rawMana: state.rawMana - canConvert * MANA_PER_ELEMENT,
|
||||
elements: {
|
||||
@@ -122,15 +131,15 @@ export const useManaStore = create<ManaState>()(
|
||||
[element]: { ...elem, current: elem.current + canConvert },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
unlockElement: (element: string, cost: number) => {
|
||||
const state = get();
|
||||
if (state.elements[element]?.unlocked) return false;
|
||||
if (state.rawMana < cost) return false;
|
||||
|
||||
|
||||
set({
|
||||
rawMana: state.rawMana - cost,
|
||||
elements: {
|
||||
@@ -138,15 +147,15 @@ export const useManaStore = create<ManaState>()(
|
||||
[element]: { ...state.elements[element], unlocked: true },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
addElementMana: (element: string, amount: number, max: number) => {
|
||||
set((state) => {
|
||||
const elem = state.elements[element];
|
||||
if (!elem) return state;
|
||||
|
||||
|
||||
return {
|
||||
elements: {
|
||||
...state.elements,
|
||||
@@ -158,22 +167,22 @@ export const useManaStore = create<ManaState>()(
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
spendElementMana: (element: string, amount: number) => {
|
||||
const state = get();
|
||||
const elem = state.elements[element];
|
||||
if (!elem || elem.current < amount) return false;
|
||||
|
||||
|
||||
set({
|
||||
elements: {
|
||||
...state.elements,
|
||||
[element]: { ...elem, current: elem.current - amount },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
setElementMax: (max: number) => {
|
||||
set((state) => ({
|
||||
elements: Object.fromEntries(
|
||||
@@ -181,21 +190,21 @@ export const useManaStore = create<ManaState>()(
|
||||
) as Record<string, ElementState>,
|
||||
}));
|
||||
},
|
||||
|
||||
|
||||
craftComposite: (target: string, recipe: string[]) => {
|
||||
const state = get();
|
||||
|
||||
|
||||
// Count required ingredients
|
||||
const costs: Record<string, number> = {};
|
||||
recipe.forEach(r => {
|
||||
costs[r] = (costs[r] || 0) + 1;
|
||||
});
|
||||
|
||||
|
||||
// Check if we have all ingredients
|
||||
for (const [r, amt] of Object.entries(costs)) {
|
||||
if ((state.elements[r]?.current || 0) < amt) return false;
|
||||
}
|
||||
|
||||
|
||||
// Deduct ingredients
|
||||
const newElems = { ...state.elements };
|
||||
for (const [r, amt] of Object.entries(costs)) {
|
||||
@@ -204,7 +213,7 @@ export const useManaStore = create<ManaState>()(
|
||||
current: newElems[r].current - amt,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Add crafted element
|
||||
const targetElem = newElems[target];
|
||||
newElems[target] = {
|
||||
@@ -212,38 +221,38 @@ export const useManaStore = create<ManaState>()(
|
||||
current: (targetElem?.current || 0) + 1,
|
||||
unlocked: true,
|
||||
};
|
||||
|
||||
|
||||
set({ elements: newElems });
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
processConvertAction: (rawMana: number) => {
|
||||
const state = get();
|
||||
const elements = { ...state.elements };
|
||||
|
||||
|
||||
const unlockedElements = Object.entries(elements)
|
||||
.filter(([, e]) => e.unlocked && e.current < e.max);
|
||||
|
||||
if (unlockedElements.length === 0 || rawMana < 100) return null;
|
||||
|
||||
|
||||
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) return null;
|
||||
|
||||
|
||||
rawMana -= canConvert * 100;
|
||||
const updatedElements = {
|
||||
...elements,
|
||||
[targetId]: { ...targetState, current: targetState.current + canConvert }
|
||||
};
|
||||
|
||||
|
||||
return { rawMana, elements: updatedElements };
|
||||
},
|
||||
|
||||
|
||||
resetMana: (
|
||||
prestigeUpgrades: Record<string, number>,
|
||||
skills: Record<string, number> = {},
|
||||
@@ -253,7 +262,7 @@ export const useManaStore = create<ManaState>()(
|
||||
const elementMax = 10 + (prestigeUpgrades.elemMax || 0) * 5;
|
||||
const startingMana = 10 + (prestigeUpgrades.manaStart || 0) * 10;
|
||||
const elements = makeInitialElements(elementMax, prestigeUpgrades);
|
||||
|
||||
|
||||
set({
|
||||
rawMana: startingMana,
|
||||
meditateTicks: 0,
|
||||
@@ -279,7 +288,7 @@ export function makeInitialElements(
|
||||
prestigeUpgrades: Record<string, number> = {}
|
||||
): Record<string, ElementState> {
|
||||
const elemStart = (prestigeUpgrades.elemStart || 0) * 5;
|
||||
|
||||
|
||||
const elements: Record<string, ElementState> = {};
|
||||
Object.keys(ELEMENTS).forEach(k => {
|
||||
const isUnlocked = BASE_UNLOCKED_ELEMENTS.includes(k);
|
||||
@@ -289,6 +298,6 @@ export function makeInitialElements(
|
||||
unlocked: isUnlocked,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user