181 lines
5.3 KiB
TypeScript
Executable File
181 lines
5.3 KiB
TypeScript
Executable File
// ─── Pact Slice ───────────────────────────────────────────────────────────────
|
|
// Manages guardian pacts, signing, and mana unlocking
|
|
|
|
import type { StateCreator } from 'zustand';
|
|
import type { GameState } from '../types';
|
|
import { GUARDIANS, ELEMENTS } from '../constants';
|
|
import { computePactMultiplier, computePactInsightMultiplier } from './computed';
|
|
|
|
export interface PactSlice {
|
|
// State
|
|
signedPacts: number[];
|
|
pendingPactOffer: number | null;
|
|
maxPacts: number;
|
|
pactSigningProgress: {
|
|
floor: number;
|
|
progress: number;
|
|
required: number;
|
|
manaCost: number;
|
|
} | null;
|
|
signedPactDetails: Record<number, {
|
|
floor: number;
|
|
guardianId: string;
|
|
signedAt: { day: number; hour: number };
|
|
skillLevels: Record<string, number>;
|
|
}>;
|
|
pactInterferenceMitigation: number;
|
|
pactSynergyUnlocked: boolean;
|
|
|
|
// Actions
|
|
acceptPact: (floor: number) => void;
|
|
declinePact: (floor: number) => void;
|
|
|
|
// Computed getters
|
|
getPactMultiplier: () => number;
|
|
getPactInsightMultiplier: () => number;
|
|
}
|
|
|
|
export const createPactSlice = (
|
|
set: StateCreator<GameState>['set'],
|
|
get: () => GameState
|
|
): PactSlice => ({
|
|
signedPacts: [],
|
|
pendingPactOffer: null,
|
|
maxPacts: 1,
|
|
pactSigningProgress: null,
|
|
signedPactDetails: {},
|
|
pactInterferenceMitigation: 0,
|
|
pactSynergyUnlocked: false,
|
|
|
|
acceptPact: (floor: number) => {
|
|
const state = get();
|
|
const guardian = GUARDIANS[floor];
|
|
if (!guardian || state.signedPacts.includes(floor)) return;
|
|
|
|
const maxPacts = 1 + (state.prestigeUpgrades.pactCapacity || 0);
|
|
if (state.signedPacts.length >= maxPacts) {
|
|
set({
|
|
log: [`⚠️ Cannot sign more pacts! Maximum: ${maxPacts}.`, ...state.log.slice(0, 49)],
|
|
});
|
|
return;
|
|
}
|
|
|
|
const baseCost = guardian.signingCost.mana;
|
|
const discount = Math.min((state.prestigeUpgrades.pactDiscount || 0) * 0.1, 0.5);
|
|
const manaCost = Math.floor(baseCost * (1 - discount));
|
|
|
|
if (state.rawMana < manaCost) {
|
|
set({
|
|
log: [`⚠️ Need ${manaCost} mana to sign pact with ${guardian.name}!`, ...state.log.slice(0, 49)],
|
|
});
|
|
return;
|
|
}
|
|
|
|
const baseTime = guardian.signingCost.time;
|
|
const haste = Math.min((state.prestigeUpgrades.pactHaste || 0) * 0.1, 0.5);
|
|
const signingTime = Math.max(1, baseTime * (1 - haste));
|
|
|
|
set({
|
|
rawMana: state.rawMana - manaCost,
|
|
pactSigningProgress: {
|
|
floor,
|
|
progress: 0,
|
|
required: signingTime,
|
|
manaCost,
|
|
},
|
|
pendingPactOffer: null,
|
|
currentAction: 'study',
|
|
log: [`📜 Beginning pact signing with ${guardian.name}... (${signingTime}h, ${manaCost} mana)`, ...state.log.slice(0, 49)],
|
|
});
|
|
},
|
|
|
|
declinePact: (floor: number) => {
|
|
const state = get();
|
|
const guardian = GUARDIANS[floor];
|
|
if (!guardian) return;
|
|
|
|
set({
|
|
pendingPactOffer: null,
|
|
log: [`🚫 Declined pact with ${guardian.name}.`, ...state.log.slice(0, 49)],
|
|
});
|
|
},
|
|
|
|
getPactMultiplier: () => computePactMultiplier(get()),
|
|
getPactInsightMultiplier: () => computePactInsightMultiplier(get()),
|
|
});
|
|
|
|
// Process pact signing progress (called during tick)
|
|
export function processPactSigning(state: GameState, deltaHours: number): Partial<GameState> {
|
|
if (!state.pactSigningProgress) return {};
|
|
|
|
const progress = state.pactSigningProgress.progress + deltaHours;
|
|
const log = [...state.log];
|
|
|
|
if (progress >= state.pactSigningProgress.required) {
|
|
const floor = state.pactSigningProgress.floor;
|
|
const guardian = GUARDIANS[floor];
|
|
if (!guardian || state.signedPacts.includes(floor)) {
|
|
return { pactSigningProgress: null };
|
|
}
|
|
|
|
const signedPacts = [...state.signedPacts, floor];
|
|
const signedPactDetails = {
|
|
...state.signedPactDetails,
|
|
[floor]: {
|
|
floor,
|
|
guardianId: guardian.element,
|
|
signedAt: { day: state.day, hour: state.hour },
|
|
skillLevels: {},
|
|
},
|
|
};
|
|
|
|
// Unlock mana types
|
|
let elements = { ...state.elements };
|
|
for (const elemId of guardian.unlocksMana) {
|
|
if (elements[elemId]) {
|
|
elements = {
|
|
...elements,
|
|
[elemId]: { ...elements[elemId], unlocked: true },
|
|
};
|
|
}
|
|
}
|
|
|
|
// Check for compound element unlocks
|
|
const unlockedSet = new Set(
|
|
Object.entries(elements)
|
|
.filter(([, e]) => e.unlocked)
|
|
.map(([id]) => id)
|
|
);
|
|
|
|
for (const [elemId, elemDef] of Object.entries(ELEMENTS)) {
|
|
if (elemDef.recipe && !elements[elemId]?.unlocked) {
|
|
const canUnlock = elemDef.recipe.every(comp => unlockedSet.has(comp));
|
|
if (canUnlock) {
|
|
elements = {
|
|
...elements,
|
|
[elemId]: { ...elements[elemId], unlocked: true },
|
|
};
|
|
log.unshift(`🔮 ${elemDef.name} mana unlocked through component synergy!`);
|
|
}
|
|
}
|
|
}
|
|
|
|
log.unshift(`📜 Pact with ${guardian.name} signed! ${guardian.unlocksMana.map(e => ELEMENTS[e]?.name || e).join(', ')} mana unlocked!`);
|
|
|
|
return {
|
|
signedPacts,
|
|
signedPactDetails,
|
|
elements,
|
|
pactSigningProgress: null,
|
|
log,
|
|
};
|
|
}
|
|
|
|
return {
|
|
pactSigningProgress: {
|
|
...state.pactSigningProgress,
|
|
progress,
|
|
},
|
|
};
|
|
}
|