Improve AI type matchup calcs (#7364)

Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
This commit is contained in:
Pawkkie 2025-07-20 06:53:14 -04:00 committed by GitHub
parent cb66393df7
commit a92f432daf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 41 additions and 32 deletions

View File

@ -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++)

View File

@ -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); }
}
}