diff --git a/include/config/ai.h b/include/config/ai.h index b44fc46ba9..b94b04b665 100644 --- a/include/config/ai.h +++ b/include/config/ai.h @@ -44,6 +44,9 @@ #define SHOULD_SWITCH_REGENERATOR_PERCENTAGE 50 #define SHOULD_SWITCH_REGENERATOR_STATS_RAISED_PERCENTAGE 20 +// AI switchin considerations +#define ALL_MOVES_BAD_STATUS_MOVES_BAD FALSE // If the AI has no moves that affect the target, ShouldSwitchIfAllMovesBad can prompt a switch. Enabling this config will ignore status moves that can affect the target when making this decision. + // AI held item-based move scoring #define LOW_ACCURACY_THRESHOLD 75 // Moves with accuracy equal OR below this value are considered low accuracy diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 4be8605412..bd4dd88fd3 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -331,6 +331,38 @@ static bool32 ShouldSwitchIfTruant(u32 battler) return FALSE; } +static u32 FindMonWithMoveOfEffectiveness(u32 battler, u32 opposingBattler, uq4_12_t effectiveness) +{ + u32 move, i, j; + s32 firstId; + s32 lastId; // + 1 + struct Pokemon *party = NULL; + + // Get party information. + GetAIPartyIndexes(battler, &firstId, &lastId); + party = GetBattlerParty(battler); + + // Find a Pokémon in the party that has a super effective move. + for (i = firstId; i < lastId; i++) + { + if (!IsValidForBattle(&party[i])) + continue; + if (i == gBattlerPartyIndexes[battler]) + continue; + if (IsAceMon(battler, i)) + continue; + + for (j = 0; j < MAX_MON_MOVES; j++) + { + move = GetMonData(&party[i], MON_DATA_MOVE1 + j); + if (move != MOVE_NONE && AI_GetMoveEffectiveness(move, battler, opposingBattler) >= effectiveness && gMovesInfo[move].power != 0) + return SetSwitchinAndSwitch(battler, i); + } + } + + return FALSE; // There is not a single Pokémon in the party that has a move with this effectiveness threshold +} + static bool32 ShouldSwitchIfAllMovesBad(u32 battler) { u32 moveIndex; @@ -355,24 +387,27 @@ static bool32 ShouldSwitchIfAllMovesBad(u32 battler) for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) { aiMove = gBattleMons[battler].moves[moveIndex]; - if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) > UQ_4_12(0.0) && aiMove != MOVE_NONE) + if (AI_GetMoveEffectiveness(aiMove, battler, opposingBattler) > UQ_4_12(0.0) && aiMove != MOVE_NONE + && (!ALL_MOVES_BAD_STATUS_MOVES_BAD || gMovesInfo[aiMove].power != 0)) // If using ALL_MOVES_BAD_STATUS_MOVES_BAD, then need power to be non-zero return FALSE; } } if (RandomPercentage(RNG_AI_SWITCH_ALL_MOVES_BAD, GetSwitchChance(SHOULD_SWITCH_ALL_MOVES_BAD))) - return SetSwitchinAndSwitch(battler, PARTY_SIZE); + { + if (AI_DATA->mostSuitableMonId[battler] == PARTY_SIZE) // No good candidate mons, find any one that can deal damage + return FindMonWithMoveOfEffectiveness(battler, opposingBattler, UQ_4_12(1.0)); + else // Good candidate mon, send that in + return SetSwitchinAndSwitch(battler, PARTY_SIZE); + } + return FALSE; } static bool32 FindMonThatHitsWonderGuard(u32 battler) { u32 opposingBattler = GetOppositeBattler(battler); - s32 i, j; - s32 firstId; - s32 lastId; // + 1 - struct Pokemon *party = NULL; - u16 move; + u32 i, move; if (IsDoubleBattle()) return FALSE; @@ -391,33 +426,10 @@ static bool32 FindMonThatHitsWonderGuard(u32 battler) } } - // Get party information. - GetAIPartyIndexes(battler, &firstId, &lastId); - party = GetBattlerParty(battler); + if (RandomPercentage(RNG_AI_SWITCH_WONDER_GUARD, GetSwitchChance(SHOULD_SWITCH_WONDER_GUARD))) + return FindMonWithMoveOfEffectiveness(battler, opposingBattler, UQ_4_12(2.0)); - // Find a Pokémon in the party that has a super effective move. - for (i = firstId; i < lastId; i++) - { - if (!IsValidForBattle(&party[i])) - continue; - if (i == gBattlerPartyIndexes[battler]) - continue; - if (IsAceMon(battler, i)) - continue; - - for (j = 0; j < MAX_MON_MOVES; j++) - { - move = GetMonData(&party[i], MON_DATA_MOVE1 + j); - if (move != MOVE_NONE) - { - // Found a mon - if (AI_GetMoveEffectiveness(move, battler, opposingBattler) >= UQ_4_12(2.0) && RandomPercentage(RNG_AI_SWITCH_WONDER_GUARD, GetSwitchChance(SHOULD_SWITCH_WONDER_GUARD))) - return SetSwitchinAndSwitch(battler, i); - } - } - } - - return FALSE; // There is not a single Pokémon in the party that has a super effective move against a mon with Wonder Guard. + return FALSE; } static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index d2ab5f0b7a..0c6201b475 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -82,6 +82,22 @@ AI_SINGLE_BATTLE_TEST("AI will switch out if it has no move that affects the pla } } +AI_SINGLE_BATTLE_TEST("When AI switches out due to having no move that affects the player, AI will send in a mon that can hit the player, even if not ideal") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES); + PLAYER(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL, MOVE_CELEBRATE); } + OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_ABRA) { Level(5); Moves(MOVE_CONFUSION); } + OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); } + } WHEN { + TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_SWITCH(opponent, 2); EXPECT_SEND_OUT(opponent, 4); } + TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_MOVE(opponent, MOVE_TACKLE); } + } +} + AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a double battle (Wonder Guard)") { PASSES_RANDOMLY(SHOULD_SWITCH_WONDER_GUARD_PERCENTAGE, 100, RNG_AI_SWITCH_WONDER_GUARD);