Modify move prediction to work in doubles (#7087)

This commit is contained in:
Alex 2025-08-11 18:26:41 +02:00 committed by GitHub
parent a9d5c2cd28
commit 38cfc4bacf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 107 additions and 93 deletions

View File

@ -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];
};

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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;

View File

@ -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)

View File

@ -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); }
}
}