[Misc][AI] Fix KO filter not accounting for move conditions (#4245)

* Only filter KO moves that won't fail

* Add Last Resort enemy command test
This commit is contained in:
innerthunder 2024-09-14 09:35:36 -07:00 committed by GitHub
parent 39a1963941
commit bdc7c95c1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 40 additions and 6 deletions

View File

@ -4410,7 +4410,10 @@ export class EnemyPokemon extends Pokemon {
const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT);
return move.category !== MoveCategory.STATUS return move.category !== MoveCategory.STATUS
&& moveTargets.some(p => p.getAttackDamage(this, move, !p.battleData.abilityRevealed, false, isCritical).damage >= p.hp); && moveTargets.some(p => {
const doesNotFail = move.applyConditions(this, p, move) || [Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP].includes(move.id);
return doesNotFail && p.getAttackDamage(this, move, !p.battleData.abilityRevealed, false, isCritical).damage >= p.hp;
});
}, this); }, this);
if (koMoves.length > 0) { if (koMoves.length > 0) {

View File

@ -6,7 +6,7 @@ import { AiType, EnemyPokemon } from "#app/field/pokemon";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const TIMEOUT = 20 * 1000; const TIMEOUT = 20 * 1000;
const NUM_TRIALS = 300; const NUM_TRIALS = 300;
@ -36,22 +36,26 @@ describe("Enemy Commands - Move Selection", () => {
phaserGame = new Phaser.Game({ phaserGame = new Phaser.Game({
type: Phaser.HEADLESS, type: Phaser.HEADLESS,
}); });
game = new GameManager(phaserGame);
game.override.ability(Abilities.BALL_FETCH);
}); });
afterEach(() => { afterEach(() => {
game.phaseInterceptor.restoreOg(); game.phaseInterceptor.restoreOg();
}); });
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.ability(Abilities.BALL_FETCH)
.enemyAbility(Abilities.BALL_FETCH);
});
it( it(
"should never use Status moves if an attack can KO", "should never use Status moves if an attack can KO",
async () => { async () => {
game.override game.override
.enemySpecies(Species.ETERNATUS) .enemySpecies(Species.ETERNATUS)
.enemyMoveset([Moves.ETERNABEAM, Moves.SLUDGE_BOMB, Moves.DRAGON_DANCE, Moves.COSMIC_POWER]) .enemyMoveset([Moves.ETERNABEAM, Moves.SLUDGE_BOMB, Moves.DRAGON_DANCE, Moves.COSMIC_POWER])
.enemyAbility(Abilities.BALL_FETCH)
.ability(Abilities.BALL_FETCH)
.startingLevel(1) .startingLevel(1)
.enemyLevel(100); .enemyLevel(100);
@ -72,4 +76,31 @@ describe("Enemy Commands - Move Selection", () => {
}); });
}, TIMEOUT }, TIMEOUT
); );
it(
"should not select Last Resort if it would fail, even if the move KOs otherwise",
async () => {
game.override
.enemySpecies(Species.KANGASKHAN)
.enemyMoveset([Moves.LAST_RESORT, Moves.GIGA_IMPACT, Moves.SPLASH, Moves.SWORDS_DANCE])
.startingLevel(1)
.enemyLevel(100);
await game.classicMode.startBattle([Species.MAGIKARP]);
const enemyPokemon = game.scene.getEnemyPokemon()!;
enemyPokemon.aiType = AiType.SMART_RANDOM;
const moveChoices: MoveChoiceSet = {};
const enemyMoveset = enemyPokemon.getMoveset();
enemyMoveset.forEach(mv => moveChoices[mv!.moveId] = 0);
getEnemyMoveChoices(enemyPokemon, moveChoices);
enemyMoveset.forEach(mv => {
if (mv?.getMove().category === MoveCategory.STATUS || mv?.moveId === Moves.LAST_RESORT) {
expect(moveChoices[mv.moveId]).toBe(0);
}
});
}, TIMEOUT
);
}); });