fix: Elemental Mana Capacity disciplines now increase element capacity
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m23s

- Add optional baseMax field to ElementState to track prestige-derived max separately from bonuses
- Add computeElementMaxWithBonuses action to manaStore that computes max = baseMax + per-element bonus
- Apply per-element cap bonuses from disciplines and equipment in game tick (elementCap_* keys)
- Fix resetMana to use correct prestige key (elementalAttune instead of nonexistent elemMax)
- Add store migration (v1->v2) to populate baseMax for existing saved games
- Extract pact ritual processing to pipelines/pact-ritual.ts
- Extract element cap bonus utilities to utils/element-cap-bonus.ts
- Fix inline element types in crafting-fabricator.ts
- Update test fixtures to include baseMax in element literals

Fixes #185
This commit is contained in:
2026-05-28 19:49:47 +02:00
parent 8fef73d233
commit 6355cf308b
11 changed files with 183 additions and 51 deletions
+42 -5
View File
@@ -39,6 +39,13 @@ export interface ManaActions {
setElementMax: (max: number) => void;
craftComposite: (target: string, recipe: string[]) => Result<void>;
/**
* Compute and apply per-element max from baseMax + bonuses.
* Caller provides the bonus map (elementCap_* from disciplines/equipment).
* This sets max = baseMax + bonus for each element, preventing double-counting.
*/
computeElementMaxWithBonuses: (perElementBonuses: Record<string, number>) => void;
// Helper for gameStore coordination
processConvertAction: (rawMana: number) => { rawMana: number; elements: Record<string, ElementState> } | null;
@@ -64,6 +71,7 @@ export const useManaStore = create<ManaStore>()(
{
current: 0,
max: 10,
baseMax: 10,
unlocked: BASE_UNLOCKED_ELEMENTS.includes(k),
}
])
@@ -148,10 +156,27 @@ export const useManaStore = create<ManaStore>()(
setElementMax: (max: number) => {
set((state) => ({
elements: Object.fromEntries(Object.entries(state.elements).map(([k, v]) => [k, { ...v, max }])) as Record<string, ElementState>,
elements: Object.fromEntries(Object.entries(state.elements).map(([k, v]) => [k, { ...v, max, baseMax: v.baseMax ?? max }])) as Record<string, ElementState>,
}));
},
computeElementMaxWithBonuses: (perElementBonuses: Record<string, number>) => {
set((state) => {
const newElements = { ...state.elements };
let changed = false;
for (const [element, bonus] of Object.entries(perElementBonuses)) {
if (newElements[element] && bonus > 0) {
const newMax = (newElements[element].baseMax ?? newElements[element].max) + bonus;
if (newElements[element].max !== newMax) {
newElements[element] = { ...newElements[element], max: newMax };
changed = true;
}
}
}
return changed ? { elements: newElements } : state;
});
},
craftComposite: (target: string, recipe: string[]) => {
const state = get();
const costs: Record<string, number> = {};
@@ -162,12 +187,13 @@ export const useManaStore = create<ManaStore>()(
}
const newElems = { ...state.elements };
const baseMax = state.elements[target]?.baseMax ?? 10;
for (const [r, amt] of Object.entries(costs)) {
newElems[r] = { ...newElems[r], current: newElems[r].current - amt };
}
const targetElem = newElems[target];
newElems[target] = { ...(targetElem || { current: 0, max: 10, unlocked: false }), current: (targetElem?.current || 0) + 1, unlocked: true };
newElems[target] = { ...(targetElem || { current: 0, max: 10, baseMax: 10, unlocked: false }), current: (targetElem?.current || 0) + 1, unlocked: true, baseMax };
set({ elements: newElems });
return okVoid();
},
@@ -191,7 +217,7 @@ export const useManaStore = create<ManaStore>()(
resetMana: (
prestigeUpgrades: Record<string, number>,
) => {
const elementMax = 10 + (prestigeUpgrades.elemMax || 0) * 5;
const elementMax = 10 + (prestigeUpgrades.elementalAttune || 0) * 25;
const startingMana = 10 + (prestigeUpgrades.manaStart || 0) * 10;
set({ rawMana: startingMana, meditateTicks: 0, totalManaGathered: 0, elements: makeInitialElements(elementMax, prestigeUpgrades) });
},
@@ -199,8 +225,19 @@ export const useManaStore = create<ManaStore>()(
{
storage: createSafeStorage(),
name: 'mana-loop-mana',
version: 1,
version: 2,
partialize: (state) => ({ rawMana: state.rawMana, meditateTicks: state.meditateTicks, totalManaGathered: state.totalManaGathered, elements: state.elements }),
migrate: (persistedState: any, _version) => {
// Migration: add baseMax to elements that don't have it
if (persistedState && persistedState.elements) {
for (const k of Object.keys(persistedState.elements)) {
if (persistedState.elements[k].baseMax === undefined) {
persistedState.elements[k].baseMax = persistedState.elements[k].max ?? 10;
}
}
}
return persistedState;
},
}
)
);
@@ -214,7 +251,7 @@ export function makeInitialElements(
const elements: Record<string, ElementState> = {};
for (const k of Object.keys(ELEMENTS)) {
const isUnlocked = BASE_UNLOCKED_ELEMENTS.includes(k);
elements[k] = { current: isUnlocked ? elemStart : 0, max: elementMax, unlocked: isUnlocked };
elements[k] = { current: isUnlocked ? elemStart : 0, max: elementMax, baseMax: elementMax, unlocked: isUnlocked };
}
return elements;
}