Files
Mana-Loop/src/lib/game/study-slice.ts
2026-03-28 06:07:39 +00:00

181 lines
6.6 KiB
TypeScript
Executable File

// ─── Study Slice ─────────────────────────────────────────────────────────────
// Actions for studying skills and spells
import type { GameState } from './types';
import { SKILLS_DEF, SPELLS_DEF, getStudyCostMultiplier } from './constants';
// ─── Study Actions Interface ──────────────────────────────────────────────────
export interface StudyActions {
startStudyingSkill: (skillId: string) => void;
startStudyingSpell: (spellId: string) => void;
cancelStudy: () => void;
startParallelStudySkill: (skillId: string) => void;
cancelParallelStudy: () => void;
}
// ─── Study Slice Factory ──────────────────────────────────────────────────────
export function createStudySlice(
set: (partial: Partial<GameState> | ((state: GameState) => Partial<GameState>)) => void,
get: () => GameState
): StudyActions {
return {
// Start studying a skill - mana is deducted per hour, not upfront
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;
}
}
// Calculate total mana cost and cost per hour
const costMult = getStudyCostMultiplier(state.skills);
const totalCost = Math.floor(sk.base * (currentLevel + 1) * costMult);
const manaCostPerHour = Math.ceil(totalCost / sk.studyTime);
// Must have at least 1 hour worth of mana to start
if (state.rawMana < manaCostPerHour) return;
// Start studying (no upfront cost - mana is deducted per hour during study)
set({
currentAction: 'study',
currentStudyTarget: {
type: 'skill',
id: skillId,
progress: state.skillProgress[skillId] || 0,
required: sk.studyTime,
manaCostPerHour: manaCostPerHour,
totalCost: totalCost,
},
log: [`📚 Started studying ${sk.name} (${manaCostPerHour} mana/hr)...`, ...state.log.slice(0, 49)],
});
},
// Start studying a spell
startStudyingSpell: (spellId: string) => {
const state = get();
const sp = SPELLS_DEF[spellId];
if (!sp || state.spells[spellId]?.learned) return;
// Calculate total mana cost and cost per hour
const costMult = getStudyCostMultiplier(state.skills);
const totalCost = Math.floor(sp.unlock * costMult);
const studyTime = sp.studyTime || (sp.tier * 4);
const manaCostPerHour = Math.ceil(totalCost / studyTime);
// Must have at least 1 hour worth of mana to start
if (state.rawMana < manaCostPerHour) return;
// Start studying (no upfront cost - mana is deducted per hour during study)
set({
currentAction: 'study',
currentStudyTarget: {
type: 'spell',
id: spellId,
progress: state.spells[spellId]?.studyProgress || 0,
required: studyTime,
manaCostPerHour: manaCostPerHour,
totalCost: totalCost,
},
spells: {
...state.spells,
[spellId]: { ...(state.spells[spellId] || { learned: false, level: 0 }), studyProgress: state.spells[spellId]?.studyProgress || 0 },
},
log: [`📚 Started studying ${sp.name} (${manaCostPerHour} mana/hr)...`, ...state.log.slice(0, 49)],
});
},
// Cancel current study (saves progress)
cancelStudy: () => {
const state = get();
if (!state.currentStudyTarget) return;
// Knowledge retention bonus
const retentionBonus = 1 + (state.skills.knowledgeRetention || 0) * 0.2;
const savedProgress = Math.min(
state.currentStudyTarget.progress,
state.currentStudyTarget.required * retentionBonus
);
// Save progress
if (state.currentStudyTarget.type === 'skill') {
set({
currentStudyTarget: null,
currentAction: 'meditate',
skillProgress: {
...state.skillProgress,
[state.currentStudyTarget.id]: savedProgress,
},
log: [`📖 Study interrupted. Progress saved.`, ...state.log.slice(0, 49)],
});
} else if (state.currentStudyTarget.type === 'spell') {
set({
currentStudyTarget: null,
currentAction: 'meditate',
spells: {
...state.spells,
[state.currentStudyTarget.id]: {
...(state.spells[state.currentStudyTarget.id] || { learned: false, level: 0 }),
studyProgress: savedProgress,
},
},
log: [`📖 Study interrupted. Progress saved.`, ...state.log.slice(0, 49)],
});
}
},
// Start parallel study of a skill (requires Parallel Mind upgrade)
startParallelStudySkill: (skillId: string) => {
const state = get();
if (state.parallelStudyTarget) return; // Already have parallel study
if (!state.currentStudyTarget) return; // Need primary study
const sk = SKILLS_DEF[skillId];
if (!sk) return;
const currentLevel = state.skills[skillId] || 0;
if (currentLevel >= sk.max) return;
// Can't study same thing in parallel
if (state.currentStudyTarget.id === skillId) return;
// Calculate mana cost for parallel study
const costMult = getStudyCostMultiplier(state.skills);
const totalCost = Math.floor(sk.base * (currentLevel + 1) * costMult);
const manaCostPerHour = Math.ceil(totalCost / sk.studyTime);
set({
parallelStudyTarget: {
type: 'skill',
id: skillId,
progress: state.skillProgress[skillId] || 0,
required: sk.studyTime,
manaCostPerHour: Math.ceil(manaCostPerHour / 2), // Half speed = half mana cost per tick
totalCost: totalCost,
},
log: [`📚 Started parallel study of ${sk.name}... (50% speed)`, ...state.log.slice(0, 49)],
});
},
// Cancel parallel study
cancelParallelStudy: () => {
set((state) => {
if (!state.parallelStudyTarget) return state;
return {
parallelStudyTarget: null,
log: ['📖 Parallel study cancelled.', ...state.log.slice(0, 49)],
};
});
},
};
}