fix: apply mana drain when practicing disciplines
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m25s

- processTick() now calls calculateManaDrain() for non-conversion disciplines
- Insufficient mana triggers auto-paused state (skips XP accrual)
- Conversion disciplines (sourceManaTypes) correctly skip pool drain
- Auto-paused disciplines can be re-activated when mana is restored
- Updated 7 tests across 3 files to reflect drain model
This commit is contained in:
2026-06-10 10:50:40 +02:00
parent 432378fa86
commit bdf2b0050f
6 changed files with 145 additions and 26 deletions
+45 -3
View File
@@ -6,6 +6,7 @@ import type { DisciplineState } from '../types/disciplines';
import type { ElementState } from '../types';
import {
calculateStatBonus,
calculateManaDrain,
canProceedDiscipline,
checkDisciplinePrerequisites,
getUnlockedPerks
@@ -85,12 +86,18 @@ export const useDisciplineStore = create<DisciplineStore>()(
// Allow re-activation if discipline exists but is paused
const existing = s.disciplines[id];
if (activeIds.includes(id)) {
// If already active and paused (manually or auto), un-pause it
// If already active and paused (manually or auto-paused due to insufficient mana), un-pause it
if (existing?.paused) {
return {
disciplines: { ...s.disciplines, [id]: { ...existing, paused: false, autoPaused: false } },
};
}
// If auto-paused due to insufficient mana, allow re-activation (un-pause when mana is restored)
if (existing?.autoPaused) {
return {
disciplines: { ...s.disciplines, [id]: { ...existing, autoPaused: false } },
};
}
return s;
}
@@ -170,13 +177,14 @@ export const useDisciplineStore = create<DisciplineStore>()(
processTick(mana) {
const s = get();
const rawMana = mana.rawMana;
let rawMana = mana.rawMana;
const elements = { ...mana.elements };
let newXP = s.totalXP;
const newDisciplines = { ...s.disciplines };
const newUnlockedEffects: string[] = [];
const newUnlockedRecipes: string[] = [];
const newProcessedPerks = [...(s.processedPerks ?? [])];
const autoPausedNames: string[] = [];
for (const id of s.activeIds ?? []) {
const disc = newDisciplines[id];
@@ -187,6 +195,40 @@ export const useDisciplineStore = create<DisciplineStore>()(
const def = DISCIPLINE_MAP[id];
if (!def) continue;
// ── Mana drain (skip for conversion disciplines) ──────────────
// Conversion disciplines (with sourceManaTypes) are handled by
// the unified conversion system — they contribute to conversion
// rates, not direct pool drain.
const isConversionDiscipline = !!(def.sourceManaTypes && def.sourceManaTypes.length > 0);
if (!isConversionDiscipline) {
const drain = calculateManaDrain(def.drainBase, disc.xp, def.difficultyFactor);
// Determine which mana pool to drain from
if (def.manaType === 'raw') {
if (rawMana < drain) {
// Insufficient raw mana — auto-pause
newDisciplines[id] = { ...disc, autoPaused: true };
autoPausedNames.push(def.name);
continue; // skip XP accrual
}
rawMana = Math.max(0, rawMana - drain);
} else {
// Element-specific drain
const elemState = elements[def.manaType];
if (!elemState || elemState.current < drain) {
// Insufficient element mana — auto-pause
newDisciplines[id] = { ...disc, autoPaused: true };
autoPausedNames.push(def.name);
continue; // skip XP accrual
}
elements[def.manaType] = {
...elemState,
current: Math.max(0, elemState.current - drain),
};
}
}
const oldXP = disc.xp;
// Compute discipline XP bonus directly to avoid circular import
let xpBonus = 0;
@@ -255,7 +297,7 @@ export const useDisciplineStore = create<DisciplineStore>()(
processedPerks: newProcessedPerks,
});
return { rawMana, elements, unlockedEffects: newUnlockedEffects, unlockedRecipes: newUnlockedRecipes, autoPausedNames: [] };
return { rawMana, elements, unlockedEffects: newUnlockedEffects, unlockedRecipes: newUnlockedRecipes, autoPausedNames };
},
}),
{ storage: createSafeStorage(), name: 'mana-loop-discipline-store', version: 1, partialize: (state) => ({ disciplines: state.disciplines, activeIds: state.activeIds, concurrentLimit: state.concurrentLimit, totalXP: state.totalXP, processedPerks: state.processedPerks }) }