piclets / src /lib /battle-engine /advanced-effects.test.ts
Fraser's picture
simple counters
1bb845a
/**
* Tests for advanced battle effects from the design document
* Covers all missing functionality that needs to be implemented
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { BattleEngine } from './BattleEngine';
import { PicletDefinition, Move, SpecialAbility } from './types';
import { PicletType, AttackType } from './types';
// Test data for advanced effects
const STANDARD_STATS = { hp: 100, attack: 80, defense: 70, speed: 60 };
describe('Advanced Battle Effects - TDD Implementation', () => {
describe('Damage Formula System', () => {
it('should handle recoil damage moves', () => {
const recoilMove: Move = {
name: "Reckless Dive",
type: AttackType.SPACE,
power: 120,
accuracy: 100,
pp: 5,
priority: 0,
flags: ['contact', 'reckless'],
effects: [
{
type: 'damage',
target: 'opponent',
amount: 'strong'
},
{
type: 'damage',
target: 'self',
formula: 'recoil',
value: 0.25
}
]
};
const testPiclet: PicletDefinition = {
name: "Recoil Tester",
description: "Tests recoil moves",
tier: 'medium',
primaryType: PicletType.SPACE,
baseStats: STANDARD_STATS,
nature: "Bold",
specialAbility: { name: "None", description: "No ability" },
movepool: [recoilMove]
};
const targetPiclet: PicletDefinition = {
name: "Target",
description: "Target dummy",
tier: 'medium',
primaryType: PicletType.BEAST,
baseStats: STANDARD_STATS,
nature: "Hardy",
specialAbility: { name: "None", description: "No ability" },
movepool: [{
name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 35,
priority: 0, flags: [], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }]
}]
};
const engine = new BattleEngine(testPiclet, targetPiclet);
const initialHp = engine.getState().playerPiclet.currentHp;
engine.executeActions(
{ type: 'move', piclet: 'player', moveIndex: 0 },
{ type: 'move', piclet: 'opponent', moveIndex: 0 }
);
const finalHp = engine.getState().playerPiclet.currentHp;
expect(finalHp).toBeLessThan(initialHp); // Should have taken recoil damage
});
it('should handle drain damage moves', () => {
const drainMove: Move = {
name: "Spectral Drain",
type: AttackType.CULTURE,
power: 60,
accuracy: 100,
pp: 10,
priority: 0,
flags: ['draining'],
effects: [
{
type: 'damage',
target: 'opponent',
formula: 'drain',
value: 0.5
}
]
};
const testPiclet: PicletDefinition = {
name: "Drain Tester",
description: "Tests drain moves",
tier: 'medium',
primaryType: PicletType.CULTURE,
baseStats: STANDARD_STATS,
nature: "Bold",
specialAbility: { name: "None", description: "No ability" },
movepool: [drainMove]
};
const targetPiclet: PicletDefinition = {
name: "Target",
description: "Target dummy",
tier: 'medium',
primaryType: PicletType.BEAST,
baseStats: STANDARD_STATS,
nature: "Hardy",
specialAbility: { name: "None", description: "No ability" },
movepool: [{
name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 35,
priority: 0, flags: [], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }]
}]
};
const engine = new BattleEngine(testPiclet, targetPiclet);
// Damage the user first to test healing
engine['state'].playerPiclet.currentHp = 50;
const initialHp = engine.getState().playerPiclet.currentHp;
engine.executeActions(
{ type: 'move', piclet: 'player', moveIndex: 0 },
{ type: 'move', piclet: 'opponent', moveIndex: 0 }
);
const log = engine.getLog();
const hasHealingMessage = log.some(msg => msg.includes('recovered') && msg.includes('HP from draining'));
expect(hasHealingMessage).toBe(true); // Should have healed from drain
});
it('should handle fixed damage moves', () => {
const fixedMove: Move = {
name: "Fixed Strike",
type: AttackType.NORMAL,
power: 0,
accuracy: 100,
pp: 10,
priority: 0,
flags: [],
effects: [
{
type: 'damage',
target: 'opponent',
formula: 'fixed',
value: 50
}
]
};
// Test implementation would verify exactly 50 damage dealt
expect(fixedMove.effects[0].formula).toBe('fixed');
expect(fixedMove.effects[0].value).toBe(50);
});
it('should handle percentage damage moves', () => {
const percentMove: Move = {
name: "Percentage Strike",
type: AttackType.NORMAL,
power: 0,
accuracy: 100,
pp: 5,
priority: 0,
flags: [],
effects: [
{
type: 'damage',
target: 'opponent',
formula: 'percentage',
value: 25 // 25% of target's max HP
}
]
};
// Test implementation would verify percentage-based damage
expect(percentMove.effects[0].formula).toBe('percentage');
expect(percentMove.effects[0].value).toBe(25);
});
});
describe('PP Manipulation System', () => {
it('should handle PP drain moves', () => {
const ppDrainMove: Move = {
name: "Mind Drain",
type: AttackType.CULTURE,
power: 40,
accuracy: 100,
pp: 15,
priority: 0,
flags: [],
effects: [
{
type: 'damage',
target: 'opponent',
amount: 'normal'
},
{
type: 'manipulatePP',
target: 'opponent',
action: 'drain',
amount: 'medium'
}
]
};
// Test would verify PP is drained from opponent's moves
expect(ppDrainMove.effects[1].type).toBe('manipulatePP');
expect(ppDrainMove.effects[1].action).toBe('drain');
});
it('should handle PP restore moves', () => {
const ppRestoreMove: Move = {
name: "Restore Energy",
type: AttackType.NORMAL,
power: 0,
accuracy: 100,
pp: 5,
priority: 0,
flags: [],
effects: [
{
type: 'manipulatePP',
target: 'self',
action: 'restore',
amount: 'large'
}
]
};
// Test would verify PP is restored to self
expect(ppRestoreMove.effects[0].type).toBe('manipulatePP');
expect(ppRestoreMove.effects[0].action).toBe('restore');
});
it('should handle specific PP manipulation', () => {
const specificPPMove: Move = {
name: "Soul Burn",
type: AttackType.SPACE,
power: 150,
accuracy: 90,
pp: 5,
priority: 0,
flags: [],
effects: [
{
type: 'damage',
target: 'opponent',
amount: 'extreme'
},
{
type: 'manipulatePP',
target: 'self',
action: 'drain',
value: 3,
targetMove: 'random',
condition: 'afterUse'
}
]
};
// Test would verify specific PP amounts are drained
expect(specificPPMove.effects[1].value).toBe(3);
expect(specificPPMove.effects[1].targetMove).toBe('random');
});
});
describe('Field Effects System', () => {
it('should handle field-wide effects', () => {
const fieldMove: Move = {
name: "Void Storm",
type: AttackType.SPACE,
power: 0,
accuracy: 100,
pp: 5,
priority: 0,
flags: [],
effects: [
{
type: 'fieldEffect',
effect: 'voidStorm',
target: 'field',
stackable: false
}
]
};
// Test would verify field effects are applied and tracked
expect(fieldMove.effects[0].type).toBe('fieldEffect');
expect(fieldMove.effects[0].target).toBe('field');
});
it('should handle side-specific effects', () => {
const sideMove: Move = {
name: "Healing Mist",
type: AttackType.FLORA,
power: 0,
accuracy: 100,
pp: 10,
priority: 0,
flags: [],
effects: [
{
type: 'fieldEffect',
effect: 'healingMist',
target: 'playerSide',
stackable: true
}
]
};
// Test would verify side effects work correctly
expect(sideMove.effects[0].target).toBe('playerSide');
expect(sideMove.effects[0].stackable).toBe(true);
});
});
describe('Counter Move System', () => {
it('should handle physical counter moves', () => {
const counterMove: Move = {
name: "Counter Strike",
type: AttackType.NORMAL,
power: 0,
accuracy: 100,
pp: 20,
priority: -5,
flags: ['lowPriority'],
effects: [
{
type: 'counter',
strength: 'strong'
}
]
};
// Test would verify counter moves work against any attacks
expect(counterMove.effects[0].type).toBe('counter');
expect(counterMove.effects[0].strength).toBe('strong');
});
it('should handle special counter moves', () => {
const specialCounterMove: Move = {
name: "Mirror Coat",
type: AttackType.CULTURE,
power: 0,
accuracy: 100,
pp: 20,
priority: -5,
flags: ['lowPriority'],
effects: [
{
type: 'counter',
strength: 'strong'
}
]
};
expect(specialCounterMove.effects[0].strength).toBe('strong');
});
});
describe('Priority Manipulation', () => {
it('should handle priority-changing effects', () => {
const priorityMove: Move = {
name: "Quick Strike",
type: AttackType.NORMAL,
power: 40,
accuracy: 100,
pp: 30,
priority: 0,
flags: [],
effects: [
{
type: 'damage',
target: 'opponent',
amount: 'weak'
},
{
type: 'priority',
target: 'self',
value: 1,
condition: 'ifLowHp'
}
]
};
// Test would verify priority changes based on conditions
expect(priorityMove.effects[1].type).toBe('priority');
expect(priorityMove.effects[1].value).toBe(1);
});
});
describe('Status Chance System', () => {
it('should handle status moves with specific chances', () => {
const chanceStatusMove: Move = {
name: "Thunder Wave",
type: AttackType.SPACE,
power: 0,
accuracy: 90,
pp: 20,
priority: 0,
flags: [],
effects: [
{
type: 'applyStatus',
target: 'opponent',
status: 'paralyze',
chance: 100
}
]
};
expect(chanceStatusMove.effects[0].chance).toBe(100);
});
it('should handle partial chance status effects', () => {
const partialChanceMove: Move = {
name: "Ice Touch",
type: AttackType.MINERAL,
power: 60,
accuracy: 100,
pp: 20,
priority: 0,
flags: ['contact'],
effects: [
{
type: 'damage',
target: 'opponent',
amount: 'normal'
},
{
type: 'applyStatus',
target: 'opponent',
status: 'freeze',
chance: 30
}
]
};
expect(partialChanceMove.effects[1].chance).toBe(30);
});
});
describe('Percentage-based Healing', () => {
it('should handle percentage healing moves', () => {
const percentHealMove: Move = {
name: "Recovery",
type: AttackType.NORMAL,
power: 0,
accuracy: 100,
pp: 10,
priority: 0,
flags: [],
effects: [
{
type: 'heal',
target: 'self',
formula: 'percentage',
value: 50 // 50% of max HP
}
]
};
expect(percentHealMove.effects[0].formula).toBe('percentage');
expect(percentHealMove.effects[0].value).toBe(50);
});
it('should handle fixed healing moves', () => {
const fixedHealMove: Move = {
name: "First Aid",
type: AttackType.NORMAL,
power: 0,
accuracy: 100,
pp: 15,
priority: 0,
flags: [],
effects: [
{
type: 'heal',
target: 'self',
formula: 'fixed',
value: 25 // Heal exactly 25 HP
}
]
};
expect(fixedHealMove.effects[0].formula).toBe('fixed');
expect(fixedHealMove.effects[0].value).toBe(25);
});
});
describe('Extended Condition System', () => {
it('should handle type-specific conditions', () => {
const typeConditionMove: Move = {
name: "Flora Boost",
type: AttackType.FLORA,
power: 60,
accuracy: 100,
pp: 15,
priority: 0,
flags: [],
effects: [
{
type: 'damage',
target: 'opponent',
amount: 'normal'
},
{
type: 'modifyStats',
target: 'self',
stats: { attack: 'increase' },
condition: 'ifMoveType:flora'
}
]
};
expect(typeConditionMove.effects[1].condition).toBe('ifMoveType:flora');
});
it('should handle status-specific conditions', () => {
const statusConditionMove: Move = {
name: "Burn Power",
type: AttackType.SPACE,
power: 80,
accuracy: 100,
pp: 10,
priority: 0,
flags: [],
effects: [
{
type: 'damage',
target: 'opponent',
amount: 'strong',
condition: 'ifStatus:burn'
}
]
};
expect(statusConditionMove.effects[0].condition).toBe('ifStatus:burn');
});
it('should handle weather-specific conditions', () => {
const weatherConditionMove: Move = {
name: "Storm Strike",
type: AttackType.SPACE,
power: 70,
accuracy: 95,
pp: 15,
priority: 0,
flags: [],
effects: [
{
type: 'damage',
target: 'opponent',
amount: 'strong',
condition: 'ifWeather:storm'
}
]
};
expect(weatherConditionMove.effects[0].condition).toBe('ifWeather:storm');
});
});
describe('Remove Status Effects', () => {
it('should handle status removal moves', () => {
const removeStatusMove: Move = {
name: "Cleanse",
type: AttackType.NORMAL,
power: 0,
accuracy: 100,
pp: 15,
priority: 0,
flags: [],
effects: [
{
type: 'removeStatus',
target: 'self',
status: 'poison'
}
]
};
expect(removeStatusMove.effects[0].type).toBe('removeStatus');
expect(removeStatusMove.effects[0].status).toBe('poison');
});
it('should handle multi-target status removal', () => {
const teamCleanseMove: Move = {
name: "Team Cleanse",
type: AttackType.NORMAL,
power: 0,
accuracy: 100,
pp: 5,
priority: 0,
flags: [],
effects: [
{
type: 'removeStatus',
target: 'allies',
status: 'confuse'
}
]
};
expect(teamCleanseMove.effects[0].target).toBe('allies');
});
});
});