From 9882e0aea6902cc256025e4b4399e9ffb655d00b Mon Sep 17 00:00:00 2001 From: Pawkkie <61265402+Pawkkie@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:22:48 -0400 Subject: [PATCH] Refactor ShouldSwitchIfAllBadMoves (#5452) * Refactor ShouldSwitchIfAllBadMoves * Rest of bugfix * Enable Shed Tail test * Revert "Enable Shed Tail test" This reverts commit e0b9a3affcf3c465c8743741e5ca61bde6c8cb18. * 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> --- include/battle.h | 3 +- include/battle_ai_main.h | 1 - src/battle_ai_main.c | 91 ---------- src/battle_ai_switch_items.c | 241 +++++++++++++++++++------ src/battle_controller_opponent.c | 3 - src/battle_controller_player_partner.c | 35 ++-- test/battle/ai/ai_switching.c | 25 ++- 7 files changed, 220 insertions(+), 179 deletions(-) diff --git a/include/battle.h b/include/battle.h index 894b74ffde..458d03ea40 100644 --- a/include/battle.h +++ b/include/battle.h @@ -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. diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index 431ed35698..2f16fe28ff 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -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 diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index fd253b5197..9099ffd5c0 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -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; diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 0400f18b53..c80b531d55 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -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)) diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index 01ec74f9c2..251085acea 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -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; diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index 47d71d87c6..f987c333ce 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -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); diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index a70163af18..a57e5d32fc 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -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 {