piclets / src /lib /battle-engine /ability-triggers.test.ts
Fraser's picture
add battle engine
1ecc382
raw
history blame
19.1 kB
/**
* Tests for special ability trigger system from the design document
* Tests all the different trigger events and their implementations
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { BattleEngine } from './BattleEngine';
import { PicletDefinition, Move, SpecialAbility } from './types';
import { PicletType, AttackType } from './types';
const STANDARD_STATS = { hp: 100, attack: 80, defense: 70, speed: 60 };
describe('Special Ability Trigger System - TDD Implementation', () => {
describe('Damage Triggers', () => {
it('should handle onDamageTaken triggers', () => {
const berserkAbility: SpecialAbility = {
name: "Berserker",
description: "Attack increases when taking damage",
triggers: [
{
event: 'onDamageTaken',
effects: [
{
type: 'modifyStats',
target: 'self',
stats: { attack: 'increase' }
}
]
}
]
};
const berserkerPiclet: PicletDefinition = {
name: "Rage Beast",
description: "Gets stronger when hurt",
tier: 'medium',
primaryType: PicletType.BEAST,
baseStats: STANDARD_STATS,
nature: "Brave",
specialAbility: berserkAbility,
movepool: [{
name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 35,
priority: 0, flags: [], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }]
}]
};
expect(berserkAbility.triggers![0].event).toBe('onDamageTaken');
});
it('should handle onDamageDealt triggers', () => {
const lifeStealAbility: SpecialAbility = {
name: "Life Steal",
description: "Heals when dealing damage",
triggers: [
{
event: 'onDamageDealt',
effects: [
{
type: 'heal',
target: 'self',
amount: 'small'
}
]
}
]
};
expect(lifeStealAbility.triggers![0].event).toBe('onDamageDealt');
});
it('should handle onContactDamage triggers', () => {
const toxicSkin: SpecialAbility = {
name: "Toxic Skin",
description: "Physical contact poisons the attacker",
triggers: [
{
event: 'onContactDamage',
effects: [
{
type: 'applyStatus',
target: 'attacker',
status: 'poison',
chance: 50
}
]
}
]
};
expect(toxicSkin.triggers![0].event).toBe('onContactDamage');
expect(toxicSkin.triggers![0].effects[0].target).toBe('attacker');
});
});
describe('Status Triggers', () => {
it('should handle onStatusInflicted triggers', () => {
const burnBoost: SpecialAbility = {
name: "Burn Boost",
description: "Fire damage energizes this Piclet, increasing attack power",
triggers: [
{
event: 'onStatusInflicted',
condition: 'ifStatus:burn',
effects: [
{
type: 'modifyStats',
target: 'self',
stats: { attack: 'greatly_increase' }
}
]
}
]
};
expect(burnBoost.triggers![0].event).toBe('onStatusInflicted');
expect(burnBoost.triggers![0].condition).toBe('ifStatus:burn');
});
it('should handle onStatusMove triggers', () => {
const statusReflect: SpecialAbility = {
name: "Status Shield",
description: "Reflects status moves back to user",
triggers: [
{
event: 'onStatusMove',
effects: [
{
type: 'mechanicOverride',
mechanic: 'targetRedirection',
value: 'reflect'
}
]
}
]
};
expect(statusReflect.triggers![0].event).toBe('onStatusMove');
});
it('should handle onStatusMoveTargeted triggers', () => {
const statusCounter: SpecialAbility = {
name: "Status Counter",
description: "When targeted by status moves, counter with damage",
triggers: [
{
event: 'onStatusMoveTargeted',
effects: [
{
type: 'damage',
target: 'attacker',
formula: 'fixed',
value: 30
}
]
}
]
};
expect(statusCounter.triggers![0].event).toBe('onStatusMoveTargeted');
});
});
describe('Critical Hit Triggers', () => {
it('should handle onCriticalHit triggers', () => {
const criticalMomentum: SpecialAbility = {
name: "Critical Momentum",
description: "Critical hits increase speed",
triggers: [
{
event: 'onCriticalHit',
effects: [
{
type: 'modifyStats',
target: 'self',
stats: { speed: 'increase' }
}
]
}
]
};
expect(criticalMomentum.triggers![0].event).toBe('onCriticalHit');
});
});
describe('HP Drain Triggers', () => {
it('should handle onHPDrained triggers', () => {
const drainPunish: SpecialAbility = {
name: "Drain Punishment",
description: "Damages opponents who try to drain HP",
triggers: [
{
event: 'onHPDrained',
effects: [
{
type: 'damage',
target: 'attacker',
formula: 'fixed',
value: 25
}
]
}
]
};
expect(drainPunish.triggers![0].event).toBe('onHPDrained');
});
});
describe('KO Triggers', () => {
it('should handle onKO triggers', () => {
const koBoost: SpecialAbility = {
name: "Victory Rush",
description: "Gets stronger after knocking out an opponent",
triggers: [
{
event: 'onKO',
effects: [
{
type: 'modifyStats',
target: 'self',
stats: { attack: 'greatly_increase', speed: 'increase' }
}
]
}
]
};
expect(koBoost.triggers![0].event).toBe('onKO');
});
});
describe('Switch Triggers', () => {
it('should handle onSwitchIn triggers', () => {
const intimidate: SpecialAbility = {
name: "Intimidate",
description: "Lowers opponent's attack when entering battle",
triggers: [
{
event: 'onSwitchIn',
effects: [
{
type: 'modifyStats',
target: 'opponent',
stats: { attack: 'decrease' }
}
]
}
]
};
expect(intimidate.triggers![0].event).toBe('onSwitchIn');
expect(intimidate.triggers![0].effects[0].target).toBe('opponent');
});
it('should handle onSwitchOut triggers', () => {
const regenerator: SpecialAbility = {
name: "Regenerator",
description: "Restores HP when switching out",
triggers: [
{
event: 'onSwitchOut',
effects: [
{
type: 'heal',
target: 'self',
amount: 'small'
}
]
}
]
};
expect(regenerator.triggers![0].event).toBe('onSwitchOut');
});
it('should handle conditional switch-in triggers', () => {
const stormCaller: SpecialAbility = {
name: "Storm Caller",
description: "Boosts attack when entering during storm weather",
triggers: [
{
event: 'onSwitchIn',
condition: 'ifWeather:storm',
effects: [
{
type: 'modifyStats',
target: 'self',
stats: { attack: 'increase' }
}
]
}
]
};
expect(stormCaller.triggers![0].condition).toBe('ifWeather:storm');
});
});
describe('Weather Triggers', () => {
it('should handle onWeatherChange triggers', () => {
const weatherAdapt: SpecialAbility = {
name: "Weather Adaptation",
description: "Adapts stats based on weather changes",
triggers: [
{
event: 'onWeatherChange',
effects: [
{
type: 'modifyStats',
target: 'self',
stats: { speed: 'increase' }
}
]
}
]
};
expect(weatherAdapt.triggers![0].event).toBe('onWeatherChange');
});
});
describe('Move Use Triggers', () => {
it('should handle beforeMoveUse triggers', () => {
const movePrep: SpecialAbility = {
name: "Move Preparation",
description: "Boosts accuracy before using moves",
triggers: [
{
event: 'beforeMoveUse',
effects: [
{
type: 'modifyStats',
target: 'self',
stats: { accuracy: 'increase' }
}
]
}
]
};
expect(movePrep.triggers![0].event).toBe('beforeMoveUse');
});
it('should handle afterMoveUse triggers', () => {
const moveRecovery: SpecialAbility = {
name: "Move Recovery",
description: "Heals slightly after using any move",
triggers: [
{
event: 'afterMoveUse',
effects: [
{
type: 'heal',
target: 'self',
formula: 'fixed',
value: 5
}
]
}
]
};
expect(moveRecovery.triggers![0].event).toBe('afterMoveUse');
});
});
describe('HP Threshold Triggers', () => {
it('should handle onLowHP triggers', () => {
const emergencyMode: SpecialAbility = {
name: "Emergency Mode",
description: "Activates emergency protocols when HP is low",
triggers: [
{
event: 'onLowHP',
effects: [
{
type: 'modifyStats',
target: 'self',
stats: { speed: 'greatly_increase', attack: 'increase' }
},
{
type: 'mechanicOverride',
mechanic: 'statusImmunity',
value: ['burn', 'poison', 'paralyze']
}
]
}
]
};
expect(emergencyMode.triggers![0].event).toBe('onLowHP');
expect(emergencyMode.triggers![0].effects).toHaveLength(2);
});
it('should handle onFullHP triggers', () => {
const fullPower: SpecialAbility = {
name: "Full Power",
description: "Maximum power when at full health",
triggers: [
{
event: 'onFullHP',
effects: [
{
type: 'modifyStats',
target: 'self',
stats: { attack: 'greatly_increase' }
}
]
}
]
};
expect(fullPower.triggers![0].event).toBe('onFullHP');
});
});
describe('Turn-Based Triggers', () => {
it('should handle endOfTurn triggers', () => {
const turnRegeneration: SpecialAbility = {
name: "Slow Regeneration",
description: "Heals at the end of each turn",
triggers: [
{
event: 'endOfTurn',
effects: [
{
type: 'heal',
target: 'self',
formula: 'percentage',
value: 10
}
]
}
]
};
expect(turnRegeneration.triggers![0].event).toBe('endOfTurn');
});
it('should handle conditional turn triggers', () => {
const sleepHeal: SpecialAbility = {
name: "Slumber Heal",
description: "Restores HP while sleeping instead of being unable to act",
triggers: [
{
event: 'endOfTurn',
condition: 'ifStatus:sleep',
effects: [
{
type: 'heal',
target: 'self',
formula: 'percentage',
value: 15
}
]
}
]
};
expect(sleepHeal.triggers![0].condition).toBe('ifStatus:sleep');
});
});
describe('Opponent Move Triggers', () => {
it('should handle onOpponentContactMove triggers', () => {
const contactPunish: SpecialAbility = {
name: "Contact Punishment",
description: "Damages opponents who use contact moves",
triggers: [
{
event: 'onOpponentContactMove',
effects: [
{
type: 'damage',
target: 'attacker',
formula: 'fixed',
value: 15
}
]
}
]
};
expect(contactPunish.triggers![0].event).toBe('onOpponentContactMove');
});
it('should handle wind currents ability from design doc', () => {
const windCurrents: SpecialAbility = {
name: "Wind Currents",
description: "Gains +25% speed when opponent uses a contact move",
triggers: [
{
event: 'onOpponentContactMove',
effects: [
{
type: 'modifyStats',
target: 'self',
stats: { speed: 'increase' }
}
]
}
]
};
const zephyrSprite: PicletDefinition = {
name: "Zephyr Sprite",
description: "A mysterious floating creature that manipulates wind currents",
tier: 'medium',
primaryType: PicletType.SPACE,
baseStats: { hp: 65, attack: 85, defense: 40, speed: 90 },
nature: "hasty",
specialAbility: windCurrents,
movepool: [{
name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 35,
priority: 0, flags: [], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }]
}]
};
expect(windCurrents.triggers![0].event).toBe('onOpponentContactMove');
expect(zephyrSprite.specialAbility.name).toBe('Wind Currents');
});
});
describe('Complex Multi-Trigger Abilities', () => {
it('should handle abilities with multiple triggers', () => {
const complexAbility: SpecialAbility = {
name: "Adaptive Guardian",
description: "Complex ability with multiple trigger conditions",
triggers: [
{
event: 'onSwitchIn',
effects: [
{
type: 'modifyStats',
target: 'self',
stats: { defense: 'increase' }
}
]
},
{
event: 'onDamageTaken',
condition: 'ifLowHp',
effects: [
{
type: 'mechanicOverride',
mechanic: 'damageReflection',
value: 0.3
}
]
},
{
event: 'endOfTurn',
condition: 'ifStatus:burn',
effects: [
{
type: 'removeStatus',
target: 'self',
status: 'burn'
},
{
type: 'modifyStats',
target: 'self',
stats: { attack: 'increase' }
}
]
}
]
};
expect(complexAbility.triggers).toHaveLength(3);
expect(complexAbility.triggers![1].condition).toBe('ifLowHp');
expect(complexAbility.triggers![2].effects).toHaveLength(2);
});
});
describe('Status-Specific Ability Examples', () => {
it('should handle Glacial Birth - starts battle frozen', () => {
const glacialBirth: SpecialAbility = {
name: "Glacial Birth",
description: "Enters battle in a frozen state but gains defensive bonuses",
triggers: [
{
event: 'onSwitchIn',
effects: [
{
type: 'applyStatus',
target: 'self',
status: 'freeze',
chance: 100
},
{
type: 'modifyStats',
target: 'self',
stats: { defense: 'greatly_increase' },
condition: 'whileFrozen'
}
]
}
]
};
expect(glacialBirth.triggers![0].effects[0].status).toBe('freeze');
expect(glacialBirth.triggers![0].effects[1].condition).toBe('whileFrozen');
});
it('should handle Cryogenic Touch - freezes on contact', () => {
const cryogenicTouch: SpecialAbility = {
name: "Cryogenic Touch",
description: "Contact moves have a chance to freeze the attacker",
triggers: [
{
event: 'onContactDamage',
effects: [
{
type: 'applyStatus',
target: 'attacker',
status: 'freeze',
chance: 30
}
]
}
]
};
expect(cryogenicTouch.triggers![0].effects[0].chance).toBe(30);
});
it('should handle Paralytic Aura - paralyzes on entry', () => {
const paralyticAura: SpecialAbility = {
name: "Paralytic Aura",
description: "Intimidating presence paralyzes the opponent upon entry",
triggers: [
{
event: 'onSwitchIn',
effects: [
{
type: 'applyStatus',
target: 'opponent',
status: 'paralyze',
chance: 75
}
]
}
]
};
expect(paralyticAura.triggers![0].effects[0].target).toBe('opponent');
expect(paralyticAura.triggers![0].effects[0].chance).toBe(75);
});
it('should handle Confusion Clarity - team status removal', () => {
const confusionClarity: SpecialAbility = {
name: "Confusion Clarity",
description: "Clear mind prevents confusion and helps allies focus",
effects: [
{
type: 'mechanicOverride',
mechanic: 'statusImmunity',
value: ['confuse']
}
],
triggers: [
{
event: 'onSwitchIn',
effects: [
{
type: 'removeStatus',
target: 'allies',
status: 'confuse'
}
]
}
]
};
expect(confusionClarity.effects![0].value).toContain('confuse');
expect(confusionClarity.triggers![0].effects[0].target).toBe('allies');
});
});
});