From 6819dd3c37a7395457564c9e5ced9827115da7f2 Mon Sep 17 00:00:00 2001 From: GhoulMage Date: Sun, 27 Oct 2024 16:32:45 +0100 Subject: [PATCH] =?UTF-8?q?New=20AI=20flag=20for=20marking=20the=20two=20l?= =?UTF-8?q?ast=20Pok=C3=A9mon=20as=20Ace=20Pok=C3=A9mon.=20(#5587)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/tutorials/ai_flags.md | 3 + include/constants/battle_ai.h | 3 +- src/battle_ai_switch_items.c | 4 ++ src/battle_controller_opponent.c | 4 ++ test/battle/ai/ai_double_ace.c | 96 ++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 test/battle/ai/ai_double_ace.c diff --git a/docs/tutorials/ai_flags.md b/docs/tutorials/ai_flags.md index 8559ff36f9..fdbfc27fac 100644 --- a/docs/tutorials/ai_flags.md +++ b/docs/tutorials/ai_flags.md @@ -136,6 +136,9 @@ Affects when the AI chooses to switch. AI will make smarter decisions about when ## `AI_FLAG_ACE_POKEMON` Marks the last Pokemon in the party as the Ace Pokemon. It will not be used unless it is the last one remaining, or is forced to be switched in (Roar, U-Turn with 1 mon remaining, etc.) +## `AI_FLAG_DOUBLE_ACE_POKEMON` +Marks the last two Pokémon in the party as Ace Pokémon, with the same behaviour as `AI_FLAG_ACE_POKEMON`. Intented for double battles where you battle one trainer id that represents two trainers, ie Twins, Couples. + ## `AI_FLAG_OMNISCIENT` AI has full knowledge of player moves, abilities, and hold items, and can use this knowledge when making decisions. diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index ca469cadeb..df6069ead9 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -48,8 +48,9 @@ #define AI_FLAG_SMART_MON_CHOICES (1 << 17) // AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are separate decisions. Automatically included by AI_FLAG_SMART_SWITCHING. #define AI_FLAG_CONSERVATIVE (1 << 18) // AI assumes all moves will low roll damage. #define AI_FLAG_SEQUENCE_SWITCHING (1 << 19) // AI switches in mons in exactly party order, and never switches mid-battle. +#define AI_FLAG_DOUBLE_ACE_POKEMON (1 << 20) // AI has *two* Ace Pokémon. The last two Pokémons in the party won't be used unless they're the last ones remaining. Goes well in battles where the trainer ID equals to twins, couples, etc. -#define AI_FLAG_COUNT 20 +#define AI_FLAG_COUNT 21 // The following options are enough to have a basic/smart trainer. Any other addtion could make the trainer worse/better depending on the flag #define AI_FLAG_BASIC_TRAINER (AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 125f488589..06a69e4f60 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -43,6 +43,10 @@ static bool32 IsAceMon(u32 battler, u32 monPartyId) && !(gBattleStruct->forcedSwitch & (1u << battler)) && monPartyId == CalculateEnemyPartyCount()-1) return TRUE; + if (AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_DOUBLE_ACE_POKEMON + && !(gBattleStruct->forcedSwitch & (1u << battler)) + && (monPartyId == CalculateEnemyPartyCount()-1 || monPartyId == CalculateEnemyPartyCount()-2)) + return TRUE; return FALSE; } diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index b0586a8584..4ede13e590 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -688,6 +688,10 @@ static void OpponentHandleChoosePokemon(u32 battler) if ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_ACE_POKEMON) && ((chosenMonId != CalculateEnemyPartyCount() - 1) || CountAIAliveNonEggMonsExcept(PARTY_SIZE) == pokemonInBattle)) continue; + if ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_DOUBLE_ACE_POKEMON) + && (((chosenMonId != CalculateEnemyPartyCount() - 1) || (chosenMonId != CalculateEnemyPartyCount() - 1)) + || CountAIAliveNonEggMonsExcept(PARTY_SIZE) == pokemonInBattle)) + continue; // mon is valid break; } diff --git a/test/battle/ai/ai_double_ace.c b/test/battle/ai/ai_double_ace.c new file mode 100644 index 0000000000..aec37b9307 --- /dev/null +++ b/test/battle/ai/ai_double_ace.c @@ -0,0 +1,96 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS { + ASSUME(gMovesInfo[MOVE_U_TURN].effect == EFFECT_HIT_ESCAPE); + ASSUME(gMovesInfo[MOVE_CRUNCH].type == TYPE_DARK); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] == TYPE_PSYCHIC); + ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] == TYPE_PSYCHIC); +} + +AI_DOUBLE_BATTLE_TEST("AI_FLAG_DOUBLE_ACE_POKEMON: U-Turn won't send out any of the Ace Mons if other options exist") +{ + u32 flag; + + PARAMETRIZE { flag = AI_FLAG_DOUBLE_ACE_POKEMON; } + PARAMETRIZE { flag = 0; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_SMART_SWITCHING | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | flag); + + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + + OPPONENT(SPECIES_GASTLY) { Moves(MOVE_U_TURN); } + OPPONENT(SPECIES_DUSKULL) { Moves(MOVE_U_TURN); } + + OPPONENT(SPECIES_HAUNTER) { Moves(MOVE_U_TURN); } + OPPONENT(SPECIES_GENGAR) { Moves(MOVE_U_TURN); } + + // Aces + // Crunch is super effective against Wobbuffet Psychic type, so normally the AI would switch them in + OPPONENT(SPECIES_POOCHYENA) { Moves(MOVE_CRUNCH); } + OPPONENT(SPECIES_MIGHTYENA) { Moves(MOVE_CRUNCH); } + } WHEN { + TURN { + EXPECT_MOVE(opponentLeft, MOVE_U_TURN); + EXPECT_MOVE(opponentRight, MOVE_U_TURN); + + if(flag == AI_FLAG_DOUBLE_ACE_POKEMON) { + EXPECT_SEND_OUT(opponentLeft, 3); + EXPECT_SEND_OUT(opponentRight, 2); + } else { + EXPECT_SEND_OUT(opponentLeft, 4); + EXPECT_SEND_OUT(opponentRight, 5); + } + } + } +} + +AI_DOUBLE_BATTLE_TEST("AI_FLAG_DOUBLE_ACE_POKEMON: U-Turn will send out an Ace Mon if no other options remain") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_SMART_SWITCHING | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_DOUBLE_ACE_POKEMON); + + PLAYER(SPECIES_WOBBUFFET) { Level(50); } + PLAYER(SPECIES_WOBBUFFET) { Level(50); } + + OPPONENT(SPECIES_GASTLY) { Moves(MOVE_U_TURN); Level(50); } + OPPONENT(SPECIES_DUSKULL) { Moves(MOVE_U_TURN); Level(5); } + + // Aces + // Should choose Poochyena as its level is higher. + OPPONENT(SPECIES_MIGHTYENA) { Moves(MOVE_CRUNCH); Level(5); } + OPPONENT(SPECIES_POOCHYENA) { Moves(MOVE_CRUNCH); Level(50); } + } WHEN { + TURN { + EXPECT_MOVE(opponentLeft, MOVE_U_TURN); + EXPECT_MOVE(opponentRight, MOVE_U_TURN); + + EXPECT_SEND_OUT(opponentLeft, 3); + EXPECT_SEND_OUT(opponentRight, 0); + } + } +} + +AI_DOUBLE_BATTLE_TEST("AI_FLAG_DOUBLE_ACE_POKEMON: Ace mons won't be switched in even if they are the best candidates") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_GENGAR].types[0] == TYPE_GHOST); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_SMART_SWITCHING | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_DOUBLE_ACE_POKEMON); + + PLAYER(SPECIES_GENGAR) { Level(10); } + PLAYER(SPECIES_GENGAR) { Level(10); } + + OPPONENT(SPECIES_RATTATA) { Moves(MOVE_TACKLE); Level(10); } + OPPONENT(SPECIES_PSYDUCK) { Moves(MOVE_TACKLE); Level(10); } + + OPPONENT(SPECIES_ABRA) { Moves(MOVE_ABSORB); Level(20); } + + // Aces + OPPONENT(SPECIES_MIGHTYENA) { Moves(MOVE_CRUNCH); Level(50); } + OPPONENT(SPECIES_POOCHYENA) { Moves(MOVE_CRUNCH); Level(50); } + } WHEN { + TURN { EXPECT_SWITCH(opponentLeft, 2); } + } +}