Rework switch AI and add more tests for ace pokemon flags (#8321)

This commit is contained in:
FosterProgramming 2025-11-23 19:04:44 +01:00 committed by GitHub
parent c7c97531ec
commit a4482f0ee2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 142 additions and 119 deletions

View File

@ -38,7 +38,8 @@ enum ShouldSwitchScenario
enum SwitchType enum SwitchType
{ {
SWITCH_AFTER_KO, SWITCH_AFTER_KO,
SWITCH_MID_BATTLE, SWITCH_MID_BATTLE_FORCED,
SWITCH_MID_BATTLE_OPTIONAL,
}; };
void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId); void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId);

View File

@ -359,13 +359,13 @@ void ComputeBattlerDecisions(u32 battler)
if (isAiBattler || CanAiPredictMove()) if (isAiBattler || CanAiPredictMove())
{ {
// Risky AI switches aggressively even mid battle // Risky AI switches aggressively even mid battle
enum SwitchType switchType = (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_RISKY) ? SWITCH_AFTER_KO : SWITCH_MID_BATTLE; enum SwitchType switchType = (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_RISKY) ? SWITCH_AFTER_KO : SWITCH_MID_BATTLE_OPTIONAL;
gAiLogicData->aiCalcInProgress = TRUE; gAiLogicData->aiCalcInProgress = TRUE;
// Setup battler and prediction data // Setup battler and prediction data
BattleAI_SetupAIData(0xF, battler); BattleAI_SetupAIData(0xF, battler);
SetupAIPredictionData(battler, switchType); SetupAIPredictionData(battler, SWITCH_MID_BATTLE_OPTIONAL);
// AI's own switching data // AI's own switching data
if (isAiBattler) if (isAiBattler)

View File

@ -1517,22 +1517,19 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva
return bestMonId; return bestMonId;
} }
static u32 GetFirstNonInvalidMon(u32 firstId, u32 lastId, u32 invalidMons, u32 battlerIn1, u32 battlerIn2) static u32 GetFirstNonInvalidMon(u32 firstId, u32 lastId, u32 invalidMons)
{ {
if (!IsDoubleBattle()) u32 chosenMonId = PARTY_SIZE;
return PARTY_SIZE; for (u32 i = (lastId-1); i > firstId; i--)
if (PARTY_SIZE != gBattleStruct->monToSwitchIntoId[battlerIn1]
&& PARTY_SIZE != gBattleStruct->monToSwitchIntoId[battlerIn2])
return PARTY_SIZE;
for (u32 chosenMonId = (lastId-1); chosenMonId >= firstId; chosenMonId--)
{ {
if ((1 << (chosenMonId)) & invalidMons) if (!((1 << i) & invalidMons))
continue; {
return chosenMonId; // first non invalid mon found // first non invalid mon found
chosenMonId = i;
break;
}
} }
return PARTY_SIZE; return chosenMonId;
} }
bool32 IsMonGrounded(enum HoldEffect heldItemEffect, enum Ability ability, enum Type type1, enum Type type2) bool32 IsMonGrounded(enum HoldEffect heldItemEffect, enum Ability ability, enum Type type1, enum Type type2)
@ -2125,7 +2122,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
int batonPassId = PARTY_SIZE, typeMatchupId = PARTY_SIZE, typeMatchupEffectiveId = PARTY_SIZE, defensiveMonId = PARTY_SIZE, aceMonId = PARTY_SIZE, trapperId = 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; int i, j, aliveCount = 0, bits = 0, aceMonCount = 0;
s32 defensiveMonHitKOThreshold = 3; // 3HKO threshold that candidate defensive mons must exceed s32 defensiveMonHitKOThreshold = 3; // 3HKO threshold that candidate defensive mons must exceed
s32 playerMonHP = gBattleMons[opposingBattler].hp, maxDamageDealt = 0, damageDealt = 0; s32 playerMonHP = gBattleMons[opposingBattler].hp, maxDamageDealt = 0, damageDealt = 0, monMaxDamage = 0;
u32 aiMove, hitsToKOAI, hitsToKOPlayer, hitsToKOAIPriority, bestPlayerMove = MOVE_NONE, bestPlayerPriorityMove = MOVE_NONE, maxHitsToKO = 0; u32 aiMove, hitsToKOAI, hitsToKOPlayer, hitsToKOAIPriority, bestPlayerMove = MOVE_NONE, bestPlayerPriorityMove = MOVE_NONE, maxHitsToKO = 0;
u32 bestResist = UQ_4_12(2.0), bestResistEffective = UQ_4_12(2.0), typeMatchup; // 2.0 is the default "Neutral" matchup from GetBattleMonTypeMatchup 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, isSwitchinFirstPriority, canSwitchinWin1v1; bool32 isFreeSwitch = IsFreeSwitch(switchType, battlerIn1, opposingBattler), isSwitchinFirst, isSwitchinFirstPriority, canSwitchinWin1v1;
@ -2167,6 +2164,8 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
hitsToKOAIPriority = GetSwitchinHitsToKO(GetMaxPriorityDamagePlayerCouldDealToSwitchin(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, &bestPlayerPriorityMove), battler); hitsToKOAIPriority = GetSwitchinHitsToKO(GetMaxPriorityDamagePlayerCouldDealToSwitchin(battler, opposingBattler, gAiLogicData->switchinCandidate.battleMon, &bestPlayerPriorityMove), battler);
typeMatchup = GetBattleMonTypeMatchup(gBattleMons[opposingBattler], gAiLogicData->switchinCandidate.battleMon); typeMatchup = GetBattleMonTypeMatchup(gBattleMons[opposingBattler], gAiLogicData->switchinCandidate.battleMon);
monMaxDamage = 0;
// Check through current mon's moves // Check through current mon's moves
for (j = 0; j < MAX_MON_MOVES; j++) for (j = 0; j < MAX_MON_MOVES; j++)
{ {
@ -2232,6 +2231,8 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
&& damageDealt < playerMonHP) && damageDealt < playerMonHP)
continue; continue;
if (damageDealt > monMaxDamage)
monMaxDamage = damageDealt;
// Check that mon isn't one shot and set best damage mon // Check that mon isn't one shot and set best damage mon
if (damageDealt > maxDamageDealt) if (damageDealt > maxDamageDealt)
{ {
@ -2275,6 +2276,8 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
trapperId = i; trapperId = i;
} }
} }
if (monMaxDamage == 0)
invalidMons |= 1u << i;
} }
batonPassId = GetRandomSwitchinWithBatonPass(aliveCount, bits, firstId, lastId, i); batonPassId = GetRandomSwitchinWithBatonPass(aliveCount, bits, firstId, lastId, i);
@ -2304,16 +2307,19 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
else if (batonPassId != PARTY_SIZE) return batonPassId; else if (batonPassId != PARTY_SIZE) return batonPassId;
else if (generic1v1MonId != PARTY_SIZE) return generic1v1MonId; 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 if (switchType == SWITCH_MID_BATTLE_OPTIONAL)
&& (IsSwitchOutEffect(GetMoveEffect(gCurrentMove)) || gAiLogicData->ejectButtonSwitch || gAiLogicData->ejectPackSwitch)) return PARTY_SIZE;
return aceMonId;
// Fallback // Fallback
u32 bestMonId = GetFirstNonInvalidMon(firstId, lastId, invalidMons, battlerIn1, battlerIn2); u32 bestMonId = GetFirstNonInvalidMon(firstId, lastId, invalidMons);
if (bestMonId != PARTY_SIZE) if (bestMonId != PARTY_SIZE)
return bestMonId; return bestMonId;
// 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)
return aceMonId;
return PARTY_SIZE; return PARTY_SIZE;
} }
@ -2429,16 +2435,17 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, enum SwitchType switchType)
if (bestMonId != PARTY_SIZE) if (bestMonId != PARTY_SIZE)
return bestMonId; return bestMonId;
// If ace mon is the last available Pokemon and U-Turn/Volt Switch or Eject Pack/Button was used - switch to the mon. if (switchType == SWITCH_MID_BATTLE_OPTIONAL)
if (aceMonId != PARTY_SIZE && CountUsablePartyMons(battler) <= aceMonCount return PARTY_SIZE;
&& (IsSwitchOutEffect(GetMoveEffect(gCurrentMove)) || gAiLogicData->ejectButtonSwitch || gAiLogicData->ejectPackSwitch))
return aceMonId;
// Fallback // Fallback
bestMonId = GetFirstNonInvalidMon(firstId, lastId, invalidMons, battlerIn1, battlerIn2); bestMonId = GetFirstNonInvalidMon(firstId, lastId, invalidMons);
if (bestMonId != PARTY_SIZE) if (bestMonId != PARTY_SIZE)
return bestMonId; return bestMonId;
if (aceMonId != PARTY_SIZE && CountUsablePartyMons(battler) <= aceMonCount)
return aceMonId;
return PARTY_SIZE; return PARTY_SIZE;
} }
} }

