|
import { describe, it, expect, beforeEach } from 'vitest'; |
|
import { BattleEngine } from './BattleEngine'; |
|
import type { PicletDefinition, SpecialAbility } from './types'; |
|
import { PicletType, AttackType } from './types'; |
|
|
|
describe('Advanced Mechanic Override System', () => { |
|
describe('Critical Hit Mechanics', () => { |
|
it('should prevent critical hits with Shell Armor ability', () => { |
|
const shellArmor: SpecialAbility = { |
|
name: "Shell Armor", |
|
description: "Hard shell prevents critical hits", |
|
effects: [ |
|
{ |
|
type: 'mechanicOverride', |
|
mechanic: 'criticalHits', |
|
condition: 'always', |
|
value: false |
|
} |
|
] |
|
}; |
|
|
|
const defender: PicletDefinition = { |
|
name: "Shell Defender", |
|
description: "Protected by hard shell", |
|
tier: 'medium', |
|
primaryType: PicletType.MINERAL, |
|
baseStats: { hp: 80, attack: 60, defense: 80, speed: 40 }, |
|
nature: "Impish", |
|
specialAbility: shellArmor, |
|
movepool: [ |
|
{ |
|
name: "Defense Curl", |
|
type: AttackType.NORMAL, |
|
power: 0, |
|
accuracy: 100, |
|
pp: 10, |
|
priority: 0, |
|
flags: [], |
|
effects: [ |
|
{ |
|
type: 'modifyStats', |
|
target: 'self', |
|
stats: { defense: 'increase' } |
|
} |
|
] |
|
} |
|
] |
|
}; |
|
|
|
const attacker: PicletDefinition = { |
|
name: "High Crit Attacker", |
|
description: "Has high critical hit rate", |
|
tier: 'medium', |
|
primaryType: PicletType.BEAST, |
|
baseStats: { hp: 70, attack: 90, defense: 50, speed: 80 }, |
|
nature: "Adamant", |
|
specialAbility: { name: "No Ability", description: "" }, |
|
movepool: [ |
|
{ |
|
name: "Slash", |
|
type: AttackType.BEAST, |
|
power: 70, |
|
accuracy: 100, |
|
pp: 10, |
|
priority: 0, |
|
flags: ['contact'], |
|
effects: [ |
|
{ |
|
type: 'damage', |
|
target: 'opponent', |
|
amount: 'normal' |
|
} |
|
] |
|
} |
|
] |
|
}; |
|
|
|
const engine = new BattleEngine(defender, attacker); |
|
|
|
|
|
const originalCritRate = (engine as any).calculateCriticalChance; |
|
(engine as any).calculateCriticalChance = () => 1.0; |
|
|
|
let criticalHitOccurred = false; |
|
for (let i = 0; i < 10; i++) { |
|
const initialHp = engine.getState().playerPiclet.currentHp; |
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const log = engine.getLog(); |
|
if (log.some(msg => msg.includes('critical') || msg.includes('Critical'))) { |
|
criticalHitOccurred = true; |
|
break; |
|
} |
|
|
|
if (engine.isGameOver()) break; |
|
} |
|
|
|
|
|
expect(criticalHitOccurred).toBe(false); |
|
|
|
|
|
(engine as any).calculateCriticalChance = originalCritRate; |
|
}); |
|
|
|
it('should guarantee critical hits with certain abilities', () => { |
|
const alwaysCrit: SpecialAbility = { |
|
name: "Super Luck", |
|
description: "Always lands critical hits", |
|
effects: [ |
|
{ |
|
type: 'mechanicOverride', |
|
mechanic: 'criticalHits', |
|
condition: 'always', |
|
value: true |
|
} |
|
] |
|
}; |
|
|
|
const critUser: PicletDefinition = { |
|
name: "Lucky Fighter", |
|
description: "Always gets critical hits", |
|
tier: 'medium', |
|
primaryType: PicletType.BEAST, |
|
baseStats: { hp: 70, attack: 80, defense: 60, speed: 90 }, |
|
nature: "Hasty", |
|
specialAbility: alwaysCrit, |
|
movepool: [ |
|
{ |
|
name: "Strike", |
|
type: AttackType.BEAST, |
|
power: 60, |
|
accuracy: 100, |
|
pp: 10, |
|
priority: 0, |
|
flags: ['contact'], |
|
effects: [ |
|
{ |
|
type: 'damage', |
|
target: 'opponent', |
|
amount: 'normal' |
|
} |
|
] |
|
} |
|
] |
|
}; |
|
|
|
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(critUser, opponent); |
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const log = engine.getLog(); |
|
expect(log.some(msg => msg.includes('critical') || msg.includes('Critical'))).toBe(true); |
|
}); |
|
}); |
|
|
|
describe('Status Immunity', () => { |
|
it('should provide immunity to specific status effects', () => { |
|
const insomnia: SpecialAbility = { |
|
name: "Insomnia", |
|
description: "Prevents sleep status", |
|
effects: [ |
|
{ |
|
type: 'mechanicOverride', |
|
mechanic: 'statusImmunity', |
|
value: ['sleep'] |
|
} |
|
] |
|
}; |
|
|
|
const insomniac: PicletDefinition = { |
|
name: "Sleepless Fighter", |
|
description: "Cannot be put to sleep", |
|
tier: 'medium', |
|
primaryType: PicletType.CULTURE, |
|
baseStats: { hp: 70, attack: 60, defense: 60, speed: 80 }, |
|
nature: "Timid", |
|
specialAbility: insomnia, |
|
movepool: [ |
|
{ |
|
name: "Tackle", |
|
type: AttackType.NORMAL, |
|
power: 40, |
|
accuracy: 100, |
|
pp: 10, |
|
priority: 0, |
|
flags: ['contact'], |
|
effects: [{ type: 'damage', target: 'opponent', amount: 'normal' }] |
|
} |
|
] |
|
}; |
|
|
|
const sleepUser: PicletDefinition = { |
|
name: "Sleep Inducer", |
|
description: "Puts opponents to sleep", |
|
tier: 'medium', |
|
primaryType: PicletType.CULTURE, |
|
baseStats: { hp: 80, attack: 50, defense: 70, speed: 60 }, |
|
nature: "Calm", |
|
specialAbility: { name: "No Ability", description: "" }, |
|
movepool: [ |
|
{ |
|
name: "Sleep Powder", |
|
type: AttackType.FLORA, |
|
power: 0, |
|
accuracy: 75, |
|
pp: 10, |
|
priority: 0, |
|
flags: [], |
|
effects: [ |
|
{ |
|
type: 'applyStatus', |
|
target: 'opponent', |
|
status: 'sleep' |
|
} |
|
] |
|
} |
|
] |
|
}; |
|
|
|
const engine = new BattleEngine(insomniac, sleepUser); |
|
|
|
engine.executeActions( |
|
{ type: 'move', piclet: 'player', moveIndex: 0 }, |
|
{ type: 'move', piclet: 'opponent', moveIndex: 0 } |
|
); |
|
|
|
const log = engine.getLog(); |
|
expect(log.some(msg => msg.includes('immune') || msg.includes('had no effect'))).toBe(true); |
|
expect(log.some(msg => msg.includes('fell asleep'))).toBe(false); |
|
}); |
|
}); |
|
|
|
describe('Type Immunity', () => { |
|
it('should provide immunity to specific attack types', () => { |
|
const levitate: SpecialAbility = { |
|
name: "Levitate", |
|
description: "Floating ability makes ground moves miss", |
|
effects: [ |
|
{ |
|
type: 'mechanicOverride', |
|
mechanic: 'typeImmunity', |
|
value: ['ground'] |
|
} |
|
] |
|
}; |
|
|
|
const levitator: PicletDefinition = { |
|
name: "Floating Fighter", |
|
description: "Levitates above ground attacks", |
|
tier: 'medium', |
|
primaryType: PicletType.SPACE, |
|
baseStats: { hp: 75, attack: 70, defense: 60, speed: 85 }, |
|
nature: "Timid", |
|
specialAbility: levitate, |
|
movepool: [ |
|
{ |
|
name: "Air Slash", |
|
type: AttackType.SPACE, |
|
power: 60, |
|
accuracy: 95, |
|
pp: 10, |
|
priority: 0, |
|
flags: [], |
|
effects: [ |
|
{ |
|
type: 'damage', |
|
target: 'opponent', |
|
amount: 'normal' |
|
} |
|
] |
|
} |
|
] |
|
}; |
|
|
|
const groundUser: PicletDefinition = { |
|
name: "Ground Attacker", |
|
description: "Uses ground-based attacks", |
|
tier: 'medium', |
|
primaryType: PicletType.MINERAL, |
|
baseStats: { hp: 80, attack: 80, defense: 70, speed: 60 }, |
|
nature: "Adamant", |
|
specialAbility: { name: "No Ability", description: "" }, |
|
movepool: [ |
|
{ |
|
name: "Earthquake", |
|
type: AttackType.MINERAL, |
|
power: 100, |
|
accuracy: 100, |
|
pp: 10, |
|
priority: 0, |
|
flags: ['ground'], |
|
effects: [ |
|
{ |
|
type: 'damage', |
|
target: 'opponent', |
|
amount: 'strong' |
|
} |
|
] |
|
} |
|
] |
|
}; |
|
|
|
const engine = new BattleEngine(levitator, groundUser); |
|
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 log = engine.getLog(); |
|
|
|
|
|
expect(finalHp).toBe(initialHp); |
|
expect(log.some(msg => msg.includes('had no effect') || msg.includes('immune'))).toBe(true); |
|
}); |
|
}); |
|
}); |