diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 06c41c6d77..84cba174b2 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -31,6 +31,7 @@ static bool32 AI_OpponentCanFaintAiWithMod(u32 battler, u32 healAmount); static u32 GetSwitchinHazardsDamage(u32 battler, struct BattlePokemon *battleMon); static bool32 CanAbilityTrapOpponent(u16 ability, u32 opponent); static u32 GetHPHealAmount(u8 itemEffectParam, struct Pokemon *mon); +static u32 GetBattleMonTypeMatchup(struct BattlePokemon opposingBattleMon, struct BattlePokemon battleMon); static void InitializeSwitchinCandidate(struct Pokemon *mon) { @@ -176,11 +177,11 @@ static bool32 AI_DoesChoiceEffectBlockMove(u32 battler, u32 move) static bool32 ShouldSwitchIfHasBadOdds(u32 battler) { //Variable initialization - u8 opposingPosition, atkType1, atkType2, defType1, defType2; + u8 opposingPosition; s32 i, damageDealt = 0, maxDamageDealt = 0, damageTaken = 0, maxDamageTaken = 0; u32 aiMove, playerMove, aiBestMove = MOVE_NONE, aiAbility = gAiLogicData->abilities[battler], opposingBattler; bool32 getsOneShot = FALSE, hasStatusMove = FALSE, hasSuperEffectiveMove = FALSE; - u16 typeEffectiveness = UQ_4_12(1.0); //baseline typing damage + u32 typeMatchup; enum BattleMoveEffects aiMoveEffect; u32 hitsToKoPlayer = 0, hitsToKoAI = 0; @@ -196,12 +197,6 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) opposingBattler = GetBattlerAtPosition(opposingPosition); u16 *playerMoves = GetMovesArray(opposingBattler); - // Gets types of player (opposingBattler) and computer (battler) - atkType1 = gBattleMons[opposingBattler].types[0]; - atkType2 = gBattleMons[opposingBattler].types[1]; - defType1 = gBattleMons[battler].types[0]; - defType2 = gBattleMons[battler].types[1]; - for (i = 0; i < MAX_MON_MOVES; i++) { aiMove = gBattleMons[battler].moves[i]; @@ -245,15 +240,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) hitsToKoPlayer = GetNoOfHitsToKOBattlerDmg(maxDamageDealt, opposingBattler); // Calculate type advantage - typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType1))); - if (atkType2 != atkType1) - typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType1))); - if (defType2 != defType1) - { - typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType2))); - if (atkType2 != atkType1) - typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType2))); - } + typeMatchup = GetBattleMonTypeMatchup(gBattleMons[opposingBattler], gBattleMons[battler]); // Get max damage mon could take for (i = 0; i < MAX_MON_MOVES; i++) @@ -303,7 +290,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) } // General bad type matchups have more wiggle room - if (typeEffectiveness >= UQ_4_12(2.0)) // If the player has at least a 2x type advantage + if (typeMatchup > UQ_4_12(2.0)) // If the player has favourable offensive matchup (2.0 is neutral, this must be worse) { if (!hasSuperEffectiveMove // If the AI doesn't have a super effective move && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 // And the current mon has at least 1/2 their HP, or 1/4 HP and Regenerator @@ -1902,25 +1889,34 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler) return hitsToKO; } -static u16 GetSwitchinTypeMatchup(u32 opposingBattler, struct BattlePokemon battleMon) +static u32 GetBattleMonTypeMatchup(struct BattlePokemon opposingBattleMon, struct BattlePokemon battleMon) { - // Check type matchup - u16 typeEffectiveness = UQ_4_12(1.0); - u8 atkType1 = GetSpeciesType(gBattleMons[opposingBattler].species, 0), atkType2 = GetSpeciesType(gBattleMons[opposingBattler].species, 1), + u32 typeEffectiveness1 = UQ_4_12(1.0), typeEffectiveness2 = UQ_4_12(1.0); + u8 atkType1 = opposingBattleMon.types[0], atkType2 = opposingBattleMon.types[1], defType1 = battleMon.types[0], defType2 = battleMon.types[1]; - // Multiply type effectiveness by a factor depending on type matchup - typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType1))); - if (atkType2 != atkType1) - typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType1))); + // Add each independent defensive type matchup together + typeEffectiveness1 = uq4_12_multiply(typeEffectiveness1, (GetTypeModifier(atkType1, defType1))); if (defType2 != defType1) + typeEffectiveness1 = uq4_12_multiply(typeEffectiveness1, (GetTypeModifier(atkType1, defType2))); + if (typeEffectiveness1 == 0) // Immunity + typeEffectiveness1 = UQ_4_12(0.1); + + if (atkType2 != atkType1) { - typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType1, defType2))); - if (atkType2 != atkType1) - typeEffectiveness = uq4_12_multiply(typeEffectiveness, (GetTypeModifier(atkType2, defType2))); + typeEffectiveness2 = uq4_12_multiply(typeEffectiveness2, (GetTypeModifier(atkType2, defType1))); + if (defType2 != defType1) + typeEffectiveness2 = uq4_12_multiply(typeEffectiveness2, (GetTypeModifier(atkType2, defType2))); + if (typeEffectiveness2 == 0) // Immunity + typeEffectiveness2 = UQ_4_12(0.1); } - return typeEffectiveness; + else + { + typeEffectiveness2 = typeEffectiveness1; + } + + return typeEffectiveness1 + typeEffectiveness2; } static int GetRandomSwitchinWithBatonPass(int aliveCount, int bits, int firstId, int lastId, int currentMonId) @@ -2049,7 +2045,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, s32 defensiveMonHitKOThreshold = 3; // 3HKO threshold that candidate defensive mons must exceed s32 playerMonHP = gBattleMons[opposingBattler].hp, maxDamageDealt = 0, damageDealt = 0; u32 aiMove, hitsToKOAI, maxHitsToKO = 0; - u16 bestResist = UQ_4_12(1.0), bestResistEffective = UQ_4_12(1.0), typeMatchup; + u32 bestResist = UQ_4_12(2.0), bestResistEffective = UQ_4_12(2.0), typeMatchup; // 2.0 is the default "Neutral" matchup from GetBattleMonTypeMatchup bool32 isFreeSwitch = IsFreeSwitch(switchType, battlerIn1, opposingBattler), isSwitchinFirst, canSwitchinWin1v1; // Iterate through mons @@ -2082,7 +2078,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId, // Get max number of hits for player to KO AI mon and type matchup for defensive switching hitsToKOAI = GetSwitchinHitsToKO(GetMaxDamagePlayerCouldDealToSwitchin(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon), battler); - typeMatchup = GetSwitchinTypeMatchup(opposingBattler, gAiLogicData->switchinCandidate.battleMon); + typeMatchup = GetBattleMonTypeMatchup(gBattleMons[opposingBattler], gAiLogicData->switchinCandidate.battleMon); // Check through current mon's moves for (j = 0; j < MAX_MON_MOVES; j++) diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 2eb6834c6c..4d1fa5d746 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -1286,3 +1286,16 @@ AI_SINGLE_BATTLE_TEST("AI_SMART_MON_CHOICES: AI sees its own weather setting abi TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); } } } + +AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI will properly consider immunities when determining switchin type matchup") +{ + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_POLIWRATH) { Moves(MOVE_WATER_GUN, MOVE_KARATE_CHOP); } + OPPONENT(SPECIES_ZIGZAGOON) { Level(1); Moves(MOVE_SCRATCH); } + OPPONENT(SPECIES_CERULEDGE) { Moves(MOVE_SPARK); } + OPPONENT(SPECIES_WHIMSICOTT) { Moves(MOVE_MEGA_DRAIN); } + } WHEN { + TURN { MOVE(player, MOVE_KARATE_CHOP); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); } + } +}