View File

@ -4567,7 +4567,9 @@ s32 CountUsablePartyMons(u32 battlerId)
} }
ret = 0; ret = 0;
for (i = 0; i < PARTY_SIZE; i++) s32 firstId, lastId;
GetAIPartyIndexes(battlerId, &firstId, &lastId);
for (i = firstId; i < lastId; i++)
{ {
if (i != battlerOnField1 && i != battlerOnField2 if (i != battlerOnField1 && i != battlerOnField2
&& GetMonData(&party[i], MON_DATA_HP) != 0 && GetMonData(&party[i], MON_DATA_HP) != 0

View File

@ -51,8 +51,6 @@ static void OpponentHandleChoosePokemon(u32 battler);
static void OpponentHandleIntroTrainerBallThrow(u32 battler); static void OpponentHandleIntroTrainerBallThrow(u32 battler);
static void OpponentHandleDrawPartyStatusSummary(u32 battler); static void OpponentHandleDrawPartyStatusSummary(u32 battler);
static void OpponentHandleEndLinkBattle(u32 battler); static void OpponentHandleEndLinkBattle(u32 battler);
static u8 CountAIAliveNonEggMonsExcept(u8 slotToIgnore);
static void OpponentBufferRunCommand(u32 battler); static void OpponentBufferRunCommand(u32 battler);
static void (*const sOpponentBufferCommands[CONTROLLER_CMDS_COUNT])(u32 battler) = static void (*const sOpponentBufferCommands[CONTROLLER_CMDS_COUNT])(u32 battler) =
@ -527,70 +525,9 @@ static void OpponentHandleChooseItem(u32 battler)
BtlController_Complete(battler); BtlController_Complete(battler);
} }
static inline bool32 IsAcePokemon(u32 chosenMonId, u32 pokemonInBattle, u32 battler)
{
return gAiThinkingStruct->aiFlags[battler] & AI_FLAG_ACE_POKEMON
&& (chosenMonId == CalculateEnemyPartyCountInSide(battler) - 1)
&& CountAIAliveNonEggMonsExcept(PARTY_SIZE) != pokemonInBattle;
}
static inline bool32 IsDoubleAceSlot(u32 battler, u32 partyId)
{
u32 partyCountEnd;
if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_DOUBLE_ACE_POKEMON))
return FALSE;
partyCountEnd = CalculateEnemyPartyCountInSide(battler);
if (partyCountEnd == 0)
return FALSE;
if (partyId == partyCountEnd - 1)
return TRUE;
if (partyCountEnd > 1 && partyId == partyCountEnd - 2)
return TRUE;
return FALSE;
}
static inline bool32 IsDoubleAcePokemon(u32 chosenMonId, u32 pokemonInBattle, u32 battler)
{
s32 battler1, battler2, firstId, lastId;
s32 i;
if (!IsDoubleAceSlot(battler, chosenMonId))
return FALSE;
if (!IsDoubleBattle())
{
battler2 = battler1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
}
else
{
battler1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
battler2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
}
GetAIPartyIndexes(battler, &firstId, &lastId);
for (i = firstId; i < lastId; i++)
{
if (!IsValidForBattle(&gEnemyParty[i])
|| i == gBattlerPartyIndexes[battler1]
|| i == gBattlerPartyIndexes[battler2]
|| i == chosenMonId)
continue;
if (!IsAcePokemon(i, pokemonInBattle, battler) && !IsDoubleAceSlot(battler, i))
return TRUE;
}
return FALSE;
}
static void OpponentHandleChoosePokemon(u32 battler) static void OpponentHandleChoosePokemon(u32 battler)
{ {
s32 chosenMonId; s32 chosenMonId;
s32 pokemonInBattle = 1;
enum SwitchType switchType = SWITCH_AFTER_KO; enum SwitchType switchType = SWITCH_AFTER_KO;
// Choosing Revival Blessing target // Choosing Revival Blessing target
@ -602,7 +539,7 @@ static void OpponentHandleChoosePokemon(u32 battler)
else if (gBattleStruct->AI_monToSwitchIntoId[battler] == PARTY_SIZE) else if (gBattleStruct->AI_monToSwitchIntoId[battler] == PARTY_SIZE)
{ {
if (IsSwitchOutEffect(GetMoveEffect(gCurrentMove)) || gAiLogicData->ejectButtonSwitch || gAiLogicData->ejectPackSwitch) if (IsSwitchOutEffect(GetMoveEffect(gCurrentMove)) || gAiLogicData->ejectButtonSwitch || gAiLogicData->ejectPackSwitch)
switchType = SWITCH_MID_BATTLE; switchType = SWITCH_MID_BATTLE_FORCED;
// reset the AI data to consider the correct on-field state at time of switch // reset the AI data to consider the correct on-field state at time of switch
SetBattlerAiData(GetBattlerAtPosition(B_POSITION_PLAYER_LEFT), gAiLogicData); SetBattlerAiData(GetBattlerAtPosition(B_POSITION_PLAYER_LEFT), gAiLogicData);
@ -610,7 +547,7 @@ static void OpponentHandleChoosePokemon(u32 battler)
SetBattlerAiData(GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT), gAiLogicData); SetBattlerAiData(GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT), gAiLogicData);
chosenMonId = GetMostSuitableMonToSwitchInto(battler, switchType); chosenMonId = GetMostSuitableMonToSwitchInto(battler, switchType);
if (chosenMonId == PARTY_SIZE) if (chosenMonId == PARTY_SIZE) // Advanced logic failed so we pick the next available battler
{ {
s32 battler1, battler2, firstId, lastId; s32 battler1, battler2, firstId, lastId;
@ -622,19 +559,14 @@ static void OpponentHandleChoosePokemon(u32 battler)
{ {
battler1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); battler1 = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
battler2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); battler2 = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
pokemonInBattle = 2;
} }
GetAIPartyIndexes(battler, &firstId, &lastId); GetAIPartyIndexes(battler, &firstId, &lastId);
for (chosenMonId = (lastId-1); chosenMonId >= firstId; chosenMonId--) for (chosenMonId = firstId; chosenMonId < lastId; chosenMonId++)
{ {
if (!IsValidForBattle(&gEnemyParty[chosenMonId]) if (IsValidForBattle(&gEnemyParty[chosenMonId])
|| chosenMonId == gBattlerPartyIndexes[battler1] && chosenMonId != gBattlerPartyIndexes[battler1]
|| chosenMonId == gBattlerPartyIndexes[battler2]) && chosenMonId != gBattlerPartyIndexes[battler2])
continue;
if (!IsAcePokemon(chosenMonId, pokemonInBattle, battler)
&& !IsDoubleAcePokemon(chosenMonId, pokemonInBattle, battler))
break; break;
} }
} }
@ -653,22 +585,6 @@ static void OpponentHandleChoosePokemon(u32 battler)
BtlController_Complete(battler); BtlController_Complete(battler);
} }
static u8 CountAIAliveNonEggMonsExcept(u8 slotToIgnore)
{
u16 i, count;
for (i = 0, count = 0; i < PARTY_SIZE; i++)
{
if (i != slotToIgnore
&& IsValidForBattle(&gEnemyParty[i]))
{
count++;
}
}
return count;
}
static void OpponentHandleIntroTrainerBallThrow(u32 battler) static void OpponentHandleIntroTrainerBallThrow(u32 battler)
{ {
BtlController_HandleIntroTrainerBallThrow(battler, 0, NULL, 0, Intro_TryShinyAnimShowHealthbox); BtlController_HandleIntroTrainerBallThrow(battler, 0, NULL, 0, Intro_TryShinyAnimShowHealthbox);

View File

@ -125,3 +125,100 @@ AI_TWO_VS_ONE_BATTLE_TEST("Battler 3 has Battler 1 AI flags set correctly (2v1)"
TURN { EXPECT_MOVE(opponentLeft, MOVE_EXPLOSION, target: playerLeft); EXPECT_MOVE(opponentRight, MOVE_EXPLOSION); } TURN { EXPECT_MOVE(opponentLeft, MOVE_EXPLOSION, target: playerLeft); EXPECT_MOVE(opponentRight, MOVE_EXPLOSION); }
} }
} }
AI_MULTI_BATTLE_TEST("Partner will not steal your pokemon when running out")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
MULTI_PLAYER(SPECIES_WOBBUFFET) { }
MULTI_PLAYER(SPECIES_WOBBUFFET) { }
MULTI_PLAYER(SPECIES_WOBBUFFET) { }
MULTI_PARTNER(SPECIES_WYNAUT) { Moves(MOVE_MEMENTO); }
MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); }
MULTI_OPPONENT_B(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); }
} WHEN {
TURN {EXPECT_MOVE(playerRight, MOVE_MEMENTO, target:opponentLeft);}
TURN {}
} THEN {
EXPECT_EQ(gAbsentBattlerFlags, (1u << GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT)));
}
}
AI_MULTI_BATTLE_TEST("Partner will not steal your pokemon to delay using their ace")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
BATTLER_AI_FLAGS(B_POSITION_PLAYER_RIGHT, AI_FLAG_ACE_POKEMON);
MULTI_PLAYER(SPECIES_WOBBUFFET) { }
MULTI_PLAYER(SPECIES_WOBBUFFET) { }
MULTI_PLAYER(SPECIES_WOBBUFFET) { }
MULTI_PARTNER(SPECIES_WYNAUT) { Moves(MOVE_MEMENTO); }
MULTI_PARTNER(SPECIES_METAGROSS) { Moves(MOVE_CELEBRATE); }
MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); }
MULTI_OPPONENT_B(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); }
} WHEN {
TURN {EXPECT_MOVE(playerRight, MOVE_MEMENTO, target:opponentLeft);}
TURN {}
} THEN {
EXPECT_EQ(SPECIES_METAGROSS, playerRight->species);
}
}
AI_MULTI_BATTLE_TEST("AI opponents do not steal their partner pokemon in multi battle to delay using their ace")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
BATTLER_AI_FLAGS(B_POSITION_OPPONENT_LEFT, AI_FLAG_ACE_POKEMON);
MULTI_PLAYER(SPECIES_WOBBUFFET) { }
MULTI_PARTNER(SPECIES_WOBBUFFET) { }
MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); HP(1);}
MULTI_OPPONENT_A(SPECIES_VENUSAUR) { Moves(MOVE_GIGA_DRAIN); }
MULTI_OPPONENT_B(SPECIES_WYNAUT) { Moves(MOVE_CELEBRATE); }
} WHEN {
TURN {MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); }
TURN {MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); }
} THEN {
EXPECT_EQ(SPECIES_VENUSAUR, opponentLeft->species);
}
}
AI_MULTI_BATTLE_TEST("AI opponents do not steal their partner pokemon in multi battle when forced out")
{
u32 item, move;
PARAMETRIZE {item = ITEM_EJECT_BUTTON; move = MOVE_TACKLE;}
PARAMETRIZE {item = ITEM_EJECT_PACK; move = MOVE_TAIL_WHIP;}
PARAMETRIZE {item = ITEM_NONE; move = MOVE_ROAR;}
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
BATTLER_AI_FLAGS(B_POSITION_OPPONENT_LEFT, AI_FLAG_ACE_POKEMON);
MULTI_PLAYER(SPECIES_WOBBUFFET) { }
MULTI_PARTNER(SPECIES_WOBBUFFET) { }
MULTI_OPPONENT_A(SPECIES_WOBBUFFET) { Moves(MOVE_CELEBRATE); Item(item);}
MULTI_OPPONENT_A(SPECIES_VENUSAUR) { Moves(MOVE_GIGA_DRAIN); }
MULTI_OPPONENT_B(SPECIES_WYNAUT) { Moves(MOVE_CELEBRATE); }
} WHEN {
TURN {MOVE(playerLeft, move, target: opponentLeft); }
} THEN {
EXPECT_EQ(SPECIES_VENUSAUR, opponentLeft->species);
}
}
AI_MULTI_BATTLE_TEST("AI opponents do not steal their partner pokemon in multi battle when forced out 2")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
BATTLER_AI_FLAGS(B_POSITION_OPPONENT_LEFT, AI_FLAG_ACE_POKEMON);
MULTI_PLAYER(SPECIES_WOBBUFFET) { }
MULTI_PARTNER(SPECIES_WOBBUFFET) { }
MULTI_OPPONENT_A(SPECIES_GOLISOPOD) { Moves(MOVE_CELEBRATE); HP(101); MaxHP(200); Ability(ABILITY_EMERGENCY_EXIT);}
MULTI_OPPONENT_A(SPECIES_VENUSAUR) { Moves(MOVE_GIGA_DRAIN); }
MULTI_OPPONENT_B(SPECIES_WYNAUT) { Moves(MOVE_CELEBRATE); }
} WHEN {
TURN {MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); }
} THEN {
EXPECT_EQ(SPECIES_VENUSAUR, opponentLeft->species);
}
}

View File

@ -359,7 +359,7 @@ AI_SINGLE_BATTLE_TEST("When AI switches out due to having no move that affects t
OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); } OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); } OPPONENT(SPECIES_ABRA) { Moves(MOVE_TACKLE); }
} WHEN { } WHEN {
TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_SWITCH(opponent, 2); EXPECT_SEND_OUT(opponent, 4); } TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_SWITCH(opponent, 2); EXPECT_SEND_OUT(opponent, 0);}
TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_MOVE(opponent, MOVE_TACKLE); } TURN { MOVE(player, MOVE_SHADOW_BALL); EXPECT_MOVE(opponent, MOVE_TACKLE); }
} }
} }