diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index d1dc40a3fb..06c41c6d77 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -262,13 +262,10 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH) { damageTaken = AI_GetDamage(opposingBattler, battler, i, AI_DEFENDING, gAiLogicData); - if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move + if (damageTaken > maxDamageTaken && !AI_DoesChoiceEffectBlockMove(opposingBattler, playerMove)) { - return maxDamageTaken = damageTaken; - break; - } - if (damageTaken > maxDamageTaken) maxDamageTaken = damageTaken; + } } } @@ -282,7 +279,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) } // Check if current mon can 1v1 in spite of bad matchup, and don't switch out if it can - if (hitsToKoPlayer < hitsToKoAI || (hitsToKoPlayer == hitsToKoAI && AI_IsFaster(battler, opposingBattler, aiBestMove))) + if ((hitsToKoPlayer != 0 && (hitsToKoPlayer < hitsToKoAI || hitsToKoAI == 0)) || (hitsToKoPlayer == hitsToKoAI && AI_IsFaster(battler, opposingBattler, aiBestMove))) return FALSE; // If we don't have any other viable options, don't switch out @@ -1810,7 +1807,7 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler) // No damage being dealt if ((damageTaken + statusDamage + recurringDamage <= recurringHealing) || damageTaken + statusDamage + recurringDamage == 0) - return startingHP; + return hitsToKO; // Mon fainted to hazards if (startingHP == 0) @@ -2018,6 +2015,18 @@ static inline bool32 IsFreeSwitch(enum SwitchType switchType, u32 battlerSwitchi static inline bool32 CanSwitchinWin1v1(u32 hitsToKOAI, u32 hitsToKOPlayer, bool32 isSwitchinFirst, bool32 isFreeSwitch) { + // Player's best move deals 0 damage + if (hitsToKOAI == 0 && hitsToKOPlayer > 0) + return TRUE; + + // AI's best move deals 0 damage + if (hitsToKOPlayer == 0 && hitsToKOAI > 0) + return FALSE; + + // Neither mon can damage the other + if (hitsToKOPlayer == 0 && hitsToKOAI == 0) + return FALSE; + // Free switch, need to outspeed or take 1 extra hit if (isFreeSwitch) { @@ -2034,7 +2043,7 @@ static inline bool32 CanSwitchinWin1v1(u32 hitsToKOAI, u32 hitsToKOPlayer, bool3 // Everything runs in the same loop to minimize computation time. This makes it harder to read, but hopefully the comments can guide you! static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, u32 battler, u32 opposingBattler, u32 battlerIn1, u32 battlerIn2, enum SwitchType switchType) { - int revengeKillerId = PARTY_SIZE, slowRevengeKillerId = PARTY_SIZE, fastThreatenId = PARTY_SIZE, slowThreatenId = PARTY_SIZE, damageMonId = PARTY_SIZE; + int revengeKillerId = PARTY_SIZE, slowRevengeKillerId = PARTY_SIZE, fastThreatenId = PARTY_SIZE, slowThreatenId = PARTY_SIZE, damageMonId = PARTY_SIZE, generic1v1MonId = PARTY_SIZE; int batonPassId = PARTY_SIZE, typeMatchupId = PARTY_SIZE, typeMatchupEffectiveId = PARTY_SIZE, defensiveMonId = PARTY_SIZE, aceMonId = PARTY_SIZE, trapperId = PARTY_SIZE; int i, j, aliveCount = 0, bits = 0, aceMonCount = 0; s32 defensiveMonHitKOThreshold = 3; // 3HKO threshold that candidate defensive mons must exceed @@ -2079,9 +2088,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, for (j = 0; j < MAX_MON_MOVES; j++) { aiMove = gAiLogicData->switchinCandidate.battleMon.moves[j]; - - if (aiMove != MOVE_NONE && !IsBattleMoveStatus(aiMove)) - damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, AI_ATTACKING); + damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, AI_ATTACKING); // Offensive switchin decisions are based on which whether switchin moves first and whether it can win a 1v1 isSwitchinFirst = AI_WhoStrikesFirstPartyMon(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, aiMove); @@ -2112,6 +2119,9 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, defensiveMonId = i; } + if (canSwitchinWin1v1) + generic1v1MonId = i; + // Check for mon with resistance and super effective move for best type matchup mon with effective move if (aiMove != MOVE_NONE && !IsBattleMoveStatus(aiMove)) { @@ -2192,6 +2202,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, else if (typeMatchupEffectiveId != PARTY_SIZE) return typeMatchupEffectiveId; else if (typeMatchupId != PARTY_SIZE) return typeMatchupId; else if (batonPassId != PARTY_SIZE) return batonPassId; + else if (generic1v1MonId != PARTY_SIZE) return generic1v1MonId; else if (damageMonId != PARTY_SIZE) return damageMonId; } else @@ -2202,6 +2213,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, else if (typeMatchupId != PARTY_SIZE) return typeMatchupId; else if (defensiveMonId != PARTY_SIZE) return defensiveMonId; else if (batonPassId != PARTY_SIZE) return batonPassId; + else if (generic1v1MonId != PARTY_SIZE) return generic1v1MonId; } // If ace mon is the last available Pokemon and U-Turn/Volt Switch or Eject Pack/Button was used - switch to the mon. if (aceMonId != PARTY_SIZE && CountUsablePartyMons(battler) <= aceMonCount diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index dbe3b8f010..8a1070b42c 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -1254,3 +1254,16 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't send out defensive mon TURN { MOVE(player, MOVE_WATER_PULSE); EXPECT_MOVE(opponent, MOVE_BULLDOZE); } } } + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI considers 0 hits to KO as losing a 1v1") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_JOLTEON) { Level(100); Ability(ABILITY_VOLT_ABSORB); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_ZIGZAGOON) { Level(1); HP(1); Moves(MOVE_TACKLE); } + OPPONENT(SPECIES_TANGELA) { Level(100); Moves(MOVE_THUNDERBOLT); } + OPPONENT(SPECIES_TANGELA) { Level(100); Moves(MOVE_GIGA_DRAIN); } + } WHEN { + TURN { MOVE(player, MOVE_TACKLE); EXPECT_SEND_OUT(opponent, 2); } + } +}