Refactor ShouldSwitchIfAllBadMoves (#5452)

* Refactor ShouldSwitchIfAllBadMoves

* Rest of bugfix

* Enable Shed Tail test

* Revert "Enable Shed Tail test"

This reverts commit e0b9a3affc.

* Better names for i, j

* Fix MAX_MON_MOVES that should be MAX_BATTLERS_COUNT

* battlerIndex  / moveIndex  fix

* Update src/battle_controller_player_partner.c

---------

Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
This commit is contained in:
Pawkkie 2024-10-16 11:22:48 -04:00 committed by GitHub
parent d4387d880c
commit 9882e0aea6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 220 additions and 179 deletions

View file

@ -369,8 +369,7 @@ struct AiLogicData
u8 effectiveness[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
u8 moveAccuracy[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex
u8 moveLimitations[MAX_BATTLERS_COUNT];
u8 shouldSwitchIfBadMoves; // Because all available moves have no/little effect. Each bit per battler.
u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch.
u8 monToSwitchInId[MAX_BATTLERS_COUNT]; // ID of the mon to switch in.
u8 mostSuitableMonId[MAX_BATTLERS_COUNT]; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId.
struct SwitchinCandidate switchinCandidate; // Struct used for deciding which mon to switch to in battle_ai_switch_items.c
u8 weatherHasEffect:1; // The same as WEATHER_HAS_EFFECT. Stored here, so it's called only once.

View file

@ -10,7 +10,6 @@ typedef s32 (*AiScoreFunc)(u32, u32, u32, s32);
// 0 - 3 are move idx
#define AI_CHOICE_FLEE 4
#define AI_CHOICE_WATCH 5
#define AI_CHOICE_SWITCH 7
// for AI_WhoStrikesFirst
#define AI_IS_FASTER 1

View file

@ -496,89 +496,6 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData)
AI_DATA->aiCalcInProgress = FALSE;
}
static bool32 AI_SwitchMonIfSuitable(u32 battler, bool32 doubleBattle)
{
u32 monToSwitchId = AI_DATA->mostSuitableMonId[battler];
if (monToSwitchId != PARTY_SIZE && IsValidForBattle(&GetBattlerParty(battler)[monToSwitchId]))
{
gBattleMoveDamage = monToSwitchId;
// Edge case: See if partner already chose to switch into the same mon
if (doubleBattle)
{
u32 partner = BATTLE_PARTNER(battler);
if (AI_DATA->shouldSwitchIfBadMoves & (1u << partner) && AI_DATA->monToSwitchId[partner] == monToSwitchId)
{
return FALSE;
}
}
AI_DATA->shouldSwitchIfBadMoves |= 1 << battler;
AI_DATA->monToSwitchId[battler] = monToSwitchId;
return TRUE;
}
return FALSE;
}
static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle)
{
u32 i, j;
// If can switch.
if (CountUsablePartyMons(battler) > 0
&& !IsBattlerTrapped(battler, TRUE)
&& !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE))
&& AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS)
&& !(AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING))
{
// Consider switching if all moves are worthless to use.
if (GetTotalBaseStat(gBattleMons[battler].species) >= 310 // Mon is not weak.
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) // Mon has more than 50% of its HP
{
s32 cap = AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93;
if (doubleBattle)
{
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
if (i != battler && IsBattlerAlive(i))
{
for (j = 0; j < MAX_MON_MOVES; j++)
{
if (gBattleStruct->aiFinalScore[battler][i][j] > cap)
break;
}
if (j != MAX_MON_MOVES)
break;
}
}
if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battler, doubleBattle))
return TRUE;
}
else
{
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (AI_THINKING_STRUCT->score[i] > cap)
break;
}
if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battler, doubleBattle))
return TRUE;
}
}
// Consider switching if your mon with truant is bodied by Protect spam.
// Or is using a double turn semi invulnerable move(such as Fly) and is faster.
if (AI_DATA->abilities[battler] == ABILITY_TRUANT
&& IsTruantMonVulnerable(battler, gBattlerTarget)
&& gDisableStructs[battler].truantCounter
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2
&& AI_SwitchMonIfSuitable(battler, doubleBattle))
{
return TRUE;
}
}
return FALSE;
}
static u32 ChooseMoveOrAction_Singles(u32 battlerAi)
{
u8 currentMoveArray[MAX_MON_MOVES];
@ -609,10 +526,6 @@ static u32 ChooseMoveOrAction_Singles(u32 battlerAi)
if (AI_THINKING_STRUCT->aiAction & AI_ACTION_WATCH)
return AI_CHOICE_WATCH;
// Switch mon if there are no good moves to use.
if (AI_ShouldSwitchIfBadMoves(battlerAi, FALSE))
return AI_CHOICE_SWITCH;
numOfBestMoves = 1;
currentMoveArray[0] = AI_THINKING_STRUCT->score[0];
consideredMoveArray[0] = 0;
@ -733,10 +646,6 @@ static u32 ChooseMoveOrAction_Doubles(u32 battlerAi)
}
}
// Switch mon if all of the moves are bad to use against any of the target.
if (AI_ShouldSwitchIfBadMoves(battlerAi, TRUE))
return AI_CHOICE_SWITCH;
mostMovePoints = bestMovePointsForTarget[0];
mostViableTargetsArray[0] = 0;
mostViableTargetsNo = 1;

