// ─── Skill Slice ────────────────────────────────────────────────────────────── // Manages skills, studying, and skill progress import type { StateCreator } from 'zustand'; import type { GameState, StudyTarget, SkillUpgradeChoice } from '../types'; import { SKILLS_DEF, SPELLS_DEF, getStudySpeedMultiplier, getStudyCostMultiplier } from '../constants'; import { getUpgradesForSkillAtMilestone, getNextTierSkill, getTierMultiplier } from '../skill-evolution'; import { computeEffects } from '../upgrade-effects'; export interface SkillSlice { // State skills: Record; skillProgress: Record; skillUpgrades: Record; skillTiers: Record; currentStudyTarget: StudyTarget | null; parallelStudyTarget: StudyTarget | null; // Actions startStudyingSkill: (skillId: string) => void; startStudyingSpell: (spellId: string) => void; startParallelStudySkill: (skillId: string) => void; cancelStudy: () => void; cancelParallelStudy: () => void; selectSkillUpgrade: (skillId: string, upgradeId: string) => void; deselectSkillUpgrade: (skillId: string, upgradeId: string) => void; commitSkillUpgrades: (skillId: string, upgradeIds: string[], milestone: 5 | 10) => void; tierUpSkill: (skillId: string) => void; // Getters getSkillUpgradeChoices: (skillId: string, milestone: 5 | 10) => { available: SkillUpgradeChoice[]; selected: string[] }; } export const createSkillSlice = ( set: StateCreator['set'], get: () => GameState ): SkillSlice => ({ skills: {}, skillProgress: {}, skillUpgrades: {}, skillTiers: {}, currentStudyTarget: null, parallelStudyTarget: null, startStudyingSkill: (skillId: string) => { const state = get(); const sk = SKILLS_DEF[skillId]; if (!sk) return; const currentLevel = state.skills[skillId] || 0; if (currentLevel >= sk.max) return; // Check prerequisites if (sk.req) { for (const [r, rl] of Object.entries(sk.req)) { if ((state.skills[r] || 0) < rl) return; } } const costMult = getStudyCostMultiplier(state.skills); const totalCost = Math.floor(sk.base * (currentLevel + 1) * costMult); const manaCostPerHour = totalCost / sk.studyTime; set({ currentAction: 'study', currentStudyTarget: { type: 'skill', id: skillId, progress: state.skillProgress[skillId] || 0, required: sk.studyTime, manaCostPerHour, }, log: [`📚 Started studying ${sk.name}... (${Math.floor(manaCostPerHour)} mana/hour)`, ...state.log.slice(0, 49)], }); }, startStudyingSpell: (spellId: string) => { const state = get(); const sp = SPELLS_DEF[spellId]; if (!sp || state.spells[spellId]?.learned) return; const costMult = getStudyCostMultiplier(state.skills); const totalCost = Math.floor(sp.unlock * costMult); const studyTime = sp.studyTime || (sp.tier * 4); const manaCostPerHour = totalCost / studyTime; set({ currentAction: 'study', currentStudyTarget: { type: 'spell', id: spellId, progress: state.spells[spellId]?.studyProgress || 0, required: studyTime, manaCostPerHour, }, spells: { ...state.spells, [spellId]: { ...(state.spells[spellId] || { learned: false, level: 0 }), studyProgress: state.spells[spellId]?.studyProgress || 0, }, }, log: [`📚 Started studying ${sp.name}... (${Math.floor(manaCostPerHour)} mana/hour)`, ...state.log.slice(0, 49)], }); }, startParallelStudySkill: (skillId: string) => { const state = get(); if (state.parallelStudyTarget) return; if (!state.currentStudyTarget) return; const sk = SKILLS_DEF[skillId]; if (!sk) return; const currentLevel = state.skills[skillId] || 0; if (currentLevel >= sk.max) return; if (state.currentStudyTarget.id === skillId) return; set({ parallelStudyTarget: { type: 'skill', id: skillId, progress: state.skillProgress[skillId] || 0, required: sk.studyTime, manaCostPerHour: 0, // Parallel study doesn't cost extra }, log: [`📚 Started parallel study of ${sk.name}... (50% speed)`, ...state.log.slice(0, 49)], }); }, cancelStudy: () => { const state = get(); if (!state.currentStudyTarget) return; const savedProgress = state.currentStudyTarget.progress; const log = ['📖 Study paused. Progress saved.', ...state.log.slice(0, 49)]; if (state.currentStudyTarget.type === 'skill') { set({ currentStudyTarget: null, currentAction: 'meditate', skillProgress: { ...state.skillProgress, [state.currentStudyTarget.id]: savedProgress, }, log, }); } else { set({ currentStudyTarget: null, currentAction: 'meditate', spells: { ...state.spells, [state.currentStudyTarget.id]: { ...(state.spells[state.currentStudyTarget.id] || { learned: false, level: 0 }), studyProgress: savedProgress, }, }, log, }); } }, cancelParallelStudy: () => { set((state) => { if (!state.parallelStudyTarget) return state; return { parallelStudyTarget: null, log: ['📖 Parallel study cancelled.', ...state.log.slice(0, 49)], }; }); }, selectSkillUpgrade: (skillId: string, upgradeId: string) => { set((state) => { const current = state.skillUpgrades?.[skillId] || []; if (current.includes(upgradeId)) return state; if (current.length >= 2) return state; return { skillUpgrades: { ...state.skillUpgrades, [skillId]: [...current, upgradeId], }, }; }); }, deselectSkillUpgrade: (skillId: string, upgradeId: string) => { set((state) => { const current = state.skillUpgrades?.[skillId] || []; return { skillUpgrades: { ...state.skillUpgrades, [skillId]: current.filter(id => id !== upgradeId), }, }; }); }, commitSkillUpgrades: (skillId: string, upgradeIds: string[], milestone: 5 | 10) => { set((state) => { const existingUpgrades = state.skillUpgrades?.[skillId] || []; const otherMilestoneUpgrades = existingUpgrades.filter( id => milestone === 5 ? id.includes('_l10') : id.includes('_l5') ); return { skillUpgrades: { ...state.skillUpgrades, [skillId]: [...otherMilestoneUpgrades, ...upgradeIds], }, }; }); }, tierUpSkill: (skillId: string) => { const state = get(); const baseSkillId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId; const currentTier = state.skillTiers?.[baseSkillId] || 1; const nextTier = currentTier + 1; if (nextTier > 5) return; const nextTierSkillId = `${baseSkillId}_t${nextTier}`; set({ skillTiers: { ...state.skillTiers, [baseSkillId]: nextTier, }, skills: { ...state.skills, [nextTierSkillId]: 0, [skillId]: 0, }, skillProgress: { ...state.skillProgress, [skillId]: 0, [nextTierSkillId]: 0, }, skillUpgrades: { ...state.skillUpgrades, [nextTierSkillId]: [], [skillId]: [], }, log: [`🌟 ${SKILLS_DEF[baseSkillId]?.name || baseSkillId} evolved to Tier ${nextTier}!`, ...state.log.slice(0, 49)], }); }, getSkillUpgradeChoices: (skillId: string, milestone: 5 | 10) => { const state = get(); const baseSkillId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId; const tier = state.skillTiers?.[baseSkillId] || 1; const available = getUpgradesForSkillAtMilestone(skillId, milestone, state.skillTiers || {}); const selected = (state.skillUpgrades?.[skillId] || []).filter(id => available.some(u => u.id === id) ); return { available, selected }; }, }); // Process study progress (called during tick) export function processStudy(state: GameState, deltaHours: number): Partial { if (state.currentAction !== 'study' || !state.currentStudyTarget) return {}; const target = state.currentStudyTarget; const studySpeedMult = getStudySpeedMultiplier(state.skills); const progressGain = deltaHours * studySpeedMult; const manaCost = progressGain * target.manaCostPerHour; let rawMana = state.rawMana; let totalManaGathered = state.totalManaGathered; let skills = state.skills; let skillProgress = state.skillProgress; let spells = state.spells; const log = [...state.log]; if (rawMana >= manaCost) { rawMana -= manaCost; totalManaGathered += manaCost; const newProgress = target.progress + progressGain; if (newProgress >= target.required) { // Study complete if (target.type === 'skill') { const skillId = target.id; const currentLevel = skills[skillId] || 0; skills = { ...skills, [skillId]: currentLevel + 1 }; skillProgress = { ...skillProgress, [skillId]: 0 }; log.unshift(`✅ ${SKILLS_DEF[skillId]?.name} Lv.${currentLevel + 1} mastered!`); } else if (target.type === 'spell') { const spellId = target.id; spells = { ...spells, [spellId]: { learned: true, level: 1, studyProgress: 0 }, }; log.unshift(`📖 ${SPELLS_DEF[spellId]?.name} learned!`); } return { rawMana, totalManaGathered, skills, skillProgress, spells, currentStudyTarget: null, currentAction: 'meditate', log, }; } return { rawMana, totalManaGathered, currentStudyTarget: { ...target, progress: newProgress }, }; } // Not enough mana log.unshift('⚠️ Not enough mana to continue studying. Progress saved.'); if (target.type === 'skill') { return { currentStudyTarget: null, currentAction: 'meditate', skillProgress: { ...skillProgress, [target.id]: target.progress }, log, }; } else { return { currentStudyTarget: null, currentAction: 'meditate', spells: { ...spells, [target.id]: { ...(spells[target.id] || { learned: false, level: 0 }), studyProgress: target.progress, }, }, log, }; } }