181 lines
6.6 KiB
TypeScript
Executable File
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)],
|
|
};
|
|
});
|
|
},
|
|
};
|
|
}
|