|
import { describe, it, expect, beforeEach } from 'vitest'; |
|
import { BattleEngine } from './BattleEngine'; |
|
import type { PicletDefinition, SpecialAbility } from './types'; |
|
import { PicletType, AttackType } from './types'; |
|
|
|
describe('Special Ability Triggers System', () => { |
|
let basicPiclet: PicletDefinition; |
|
let abilityPiclet: PicletDefinition; |
|
|
|
beforeEach(() => { |
|
|
|
basicPiclet = { |
|
name: "Basic Fighter", |
|
description: "Standard test piclet", |
|
tier: 'medium', |
|
primaryType: PicletType.BEAST, |
|
baseStats: { hp: 80, attack: 60, defense: 60, speed: 60 }, |
|
nature: "Hardy", |
|
specialAbility: { name: "No Ability", description: "" }, |
|
movepool: [ |
|
{ |
|
name: "Basic Attack", |
|
type: AttackType.BEAST, |
|
power: 50, |
|
accuracy: 100, |
|
pp: 20, |
|
priority: 0, |
|
flags: ['contact'], |
|
effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] |
|
} |
|
] |
|
}; |
|
|
|
|
|
abilityPiclet = { |
|
name: "Ability User", |
|
description: "Has special abilities", |
|
tier: 'medium', |
|
primaryType: PicletType.BEAST, |
|
baseStats: { hp: 100, attack: 70, defense: 70, speed: 50 }, |
|
nature: "Bold", |
|
specialAbility: { |
|
name: "Test Ability", |
|
description: "Triggers on various events", |
|
triggers: [ |
|
{ |
|
event: 'onDamageTaken', |
|
condition: 'always', |
|
effects: [ |
|
{ |
|
type: 'modifyStats', |
|
target: 'self', |
|
stats: { |
|
attack: 'increase' |
|
} |
|
} |
|
] |
|
} |
|
] |
|
}, |
|
movepool: [ |
|
{ |
|
name: "Power Strike", |
|
type: AttackType.BEAST, |
|
power: 60, |
|
accuracy: 100, |
|
pp: 15, |
|
priority: 0, |
|
flags: ['contact'], |
|
effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] |
|
} |
|
] |
|
}; |
|
}); |
|
|
|
describe('onDamageTaken Trigger', () => { |
|
it('should trigger when piclet takes damage', () => { |
|
const engine = new BattleEngine(abilityPiclet, basicPiclet); |
|
const initialAttack = engine.getState().playerPiclet.attack; |
|
|
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const finalAttack = engine.getState().playerPiclet.attack; |
|
const log = engine.getLog(); |
|
|
|
|
|
expect(finalAttack).toBeGreaterThan(initialAttack); |
|
expect(log.some(msg => msg.includes('Test Ability') && msg.includes('triggered'))).toBe(true); |
|
}); |
|
}); |
|
|
|
describe('endOfTurn Trigger', () => { |
|
it('should trigger at the end of every turn', () => { |
|
const endTurnAbility: PicletDefinition = { |
|
...abilityPiclet, |
|
specialAbility: { |
|
name: "Regeneration", |
|
description: "Heals at end of turn", |
|
triggers: [ |
|
{ |
|
event: 'endOfTurn', |
|
condition: 'always', |
|
effects: [ |
|
{ |
|
type: 'heal', |
|
target: 'self', |
|
amount: 'small' |
|
} |
|
] |
|
} |
|
] |
|
} |
|
}; |
|
|
|
const engine = new BattleEngine(endTurnAbility, basicPiclet); |
|
|
|
|
|
engine['state'].playerPiclet.currentHp = Math.floor(engine['state'].playerPiclet.maxHp * 0.9); |
|
const initialHp = engine.getState().playerPiclet.currentHp; |
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const log = engine.getLog(); |
|
console.log('Regeneration test log:', log); |
|
console.log('Initial HP:', initialHp, 'Final HP:', engine.getState().playerPiclet.currentHp); |
|
|
|
|
|
expect(log.some(msg => msg.includes('Regeneration') && msg.includes('triggered'))).toBe(true); |
|
|
|
|
|
expect(log.some(msg => msg.includes('recovered') || msg.includes('healed'))).toBe(true); |
|
}); |
|
}); |
|
|
|
describe('onDamageDealt Trigger', () => { |
|
it('should trigger when piclet deals damage to opponent', () => { |
|
const damageDealer: PicletDefinition = { |
|
...abilityPiclet, |
|
specialAbility: { |
|
name: "Combat High", |
|
description: "Gains speed when dealing damage", |
|
triggers: [ |
|
{ |
|
event: 'onDamageDealt', |
|
condition: 'always', |
|
effects: [ |
|
{ |
|
type: 'modifyStats', |
|
target: 'self', |
|
stats: { |
|
speed: 'increase' |
|
} |
|
} |
|
] |
|
} |
|
] |
|
} |
|
}; |
|
|
|
const engine = new BattleEngine(damageDealer, basicPiclet); |
|
const initialSpeed = engine.getState().playerPiclet.speed; |
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const finalSpeed = engine.getState().playerPiclet.speed; |
|
const log = engine.getLog(); |
|
|
|
expect(finalSpeed).toBeGreaterThan(initialSpeed); |
|
expect(log.some(msg => msg.includes('Combat High') && msg.includes('triggered'))).toBe(true); |
|
}); |
|
}); |
|
|
|
describe('onCriticalHit Trigger', () => { |
|
it('should trigger when dealing a critical hit', () => { |
|
const criticalHitter: PicletDefinition = { |
|
...abilityPiclet, |
|
specialAbility: { |
|
name: "Critical Momentum", |
|
description: "Gains attack on critical hits", |
|
triggers: [ |
|
{ |
|
event: 'onCriticalHit', |
|
condition: 'always', |
|
effects: [ |
|
{ |
|
type: 'modifyStats', |
|
target: 'self', |
|
stats: { |
|
attack: 'increase' |
|
} |
|
} |
|
] |
|
} |
|
] |
|
} |
|
}; |
|
|
|
const engine = new BattleEngine(criticalHitter, basicPiclet); |
|
|
|
|
|
const originalRandom = Math.random; |
|
Math.random = () => 0.01; |
|
|
|
const initialAttack = engine.getState().playerPiclet.attack; |
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
|
|
Math.random = originalRandom; |
|
|
|
const finalAttack = engine.getState().playerPiclet.attack; |
|
const log = engine.getLog(); |
|
|
|
expect(log.some(msg => msg.includes('A critical hit!'))).toBe(true); |
|
expect(finalAttack).toBeGreaterThan(initialAttack); |
|
expect(log.some(msg => msg.includes('Critical Momentum') && msg.includes('triggered'))).toBe(true); |
|
}); |
|
}); |
|
|
|
describe('onContactDamage Trigger', () => { |
|
it('should trigger only when hit by contact moves', () => { |
|
const contactSensitive: PicletDefinition = { |
|
...abilityPiclet, |
|
specialAbility: { |
|
name: "Spiky Skin", |
|
description: "Hurts attackers that make contact", |
|
triggers: [ |
|
{ |
|
event: 'onContactDamage', |
|
condition: 'always', |
|
effects: [ |
|
{ |
|
type: 'damage', |
|
target: 'opponent', |
|
amount: 'small' |
|
} |
|
] |
|
} |
|
] |
|
} |
|
}; |
|
|
|
const engine = new BattleEngine(contactSensitive, basicPiclet); |
|
const initialOpponentHp = engine.getState().opponentPiclet.currentHp; |
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const finalOpponentHp = engine.getState().opponentPiclet.currentHp; |
|
const log = engine.getLog(); |
|
|
|
|
|
expect(log.some(msg => msg.includes('Spiky Skin') && msg.includes('triggered'))).toBe(true); |
|
|
|
|
|
expect(finalOpponentHp).toBeLessThan(initialOpponentHp); |
|
}); |
|
}); |
|
|
|
describe('Conditional Triggers', () => { |
|
it('should respect ifLowHp condition', () => { |
|
const conditionalAbility: PicletDefinition = { |
|
...abilityPiclet, |
|
specialAbility: { |
|
name: "Desperation", |
|
description: "Only triggers when HP is low", |
|
triggers: [ |
|
{ |
|
event: 'onDamageTaken', |
|
condition: 'ifLowHp', |
|
effects: [ |
|
{ |
|
type: 'modifyStats', |
|
target: 'self', |
|
stats: { |
|
attack: 'greatly_increase' |
|
} |
|
} |
|
] |
|
} |
|
] |
|
} |
|
}; |
|
|
|
const engine = new BattleEngine(conditionalAbility, basicPiclet); |
|
const initialAttack = engine.getState().playerPiclet.attack; |
|
|
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const midAttack = engine.getState().playerPiclet.attack; |
|
expect(midAttack).toBe(initialAttack); |
|
|
|
|
|
const lowHpEngine = new BattleEngine(conditionalAbility, basicPiclet); |
|
|
|
|
|
lowHpEngine['state'].playerPiclet.currentHp = Math.floor(lowHpEngine['state'].playerPiclet.maxHp * 0.15); |
|
|
|
lowHpEngine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const finalAttack = lowHpEngine.getState().playerPiclet.attack; |
|
const log = lowHpEngine.getLog(); |
|
|
|
expect(finalAttack).toBeGreaterThan(initialAttack); |
|
expect(log.some(msg => msg.includes('Desperation') && msg.includes('triggered'))).toBe(true); |
|
}); |
|
}); |
|
|
|
describe('Multiple Triggers on Same Ability', () => { |
|
it('should handle multiple triggers on the same ability', () => { |
|
const multiTriggerAbility: PicletDefinition = { |
|
...abilityPiclet, |
|
specialAbility: { |
|
name: "Adaptive Fighter", |
|
description: "Multiple trigger conditions", |
|
triggers: [ |
|
{ |
|
event: 'onDamageTaken', |
|
condition: 'always', |
|
effects: [ |
|
{ |
|
type: 'modifyStats', |
|
target: 'self', |
|
stats: { |
|
defense: 'increase' |
|
} |
|
} |
|
] |
|
}, |
|
{ |
|
event: 'onDamageDealt', |
|
condition: 'always', |
|
effects: [ |
|
{ |
|
type: 'modifyStats', |
|
target: 'self', |
|
stats: { |
|
attack: 'increase' |
|
} |
|
} |
|
] |
|
} |
|
] |
|
} |
|
}; |
|
|
|
const engine = new BattleEngine(multiTriggerAbility, basicPiclet); |
|
const initialAttack = engine.getState().playerPiclet.attack; |
|
const initialDefense = engine.getState().playerPiclet.defense; |
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const finalAttack = engine.getState().playerPiclet.attack; |
|
const finalDefense = engine.getState().playerPiclet.defense; |
|
const log = engine.getLog(); |
|
|
|
|
|
expect(finalAttack).toBeGreaterThan(initialAttack); |
|
expect(finalDefense).toBeGreaterThan(initialDefense); |
|
expect(log.some(msg => msg.includes('Adaptive Fighter') && msg.includes('triggered'))).toBe(true); |
|
}); |
|
}); |
|
}); |