From bdc7c95c1cfc0e9a3177cfadde2c1170e92f0268 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Sat, 14 Sep 2024 09:35:36 -0700 Subject: [PATCH] [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 --- src/field/pokemon.ts | 5 ++++- src/test/enemy_command.test.ts | 41 +++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 43a7090bff5..faae3b06ba0 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4410,7 +4410,10 @@ export class EnemyPokemon extends Pokemon { const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); 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); if (koMoves.length > 0) { diff --git a/src/test/enemy_command.test.ts b/src/test/enemy_command.test.ts index 39b77845ea9..9a2caa56dfc 100644 --- a/src/test/enemy_command.test.ts +++ b/src/test/enemy_command.test.ts @@ -6,7 +6,7 @@ import { AiType, EnemyPokemon } from "#app/field/pokemon"; import { randSeedInt } from "#app/utils"; import GameManager from "#test/utils/gameManager"; 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 NUM_TRIALS = 300; @@ -36,22 +36,26 @@ describe("Enemy Commands - Move Selection", () => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, }); - game = new GameManager(phaserGame); - game.override.ability(Abilities.BALL_FETCH); }); afterEach(() => { game.phaseInterceptor.restoreOg(); }); + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override + .ability(Abilities.BALL_FETCH) + .enemyAbility(Abilities.BALL_FETCH); + }); + it( "should never use Status moves if an attack can KO", async () => { game.override .enemySpecies(Species.ETERNATUS) .enemyMoveset([Moves.ETERNABEAM, Moves.SLUDGE_BOMB, Moves.DRAGON_DANCE, Moves.COSMIC_POWER]) - .enemyAbility(Abilities.BALL_FETCH) - .ability(Abilities.BALL_FETCH) .startingLevel(1) .enemyLevel(100); @@ -72,4 +76,31 @@ describe("Enemy Commands - Move Selection", () => { }); }, 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 + ); });