Skip to content

Commit

Permalink
Fixed ace switching bugs (#5922)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pawkkie authored Jan 1, 2025
1 parent 55f0d3a commit 8d81844
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 12 deletions.
19 changes: 7 additions & 12 deletions src/battle_ai_switch_items.c
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,6 @@ bool32 ShouldSwitch(u32 battler)
struct Pokemon *party;
s32 i;
s32 availableToSwitch;
bool32 hasAceMon = FALSE;

if (gBattleMons[battler].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION))
return FALSE;
Expand Down Expand Up @@ -966,20 +965,14 @@ bool32 ShouldSwitch(u32 battler)
continue;
if (IsAceMon(battler, i))
{
hasAceMon = TRUE;
continue;
}

availableToSwitch++;
}

if (availableToSwitch == 0)
{
if (hasAceMon) // If the ace mon is the only available mon, use it
availableToSwitch++;
else
return FALSE;
}

// NOTE: The sequence of the below functions matter! Do not change unless you have carefully considered the outcome.
// Since the order is sequencial, and some of these functions prompt switch to specific party members.
Expand Down Expand Up @@ -1771,7 +1764,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
{
int revengeKillerId = PARTY_SIZE, slowRevengeKillerId = PARTY_SIZE, fastThreatenId = PARTY_SIZE, slowThreatenId = PARTY_SIZE, damageMonId = 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;
int i, j, aliveCount = 0, bits = 0, aceMonCount = 0;
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;
Expand All @@ -1794,6 +1787,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
else if (IsAceMon(battler, i))
{
aceMonId = i;
aceMonCount++;
continue;
}
else
Expand Down Expand Up @@ -1940,7 +1934,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
else if (batonPassId != PARTY_SIZE) return batonPassId;
}
// If ace mon is the last available Pokemon and U-Turn/Volt Switch was used - switch to the mon.
if (aceMonId != PARTY_SIZE && IsSwitchOutEffect(gMovesInfo[gLastUsedMove].effect))
if (aceMonId != PARTY_SIZE && CountUsablePartyMons(battler) <= aceMonCount && IsSwitchOutEffect(gMovesInfo[gLastUsedMove].effect))
return aceMonId;

return PARTY_SIZE;
Expand Down Expand Up @@ -2018,7 +2012,7 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd)
// This all handled by the GetBestMonIntegrated function if the AI_FLAG_SMART_MON_CHOICES flag is set
else
{
s32 i, aliveCount = 0;
s32 i, aliveCount = 0, aceMonCount = 0;
u32 invalidMons = 0, aceMonId = PARTY_SIZE;
// Get invalid slots ids.
for (i = firstId; i < lastId; i++)
Expand All @@ -2035,6 +2029,7 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd)
else if (IsAceMon(battler, i)) // Save Ace Pokemon for last.
{
aceMonId = i;
aceMonCount++;
invalidMons |= 1u << i;
}
else
Expand All @@ -2054,8 +2049,8 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd)
if (bestMonId != PARTY_SIZE)
return bestMonId;

// If ace mon is the last available Pokemon and switch move was used - switch to the mon.
if (aceMonId != PARTY_SIZE)
// If ace mon is the last available Pokemon and U-Turn/Volt Switch was used - switch to the mon.
if (aceMonId != PARTY_SIZE && CountUsablePartyMons(battler) <= aceMonCount && IsSwitchOutEffect(gMovesInfo[gLastUsedMove].effect))
return aceMonId;

return PARTY_SIZE;
Expand Down
58 changes: 58 additions & 0 deletions test/battle/ai/ai_switching.c
Original file line number Diff line number Diff line change
Expand Up @@ -909,3 +909,61 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI correctly handles abilities
TURN { MOVE(player, MOVE_WATER_GUN); EXPECT_MOVE(opponent, MOVE_ABSORB); }
}
}

AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't switch out if Yawn'd with only Ace mon remaining")
{
u32 aceFlag;
PARAMETRIZE{ aceFlag = 0; }
PARAMETRIZE{ aceFlag = AI_FLAG_ACE_POKEMON; }
GIVEN {
ASSUME(gMovesInfo[MOVE_YAWN].effect == EFFECT_YAWN);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | aceFlag | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_SLOWKING) { Moves(MOVE_YAWN, MOVE_CONFUSION, MOVE_POWER_GEM, MOVE_WATER_PULSE); Item(ITEM_LEFTOVERS); }
OPPONENT(SPECIES_SCOLIPEDE) { Moves(MOVE_POISON_TAIL); }
OPPONENT(SPECIES_ABSOL) { Moves(MOVE_KNOCK_OFF, MOVE_CRUNCH); }
} WHEN {
TURN { MOVE(player, MOVE_YAWN); EXPECT_MOVE(opponent, MOVE_POISON_TAIL); }
if (aceFlag)
TURN { MOVE(player, MOVE_POWER_GEM); EXPECT_MOVE(opponent, MOVE_POISON_TAIL); }
else
TURN { MOVE(player, MOVE_POWER_GEM); EXPECT_SWITCH(opponent, 1); }
}
}

AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI won't switch in ace mon after U-Turn if other options available")
{
u32 aceFlag;
PARAMETRIZE{ aceFlag = 0; }
PARAMETRIZE{ aceFlag = AI_FLAG_ACE_POKEMON; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | aceFlag | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SURF); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_U_TURN); }
OPPONENT(SPECIES_NUMEL) { Level(5); Moves(MOVE_SPLASH); }
OPPONENT(SPECIES_SCIZOR) { Moves(MOVE_BUG_BITE); }
} WHEN {
if (aceFlag)
TURN { EXPECT_MOVE(opponent, MOVE_U_TURN); EXPECT_SEND_OUT(opponent, 1); MOVE(player, MOVE_SURF); }
else
TURN { EXPECT_MOVE(opponent, MOVE_U_TURN); EXPECT_SEND_OUT(opponent, 2); MOVE(player, MOVE_SURF); }
}
}

AI_SINGLE_BATTLE_TEST("Switch AI: AI won't switch in ace mon after U-Turn if other options available")
{
u32 aceFlag;
PARAMETRIZE{ aceFlag = 0; }
PARAMETRIZE{ aceFlag = AI_FLAG_ACE_POKEMON; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | aceFlag | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_SURF); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_U_TURN); }
OPPONENT(SPECIES_NUMEL) { Level(5); Moves(MOVE_SPLASH); }
OPPONENT(SPECIES_SCIZOR) { Moves(MOVE_BUG_BITE); }
} WHEN {
if (aceFlag)
TURN { EXPECT_MOVE(opponent, MOVE_U_TURN); EXPECT_SEND_OUT(opponent, 1); MOVE(player, MOVE_SURF); }
else
TURN { EXPECT_MOVE(opponent, MOVE_U_TURN); EXPECT_SEND_OUT(opponent, 2); MOVE(player, MOVE_SURF); }
}
}

0 comments on commit 8d81844

Please sign in to comment.