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
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:
@@ -335,6 +335,21 @@ export const useCraftingStore = create<CraftingStore>()(
|
||||
unequipItem: (slot: EquipmentSlot) => {
|
||||
unequipItemAction(slot, set);
|
||||
},
|
||||
|
||||
unlockEffects: (effectIds: string[]) => {
|
||||
set((state) => {
|
||||
const existing = new Set(state.unlockedEffects);
|
||||
let changed = false;
|
||||
for (const id of effectIds) {
|
||||
if (!existing.has(id)) {
|
||||
existing.add(id);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (!changed) return state;
|
||||
return { unlockedEffects: Array.from(existing) };
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
{
|
||||
|
||||
@@ -69,6 +69,7 @@ export interface CraftingActions {
|
||||
setSelectedEquipmentInstance: (id: string | null) => void;
|
||||
resetEnchantmentSelection: () => void;
|
||||
clearLastError: () => void;
|
||||
unlockEffects: (effectIds: string[]) => void;
|
||||
}
|
||||
|
||||
export type CraftingStore = CraftingState & CraftingActions;
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -267,6 +267,14 @@ export const useGameStore = create<GameCoordinatorStore>()(
|
||||
rawMana = disciplineResult.rawMana;
|
||||
elements = disciplineResult.elements;
|
||||
|
||||
// Unlock enchantment effects from newly unlocked discipline perks
|
||||
if (disciplineResult.unlockedEffects.length > 0) {
|
||||
useCraftingStore.getState().unlockEffects(disciplineResult.unlockedEffects);
|
||||
for (const effectId of disciplineResult.unlockedEffects) {
|
||||
addLog(`✨ Discipline insight unlocked: ${effectId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Combat — delegate to combatStore
|
||||
if (ctx.combat.currentAction === 'climb') {
|
||||
const combatResult = useCombatStore.getState().processCombatTick(
|
||||
|
||||
Reference in New Issue
Block a user