Modify move prediction to work in doubles (#7087)
This commit is contained in:
parent
a9d5c2cd28
commit
38cfc4bacf
@ -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];
|
||||
};
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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); }
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user