Fix switching 1v1 calcs not handling 0 (#7131)

This commit is contained in:
Pawkkie 2025-07-13 11:45:59 -04:00 committed by GitHub
parent b55ee2288b
commit 06c54a2d60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 36 additions and 11 deletions

View File

@ -261,13 +261,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_DoesChoiceItemBlockMove(opposingBattler, playerMove))
{
return maxDamageTaken = damageTaken;
break;
}
if (damageTaken > maxDamageTaken)
maxDamageTaken = damageTaken;
}
}
}
@ -281,7 +278,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
@ -1805,7 +1802,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)
@ -2012,6 +2009,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)
{
@ -2028,7 +2037,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
@ -2073,9 +2082,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);
@ -2106,6 +2113,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))
{
@ -2184,6 +2194,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
@ -2194,6 +2205,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

View File

@ -1255,3 +1255,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); }
}
}