diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index f5d105d04c..ad1023ac15 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -480,7 +480,6 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) u32 opposingBattler = GetOppositeBattler(battler); u32 incomingMove = GetIncomingMove(battler, opposingBattler, gAiLogicData); enum Type incomingType = CheckDynamicMoveType(GetBattlerMon(opposingBattler), incomingMove, opposingBattler, MON_IN_BATTLE); - bool32 isOpposingBattlerChargingOrInvulnerable = !BreaksThroughSemiInvulnerablity(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove); s32 i, j; if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) @@ -530,42 +529,42 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_FLASH_FIRE; } - if (incomingType == TYPE_WATER || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_WATER)) + if (incomingType == TYPE_WATER) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_WATER_ABSORB; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_DRY_SKIN; if (GetConfig(B_REDIRECT_ABILITY_IMMUNITY) >= GEN_5) absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_STORM_DRAIN; } - if (incomingType == TYPE_ELECTRIC || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_ELECTRIC)) + if (incomingType == TYPE_ELECTRIC) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_VOLT_ABSORB; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_MOTOR_DRIVE; if (GetConfig(B_REDIRECT_ABILITY_IMMUNITY) >= GEN_5) absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_LIGHTNING_ROD; } - if (incomingType == TYPE_GRASS || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GRASS)) + if (incomingType == TYPE_GRASS) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_SAP_SIPPER; } - if (incomingType == TYPE_GROUND || (isOpposingBattlerChargingOrInvulnerable && incomingType == TYPE_GROUND)) + if (incomingType == TYPE_GROUND) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_EARTH_EATER; absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_LEVITATE; } - if (IsSoundMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsSoundMove(incomingMove))) + if (IsSoundMove(incomingMove)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_SOUNDPROOF; } - if (IsBallisticMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsBallisticMove(incomingMove))) + if (IsBallisticMove(incomingMove)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_BULLETPROOF; } - if (IsWindMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsWindMove(incomingMove))) + if (IsWindMove(incomingMove)) { absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_WIND_RIDER; } - if (IsPowderMove(incomingMove) || (isOpposingBattlerChargingOrInvulnerable && IsPowderMove(incomingMove))) + if (IsPowderMove(incomingMove)) { if (GetConfig(B_POWDER_OVERCOAT) >= GEN_6) absorbingTypeAbilities[numAbsorbingAbilities++] = ABILITY_OVERCOAT; @@ -617,14 +616,23 @@ static bool32 ShouldSwitchIfOpponentChargingOrInvulnerable(u32 battler) { u32 opposingBattler = GetOppositeBattler(battler); u32 incomingMove = GetIncomingMove(battler, opposingBattler, gAiLogicData); - - bool32 isOpposingBattlerChargingOrInvulnerable = !BreaksThroughSemiInvulnerablity(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove); + enum BattleMoveEffects effect = GetMoveEffect(incomingMove); if (IsDoubleBattle() || !(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) return FALSE; + // Two-turn attacks that charge without entering semi-invulnerable state (e.g. Solar Beam). + // First turn of Fly/Dive/Bounce/Sky Drop: move is selected this turn but user is not yet semi-invulnerable. + // Opponent is already semi-invulnerable. + if (!(IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove) + || ((effect == EFFECT_SEMI_INVULNERABLE || effect == EFFECT_SKY_DROP) && !IsSemiInvulnerable(opposingBattler, CHECK_ALL)) + || IsSemiInvulnerable(opposingBattler, CHECK_ALL))) + { + return FALSE; + } + // In a world with a unified ShouldSwitch function, also want to check whether we already win 1v1 and if we do don't switch; not worth doubling the HasBadOdds computation for now - if (isOpposingBattlerChargingOrInvulnerable && gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE && RandomPercentage(RNG_AI_SWITCH_FREE_TURN, GetSwitchChance(SHOULD_SWITCH_FREE_TURN))) + if (gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE && RandomPercentage(RNG_AI_SWITCH_FREE_TURN, GetSwitchChance(SHOULD_SWITCH_FREE_TURN))) return SetSwitchinAndSwitch(battler, PARTY_SIZE); return FALSE; @@ -654,7 +662,7 @@ static bool32 ShouldSwitchIfTrapperInParty(u32 battler) for (i = firstId; i < lastId; i++) { if (IsAceMon(battler, i)) - return FALSE; + continue; monAbility = GetMonAbility(&party[i]); diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 83a93bb60c..8e6f0b2c36 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -54,7 +54,7 @@ static bool32 AI_IsDoubleSpreadMove(u32 battlerAtk, u32 move) if (moveTargetType == MOVE_TARGET_BOTH && battlerAtk == BATTLE_PARTNER(battlerDef)) continue; - if (IsBattlerAlive(battlerDef) && !IsSemiInvulnerable(battlerDef, move)) + if (IsBattlerAlive(battlerDef) && (!IsSemiInvulnerable(battlerDef, CHECK_ALL) || BreaksThroughSemiInvulnerablity(battlerDef, move))) numOfTargets++; } diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 7e0e923e71..af67f4d051 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -1030,6 +1030,21 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if player's m } } +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out on turn 1 if it predicts a semi-invulnerable move and it has a good switchin") +{ + PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_MOVE); + PASSES_RANDOMLY(SHOULD_SWITCH_FREE_TURN_PERCENTAGE, 100, RNG_AI_SWITCH_FREE_TURN); + GIVEN { + ASSUME(GetMoveType(MOVE_DIVE) == TYPE_WATER); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE); + PLAYER(SPECIES_LUVDISC) { Level(1); Moves(MOVE_DIVE); } + OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_PIKACHU) { Moves(MOVE_THUNDERBOLT); } + } WHEN { + TURN { MOVE(player, MOVE_DIVE); EXPECT_SWITCH(opponent, 1); } + } +} + AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has an absorber but current mon has SE move 33% of the time") { PASSES_RANDOMLY(33, 100, RNG_AI_SWITCH_ABSORBING_STAY_IN);