diff --git a/include/battle.h b/include/battle.h index 1fe0ed4ef7..662fc23b1b 100644 --- a/include/battle.h +++ b/include/battle.h @@ -292,6 +292,8 @@ struct AiLogicData s32 simulatedDmg[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex u8 effectiveness[MAX_BATTLERS_COUNT][MAX_BATTLERS_COUNT][MAX_MON_MOVES]; // attacker, target, moveIndex u8 moveLimitations[MAX_BATTLERS_COUNT]; + bool8 shouldSwitchMon; // Because all available moves have no/little effect. Each bit per battler. + u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch. }; struct AI_ThinkingStruct @@ -304,8 +306,7 @@ struct AI_ThinkingStruct u32 aiFlags; u8 aiAction; u8 aiLogicId; - struct AI_SavedBattleMon saved[4]; - bool8 switchMon; // Because all available moves have no/little effect. + struct AI_SavedBattleMon saved[MAX_BATTLERS_COUNT]; }; #define AI_MOVE_HISTORY_COUNT 3 diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 672f24351b..4c4251bf4d 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -403,6 +403,78 @@ void GetAiLogicData(void) } } +static bool32 AI_SwitchMonIfSuitable(u32 battlerId) +{ + u32 monToSwitchId = GetMostSuitableMonToSwitchInto(); + if (monToSwitchId != PARTY_SIZE) + { + AI_DATA->shouldSwitchMon |= gBitTable[battlerId]; + AI_DATA->monToSwitchId[battlerId] = monToSwitchId; + return TRUE; + } + return FALSE; +} + +static bool32 AI_ShouldSwitchIfBadMoves(u32 battlerId, bool32 doubleBattle) +{ + u32 i, j; + // If can switch. + if (CountUsablePartyMons(battlerId) > 0 + && !IsBattlerTrapped(battlerId, TRUE) + && !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE)) + && AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS)) + { + // Consider switching if all moves are worthless to use. + if (GetTotalBaseStat(gBattleMons[battlerId].species) >= 310 // Mon is not weak. + && gBattleMons[battlerId].hp >= gBattleMons[battlerId].maxHP / 2) // Mon has more than 50% of its HP + { + s32 cap = AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93; + if (doubleBattle) + { + for (i = 0; i < MAX_BATTLERS_COUNT; i++) + { + if (i != battlerId && IsBattlerAlive(i)) + { + for (j = 0; j < MAX_MON_MOVES; j++) + { + if (gBattleStruct->aiFinalScore[battlerId][i][j] > cap) + break; + } + if (j != MAX_MON_MOVES) + break; + } + } + if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battlerId)) + 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(battlerId)) + 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 (GetBattlerAbility(battlerId) == ABILITY_TRUANT + && IsTruantMonVulnerable(battlerId, gBattlerTarget) + && gDisableStructs[battlerId].truantCounter + && gBattleMons[battlerId].hp >= gBattleMons[battlerId].maxHP / 2 + && AI_SwitchMonIfSuitable(battlerId)) + { + return TRUE; + } + } + return FALSE; +} + static u8 ChooseMoveOrAction_Singles(void) { u8 currentMoveArray[MAX_MON_MOVES]; @@ -436,46 +508,9 @@ static u8 ChooseMoveOrAction_Singles(void) gActiveBattler = sBattler_AI; - // If can switch. - if (CountUsablePartyMons(sBattler_AI) > 0 - && !IsAbilityPreventingEscape(sBattler_AI) - && !(gBattleMons[gActiveBattler].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION)) - && !(gStatuses3[gActiveBattler] & STATUS3_ROOTED) - && !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE)) - && AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS)) - { - // Consider switching if all moves are worthless to use. - if (GetTotalBaseStat(gBattleMons[sBattler_AI].species) >= 310 // Mon is not weak. - && gBattleMons[sBattler_AI].hp >= gBattleMons[sBattler_AI].maxHP / 2) - { - s32 cap = AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93; - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (AI_THINKING_STRUCT->score[i] > cap) - break; - } - - if (i == MAX_MON_MOVES && GetMostSuitableMonToSwitchInto() != PARTY_SIZE) - { - AI_THINKING_STRUCT->switchMon = TRUE; - return AI_CHOICE_SWITCH; - } - } - - // 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 (GetBattlerAbility(sBattler_AI) == ABILITY_TRUANT - && IsTruantMonVulnerable(sBattler_AI, gBattlerTarget) - && gDisableStructs[sBattler_AI].truantCounter - && gBattleMons[sBattler_AI].hp >= gBattleMons[sBattler_AI].maxHP / 2) - { - if (GetMostSuitableMonToSwitchInto() != PARTY_SIZE) - { - AI_THINKING_STRUCT->switchMon = TRUE; - return AI_CHOICE_SWITCH; - } - } - } + // Switch mon if there are no good moves to use. + if (AI_ShouldSwitchIfBadMoves(sBattler_AI, FALSE)) + return AI_CHOICE_SWITCH; numOfBestMoves = 1; currentMoveArray[0] = AI_THINKING_STRUCT->score[0]; @@ -590,7 +625,6 @@ static u8 ChooseMoveOrAction_Doubles(void) if (i == BATTLE_PARTNER(sBattler_AI) && bestMovePointsForTarget[i] < 100) { bestMovePointsForTarget[i] = -1; - mostViableMovesScores[0] = mostViableMovesScores[0]; // Needed to match. } } @@ -600,6 +634,10 @@ static u8 ChooseMoveOrAction_Doubles(void) } } + // Switch mon if all of the moves are bad to use against any of the target. + if (AI_ShouldSwitchIfBadMoves(sBattler_AI, 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 0045608b08..faaeff12b1 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -60,10 +60,10 @@ void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId) static bool8 ShouldSwitchIfAllBadMoves(void) { - if (gBattleResources->ai->switchMon) + if (AI_DATA->shouldSwitchMon & gBitTable[gActiveBattler]) { - gBattleResources->ai->switchMon = 0; - *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; + AI_DATA->shouldSwitchMon &= ~(gBitTable[gActiveBattler]); + gBattleStruct->AI_monToSwitchIntoId[gActiveBattler] = AI_DATA->monToSwitchId[gActiveBattler]; BtlController_EmitTwoReturnValues(BUFFER_B, B_ACTION_SWITCH, 0); return TRUE; } diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index 3f6014302b..3e21f78bec 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -352,23 +352,30 @@ static void PlayerPartnerHandleChooseMove(u32 battler) chosenMoveId = gBattleStruct->aiMoveOrAction[battler]; gBattlerTarget = gBattleStruct->aiChosenTarget[battler]; - if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED)) - gBattlerTarget = battler; - if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH) + if (chosenMoveId == AI_CHOICE_SWITCH) { - gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); - if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) - gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); + BtlController_EmitTwoReturnValues(BUFFER_B, 10, 0xFFFF); } - - if (ShouldUseZMove(battler, gBattlerTarget, moveInfo->moves[chosenMoveId])) - QueueZMove(battler, moveInfo->moves[chosenMoveId]); - - // If partner can mega evolve, do it. - if (CanMegaEvolve(battler)) - BtlController_EmitTwoReturnValues(BUFFER_B, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); else - BtlController_EmitTwoReturnValues(BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8)); + { + if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED)) + gBattlerTarget = battler; + if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH) + { + gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); + if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) + gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); + } + + if (ShouldUseZMove(battler, gBattlerTarget, moveInfo->moves[chosenMoveId])) + QueueZMove(battler, moveInfo->moves[chosenMoveId]); + + // If partner can mega evolve, do it. + if (CanMegaEvolve(battler)) + BtlController_EmitTwoReturnValues(BUFFER_B, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); + else + BtlController_EmitTwoReturnValues(BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8)); + } PlayerPartnerBufferExecCompleted(battler); } @@ -382,7 +389,7 @@ static void PlayerPartnerHandleChoosePokemon(u32 battler) chosenMonId = gSelectedMonPartyId = GetFirstFaintedPartyIndex(battler); } // Switching out - else + else if (gBattleStruct->monToSwitchIntoId[battler] == PARTY_SIZE) { chosenMonId = GetMostSuitableMonToSwitchInto(); if (chosenMonId == PARTY_SIZE) // just switch to the next mon @@ -402,6 +409,12 @@ static void PlayerPartnerHandleChoosePokemon(u32 battler) } *(gBattleStruct->monToSwitchIntoId + battler) = chosenMonId; } + else // Mon to switch out has been already chosen. + { + chosenMonId = gBattleStruct->monToSwitchIntoId[battler]; + *(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE; + *(gBattleStruct->monToSwitchIntoId + battler) = chosenMonId; + } BtlController_EmitChosenMonReturnValue(BUFFER_B, chosenMonId, NULL); PlayerPartnerBufferExecCompleted(battler); }