feat: add enchanter disciplines to unlock enchantment effects via perk progression
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s

- Add unlocksEffects field to DisciplinePerk type
- Add unlockEffects action to crafting store (deduplicating merge)
- Modify discipline processTick to detect perk thresholds and return unlocked effect IDs
- Wire gameStore tick to pass unlocked effects to crafting store
- Create 8 new enchanter disciplines with tiered effect unlocks:
  Basic/Advanced Weapon, Utility, Mana, Basic/Intermediate/Advanced Spell, Special
- Higher-tier disciplines require prerequisite disciplines
- Add processedPerks tracking to prevent duplicate unlocks
- Split enchanter disciplines into modular files (enchanter, enchanter-utility, enchanter-spells, enchanter-special)
- All tests pass (784/784), no new TS errors, all files under 400 lines
This commit is contained in:
2026-05-23 19:29:45 +02:00
parent 868dfb6225
commit 14f25fffda
11 changed files with 576 additions and 4 deletions
+31 -1
View File
@@ -7,9 +7,13 @@ import {
calculateManaDrain,
calculateStatBonus,
canProceedDiscipline,
getUnlockedPerks,
} from '../utils/discipline-math';
import { baseDisciplines } from '../data/disciplines/base';
import { enchanterDisciplines } from '../data/disciplines/enchanter';
import { enchanterUtilityDisciplines } from '../data/disciplines/enchanter-utility';
import { enchanterSpellDisciplines } from '../data/disciplines/enchanter-spells';
import { enchanterSpecialDisciplines } from '../data/disciplines/enchanter-special';
import { fabricatorDisciplines } from '../data/disciplines/fabricator';
import { invokerDisciplines } from '../data/disciplines/invoker';
import { MAX_CONCURRENT_DISCIPLINES } from '../types/disciplines';
@@ -17,6 +21,9 @@ import { MAX_CONCURRENT_DISCIPLINES } from '../types/disciplines';
const ALL_DISCIPLINES = [
...baseDisciplines,
...enchanterDisciplines,
...enchanterUtilityDisciplines,
...enchanterSpellDisciplines,
...enchanterSpecialDisciplines,
...fabricatorDisciplines,
...invokerDisciplines,
];
@@ -27,6 +34,7 @@ export interface DisciplineStoreState {
activeIds: string[];
concurrentLimit: number;
totalXP: number;
processedPerks: string[];
}
export interface DisciplineStoreActions {
@@ -35,6 +43,7 @@ export interface DisciplineStoreActions {
processTick: (mana: { rawMana: number; elements: Record<string, { current: number }> }) => {
rawMana: number;
elements: Record<string, { current: number }>;
unlockedEffects: string[];
};
}
@@ -47,6 +56,7 @@ export const useDisciplineStore = create<DisciplineStore>()(
activeIds: [],
concurrentLimit: MAX_CONCURRENT_DISCIPLINES,
totalXP: 0,
processedPerks: [],
activate(id, gameState) {
set((s) => {
@@ -95,6 +105,8 @@ export const useDisciplineStore = create<DisciplineStore>()(
const elements = { ...mana.elements };
let newXP = s.totalXP;
const newDisciplines = { ...s.disciplines };
const newUnlockedEffects: string[] = [];
const newProcessedPerks = [...s.processedPerks];
for (const id of s.activeIds) {
const disc = newDisciplines[id];
@@ -122,8 +134,25 @@ export const useDisciplineStore = create<DisciplineStore>()(
};
}
const oldXP = disc.xp;
newDisciplines[id] = { ...disc, xp: disc.xp + 1 };
newXP += 1;
// Check for newly unlocked perks that unlock effects
if (def.perks.length > 0) {
const oldPerks = getUnlockedPerks(def, oldXP);
const newPerks = getUnlockedPerks(def, disc.xp + 1);
const oldPerkIds = new Set(oldPerks.map(p => p.id));
for (const perk of newPerks) {
if (!oldPerkIds.has(perk.id) && perk.unlocksEffects) {
const perkKey = `${id}:${perk.id}`;
if (!newProcessedPerks.includes(perkKey)) {
newUnlockedEffects.push(...perk.unlocksEffects);
newProcessedPerks.push(perkKey);
}
}
}
}
}
const newLimit = Math.min(
@@ -135,9 +164,10 @@ export const useDisciplineStore = create<DisciplineStore>()(
disciplines: newDisciplines,
totalXP: newXP,
concurrentLimit: Math.max(s.concurrentLimit, newLimit),
processedPerks: newProcessedPerks,
});
return { rawMana, elements };
return { rawMana, elements, unlockedEffects: newUnlockedEffects };
},
}),
{ storage: createSafeStorage(), name: 'mana-loop-discipline-store' }