import { describe, it, expect, beforeEach } from 'vitest'; import { BattleEngine } from './BattleEngine'; import type { PicletDefinition, Move, SpecialAbility } from './types'; import { PicletType, AttackType } from './types'; describe('Missing Battle System Features', () => { describe('manipulatePP Effects', () => { it('should drain opponent PP', () => { const ppDrainer: PicletDefinition = { name: "PP Drainer", description: "Drains opponent's PP", tier: 'medium', primaryType: PicletType.CULTURE, baseStats: { hp: 80, attack: 60, defense: 60, speed: 70 }, nature: "Calm", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Mind Drain", type: AttackType.CULTURE, power: 0, accuracy: 100, pp: 10, priority: 0, flags: [], effects: [ { type: 'manipulatePP', target: 'opponent', action: 'drain', amount: 'medium' } ] } ] }; const opponent: PicletDefinition = { name: "Opponent", description: "Standard opponent", tier: 'medium', primaryType: PicletType.BEAST, baseStats: { hp: 80, attack: 60, defense: 60, speed: 70 }, nature: "Hardy", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 10, priority: 0, flags: ['contact'], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] } ] }; const engine = new BattleEngine(ppDrainer, opponent); const initialPP = engine.getState().opponentPiclet.moves[0].currentPP; engine.executeActions( { type: 'move', piclet: 'player', moveIndex: 0 }, { type: 'move', piclet: 'opponent', moveIndex: 0 } ); const finalPP = engine.getState().opponentPiclet.moves[0].currentPP; expect(finalPP).toBeLessThan(initialPP); expect(engine.getLog().some(msg => msg.includes('PP was drained'))).toBe(true); }); it('should restore own PP', () => { const ppRestorer: PicletDefinition = { name: "PP Restorer", description: "Restores own PP", tier: 'medium', primaryType: PicletType.FLORA, baseStats: { hp: 80, attack: 60, defense: 60, speed: 70 }, nature: "Calm", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "PP Restore", type: AttackType.FLORA, power: 0, accuracy: 100, pp: 5, priority: 0, flags: [], effects: [ { type: 'manipulatePP', target: 'self', action: 'restore', amount: 'large' } ] } ] }; const opponent: PicletDefinition = { name: "Opponent", description: "Standard opponent", tier: 'medium', primaryType: PicletType.BEAST, baseStats: { hp: 80, attack: 60, defense: 60, speed: 70 }, nature: "Hardy", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 10, priority: 0, flags: ['contact'], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] } ] }; const engine = new BattleEngine(ppRestorer, opponent); // Use the PP restore move multiple times to drain it engine['state'].playerPiclet.moves[0].currentPP = 1; const initialPP = engine['state'].playerPiclet.moves[0].currentPP; engine.executeActions( { type: 'move', piclet: 'player', moveIndex: 0 }, { type: 'move', piclet: 'opponent', moveIndex: 0 } ); const finalPP = engine.getState().playerPiclet.moves[0].currentPP; expect(finalPP).toBeGreaterThan(initialPP); expect(engine.getLog().some(msg => msg.includes('PP was restored'))).toBe(true); }); }); describe('fieldEffect System', () => { it('should apply field effects that persist across turns', () => { const fieldEffectUser: PicletDefinition = { name: "Field Controller", description: "Controls battlefield effects", tier: 'medium', primaryType: PicletType.SPACE, baseStats: { hp: 80, attack: 60, defense: 60, speed: 70 }, nature: "Calm", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Reflect", type: AttackType.SPACE, power: 0, accuracy: 100, pp: 10, priority: 0, flags: [], effects: [ { type: 'fieldEffect', effect: 'reflect', target: 'playerSide', stackable: false } ] } ] }; const opponent: PicletDefinition = { name: "Opponent", description: "Standard opponent", tier: 'medium', primaryType: PicletType.BEAST, baseStats: { hp: 80, attack: 60, defense: 60, speed: 70 }, nature: "Hardy", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Physical Attack", type: AttackType.BEAST, power: 60, accuracy: 100, pp: 10, priority: 0, flags: ['contact'], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] } ] }; const engine = new BattleEngine(fieldEffectUser, opponent); // Apply reflect engine.executeActions( { type: 'move', piclet: 'player', moveIndex: 0 }, { type: 'move', piclet: 'opponent', moveIndex: 0 } ); expect(engine.getLog().some(msg => msg.includes('Reflect') && msg.includes('applied'))).toBe(true); // Check if reflect reduces physical damage in subsequent turns 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; const damage = initialHp - finalHp; // Reflect should reduce physical damage expect(damage).toBeLessThan(30); // Should be reduced from normal ~40-50 damage }); it('should handle spikes field effect', () => { const spikesUser: PicletDefinition = { name: "Spikes User", description: "Sets entry hazards", tier: 'medium', primaryType: PicletType.MINERAL, baseStats: { hp: 80, attack: 60, defense: 60, speed: 70 }, nature: "Impish", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Spikes", type: AttackType.MINERAL, power: 0, accuracy: 100, pp: 10, priority: 0, flags: [], effects: [ { type: 'fieldEffect', effect: 'spikes', target: 'opponentSide', stackable: true } ] } ] }; const opponent: PicletDefinition = { name: "Opponent", description: "Standard opponent", tier: 'medium', primaryType: PicletType.BEAST, baseStats: { hp: 80, attack: 60, defense: 60, speed: 70 }, nature: "Hardy", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Tackle", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 10, priority: 0, flags: ['contact'], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] } ] }; const engine = new BattleEngine(spikesUser, opponent); engine.executeActions( { type: 'move', piclet: 'player', moveIndex: 0 }, { type: 'move', piclet: 'opponent', moveIndex: 0 } ); expect(engine.getLog().some(msg => msg.includes('Spikes') && msg.includes('set'))).toBe(true); // TODO: Test spikes damage when switching (requires switching mechanics) }); }); describe('counter Effects', () => { it('should counter physical attacks', () => { const counterUser: PicletDefinition = { name: "Counter Fighter", description: "Counters physical attacks", tier: 'medium', primaryType: PicletType.BEAST, baseStats: { hp: 100, attack: 60, defense: 80, speed: 50 }, nature: "Brave", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Counter", type: AttackType.BEAST, power: 0, accuracy: 100, pp: 10, priority: 1, // High priority to set up counter before opponent attacks flags: ['lowPriority'], effects: [ { type: 'counter', strength: 'strong' } ] } ] }; const opponent: PicletDefinition = { name: "Physical Attacker", description: "Uses physical moves", tier: 'medium', primaryType: PicletType.BEAST, baseStats: { hp: 80, attack: 80, defense: 60, speed: 70 }, nature: "Adamant", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Physical Strike", type: AttackType.BEAST, power: 80, accuracy: 100, pp: 10, priority: 0, flags: ['contact'], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] } ] }; const engine = new BattleEngine(counterUser, opponent); 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; expect(finalOpponentHp).toBeLessThan(initialOpponentHp); expect(engine.getLog().some(msg => msg.includes('countered') || msg.includes('Counter'))).toBe(true); }); }); describe('priority Effects', () => { it('should modify move priority conditionally', () => { const priorityUser: PicletDefinition = { name: "Priority User", description: "Uses priority moves based on conditions", tier: 'medium', primaryType: PicletType.SPACE, baseStats: { hp: 60, attack: 70, defense: 50, speed: 40 }, nature: "Quiet", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Desperation Strike", type: AttackType.SPACE, power: 60, accuracy: 100, pp: 10, priority: 0, flags: [], effects: [ { type: 'damage', target: 'opponent', amount: 'normal' }, { type: 'priority', target: 'self', value: 1, condition: 'ifLowHp' } ] } ] }; const fastOpponent: PicletDefinition = { name: "Fast Opponent", description: "Very fast opponent", tier: 'medium', primaryType: PicletType.BEAST, baseStats: { hp: 80, attack: 60, defense: 60, speed: 100 }, nature: "Timid", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Quick Attack", type: AttackType.NORMAL, power: 40, accuracy: 100, pp: 10, priority: 0, flags: [], effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] } ] }; const engine = new BattleEngine(priorityUser, fastOpponent); // Damage the priority user to trigger low HP condition engine['state'].playerPiclet.currentHp = Math.floor(engine['state'].playerPiclet.maxHp * 0.2); engine.executeActions( { type: 'move', piclet: 'player', moveIndex: 0 }, { type: 'move', piclet: 'opponent', moveIndex: 0 } ); const log = engine.getLog(); const playerMoveIndex = log.findIndex(msg => msg.includes('Priority User used Desperation Strike')); const opponentMoveIndex = log.findIndex(msg => msg.includes('Fast Opponent used Quick Attack')); // When at low HP, priority user should go first despite lower speed expect(playerMoveIndex).toBeLessThan(opponentMoveIndex); }); }); describe('removeStatus Effects', () => { it('should remove status effects from target', () => { // Simple test: create a move that only removes poison status const cleanser: PicletDefinition = { name: "Cleanser", description: "Can remove poison", tier: 'medium', primaryType: PicletType.FLORA, baseStats: { hp: 100, attack: 50, defense: 50, speed: 50 }, nature: "Calm", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Cleanse", type: AttackType.FLORA, power: 0, accuracy: 100, pp: 10, priority: 0, flags: [], effects: [ { type: 'removeStatus', target: 'self', status: 'poison' } ] } ] }; const dummy: PicletDefinition = { name: "Dummy", description: "Does nothing", tier: 'low', primaryType: PicletType.BEAST, baseStats: { hp: 50, attack: 30, defense: 30, speed: 30 }, nature: "Docile", specialAbility: { name: "No Ability", description: "" }, movepool: [ { name: "Do Nothing", type: AttackType.NORMAL, power: 0, accuracy: 100, pp: 20, priority: 0, flags: [], effects: [] } ] }; const engine = new BattleEngine(cleanser, dummy); const playerPiclet = engine.getState().playerPiclet; // Test the removeStatus effect by directly calling it // This bypasses any turn/timing issues const mockEffect = { status: 'poison' }; // First add poison manually playerPiclet.statusEffects.push('poison'); expect(playerPiclet.statusEffects.includes('poison')).toBe(true); // Call the removeStatus effect processor directly engine['processRemoveStatusEffect'](mockEffect, playerPiclet); // Check if poison was removed expect(playerPiclet.statusEffects.includes('poison')).toBe(false); }); }); });