fix: resolve 3 critical bugs — #354 attunement ReferenceError, #353 preparation mana exploit, #352 golem mana wipe
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m1s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m1s
- #354: unlockAttunement now uses _get() instead of undefined 'state' variable - #353: startPreparing now deducts raw mana from the mana store after validation - #352: processGolemAttacks/processBasicAttack accept current mana as params instead of initializing to 0/{} - Updated golem-combat-actions.test.ts to pass new currentRawMana/currentElements params - Added regression tests for all 3 bugs (16 new tests, all passing)
This commit is contained in:
@@ -80,7 +80,8 @@ export const useAttunementStore = create<AttunementStoreState>()(
|
||||
unlockAttunement: (attunementId: string, defeatedGuardians: number[]) => {
|
||||
const def = ATTUNEMENTS_DEF[attunementId];
|
||||
if (!def) return false;
|
||||
if (state.attunements[attunementId]?.active) return false;
|
||||
const currentState = _get();
|
||||
if (currentState.attunements[attunementId]?.active) return false;
|
||||
|
||||
// Check unlock conditions
|
||||
if (attunementId === 'invoker') {
|
||||
|
||||
@@ -323,6 +323,8 @@ export function processCombatTick(
|
||||
getFloorElement(currentFloor),
|
||||
get,
|
||||
set,
|
||||
rawMana,
|
||||
elements,
|
||||
);
|
||||
rawMana = golemResult.rawMana;
|
||||
elements = golemResult.elements;
|
||||
|
||||
@@ -96,6 +96,8 @@ function runGolemAttacks(
|
||||
() => enemy,
|
||||
() => [enemy],
|
||||
() => {},
|
||||
100,
|
||||
{},
|
||||
);
|
||||
return { result, capturedDamage };
|
||||
}
|
||||
@@ -136,7 +138,9 @@ describe('processGolemAttacks - spell damage and mana cost (fixes #1, #2)', () =
|
||||
() => enemy,
|
||||
() => [enemy],
|
||||
() => {},
|
||||
);
|
||||
100,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(spellCastCount).toBeGreaterThan(0);
|
||||
});
|
||||
@@ -160,7 +164,9 @@ describe('processGolemAttacks - spell damage and mana cost (fixes #1, #2)', () =
|
||||
() => enemy,
|
||||
() => [enemy],
|
||||
() => {},
|
||||
);
|
||||
100,
|
||||
{},
|
||||
);
|
||||
|
||||
// Should fall back to basic attack since mana is insufficient
|
||||
expect(damageCount).toBeGreaterThan(0);
|
||||
@@ -245,7 +251,9 @@ describe('processGolemAttacks - enchantment effects (fix #4)', () => {
|
||||
() => enemy,
|
||||
() => [enemy],
|
||||
(enemyId, effects) => { appliedEffects.push({ enemyId, effects }); },
|
||||
);
|
||||
100,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(appliedEffects.length).toBeGreaterThan(0);
|
||||
expect(appliedEffects[0].effects.some(e => e.type === 'burn')).toBe(true);
|
||||
@@ -267,7 +275,9 @@ describe('processGolemAttacks - enchantment effects (fix #4)', () => {
|
||||
() => enemy,
|
||||
() => [enemy],
|
||||
(_, effects) => { appliedEffects.push(effects); },
|
||||
);
|
||||
100,
|
||||
{},
|
||||
);
|
||||
|
||||
const allEffectTypes = appliedEffects.flat().map(e => e.type);
|
||||
expect(allEffectTypes).toContain('burn');
|
||||
@@ -291,7 +301,9 @@ describe('processGolemAttacks - enchantment effects (fix #4)', () => {
|
||||
() => enemy,
|
||||
() => [enemy],
|
||||
() => { effectsCalled = true; },
|
||||
);
|
||||
100,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(effectsCalled).toBe(false);
|
||||
});
|
||||
|
||||
@@ -248,9 +248,11 @@ export function processGolemAttacks(
|
||||
getTargetEnemy: () => EnemyState | null,
|
||||
getTargetEnemies: () => EnemyState[],
|
||||
onApplyEnchantmentEffects: (enemyId: string, effects: GolemEnchantmentEffect[]) => void,
|
||||
currentRawMana: number,
|
||||
currentElements: Record<string, { current: number; max: number; unlocked: boolean }>,
|
||||
): GolemCombatResult {
|
||||
let rawMana = 0;
|
||||
let elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
|
||||
let rawMana = currentRawMana;
|
||||
let elements: Record<string, { current: number; max: number; unlocked: boolean }> = { ...currentElements };
|
||||
let floorHP = 0;
|
||||
let floorMaxHP = 0;
|
||||
const logMessages: string[] = [];
|
||||
@@ -321,7 +323,7 @@ export function processGolemAttacks(
|
||||
onDamageDealt,
|
||||
applyDamageToRoom,
|
||||
onApplyEnchantmentEffects,
|
||||
});
|
||||
}, rawMana, elements);
|
||||
rawMana = basicResult.rawMana;
|
||||
elements = basicResult.elements;
|
||||
floorHP = basicResult.floorHP;
|
||||
|
||||
@@ -95,9 +95,9 @@ export interface BasicAttackResult {
|
||||
* AoE frames distribute damage across up to frame.aoeTargets enemies (spec §11).
|
||||
* Single-target frames attack the lowest-HP enemy.
|
||||
*/
|
||||
export function processBasicAttack(ctx: BasicAttackContext): BasicAttackResult {
|
||||
let rawMana = 0;
|
||||
let elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
|
||||
export function processBasicAttack(ctx: BasicAttackContext, currentRawMana: number, currentElements: Record<string, { current: number; max: number; unlocked: boolean }>): BasicAttackResult {
|
||||
let rawMana = currentRawMana;
|
||||
let elements: Record<string, { current: number; max: number; unlocked: boolean }> = { ...currentElements };
|
||||
let floorHP = 0;
|
||||
let floorMaxHP = 0;
|
||||
let totalDamageDealt = 0;
|
||||
@@ -174,6 +174,8 @@ export function processGolemAttacksFromStore(
|
||||
enemyElement: string,
|
||||
get: () => CombatStore,
|
||||
set: (s: Partial<CombatState>) => void,
|
||||
currentRawMana: number,
|
||||
currentElements: Record<string, { current: number; max: number; unlocked: boolean }>,
|
||||
): GolemCombatResult {
|
||||
return processGolemAttacks(
|
||||
activeGolems,
|
||||
@@ -212,5 +214,7 @@ export function processGolemAttacksFromStore(
|
||||
});
|
||||
set({ currentRoom: { ...room, enemies: updatedEnemies } });
|
||||
},
|
||||
currentRawMana,
|
||||
currentElements,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user