diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 848302db25..60030acef5 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -180,6 +180,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) bool32 getsOneShot = FALSE, hasStatusMove = FALSE, hasSuperEffectiveMove = FALSE; u16 typeEffectiveness = UQ_4_12(1.0); //baseline typing damage enum BattleMoveEffects aiMoveEffect; + u32 hitsToKoPlayer = 0, hitsToKoAI = 0; // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) @@ -238,6 +239,8 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) } } + hitsToKoPlayer = GetNoOfHitsToKOBattlerDmg(maxDamageDealt, opposingBattler); + // Calculate type advantage typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType1))); if (atkType2 != atkType1) @@ -266,6 +269,8 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) } } + hitsToKoAI = GetNoOfHitsToKOBattlerDmg(maxDamageTaken, battler); + // Check if mon gets one shot if(maxDamageTaken > gBattleMons[battler].hp && !(gItemsInfo[gBattleMons[battler].item].holdEffect == HOLD_EFFECT_FOCUS_SASH || (!IsMoldBreakerTypeAbility(opposingBattler, gBattleMons[opposingBattler].ability) && B_STURDY >= GEN_5 && aiAbility == ABILITY_STURDY))) @@ -273,12 +278,9 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) getsOneShot = TRUE; } - // Check if current mon can outspeed and KO in spite of bad matchup, and don't switch out if it can - if(damageDealt > gBattleMons[opposingBattler].hp) - { - if (AI_IsFaster(battler, opposingBattler, aiBestMove)) - return FALSE; - } + // 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))) + return FALSE; // If we don't have any other viable options, don't switch out if (gAiLogicData->mostSuitableMonId[battler] == PARTY_SIZE) @@ -2063,14 +2065,6 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, hitsToKOAI = GetSwitchinHitsToKO(GetMaxDamagePlayerCouldDealToSwitchin(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon), battler); typeMatchup = GetSwitchinTypeMatchup(opposingBattler, gAiLogicData->switchinCandidate.battleMon); - // Track max hits to KO and set defensive mon - if(hitsToKOAI > maxHitsToKO) - { - maxHitsToKO = hitsToKOAI; - if(maxHitsToKO > defensiveMonHitKOThreshold) - defensiveMonId = i; - } - // Check through current mon's moves for (j = 0; j < MAX_MON_MOVES; j++) { @@ -2100,6 +2094,14 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, } } + // Track max hits to KO and set defensive mon + if(hitsToKOAI > maxHitsToKO && (canSwitchinWin1v1 || gAiThinkingStruct->aiFlags[battler] & AI_FLAG_STALL)) + { + maxHitsToKO = hitsToKOAI; + if(maxHitsToKO > defensiveMonHitKOThreshold) + defensiveMonId = i; + } + // Check for mon with resistance and super effective move for best type matchup mon with effective move if (aiMove != MOVE_NONE && !IsBattleMoveStatus(aiMove)) { diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index c5205ecdc6..46c419f211 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -296,7 +296,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: Mid-battle switches prioritize PARAMETRIZE { expectedIndex = 3; move1 = MOVE_SCRATCH; move2 = MOVE_SCRATCH; aiSmartSwitchFlags = 0; } // When not smart, AI will only switch in a defensive mon if it has a SE move, otherwise will just default to damage PARAMETRIZE { expectedIndex = 1; move1 = MOVE_GIGA_DRAIN; move2 = MOVE_SCRATCH; aiSmartSwitchFlags = 0; } - PARAMETRIZE { expectedIndex = 2; move1 = MOVE_SCRATCH; move2 = MOVE_SCRATCH; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } // When smart, AI will prioritize SE move, but still switch in good type matchup without SE move + PARAMETRIZE { expectedIndex = 2; move1 = MOVE_SCRATCH; move2 = MOVE_WATER_PULSE; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } // When smart, AI will prioritize SE move, but still switch in good type matchup without SE move PARAMETRIZE { expectedIndex = 1; move1 = MOVE_GIGA_DRAIN; move2 = MOVE_SCRATCH; aiSmartSwitchFlags = AI_FLAG_SMART_MON_CHOICES; } GIVEN { @@ -1180,3 +1180,62 @@ AI_SINGLE_BATTLE_TEST("Switch AI: AI will use pivot move to activate Palafin's Z TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_FLIP_TURN); EXPECT_SEND_OUT(opponent, 1); } } } + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't send out defensive mon that can lose 1v1, or switch out a mon that can win 1v1 even with bad type matchup") +{ + PASSES_RANDOMLY(100, 100, RNG_AI_SWITCH_HASBADODDS); + 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_PANPOUR) { + Level(15); + Moves(MOVE_WATER_PULSE, MOVE_PLAY_NICE, MOVE_FURY_SWIPES, MOVE_LICK); + Item(ITEM_MYSTIC_WATER); + Ability(ABILITY_GLUTTONY); + Nature(NATURE_MODEST); + HPIV(31); + AttackIV(31); + DefenseIV(31); + SpAttackIV(31); + SpDefenseIV(31); + SpeedIV(31); } + OPPONENT(SPECIES_RHYHORN) { + Level(14); + Moves(MOVE_ROCK_TOMB, MOVE_HORN_ATTACK, MOVE_BULLDOZE, MOVE_ROCK_SMASH); + Item(ITEM_RINDO_BERRY); + Ability(ABILITY_LIGHTNING_ROD); + Nature(NATURE_ADAMANT); + HPIV(31); + AttackIV(31); + DefenseIV(31); + SpAttackIV(31); + SpDefenseIV(31); + SpeedIV(31); } + OPPONENT(SPECIES_GLIGAR) { + Level(15); + Moves(MOVE_WING_ATTACK, MOVE_QUICK_ATTACK, MOVE_BULLDOZE); + Item(ITEM_ORAN_BERRY); + Ability(ABILITY_SAND_VEIL); + Nature(NATURE_ADAMANT); + HPIV(31); + AttackIV(31); + DefenseIV(31); + SpAttackIV(31); + SpDefenseIV(31); + SpeedIV(31); } + OPPONENT(SPECIES_WOOPER_PALDEA) { + Level(15); + Moves(MOVE_MUD_SHOT, MOVE_ACID_SPRAY, MOVE_YAWN, MOVE_SANDSTORM); + Item(ITEM_ORAN_BERRY); + Ability(ABILITY_WATER_ABSORB); + Nature(NATURE_MODEST); + HPIV(31); + AttackIV(31); + DefenseIV(31); + SpAttackIV(31); + SpDefenseIV(31); + SpeedIV(31); } + } WHEN { + TURN { MOVE(player, MOVE_WATER_PULSE); EXPECT_MOVE(opponent, MOVE_BULLDOZE); EXPECT_SEND_OUT(opponent, 1); } + TURN { MOVE(player, MOVE_WATER_PULSE); EXPECT_MOVE(opponent, MOVE_BULLDOZE); } + } +}