diff --git a/include/battle.h b/include/battle.h index 47739ca264..086eb68f51 100644 --- a/include/battle.h +++ b/include/battle.h @@ -329,12 +329,12 @@ struct AiLogicData u8 ejectButtonSwitch:1; // Tracks whether current switch out was from Eject Button u8 ejectPackSwitch:1; // Tracks whether current switch out was from Eject Pack u8 predictingSwitch:1; // Determines whether AI will use switch predictions this turn or not - u8 predictingMove:1; // Determines whether AI will use move predictions this turn or not u8 aiPredictionInProgress:1; // Tracks whether the AI is in the middle of running prediction calculations - u8 padding:2; - u8 shouldSwitch; // Stores result of ShouldSwitch, which decides whether a mon should be switched out u8 aiCalcInProgress:1; - u8 battlerDoingPrediction; // Stores which battler is currently running its prediction calcs + u8 predictingMove:1; // Determines whether AI will use move predictions this turn or not + u8 padding1:1; + u8 shouldSwitch:4; // Stores result of ShouldSwitch, which decides whether a mon should be switched out + u8 padding2:4; u16 predictedMove[MAX_BATTLERS_COUNT]; }; diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 448fbf71c5..118fbf5984 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -80,6 +80,7 @@ u32 AI_GetDamage(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcC bool32 IsAiVsAiBattle(void); bool32 BattlerHasAi(u32 battlerId); bool32 IsAiBattlerAware(u32 battlerId); +bool32 CanAiPredictMove(void); bool32 IsAiBattlerAssumingStab(void); bool32 IsAiBattlerAssumingStatusMoves(void); bool32 ShouldRecordStatusMove(u32 move); @@ -297,7 +298,6 @@ bool32 IsBattlerPredictedToSwitch(u32 battler); u32 GetIncomingMove(u32 battler, u32 opposingBattler, struct AiLogicData *aiData); bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef); bool32 HasBattlerSideAbility(u32 battlerDef, u32 ability, struct AiLogicData *aiData); -u32 GetThinkingBattler(u32 battler); bool32 IsNaturalEnemy(u32 speciesAttacker, u32 speciesTarget); // These are for the purpose of not doubling up on moves during double battles. diff --git a/include/config/ai.h b/include/config/ai.h index 8d015ce9e4..b6eb7bfe21 100644 --- a/include/config/ai.h +++ b/include/config/ai.h @@ -52,7 +52,7 @@ #define AI_GOOD_SCORE_THRESHOLD 100 // Move scores above this threshold are considered "good" when deciding switching // AI held item-based move scoring -#define LOW_ACCURACY_THRESHOLD 75 // Moves with accuracy equal OR below this value are considered low accuracy +#define LOW_ACCURACY_THRESHOLD 75 // Moves with accuracy equal OR below this value are considered low accuracy // AI move scoring #define STATUS_MOVE_FOCUS_PUNCH_CHANCE 50 // Chance the AI will use a status move if the player's best move is Focus Punch @@ -73,7 +73,7 @@ // AI Terastalization chances #define AI_CONSERVE_TERA_CHANCE_PER_MON 10 // Chance for AI with smart tera flag to decide not to tera before considering defensive benefit is this*(X-1), where X is the number of alive pokemon that could tera -#define AI_TERA_PREDICT_CHANCE 40 // Chance for AI with smart tera flag to tera in the situation where tera would save it from a KO, but could be punished by a KO from a different move. +#define AI_TERA_PREDICT_CHANCE 40 // Chance for AI with smart tera flag to tera in the situation where tera would save it from a KO, but could be punished by a KO from a different move. // AI_FLAG_PP_STALL_PREVENTION settings #define PP_STALL_DISREGARD_MOVE_PERCENTAGE 50 // Detection chance per roll diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 5e75f1dfb3..5460e31dee 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -33,6 +33,7 @@ #define AI_ACTION_WATCH (1 << 2) #define AI_ACTION_DO_NOT_ATTACK (1 << 3) +static u32 ChooseMoveOrAction(u32 battler); static u32 ChooseMoveOrAction_Singles(u32 battler); static u32 ChooseMoveOrAction_Doubles(u32 battler); static inline void BattleAI_DoAIProcessing(struct AiThinkingStruct *aiThink, u32 battlerAtk, u32 battlerDef); @@ -278,16 +279,17 @@ void BattleAI_SetupFlags(void) { gAiThinkingStruct->aiFlags[B_POSITION_PLAYER_RIGHT] = gAiThinkingStruct->aiFlags[B_POSITION_PLAYER_LEFT]; } - else + else // Assign ai flags for player for prediction { - gAiThinkingStruct->aiFlags[B_POSITION_PLAYER_RIGHT] = 0; // player + u64 aiFlags = GetAiFlags(TRAINER_BATTLE_PARAM.opponentA) | GetAiFlags(TRAINER_BATTLE_PARAM.opponentB); + gAiThinkingStruct->aiFlags[B_POSITION_PLAYER_RIGHT] = aiFlags; + gAiThinkingStruct->aiFlags[B_POSITION_PLAYER_LEFT] = aiFlags; } } void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) { - u32 moveLimitations, moveLimitationsTarget; - u32 defaultScoreMovesTarget = defaultScoreMoves; + u32 moveLimitations; u64 flags[MAX_BATTLERS_COUNT]; u32 moveIndex; @@ -313,25 +315,6 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler) gBattlerTarget = SetRandomTarget(battler); gAiBattleData->chosenTarget[battler] = gBattlerTarget; - - // Initialize move prediction scores - if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_PREDICT_MOVE) - { - u32 opposingBattler = GetOppositeBattler(battler); - moveLimitationsTarget = gAiLogicData->moveLimitations[opposingBattler]; - - for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++) - { - if (moveLimitationsTarget & (1u << moveIndex)) - SET_SCORE(opposingBattler, moveIndex, 0); - if (defaultScoreMovesTarget & 1) - SET_SCORE(opposingBattler, moveIndex, AI_SCORE_DEFAULT); - else - SET_SCORE(opposingBattler, moveIndex, 0); - - defaultScoreMovesTarget >>= 1; - } - } } bool32 BattlerChoseNonMoveAction(void) @@ -355,7 +338,6 @@ void SetupAIPredictionData(u32 battler, enum SwitchType switchType) { s32 opposingBattler = GetOppositeBattler(battler); gAiLogicData->aiPredictionInProgress = TRUE; - gAiLogicData->battlerDoingPrediction = battler; // Switch prediction if ((gAiThinkingStruct->aiFlags[battler] & AI_FLAG_PREDICT_SWITCH)) @@ -364,31 +346,21 @@ void SetupAIPredictionData(u32 battler, enum SwitchType switchType) if (ShouldSwitch(opposingBattler)) gAiLogicData->shouldSwitch |= (1u << opposingBattler); gBattleStruct->prevTurnSpecies[opposingBattler] = gBattleMons[opposingBattler].species; - - // Determine whether AI will use predictions this turn - gAiLogicData->predictingSwitch = RandomPercentage(RNG_AI_PREDICT_SWITCH, PREDICT_SWITCH_CHANCE); } - // Move prediction - if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_PREDICT_MOVE) - { - gAiLogicData->predictedMove[opposingBattler] = gBattleMons[opposingBattler].moves[BattleAI_ChooseMoveIndex(opposingBattler)]; - ModifySwitchAfterMoveScoring(opposingBattler); + // Determine whether AI will use predictions this turn + gAiLogicData->predictingSwitch = RandomPercentage(RNG_AI_PREDICT_SWITCH, PREDICT_SWITCH_CHANCE); - // Determine whether AI will use predictions this turn - gAiLogicData->predictingMove = RandomPercentage(RNG_AI_PREDICT_MOVE, PREDICT_MOVE_CHANCE); - } gAiLogicData->aiPredictionInProgress = FALSE; } void ComputeBattlerDecisions(u32 battler) { - if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) - && (BattlerHasAi(battler) - && !(gBattleTypeFlags & BATTLE_TYPE_PALACE))) + bool32 isAiBattler = (gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) && (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE)); + if (isAiBattler || CanAiPredictMove()) { // If ai is about to flee or chosen to watch player, no need to calc anything - if (BattlerChoseNonMoveAction()) + if (isAiBattler && BattlerChoseNonMoveAction()) return; // Risky AI switches aggressively even mid battle @@ -401,10 +373,13 @@ void ComputeBattlerDecisions(u32 battler) SetupAIPredictionData(battler, switchType); // AI's own switching data - gAiLogicData->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, switchType); - if (ShouldSwitch(battler)) - gAiLogicData->shouldSwitch |= (1u << battler); - gBattleStruct->prevTurnSpecies[battler] = gBattleMons[battler].species; + if (isAiBattler) + { + gAiLogicData->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, switchType); + if (ShouldSwitch(battler)) + gAiLogicData->shouldSwitch |= (1u << battler); + gBattleStruct->prevTurnSpecies[battler] = gBattleMons[battler].species; + } // AI's move scoring gAiBattleData->chosenMoveIndex[battler] = BattleAI_ChooseMoveIndex(battler); // Calculate score and chose move index @@ -425,6 +400,13 @@ void ReconsiderGimmick(u32 battlerAtk, u32 battlerDef, u16 move) SetAIUsingGimmick(battlerAtk, NO_GIMMICK); } +static u32 ChooseMoveOrAction(u32 battler) +{ + if (IsDoubleBattle()) + return ChooseMoveOrAction_Doubles(battler); + return ChooseMoveOrAction_Singles(battler); +} + u32 BattleAI_ChooseMoveIndex(u32 battler) { u32 chosenMoveIndex; @@ -434,16 +416,11 @@ u32 BattleAI_ChooseMoveIndex(u32 battler) if (gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_TERA && (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_TERA)) DecideTerastal(battler); - - if (!IsDoubleBattle()) - chosenMoveIndex = ChooseMoveOrAction_Singles(battler); - else - chosenMoveIndex = ChooseMoveOrAction_Doubles(battler); + chosenMoveIndex = ChooseMoveOrAction(battler); if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_NONE) ReconsiderGimmick(battler, gBattlerTarget, gBattleMons[battler].moves[chosenMoveIndex]); - // Clear protect structures, some flags may be set during AI calcs // e.g. pranksterElevated from GetBattleMovePriority memset(&gProtectStructs, 0, MAX_BATTLERS_COUNT * sizeof(struct ProtectStruct)); @@ -573,7 +550,6 @@ void RecordStatusMoves(u32 battler) void SetBattlerAiData(u32 battler, struct AiLogicData *aiData) { u32 ability, holdEffect; - ability = aiData->abilities[battler] = AI_DecideKnownAbilityForTurn(battler); aiData->items[battler] = gBattleMons[battler].item; holdEffect = aiData->holdEffects[battler] = AI_DecideHoldEffectForTurn(battler); @@ -691,6 +667,20 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData) SetBattlerAiMovesData(aiData, battlerAtk, battlersCount, weather); } + + for (battlerAtk = 0; battlerAtk < battlersCount; battlerAtk++) + { + // Prediction limited to player side but can be expanded to read partners move in the future + if (!IsOnPlayerSide(battlerAtk) || !CanAiPredictMove()) + continue; + + // This can potentially be cleaned up more + BattleAI_SetupAIData(0xF, battlerAtk); + u32 chosenMoveIndex = ChooseMoveOrAction(battlerAtk); + gAiLogicData->predictedMove[battlerAtk] = gBattleMons[battlerAtk].moves[chosenMoveIndex]; + aiData->predictingMove = RandomPercentage(RNG_AI_PREDICT_MOVE, PREDICT_MOVE_CHANCE); + } + if (DEBUG_AI_DELAY_TIMER) // We add to existing to compound multiple calls gBattleStruct->aiDelayCycles += CycleCountEnd(); @@ -749,7 +739,7 @@ static u32 ChooseMoveOrAction_Singles(u32 battler) u8 consideredMoveArray[MAX_MON_MOVES]; u32 numOfBestMoves; s32 i; - u64 flags = gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)]; + u64 flags = gAiThinkingStruct->aiFlags[battler]; u32 opposingBattler = GetOppositeBattler(battler); gAiLogicData->partnerMove = 0; // no ally @@ -831,7 +821,7 @@ static u32 ChooseMoveOrAction_Doubles(u32 battler) gAiLogicData->partnerMove = GetAllyChosenMove(battler); gAiThinkingStruct->aiLogicId = 0; gAiThinkingStruct->movesetIndex = 0; - flags = gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)]; + flags = gAiThinkingStruct->aiFlags[battler]; while (flags != 0) { @@ -1139,7 +1129,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) abilityDef = ABILITY_NONE; // If a pokemon can be guaranteed flinched, don't target the pokemon that can't be flinched. - if (hasTwoOpponents + if (hasTwoOpponents && !IsFlinchGuaranteed(battlerAtk, battlerDef, move) && IsFlinchGuaranteed(battlerAtk, BATTLE_PARTNER(battlerDef), move) && aiData->effectiveness[battlerAtk][BATTLE_PARTNER(battlerDef)][gAiThinkingStruct->movesetIndex] != UQ_4_12(0.0)) ADJUST_SCORE(-5); @@ -1975,7 +1965,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_CHILLY_RECEPTION: if (CountUsablePartyMons(battlerAtk) == 0) ADJUST_SCORE(-10); - else if (weather & (B_WEATHER_ICY_ANY | B_WEATHER_PRIMAL_ANY) + else if (weather & (B_WEATHER_ICY_ANY | B_WEATHER_PRIMAL_ANY) || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-8); break; @@ -2254,12 +2244,14 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); decreased = TRUE; } + break; case PROTECT_WIDE_GUARD: if(!(GetBattlerMoveTargetType(battlerAtk, predictedMove) & (MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_BOTH))) { ADJUST_SCORE(-10); decreased = TRUE; } + break; case PROTECT_CRAFTY_SHIELD: if (!hasPartner) { @@ -3000,7 +2992,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) SetTypeBeforeUsingMove(move, battlerAtk); moveType = GetBattleMoveType(move); - + bool32 hasPartner = HasPartner(battlerAtk); u32 friendlyFireThreshold = GetFriendlyFireKOThreshold(battlerAtk); u32 noOfHitsToKOPartner = GetNoOfHitsToKOBattler(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING); @@ -3095,7 +3087,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) // Both Pokemon use Trick Room on the final turn of Trick Room to anticipate both opponents Protecting to stall out. // This unsets Trick Room and resets it with a full timer. case EFFECT_TRICK_ROOM: - if (hasPartner && gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer == gBattleTurnCounter + if (hasPartner && gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer == gBattleTurnCounter && ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_TRICK_ROOM) && HasMoveWithEffect(battlerAtkPartner, MOVE_TRICK_ROOM) && RandomPercentage(RNG_AI_REFRESH_TRICK_ROOM_ON_LAST_TURN, DOUBLE_TRICK_ROOM_ON_LAST_TURN_CHANCE)) @@ -5741,8 +5733,8 @@ static s32 AI_AttacksPartner(u32 battlerAtk, u32 battlerDef, u32 move, s32 score u32 hitsToKO = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, gAiThinkingStruct->movesetIndex, AI_ATTACKING); - if (GetMoveTarget(move) == MOVE_TARGET_FOES_AND_ALLY && hitsToKO > 0 && - (GetNoOfHitsToKOBattler(battlerAtk, FOE(battlerAtk), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0 || GetNoOfHitsToKOBattler(battlerAtk, FOE(battlerDef), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0)) + if (GetMoveTarget(move) == MOVE_TARGET_FOES_AND_ALLY && hitsToKO > 0 && + (GetNoOfHitsToKOBattler(battlerAtk, FOE(battlerAtk), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0 || GetNoOfHitsToKOBattler(battlerAtk, FOE(battlerDef), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0)) ADJUST_SCORE(BEST_EFFECT); if (hitsToKO > 0) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 379ce723fd..6f470d3c84 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -111,11 +111,11 @@ u32 GetSwitchChance(enum ShouldSwitchScenario shouldSwitchScenario) static bool32 IsAceMon(u32 battler, u32 monPartyId) { - if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_ACE_POKEMON + if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_ACE_POKEMON && !gBattleStruct->battlerState[battler].forcedSwitch && monPartyId == CalculateEnemyPartyCountInSide(battler)-1) return TRUE; - if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_DOUBLE_ACE_POKEMON + if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_DOUBLE_ACE_POKEMON && !gBattleStruct->battlerState[battler].forcedSwitch && (monPartyId == CalculateEnemyPartyCount()-1 || monPartyId == CalculateEnemyPartyCount()-2)) return TRUE; @@ -215,7 +215,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler) bool32 canBattlerWin1v1 = FALSE, isBattlerFirst, isBattlerFirstPriority; // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer - if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) + if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) return FALSE; // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic @@ -486,7 +486,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler) bool32 isOpposingBattlerChargingOrInvulnerable = !BreaksThroughSemiInvulnerablity(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove); s32 i, j; - if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) + if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) return FALSE; if (GetMoveEffect(incomingMove) == EFFECT_HIDDEN_POWER && RandomPercentage(RNG_AI_SWITCH_ABSORBING_HIDDEN_POWER, SHOULD_SWITCH_ABSORBS_HIDDEN_POWER_PERCENTAGE)) return FALSE; @@ -618,7 +618,7 @@ static bool32 ShouldSwitchIfOpponentChargingOrInvulnerable(u32 battler) bool32 isOpposingBattlerChargingOrInvulnerable = !BreaksThroughSemiInvulnerablity(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove); - if (IsDoubleBattle() || !(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) + if (IsDoubleBattle() || !(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) return FALSE; if (isOpposingBattlerChargingOrInvulnerable && gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE && RandomPercentage(RNG_AI_SWITCH_FREE_TURN, GetSwitchChance(SHOULD_SWITCH_FREE_TURN))) @@ -637,7 +637,7 @@ static bool32 ShouldSwitchIfTrapperInParty(u32 battler) s32 opposingBattler = GetOppositeBattler(battler); // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer - if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) + if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) return FALSE; // Check if current mon has an ability that traps opponent @@ -681,7 +681,7 @@ static bool32 ShouldSwitchIfBadlyStatused(u32 battler) && RandomPercentage(RNG_AI_SWITCH_PERISH_SONG, GetSwitchChance(SHOULD_SWITCH_PERISH_SONG))) return SetSwitchinAndSwitch(battler, PARTY_SIZE); - if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING) + if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING) { //Yawn if (gStatuses3[battler] & STATUS3_YAWN @@ -869,7 +869,7 @@ static bool32 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u32 perc u16 move; // Similar functionality handled more thoroughly by ShouldSwitchIfHasBadOdds - if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING) + if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING) return FALSE; if (gLastLandedMoves[battler] == MOVE_NONE) @@ -1007,7 +1007,7 @@ static bool32 ShouldSwitchIfEncored(u32 battler) u32 opposingBattler = GetOppositeBattler(battler); // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer - if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) + if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) return FALSE; // If not Encore'd don't switch @@ -1057,7 +1057,7 @@ static bool32 ShouldSwitchIfAttackingStatsLowered(u32 battler) s8 spAttackingStage = gBattleMons[battler].statStages[STAT_SPATK]; // Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer - if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) + if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) return FALSE; // Physical attacker @@ -1117,7 +1117,7 @@ bool32 ShouldSwitch(u32 battler) return FALSE; // Sequence Switching AI never switches mid-battle - if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SEQUENCE_SWITCHING) + if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING) return FALSE; availableToSwitch = 0; @@ -1169,7 +1169,7 @@ bool32 ShouldSwitch(u32 battler) if (ShouldSwitchIfWonderGuard(battler)) return TRUE; - if ((gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE)) + if ((gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE)) return FALSE; if (ShouldSwitchIfTrapperInParty(battler)) return TRUE; @@ -1197,7 +1197,7 @@ bool32 ShouldSwitch(u32 battler) // Removing switch capabilites under specific conditions // These Functions prevent the "FindMonWithFlagsAndSuperEffective" from getting out of hand. // We don't use FindMonWithFlagsAndSuperEffective with AI_FLAG_SMART_SWITCHING, so we can bail early. - if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING) + if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING) return FALSE; if (CanUseSuperEffectiveMoveAgainstOpponents(battler)) return FALSE; @@ -1216,7 +1216,7 @@ bool32 ShouldSwitch(u32 battler) bool32 ShouldSwitchIfAllScoresBad(u32 battler) { u32 i, score, opposingBattler = GetOppositeBattler(battler); - if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)) + if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)) return FALSE; for (i = 0; i < MAX_MON_MOVES; i++) @@ -1268,7 +1268,7 @@ void ModifySwitchAfterMoveScoring(u32 battler) return; // Sequence Switching AI never switches mid-battle - if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SEQUENCE_SWITCHING) + if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING) return; availableToSwitch = 0; @@ -2329,14 +2329,14 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, enum SwitchType switchType) GetAIPartyIndexes(battler, &firstId, &lastId); party = GetBattlerParty(battler); - if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SEQUENCE_SWITCHING) + if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING) { bestMonId = GetNextMonInParty(party, firstId, lastId, battlerIn1, battlerIn2); return bestMonId; } // Only use better mon selection if AI_FLAG_SMART_MON_CHOICES is set for the trainer. - if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_MON_CHOICES && !IsDoubleBattle()) // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic + if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_MON_CHOICES && !IsDoubleBattle()) // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic { bestMonId = GetBestMonIntegrated(party, firstId, lastId, battler, opposingBattler, battlerIn1, battlerIn2, switchType); return bestMonId; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 624dd7cf5a..e044f7b3e9 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -173,11 +173,17 @@ bool32 IsAiBattlerPredictingAbility(u32 battlerId) return BattlerHasAi(battlerId); } +bool32 CanAiPredictMove(void) +{ + return gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_PREDICT_MOVE + || gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_PREDICT_MOVE; +} + bool32 IsBattlerPredictedToSwitch(u32 battler) { // Check for prediction flag on AI, whether they're using those predictions this turn, and whether the AI thinks the player should switch - if (gAiThinkingStruct->aiFlags[gAiLogicData->battlerDoingPrediction] & AI_FLAG_PREDICT_SWITCH - || gAiThinkingStruct->aiFlags[gAiLogicData->battlerDoingPrediction] & AI_FLAG_PREDICT_SWITCH) + if (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_PREDICT_SWITCH + || gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_PREDICT_SWITCH) { if (gAiLogicData->predictingSwitch && gAiLogicData->shouldSwitch & (1u << battler)) return TRUE; @@ -188,9 +194,8 @@ bool32 IsBattlerPredictedToSwitch(u32 battler) // Either a predicted move or the last used move from an opposing battler u32 GetIncomingMove(u32 battler, u32 opposingBattler, struct AiLogicData *aiData) { - if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_PREDICT_MOVE && aiData->predictingMove) + if (aiData->predictingMove && CanAiPredictMove()) return aiData->predictedMove[opposingBattler]; - return aiData->lastUsedMove[opposingBattler]; } @@ -5702,13 +5707,6 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData return WEAK_EFFECT; } -u32 GetThinkingBattler(u32 battler) -{ - if (gAiLogicData->aiPredictionInProgress) - return gAiLogicData->battlerDoingPrediction; - return battler; -} - bool32 IsNaturalEnemy(u32 speciesAttacker, u32 speciesTarget) { if (B_WILD_NATURAL_ENEMIES != TRUE) diff --git a/test/battle/ai/ai_check_viability.c b/test/battle/ai/ai_check_viability.c index 1407023ece..f18f7aceb1 100644 --- a/test/battle/ai/ai_check_viability.c +++ b/test/battle/ai/ai_check_viability.c @@ -339,3 +339,27 @@ AI_SINGLE_BATTLE_TEST("AI uses Skill Swap against Poison Heal") TURN { NOT_EXPECT_MOVE(opponent, MOVE_SKILL_SWAP); } } } + +AI_SINGLE_BATTLE_TEST("AI uses Quick Guard against Quick Attack when opponent would take poison damage") +{ + PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_MOVE); + GIVEN { + PLAYER(SPECIES_RATTATA) { Moves(MOVE_QUICK_ATTACK); Status1(STATUS1_TOXIC_POISON); } + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE); + OPPONENT(SPECIES_RATTATA) { Moves(MOVE_QUICK_GUARD, MOVE_TACKLE); } + } WHEN { + TURN { MOVE(player, MOVE_QUICK_ATTACK); EXPECT_MOVE(opponent, MOVE_QUICK_GUARD); } + } +} + +AI_SINGLE_BATTLE_TEST("AI uses Wide Guard against Earthquake when opponent would take poison damage") +{ + PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_MOVE); + GIVEN { + PLAYER(SPECIES_RATTATA) { Moves(MOVE_EARTHQUAKE); Status1(STATUS1_TOXIC_POISON); } + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE); + OPPONENT(SPECIES_RATTATA) { Moves(MOVE_WIDE_GUARD, MOVE_TACKLE); } + } WHEN { + TURN { MOVE(player, MOVE_EARTHQUAKE); EXPECT_MOVE(opponent, MOVE_WIDE_GUARD); } + } +}