feat: implement attunement leveling and debug functions
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 2m4s
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 2m4s
- Add attunement XP system with level-scaled progression (100 * 3^(level-2) XP per level) - Add addAttunementXP function with automatic level-up handling - Add debug functions: debugUnlockAttunement, debugAddElementalMana, debugSetTime, debugAddAttunementXP, debugSetFloor - Update attunement conversion to use level-scaled conversion rate - Import getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL in store
This commit is contained in:
@@ -39,7 +39,7 @@ import {
|
|||||||
} from './crafting-slice';
|
} from './crafting-slice';
|
||||||
import { EQUIPMENT_TYPES } from './data/equipment';
|
import { EQUIPMENT_TYPES } from './data/equipment';
|
||||||
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
||||||
import { ATTUNEMENTS_DEF, getTotalAttunementRegen } from './data/attunements';
|
import { ATTUNEMENTS_DEF, getTotalAttunementRegen, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
||||||
|
|
||||||
// Default empty effects for when effects aren't provided
|
// Default empty effects for when effects aren't provided
|
||||||
const DEFAULT_EFFECTS: ComputedEffects = {
|
const DEFAULT_EFFECTS: ComputedEffects = {
|
||||||
@@ -568,6 +568,16 @@ interface GameStore extends GameState, CraftingActions {
|
|||||||
commitSkillUpgrades: (skillId: string, upgradeIds: string[]) => void;
|
commitSkillUpgrades: (skillId: string, upgradeIds: string[]) => void;
|
||||||
tierUpSkill: (skillId: string) => void;
|
tierUpSkill: (skillId: string) => void;
|
||||||
|
|
||||||
|
// Attunement XP and leveling
|
||||||
|
addAttunementXP: (attunementId: string, amount: number) => void;
|
||||||
|
|
||||||
|
// Debug functions
|
||||||
|
debugUnlockAttunement: (attunementId: string) => void;
|
||||||
|
debugAddElementalMana: (element: string, amount: number) => void;
|
||||||
|
debugSetTime: (day: number, hour: number) => void;
|
||||||
|
debugAddAttunementXP: (attunementId: string, amount: number) => void;
|
||||||
|
debugSetFloor: (floor: number) => void;
|
||||||
|
|
||||||
// Computed getters
|
// Computed getters
|
||||||
getMaxMana: () => number;
|
getMaxMana: () => number;
|
||||||
getRegen: () => number;
|
getRegen: () => number;
|
||||||
@@ -679,8 +689,11 @@ export const useGameStore = create<GameStore>()(
|
|||||||
const elem = elements[attDef.primaryManaType];
|
const elem = elements[attDef.primaryManaType];
|
||||||
if (!elem || !elem.unlocked) return;
|
if (!elem || !elem.unlocked) return;
|
||||||
|
|
||||||
|
// Get level-scaled conversion rate
|
||||||
|
const scaledConversionRate = getAttunementConversionRate(attId, attState.level || 1);
|
||||||
|
|
||||||
// Convert raw mana to primary type
|
// Convert raw mana to primary type
|
||||||
const conversionAmount = attDef.conversionRate * HOURS_PER_TICK;
|
const conversionAmount = scaledConversionRate * HOURS_PER_TICK;
|
||||||
const actualConversion = Math.min(conversionAmount, rawMana, elem.max - elem.current);
|
const actualConversion = Math.min(conversionAmount, rawMana, elem.max - elem.current);
|
||||||
|
|
||||||
if (actualConversion > 0) {
|
if (actualConversion > 0) {
|
||||||
@@ -1668,6 +1681,140 @@ export const useGameStore = create<GameStore>()(
|
|||||||
if (!instance) return 0;
|
if (!instance) return 0;
|
||||||
return instance.totalCapacity - instance.usedCapacity;
|
return instance.totalCapacity - instance.usedCapacity;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Attunement XP and leveling
|
||||||
|
addAttunementXP: (attunementId: string, amount: number) => {
|
||||||
|
const state = get();
|
||||||
|
const attState = state.attunements[attunementId];
|
||||||
|
if (!attState?.active) return;
|
||||||
|
|
||||||
|
let newXP = attState.experience + amount;
|
||||||
|
let newLevel = attState.level;
|
||||||
|
|
||||||
|
// Check for level ups
|
||||||
|
while (newLevel < MAX_ATTUNEMENT_LEVEL) {
|
||||||
|
const xpNeeded = getAttunementXPForLevel(newLevel + 1);
|
||||||
|
if (newXP >= xpNeeded) {
|
||||||
|
newXP -= xpNeeded;
|
||||||
|
newLevel++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap XP at max level
|
||||||
|
if (newLevel >= MAX_ATTUNEMENT_LEVEL) {
|
||||||
|
newXP = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
set({
|
||||||
|
attunements: {
|
||||||
|
...state.attunements,
|
||||||
|
[attunementId]: {
|
||||||
|
...attState,
|
||||||
|
level: newLevel,
|
||||||
|
experience: newXP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
log: newLevel > attState.level
|
||||||
|
? [`🌟 ${attunementId} attunement reached Level ${newLevel}!`, ...state.log.slice(0, 49)]
|
||||||
|
: state.log,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Debug functions
|
||||||
|
debugUnlockAttunement: (attunementId: string) => {
|
||||||
|
const state = get();
|
||||||
|
const def = ATTUNEMENTS_DEF[attunementId];
|
||||||
|
if (!def) return;
|
||||||
|
|
||||||
|
set({
|
||||||
|
attunements: {
|
||||||
|
...state.attunements,
|
||||||
|
[attunementId]: {
|
||||||
|
id: attunementId,
|
||||||
|
active: true,
|
||||||
|
level: 1,
|
||||||
|
experience: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Unlock the primary mana type if applicable
|
||||||
|
elements: def.primaryManaType && state.elements[def.primaryManaType]
|
||||||
|
? {
|
||||||
|
...state.elements,
|
||||||
|
[def.primaryManaType]: {
|
||||||
|
...state.elements[def.primaryManaType],
|
||||||
|
unlocked: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: state.elements,
|
||||||
|
log: [`🔓 Debug: Unlocked ${def.name} attunement!`, ...state.log.slice(0, 49)],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
debugAddElementalMana: (element: string, amount: number) => {
|
||||||
|
const state = get();
|
||||||
|
const elem = state.elements[element];
|
||||||
|
if (!elem?.unlocked) return;
|
||||||
|
|
||||||
|
set({
|
||||||
|
elements: {
|
||||||
|
...state.elements,
|
||||||
|
[element]: {
|
||||||
|
...elem,
|
||||||
|
current: Math.min(elem.current + amount, elem.max * 10), // Allow overflow
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
debugSetTime: (day: number, hour: number) => {
|
||||||
|
set({
|
||||||
|
day,
|
||||||
|
hour,
|
||||||
|
incursionStrength: getIncursionStrength(day, hour),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
debugAddAttunementXP: (attunementId: string, amount: number) => {
|
||||||
|
const state = get();
|
||||||
|
const attState = state.attunements[attunementId];
|
||||||
|
if (!attState) return;
|
||||||
|
|
||||||
|
let newXP = attState.experience + amount;
|
||||||
|
let newLevel = attState.level;
|
||||||
|
|
||||||
|
while (newLevel < MAX_ATTUNEMENT_LEVEL) {
|
||||||
|
const xpNeeded = getAttunementXPForLevel(newLevel + 1);
|
||||||
|
if (newXP >= xpNeeded) {
|
||||||
|
newXP -= xpNeeded;
|
||||||
|
newLevel++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set({
|
||||||
|
attunements: {
|
||||||
|
...state.attunements,
|
||||||
|
[attunementId]: {
|
||||||
|
...attState,
|
||||||
|
level: newLevel,
|
||||||
|
experience: newXP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
debugSetFloor: (floor: number) => {
|
||||||
|
const state = get();
|
||||||
|
set({
|
||||||
|
currentFloor: floor,
|
||||||
|
floorHP: getFloorMaxHP(floor),
|
||||||
|
floorMaxHP: getFloorMaxHP(floor),
|
||||||
|
maxFloorReached: Math.max(state.maxFloorReached, floor),
|
||||||
|
});
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'mana-loop-storage',
|
name: 'mana-loop-storage',
|
||||||
|
|||||||
Reference in New Issue
Block a user