Initial commit
This commit is contained in:
Executable
+197
@@ -0,0 +1,197 @@
|
||||
// ─── Mana Slice ───────────────────────────────────────────────────────────────
|
||||
// Manages raw mana, elements, and meditation
|
||||
|
||||
import type { StateCreator } from 'zustand';
|
||||
import type { GameState, ElementState, SpellCost } from '../types';
|
||||
import { ELEMENTS, MANA_PER_ELEMENT, BASE_UNLOCKED_ELEMENTS } from '../constants';
|
||||
import { computeMaxMana, computeElementMax, computeClickMana, canAffordSpellCost, getMeditationBonus } from './computed';
|
||||
import { computeEffects } from '../upgrade-effects';
|
||||
|
||||
export interface ManaSlice {
|
||||
// State
|
||||
rawMana: number;
|
||||
totalManaGathered: number;
|
||||
meditateTicks: number;
|
||||
elements: Record<string, ElementState>;
|
||||
|
||||
// Actions
|
||||
gatherMana: () => void;
|
||||
convertMana: (element: string, amount: number) => void;
|
||||
unlockElement: (element: string) => void;
|
||||
craftComposite: (target: string) => void;
|
||||
|
||||
// Computed getters
|
||||
getMaxMana: () => number;
|
||||
getRegen: () => number;
|
||||
getClickMana: () => number;
|
||||
getMeditationMultiplier: () => number;
|
||||
}
|
||||
|
||||
export const createManaSlice = (
|
||||
set: StateCreator<GameState>['set'],
|
||||
get: () => GameState
|
||||
): ManaSlice => ({
|
||||
rawMana: 10,
|
||||
totalManaGathered: 0,
|
||||
meditateTicks: 0,
|
||||
elements: (() => {
|
||||
const elems: Record<string, ElementState> = {};
|
||||
const pu = get().prestigeUpgrades;
|
||||
const elemMax = computeElementMax(get());
|
||||
|
||||
Object.keys(ELEMENTS).forEach((k) => {
|
||||
const isUnlocked = BASE_UNLOCKED_ELEMENTS.includes(k);
|
||||
let startAmount = 0;
|
||||
|
||||
if (isUnlocked && pu.elemStart) {
|
||||
startAmount = pu.elemStart * 5;
|
||||
}
|
||||
|
||||
elems[k] = {
|
||||
current: startAmount,
|
||||
max: elemMax,
|
||||
unlocked: isUnlocked,
|
||||
};
|
||||
});
|
||||
return elems;
|
||||
})(),
|
||||
|
||||
gatherMana: () => {
|
||||
const state = get();
|
||||
let cm = computeClickMana(state);
|
||||
|
||||
// Mana overflow bonus
|
||||
const overflowBonus = 1 + (state.skills.manaOverflow || 0) * 0.25;
|
||||
cm = Math.floor(cm * overflowBonus);
|
||||
|
||||
const effects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {});
|
||||
const max = computeMaxMana(state, effects);
|
||||
|
||||
// Mana Echo: 10% chance to gain double mana from clicks
|
||||
const hasManaEcho = effects.specials?.has('MANA_ECHO') ?? false;
|
||||
if (hasManaEcho && Math.random() < 0.1) {
|
||||
cm *= 2;
|
||||
}
|
||||
|
||||
set({
|
||||
rawMana: Math.min(state.rawMana + cm, max),
|
||||
totalManaGathered: state.totalManaGathered + cm,
|
||||
});
|
||||
},
|
||||
|
||||
convertMana: (element: string, amount: number = 1) => {
|
||||
const state = get();
|
||||
const e = state.elements[element];
|
||||
if (!e?.unlocked) return;
|
||||
|
||||
const cost = MANA_PER_ELEMENT * amount;
|
||||
if (state.rawMana < cost) return;
|
||||
if (e.current >= e.max) return;
|
||||
|
||||
const canConvert = Math.min(
|
||||
amount,
|
||||
Math.floor(state.rawMana / MANA_PER_ELEMENT),
|
||||
e.max - e.current
|
||||
);
|
||||
|
||||
set({
|
||||
rawMana: state.rawMana - canConvert * MANA_PER_ELEMENT,
|
||||
elements: {
|
||||
...state.elements,
|
||||
[element]: { ...e, current: e.current + canConvert },
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
unlockElement: (element: string) => {
|
||||
const state = get();
|
||||
if (state.elements[element]?.unlocked) return;
|
||||
|
||||
const cost = 500;
|
||||
if (state.rawMana < cost) return;
|
||||
|
||||
set({
|
||||
rawMana: state.rawMana - cost,
|
||||
elements: {
|
||||
...state.elements,
|
||||
[element]: { ...state.elements[element], unlocked: true },
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
craftComposite: (target: string) => {
|
||||
const state = get();
|
||||
const edef = ELEMENTS[target];
|
||||
if (!edef?.recipe) return;
|
||||
|
||||
const recipe = edef.recipe;
|
||||
const costs: Record<string, number> = {};
|
||||
recipe.forEach((r) => {
|
||||
costs[r] = (costs[r] || 0) + 1;
|
||||
});
|
||||
|
||||
// Check ingredients
|
||||
for (const [r, amt] of Object.entries(costs)) {
|
||||
if ((state.elements[r]?.current || 0) < amt) return;
|
||||
}
|
||||
|
||||
const newElems = { ...state.elements };
|
||||
for (const [r, amt] of Object.entries(costs)) {
|
||||
newElems[r] = { ...newElems[r], current: newElems[r].current - amt };
|
||||
}
|
||||
|
||||
// Elemental crafting bonus
|
||||
const craftBonus = 1 + (state.skills.elemCrafting || 0) * 0.25;
|
||||
const outputAmount = Math.floor(craftBonus);
|
||||
|
||||
const effects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {});
|
||||
const elemMax = computeElementMax(state, effects);
|
||||
newElems[target] = {
|
||||
...(newElems[target] || { current: 0, max: elemMax, unlocked: false }),
|
||||
current: (newElems[target]?.current || 0) + outputAmount,
|
||||
max: elemMax,
|
||||
unlocked: true,
|
||||
};
|
||||
|
||||
set({
|
||||
elements: newElems,
|
||||
});
|
||||
},
|
||||
|
||||
getMaxMana: () => computeMaxMana(get()),
|
||||
getRegen: () => {
|
||||
const state = get();
|
||||
const effects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {});
|
||||
// This would need proper regen calculation
|
||||
return 2;
|
||||
},
|
||||
getClickMana: () => computeClickMana(get()),
|
||||
getMeditationMultiplier: () => {
|
||||
const state = get();
|
||||
const effects = computeEffects(state.skillUpgrades || {}, state.skillTiers || {});
|
||||
return getMeditationBonus(state.meditateTicks, state.skills, effects.meditationEfficiency);
|
||||
},
|
||||
});
|
||||
|
||||
// Helper function to deduct spell cost
|
||||
export function deductSpellCost(
|
||||
cost: SpellCost,
|
||||
rawMana: number,
|
||||
elements: Record<string, ElementState>
|
||||
): { rawMana: number; elements: Record<string, ElementState> } {
|
||||
const newElements = { ...elements };
|
||||
|
||||
if (cost.type === 'raw') {
|
||||
return { rawMana: rawMana - cost.amount, elements: newElements };
|
||||
} else if (cost.element && newElements[cost.element]) {
|
||||
newElements[cost.element] = {
|
||||
...newElements[cost.element],
|
||||
current: newElements[cost.element].current - cost.amount,
|
||||
};
|
||||
return { rawMana, elements: newElements };
|
||||
}
|
||||
|
||||
return { rawMana, elements: newElements };
|
||||
}
|
||||
|
||||
export { canAffordSpellCost };
|
||||
Reference in New Issue
Block a user