573130cdb1
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m24s
- Fix conversion rate level scaling from linear (1+level*0.5) to exponential (1.5^(level-1)) in conversion-rates.ts - Fix getAttunementLevelMultiplier formula to match spec §4.3 - Add level-up logging in attunementStore.ts via combat store addActivityLog - Clarify getAttunementConversionRate returns flat base rate (level scaling applied separately) - Update spec §8 to describe time-based puzzle room system matching code implementation - Add 17 regression tests verifying exponential scaling, base rate behavior, and spec table values
111 lines
3.1 KiB
TypeScript
111 lines
3.1 KiB
TypeScript
// Attunement Store
|
|
// Handles attunements, XP, leveling, and related actions
|
|
|
|
import { create } from 'zustand';
|
|
import { persist } from 'zustand/middleware';
|
|
import { createSafeStorage } from '../utils/safe-persist';
|
|
import type { AttunementState } from '../types';
|
|
import { getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL, ATTUNEMENTS_DEF } from '../data/attunements';
|
|
import { useCombatStore } from './combatStore';
|
|
|
|
export interface AttunementStoreState {
|
|
attunements: Record<string, AttunementState>;
|
|
|
|
// Actions
|
|
addAttunementXP: (attunementId: string, amount: number) => void;
|
|
debugUnlockAttunement: (attunementId: string) => void;
|
|
setAttunements: (attunements: Record<string, AttunementState>) => void;
|
|
|
|
// Reset
|
|
resetAttunements: () => void;
|
|
}
|
|
|
|
const initialState = {
|
|
attunements: {
|
|
enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 } as AttunementState,
|
|
},
|
|
};
|
|
|
|
export const useAttunementStore = create<AttunementStoreState>()(
|
|
persist(
|
|
(set, _get) => ({
|
|
...initialState,
|
|
|
|
addAttunementXP: (attunementId: string, amount: number) => {
|
|
set((state) => {
|
|
const attState = state.attunements?.[attunementId];
|
|
if (!attState) return state;
|
|
|
|
let newXP = attState.experience + amount;
|
|
let newLevel = attState.level;
|
|
|
|
// Level up if enough XP
|
|
while (newLevel < MAX_ATTUNEMENT_LEVEL) {
|
|
const xpNeeded = getAttunementXPForLevel(newLevel + 1);
|
|
if (newXP >= xpNeeded) {
|
|
newXP -= xpNeeded;
|
|
newLevel++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Log level-up (outside set to avoid nested state updates)
|
|
if (newLevel > attState.level) {
|
|
const def = ATTUNEMENTS_DEF[attunementId];
|
|
const name = def?.name || attunementId;
|
|
// Use setTimeout to log after state update completes
|
|
setTimeout(() => {
|
|
useCombatStore.getState().addActivityLog(
|
|
'attunement',
|
|
`${name} reached level ${newLevel}!`
|
|
);
|
|
}, 0);
|
|
}
|
|
|
|
return {
|
|
attunements: {
|
|
...state.attunements,
|
|
[attunementId]: {
|
|
...attState,
|
|
level: newLevel,
|
|
experience: newXP,
|
|
},
|
|
},
|
|
};
|
|
});
|
|
},
|
|
|
|
debugUnlockAttunement: (attunementId: string) => {
|
|
set((state) => ({
|
|
attunements: {
|
|
...state.attunements,
|
|
[attunementId]: {
|
|
id: attunementId,
|
|
active: true,
|
|
level: 1,
|
|
experience: 0,
|
|
} as AttunementState,
|
|
},
|
|
}));
|
|
},
|
|
|
|
setAttunements: (attunements: Record<string, AttunementState>) => {
|
|
set({ attunements });
|
|
},
|
|
|
|
resetAttunements: () => {
|
|
set(initialState);
|
|
},
|
|
}),
|
|
{
|
|
storage: createSafeStorage(),
|
|
name: 'mana-loop-attunements',
|
|
version: 1,
|
|
partialize: (state) => ({
|
|
attunements: state.attunements,
|
|
}),
|
|
}
|
|
)
|
|
);
|