View file

@ -65,6 +65,26 @@ void GetAIPartyIndexes(u32 battler, s32 *firstId, s32 *lastId)
}
}
bool32 IsSwitchinIdValid(u32 battler, u32 switchinId)
{
if (IsDoubleBattle())
{
u32 partner = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerAtPosition(battler)));
// Edge case: See if partner already chose to switch into the same mon
if (switchinId == PARTY_SIZE) // Generic switch
{
if ((AI_DATA->shouldSwitch & (1u << partner)) && AI_DATA->monToSwitchInId[partner] == AI_DATA->mostSuitableMonId[battler])
return FALSE;
}
else
{
if ((AI_DATA->shouldSwitch & (1u << partner)) && AI_DATA->monToSwitchInId[partner] == switchinId)
return FALSE;
}
}
return TRUE;
}
// Note that as many return statements as possible are INTENTIONALLY put after all of the loops;
// the function can take a max of about 0.06s to run, and this prevents the player from identifying
// whether the mon will switch or not by seeing how long the delay is before they select a move
@ -185,9 +205,12 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult)
// Switch mon out
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
}
}
// General bad type matchups have more wiggle room
@ -208,6 +231,75 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult)
// Switch mon out
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
}
}
}
return FALSE;
}
static bool32 ShouldSwitchIfAllMovesBad(u32 battler, bool32 emitResult)
{
u32 battlerIndex, moveIndex;
bool32 switchOut = FALSE;
// Consider switching if all moves are worthless to use.
if (GetTotalBaseStat(gBattleMons[battler].species) >= 310 // Mon is not weak.
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) // Mon has more than 50% of its HP
{
s32 cap = AI_THINKING_STRUCT->aiFlags[battler] & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93;
if (IsDoubleBattle())
{
for (battlerIndex = 0; battlerIndex < MAX_BATTLERS_COUNT; battlerIndex++)
{
if (battlerIndex != battler && IsBattlerAlive(battlerIndex))
{
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
{
if (gBattleStruct->aiFinalScore[battler][battlerIndex][moveIndex] > cap)
break;
}
if (moveIndex != MAX_MON_MOVES)
break;
}
}
if (battlerIndex == MAX_BATTLERS_COUNT && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE)
switchOut = TRUE;
}
else
{
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
{
if (AI_THINKING_STRUCT->score[moveIndex] > cap)
break;
}
if (moveIndex == MAX_MON_MOVES && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE)
switchOut = TRUE;
}
}
// Consider switching if your mon with truant is bodied by Protect spam.
// Or is using a double turn semi invulnerable move(such as Fly) and is faster.
if (AI_DATA->abilities[battler] == ABILITY_TRUANT
&& IsTruantMonVulnerable(battler, gBattlerTarget)
&& gDisableStructs[battler].truantCounter
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2
&& AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE)
{
switchOut = TRUE;
}
if (switchOut)
{
// Switch mon out
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
@ -216,22 +308,6 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult)
return FALSE;
}
static bool32 ShouldSwitchIfAllBadMoves(u32 battler, bool32 emitResult)
{
if (AI_DATA->shouldSwitchIfBadMoves & (1u << battler))
{
AI_DATA->shouldSwitchIfBadMoves &= ~(1u << battler);
gBattleStruct->AI_monToSwitchIntoId[battler] = AI_DATA->monToSwitchId[battler];
if (emitResult)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
return TRUE;
}
else
{
return FALSE;
}
}
static bool32 ShouldSwitchIfWonderGuard(u32 battler, bool32 emitResult)
{
u8 opposingPosition;
@ -288,9 +364,12 @@ static bool32 ShouldSwitchIfWonderGuard(u32 battler, bool32 emitResult)
{
// We found a mon.
gBattleStruct->AI_monToSwitchIntoId[battler] = i;
if (emitResult)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
return TRUE;
}
}
}
}
@ -403,9 +482,12 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler, bool32 emitResult)
{
// we found a mon.
gBattleStruct->AI_monToSwitchIntoId[battler] = i;
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
}
}
}
}
@ -445,9 +527,12 @@ static bool32 FindMonThatTrapsOpponent(u32 battler, bool32 emitResult)
if (i == AI_DATA->mostSuitableMonId[battler]) // If mon in slot i is the most suitable switchin candidate, then it's a trapper than wins 1v1
{
gBattleStruct->AI_monToSwitchIntoId[battler] = i;
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
}
}
}
}
@ -520,9 +605,12 @@ static bool32 ShouldSwitchIfGameStatePrompt(u32 battler, bool32 emitResult)
|| GetMonAbility(&party[i]) == ABILITY_ELECTRIC_SURGE)) //Ally has Misty or Electric Surge
{
*(gBattleStruct->AI_monToSwitchIntoId + BATTLE_PARTNER(battler)) = i;
if (emitResult)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
switchMon = FALSE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
switchMon = FALSE;
}
break;
}
}
@ -611,9 +699,13 @@ static bool32 ShouldSwitchIfGameStatePrompt(u32 battler, bool32 emitResult)
{
if (!monIdChosen)
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
if (emitResult)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
return TRUE;
}
return FALSE;
}
else
{
@ -668,10 +760,13 @@ static bool32 ShouldSwitchIfAbilityBenefit(u32 battler, bool32 emitResult)
}
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
if (emitResult)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
return TRUE;
}
return FALSE;
}
static bool32 HasSuperEffectiveMoveAgainstOpponents(u32 battler, bool32 noRng)
@ -811,9 +906,12 @@ static bool32 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u32 modu
if (AI_GetTypeEffectiveness(move, battler, battlerIn1) >= UQ_4_12(2.0) && Random() % moduloPercent == 0)
{
gBattleStruct->AI_monToSwitchIntoId[battler] = i;
if (emitResult)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_SWITCH, 0);
return TRUE;
}
}
}
}
@ -902,9 +1000,12 @@ static bool32 ShouldSwitchIfEncored(u32 battler, bool32 emitResult)
if (Random() & 1)
{
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
}
}
return FALSE;
@ -919,9 +1020,13 @@ static bool32 ShouldSwitchIfBadChoiceLock(u32 battler, bool32 emitResult)
if (gMovesInfo[gLastUsedMove].category == DAMAGE_CATEGORY_STATUS)
{
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
}
}
}
@ -950,18 +1055,24 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult)
if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1))
{
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
}
}
}
// If at -3 or worse, switch out regardless
else if (attackingStage < DEFAULT_STAT_STAGE - 2)
{
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
}
}
}
@ -977,16 +1088,24 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult)
if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1))
{
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
}
}
}
// If at -3 or worse, switch out regardless
else if (spAttackingStage < DEFAULT_STAT_STAGE - 2)
{
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
if (IsSwitchinIdValid(battler, gBattleStruct->AI_monToSwitchIntoId[battler]))
{
if (emitResult)
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
}
}
}
return FALSE;
@ -1019,11 +1138,12 @@ bool32 ShouldSwitch(u32 battler, bool32 emitResult)
if (IsDoubleBattle())
{
u32 partner = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerAtPosition(battler)));
battlerIn1 = battler;
if (gAbsentBattlerFlags & (1u << GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)))))
if (gAbsentBattlerFlags & (1u << partner))
battlerIn2 = battler;
else
battlerIn2 = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battler)));
battlerIn2 = partner;
}
else
{
@ -1083,7 +1203,7 @@ bool32 ShouldSwitch(u32 battler, bool32 emitResult)
//These Functions can prompt switch to generic pary members
if ((AI_THINKING_STRUCT->aiFlags[battler] & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE))
return FALSE;
if (ShouldSwitchIfAllBadMoves(battler, emitResult))
if (ShouldSwitchIfAllMovesBad(battler, emitResult))
return TRUE;
if (ShouldSwitchIfAbilityBenefit(battler, emitResult))
return TRUE;
@ -1170,6 +1290,7 @@ void AI_TrySwitchOrUseItem(u32 battler)
}
*(gBattleStruct->monToSwitchIntoId + battler) = gBattleStruct->AI_monToSwitchIntoId[battler];
AI_DATA->monToSwitchInId[battler] = gBattleStruct->AI_monToSwitchIntoId[battler];
return;
}
else if (ShouldUseItem(battler))

View file

@ -550,9 +550,6 @@ static void OpponentHandleChooseMove(u32 battler)
case AI_CHOICE_FLEE:
BtlController_EmitTwoReturnValues(battler, BUFFER_B, B_ACTION_RUN, 0);
break;
case AI_CHOICE_SWITCH:
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, 0xFFFF);
break;
case 6:
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 15, gBattlerTarget);
break;

View file

@ -354,31 +354,24 @@ static void PlayerPartnerHandleChooseMove(u32 battler)
chosenMoveId = gBattleStruct->aiMoveOrAction[battler];
gBattlerTarget = gBattleStruct->aiChosenTarget[battler];
if (chosenMoveId == AI_CHOICE_SWITCH)
if (gMovesInfo[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
gBattlerTarget = battler;
else if (gMovesInfo[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH)
{
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, 0xFFFF);
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
if (gAbsentBattlerFlags & (1u << gBattlerTarget))
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
}
// If partner can and should use a gimmick (considering trainer data), do it
if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_NONE
&& !(gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_Z_MOVE
&& !ShouldUseZMove(battler, gBattlerTarget, moveInfo->moves[chosenMoveId])))
{
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_GIMMICK) | (gBattlerTarget << 8));
}
else
{
if (gMovesInfo[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED))
gBattlerTarget = battler;
if (gMovesInfo[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH)
{
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT);
if (gAbsentBattlerFlags & (1u << gBattlerTarget))
gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT);
}
// If partner can and should use a gimmick (considering trainer data), do it
if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_NONE
&& !(gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_Z_MOVE
&& !ShouldUseZMove(battler, gBattlerTarget, moveInfo->moves[chosenMoveId])))
{
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (RET_GIMMICK) | (gBattlerTarget << 8));
}
else
{
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8));
}
BtlController_EmitTwoReturnValues(battler, BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8));
}
PlayerPartnerBufferExecCompleted(battler);

View file

@ -39,7 +39,7 @@ AI_SINGLE_BATTLE_TEST("AI switches if Perish Song is about to kill")
}
}
AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a double battle")
AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a double battle (all bad moves)")
{
u32 flags;
@ -67,6 +67,29 @@ AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spot
}
}
AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a double battle (Wonder Guard)")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_SHEDINJA);
PLAYER(SPECIES_SHEDINJA);
// No moves to damage player.
OPPONENT(SPECIES_LINOONE) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_LINOONE) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); }
} WHEN {
TURN { EXPECT_SWITCH(opponentLeft, 3); };
} SCENE {
MESSAGE("{PKMN} TRAINER LEAF withdrew Linoone!");
MESSAGE("{PKMN} TRAINER LEAF sent out Gengar!");
NONE_OF {
MESSAGE("{PKMN} TRAINER LEAF withdrew Zigzagoon!");
MESSAGE("{PKMN} TRAINER LEAF sent out Gengar!");
}
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: U-Turn will send out Ace Mon if it's the only one remaining")
{
GIVEN {