Fix AI's KO evaluation getting messed up by priority (#7533)

This commit is contained in:
Pawkkie 2025-08-12 04:30:14 -04:00 committed by GitHub
parent f4c91b5539
commit eab10dc2f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 130 additions and 70 deletions

View File

@ -296,6 +296,7 @@ u32 IncreaseSubstituteMoveScore(u32 battlerAtk, u32 battlerDef, u32 move);
bool32 IsBattlerItemEnabled(u32 battler); bool32 IsBattlerItemEnabled(u32 battler);
bool32 IsBattlerPredictedToSwitch(u32 battler); bool32 IsBattlerPredictedToSwitch(u32 battler);
u32 GetIncomingMove(u32 battler, u32 opposingBattler, struct AiLogicData *aiData); u32 GetIncomingMove(u32 battler, u32 opposingBattler, struct AiLogicData *aiData);
u32 GetIncomingMoveSpeedCheck(u32 battler, u32 opposingBattler, struct AiLogicData *aiData);
bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef); bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef);
bool32 HasBattlerSideAbility(u32 battlerDef, u32 ability, struct AiLogicData *aiData); bool32 HasBattlerSideAbility(u32 battlerDef, u32 ability, struct AiLogicData *aiData);
bool32 IsNaturalEnemy(u32 speciesAttacker, u32 speciesTarget); bool32 IsNaturalEnemy(u32 speciesAttacker, u32 speciesTarget);

View File

@ -742,7 +742,10 @@ static u32 ChooseMoveOrAction_Singles(u32 battler)
u64 flags = gAiThinkingStruct->aiFlags[battler]; u64 flags = gAiThinkingStruct->aiFlags[battler];
u32 opposingBattler = GetOppositeBattler(battler); u32 opposingBattler = GetOppositeBattler(battler);
gAiThinkingStruct->aiLogicId = 0;
gAiThinkingStruct->movesetIndex = 0;
gAiLogicData->partnerMove = 0; // no ally gAiLogicData->partnerMove = 0; // no ally
while (flags != 0) while (flags != 0)
{ {
if (flags & 1) if (flags & 1)
@ -1078,6 +1081,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
u32 i; u32 i;
u32 weather; u32 weather;
u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData); u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData);
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
u32 abilityAtk = aiData->abilities[battlerAtk]; u32 abilityAtk = aiData->abilities[battlerAtk];
u32 abilityDef = aiData->abilities[battlerDef]; u32 abilityDef = aiData->abilities[battlerDef];
s32 atkPriority = GetBattleMovePriority(battlerAtk, abilityAtk, move); s32 atkPriority = GetBattleMovePriority(battlerAtk, abilityAtk, move);
@ -1088,7 +1092,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (IsPowderMove(move) && !IsAffectedByPowder(battlerDef, aiData->abilities[battlerDef], aiData->holdEffects[battlerDef])) if (IsPowderMove(move) && !IsAffectedByPowder(battlerDef, aiData->abilities[battlerDef], aiData->holdEffects[battlerDef]))
RETURN_SCORE_MINUS(10); RETURN_SCORE_MINUS(10);
if (!BreaksThroughSemiInvulnerablity(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) if (!BreaksThroughSemiInvulnerablity(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
RETURN_SCORE_MINUS(10); RETURN_SCORE_MINUS(10);
if (IsTwoTurnNotSemiInvulnerableMove(battlerAtk, move) && CanTargetFaintAi(battlerDef, battlerAtk)) if (IsTwoTurnNotSemiInvulnerableMove(battlerAtk, move) && CanTargetFaintAi(battlerDef, battlerAtk))
@ -1320,7 +1324,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
switch (moveEffect) switch (moveEffect)
{ {
case EFFECT_HIT: // only applies to Vital Throw case EFFECT_HIT: // only applies to Vital Throw
if (GetBattleMovePriority(battlerAtk, aiData->abilities[battlerAtk], move) < 0 && AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && aiData->hpPercents[battlerAtk] < 40) if (GetBattleMovePriority(battlerAtk, aiData->abilities[battlerAtk], move) < 0 && AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) && aiData->hpPercents[battlerAtk] < 40)
ADJUST_SCORE(-2); // don't want to move last ADJUST_SCORE(-2); // don't want to move last
break; break;
default: default:
@ -1775,7 +1779,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
&& (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
&& !DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) && !DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
{ {
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker should go first if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) // Attacker should go first
{ {
if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF) if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF)
ADJUST_SCORE(-10); // no anticipated move to disable ADJUST_SCORE(-10); // no anticipated move to disable
@ -1797,7 +1801,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
&& (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
&& !DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) && !DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
{ {
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker should go first if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) // Attacker should go first
{ {
if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF) if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF)
ADJUST_SCORE(-10); // no anticipated move to encore ADJUST_SCORE(-10); // no anticipated move to encore
@ -2184,7 +2188,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break; break;
case EFFECT_SPITE: case EFFECT_SPITE:
case EFFECT_MIMIC: case EFFECT_MIMIC:
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker should go first if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) // Attacker should go first
{ {
if (gLastMoves[battlerDef] == MOVE_NONE if (gLastMoves[battlerDef] == MOVE_NONE
|| gLastMoves[battlerDef] == 0xFFFF) || gLastMoves[battlerDef] == 0xFFFF)
@ -2360,7 +2364,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (hasPartner) if (hasPartner)
{ {
if (IsHazardMove(aiData->partnerMove) // partner is going to set up hazards if (IsHazardMove(aiData->partnerMove) // partner is going to set up hazards
&& AI_IsFaster(BATTLE_PARTNER(battlerAtk), battlerAtk, aiData->partnerMove, predictedMove, CONSIDER_PRIORITY)) // partner is going to set up before the potential Defog && AI_IsFaster(BATTLE_PARTNER(battlerAtk), battlerAtk, aiData->partnerMove, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) // partner is going to set up before the potential Defog
{ {
ADJUST_SCORE(-10); ADJUST_SCORE(-10);
break; // Don't use Defog if partner is going to set up hazards break; // Don't use Defog if partner is going to set up hazards
@ -2388,7 +2392,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break; break;
case EFFECT_SEMI_INVULNERABLE: case EFFECT_SEMI_INVULNERABLE:
if (predictedMove != MOVE_NONE if (predictedMove != MOVE_NONE
&& AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)
&& GetMoveEffect(predictedMove) == EFFECT_SEMI_INVULNERABLE) && GetMoveEffect(predictedMove) == EFFECT_SEMI_INVULNERABLE)
ADJUST_SCORE(-10); // Don't Fly/dig/etc if opponent is going to fly/dig/etc after you ADJUST_SCORE(-10); // Don't Fly/dig/etc if opponent is going to fly/dig/etc after you
@ -2536,7 +2540,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_ME_FIRST: case EFFECT_ME_FIRST:
if (predictedMove != MOVE_NONE) if (predictedMove != MOVE_NONE)
{ {
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
ADJUST_SCORE(-10); // Target is predicted to go first, Me First will fail ADJUST_SCORE(-10); // Target is predicted to go first, Me First will fail
else if (GetMoveEffect(predictedMove) != GetMoveEffect(move)) else if (GetMoveEffect(predictedMove) != GetMoveEffect(move))
return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score); return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score);
@ -2712,7 +2716,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
} }
break; break;
case EFFECT_ELECTRIFY: case EFFECT_ELECTRIFY:
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)
//|| GetMoveTypeSpecial(battlerDef, predictedMove) == TYPE_ELECTRIC // Move will already be electric type //|| GetMoveTypeSpecial(battlerDef, predictedMove) == TYPE_ELECTRIC // Move will already be electric type
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
ADJUST_SCORE(-10); ADJUST_SCORE(-10);
@ -2744,7 +2748,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_INSTRUCT: case EFFECT_INSTRUCT:
{ {
u32 instructedMove; u32 instructedMove;
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
instructedMove = predictedMove; instructedMove = predictedMove;
else else
instructedMove = gLastMoves[battlerDef]; instructedMove = gLastMoves[battlerDef];
@ -2781,21 +2785,21 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break; break;
case EFFECT_QUASH: case EFFECT_QUASH:
if (!hasPartner if (!hasPartner
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) || AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
ADJUST_SCORE(-10); ADJUST_SCORE(-10);
break; break;
case EFFECT_AFTER_YOU: case EFFECT_AFTER_YOU:
if (!IsTargetingPartner(battlerAtk, battlerDef) if (!IsTargetingPartner(battlerAtk, battlerDef)
|| !hasPartner || !hasPartner
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) || AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
ADJUST_SCORE(-10); ADJUST_SCORE(-10);
break; break;
case EFFECT_SUCKER_PUNCH: case EFFECT_SUCKER_PUNCH:
if ((HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_STATUS) && RandomPercentage(RNG_AI_SUCKER_PUNCH, SUCKER_PUNCH_CHANCE)) // Player has a status move if ((HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_STATUS) && RandomPercentage(RNG_AI_SUCKER_PUNCH, SUCKER_PUNCH_CHANCE)) // Player has a status move
|| (IsBattleMoveStatus(predictedMove) && RandomPercentage(RNG_AI_SUCKER_PUNCH, SUCKER_PUNCH_PREDICTION_CHANCE) && (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_PREDICT_MOVE)) // AI actively predicting incoming status move || (IsBattleMoveStatus(predictedMove) && RandomPercentage(RNG_AI_SUCKER_PUNCH, SUCKER_PUNCH_PREDICTION_CHANCE) && (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_PREDICT_MOVE)) // AI actively predicting incoming status move
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Opponent going first || AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) // Opponent going first
ADJUST_SCORE(-10); ADJUST_SCORE(-10);
break; break;
case EFFECT_TAILWIND: case EFFECT_TAILWIND:
@ -2832,7 +2836,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10); ADJUST_SCORE(-10);
break; break;
case EFFECT_FLAIL: case EFFECT_FLAIL:
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) // Opponent should go first if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) // Opponent should go first
|| aiData->hpPercents[battlerAtk] > 50) || aiData->hpPercents[battlerAtk] > 50)
ADJUST_SCORE(-4); ADJUST_SCORE(-4);
break; break;
@ -2867,7 +2871,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
else if (CanAIFaintTarget(battlerAtk, battlerDef, 0)) else if (CanAIFaintTarget(battlerAtk, battlerDef, 0))
ADJUST_SCORE(-10); ADJUST_SCORE(-10);
else if (CanTargetFaintAi(battlerDef, battlerAtk) else if (CanTargetFaintAi(battlerDef, battlerAtk)
&& AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) && AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
ADJUST_SCORE(-10); ADJUST_SCORE(-10);
break; break;
case EFFECT_JUNGLE_HEALING: case EFFECT_JUNGLE_HEALING:
@ -2898,7 +2902,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
u32 defPrio = GetBattleMovePriority(battlerDef, aiData->abilities[battlerDef], predictedMove); u32 defPrio = GetBattleMovePriority(battlerDef, aiData->abilities[battlerDef], predictedMove);
if (predictedMove == MOVE_NONE if (predictedMove == MOVE_NONE
|| IsBattleMoveStatus(predictedMove) || IsBattleMoveStatus(predictedMove)
|| AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) || AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)
|| defPrio < 1 || defPrio < 1
|| defPrio > 3) // Opponent going first or not using priority move || defPrio > 3) // Opponent going first or not using priority move
ADJUST_SCORE(-10); ADJUST_SCORE(-10);
@ -2967,6 +2971,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{ {
u32 movesetIndex = gAiThinkingStruct->movesetIndex; u32 movesetIndex = gAiThinkingStruct->movesetIndex;
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
if (IsTargetingPartner(battlerAtk, battlerDef)) if (IsTargetingPartner(battlerAtk, battlerDef))
return score; return score;
@ -2978,7 +2983,7 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, AI_ATTACKING) if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, movesetIndex, AI_ATTACKING)
&& effect != EFFECT_EXPLOSION && effect != EFFECT_MISTY_EXPLOSION) && effect != EFFECT_EXPLOSION && effect != EFFECT_MISTY_EXPLOSION)
{ {
if (AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)) if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
ADJUST_SCORE(FAST_KILL); ADJUST_SCORE(FAST_KILL);
else else
ADJUST_SCORE(SLOW_KILL); ADJUST_SCORE(SLOW_KILL);
@ -3012,6 +3017,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
bool32 partnerProtecting = IsAllyProtectingFromMove(battlerAtk, move, aiData->partnerMove) && !MoveIgnoresProtect(move); bool32 partnerProtecting = IsAllyProtectingFromMove(battlerAtk, move, aiData->partnerMove) && !MoveIgnoresProtect(move);
bool32 partnerHasBadAbility = (gAbilitiesInfo[atkPartnerAbility].aiRating < 0); bool32 partnerHasBadAbility = (gAbilitiesInfo[atkPartnerAbility].aiRating < 0);
u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData); u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData);
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
SetTypeBeforeUsingMove(move, battlerAtk); SetTypeBeforeUsingMove(move, battlerAtk);
moveType = GetBattleMoveType(move); moveType = GetBattleMoveType(move);
@ -3059,7 +3065,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
// Adjust for always crit moves // Adjust for always crit moves
if (MoveAlwaysCrits(aiData->partnerMove) && aiData->abilities[battlerAtk] == ABILITY_ANGER_POINT) if (MoveAlwaysCrits(aiData->partnerMove) && aiData->abilities[battlerAtk] == ABILITY_ANGER_POINT)
{ {
if (AI_IsSlower(battlerAtk, battlerAtkPartner, move, predictedMove, CONSIDER_PRIORITY)) // Partner moving first if (AI_IsSlower(battlerAtk, battlerAtkPartner, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) // Partner moving first
{ {
// discourage raising our attack since it's about to be maxed out // discourage raising our attack since it's about to be maxed out
if (IsAttackBoostMoveEffect(effect)) if (IsAttackBoostMoveEffect(effect))
@ -3232,7 +3238,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case ABILITY_ANGER_POINT: case ABILITY_ANGER_POINT:
if (MoveAlwaysCrits(move) if (MoveAlwaysCrits(move)
&& BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK)
&& AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMove, CONSIDER_PRIORITY) && AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)
&& isFriendlyFireOK) && isFriendlyFireOK)
{ {
if (MoveAlwaysCrits(move)) if (MoveAlwaysCrits(move))
@ -3551,7 +3557,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_INSTRUCT: case EFFECT_INSTRUCT:
{ {
u16 instructedMove; u16 instructedMove;
if (AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMove, CONSIDER_PRIORITY)) if (AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
instructedMove = aiData->partnerMove; instructedMove = aiData->partnerMove;
else else
instructedMove = gLastMoves[battlerAtkPartner]; instructedMove = gLastMoves[battlerAtkPartner];
@ -3568,8 +3574,8 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (!(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && HasMoveWithEffect(battlerAtkPartner, EFFECT_TRICK_ROOM)) if (!(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && HasMoveWithEffect(battlerAtkPartner, EFFECT_TRICK_ROOM))
ADJUST_SCORE(DECENT_EFFECT); ADJUST_SCORE(DECENT_EFFECT);
if (AI_IsSlower(battlerAtkPartner, FOE(battlerAtkPartner), aiData->partnerMove, predictedMove, CONSIDER_PRIORITY) // Opponent mon 1 goes before partner if (AI_IsSlower(battlerAtkPartner, FOE(battlerAtkPartner), aiData->partnerMove, predictedMoveSpeedCheck, CONSIDER_PRIORITY) // Opponent mon 1 goes before partner
&& AI_IsSlower(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)), aiData->partnerMove, predictedMove, CONSIDER_PRIORITY)) // Opponent mon 2 goes before partner && AI_IsSlower(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)), aiData->partnerMove, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) // Opponent mon 2 goes before partner
{ {
if (partnerEffect == EFFECT_COUNTER || partnerEffect == EFFECT_MIRROR_COAT) if (partnerEffect == EFFECT_COUNTER || partnerEffect == EFFECT_MIRROR_COAT)
break; // These moves need to go last break; // These moves need to go last
@ -3578,8 +3584,8 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break; break;
case EFFECT_HEAL_PULSE: case EFFECT_HEAL_PULSE:
case EFFECT_HIT_ENEMY_HEAL_ALLY: case EFFECT_HIT_ENEMY_HEAL_ALLY:
if (AI_IsFaster(battlerAtk, FOE(battlerAtk), move, predictedMove, CONSIDER_PRIORITY) if (AI_IsFaster(battlerAtk, FOE(battlerAtk), move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)
&& AI_IsFaster(battlerAtk, BATTLE_PARTNER(FOE(battlerAtk)), move, predictedMove, CONSIDER_PRIORITY) && AI_IsFaster(battlerAtk, BATTLE_PARTNER(FOE(battlerAtk)), move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)
&& gBattleMons[battlerAtkPartner].hp < gBattleMons[battlerAtkPartner].maxHP / 2) && gBattleMons[battlerAtkPartner].hp < gBattleMons[battlerAtkPartner].maxHP / 2)
RETURN_SCORE_PLUS(WEAK_EFFECT); RETURN_SCORE_PLUS(WEAK_EFFECT);
break; break;
@ -3699,9 +3705,9 @@ static enum MoveComparisonResult CompareMoveAccuracies(u32 battlerAtk, u32 battl
static enum MoveComparisonResult CompareMoveSpeeds(u32 battlerAtk, u32 battlerDef, u16 move1, u16 move2) static enum MoveComparisonResult CompareMoveSpeeds(u32 battlerAtk, u32 battlerDef, u16 move1, u16 move2)
{ {
u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData); u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
u32 speed1 = AI_WhoStrikesFirst(battlerAtk, battlerDef, move1, predictedMove, CONSIDER_PRIORITY); u32 speed1 = AI_WhoStrikesFirst(battlerAtk, battlerDef, move1, predictedMoveSpeedCheck, CONSIDER_PRIORITY);
u32 speed2 = AI_WhoStrikesFirst(battlerAtk, battlerDef, move2, predictedMove, CONSIDER_PRIORITY); u32 speed2 = AI_WhoStrikesFirst(battlerAtk, battlerDef, move2, predictedMoveSpeedCheck, CONSIDER_PRIORITY);
if (speed1 == AI_IS_FASTER && speed2 == AI_IS_SLOWER) if (speed1 == AI_IS_FASTER && speed2 == AI_IS_SLOWER)
return MOVE_WON_COMPARISON; return MOVE_WON_COMPARISON;
@ -3937,6 +3943,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
s32 score = 0; s32 score = 0;
u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData); u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData);
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
u32 predictedType = GetMoveType(predictedMove); u32 predictedType = GetMoveType(predictedMove);
u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove); u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove);
bool32 isBattle1v1 = IsBattle1v1(); bool32 isBattle1v1 = IsBattle1v1();
@ -4235,7 +4242,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
ADJUST_SCORE(IncreaseSubstituteMoveScore(battlerAtk, battlerDef, move)); ADJUST_SCORE(IncreaseSubstituteMoveScore(battlerAtk, battlerDef, move));
break; break;
case EFFECT_MIMIC: case EFFECT_MIMIC:
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
{ {
if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF
&& (GetMoveEffect(gLastMoves[battlerDef]) != GetMoveEffect(move))) && (GetMoveEffect(gLastMoves[battlerDef]) != GetMoveEffect(move)))
@ -4307,7 +4314,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
&& (gLastMoves[battlerDef] != MOVE_NONE) && (gLastMoves[battlerDef] != MOVE_NONE)
&& (gLastMoves[battlerDef] != 0xFFFF) && (gLastMoves[battlerDef] != 0xFFFF)
&& (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB)
&& (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY))) && (AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)))
{ {
if (CanTargetMoveFaintAi(gLastMoves[battlerDef], battlerDef, battlerAtk, 1)) if (CanTargetMoveFaintAi(gLastMoves[battlerDef], battlerDef, battlerAtk, 1))
ADJUST_SCORE(GOOD_EFFECT); // Disable move that can kill attacker ADJUST_SCORE(GOOD_EFFECT); // Disable move that can kill attacker
@ -4347,7 +4354,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
case EFFECT_DESTINY_BOND: case EFFECT_DESTINY_BOND:
if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
break; break;
else if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && CanTargetFaintAi(battlerDef, battlerAtk)) else if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) && CanTargetFaintAi(battlerDef, battlerAtk))
ADJUST_SCORE(GOOD_EFFECT); ADJUST_SCORE(GOOD_EFFECT);
break; break;
case EFFECT_SPITE: case EFFECT_SPITE:
@ -4564,7 +4571,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
if (predictedMove != MOVE_NONE && isBattle1v1) if (predictedMove != MOVE_NONE && isBattle1v1)
{ {
enum BattleMoveEffects predictedEffect = GetMoveEffect(predictedMove); enum BattleMoveEffects predictedEffect = GetMoveEffect(predictedMove);
if ((AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) if ((AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
&& (predictedEffect == EFFECT_EXPLOSION && (predictedEffect == EFFECT_EXPLOSION
|| predictedEffect == EFFECT_MISTY_EXPLOSION || predictedEffect == EFFECT_MISTY_EXPLOSION
|| predictedEffect == EFFECT_PROTECT)) || predictedEffect == EFFECT_PROTECT))
@ -4613,7 +4620,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
break; break;
case EFFECT_ATTRACT: case EFFECT_ATTRACT:
if (isBattle1v1 if (isBattle1v1
&& (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) && (AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
&& BattlerWillFaintFromSecondaryDamage(battlerDef, aiData->abilities[battlerDef])) && BattlerWillFaintFromSecondaryDamage(battlerDef, aiData->abilities[battlerDef]))
break; // Don't use if the attract won't have a change to activate break; // Don't use if the attract won't have a change to activate
if (gBattleMons[battlerDef].status1 & STATUS1_ANY if (gBattleMons[battlerDef].status1 & STATUS1_ANY
@ -4661,7 +4668,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
if (hasPartner) if (hasPartner)
{ {
if (IsHazardMove(aiData->partnerMove) // Partner is going to set up hazards if (IsHazardMove(aiData->partnerMove) // Partner is going to set up hazards
&& AI_IsSlower(battlerAtk, BATTLE_PARTNER(battlerAtk), move, predictedMove, CONSIDER_PRIORITY)) // Partner going first && AI_IsSlower(battlerAtk, BATTLE_PARTNER(battlerAtk), move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) // Partner going first
break; // Don't use Defog if partner is going to set up hazards break; // Don't use Defog if partner is going to set up hazards
} }
ADJUST_SCORE(IncreaseStatDownScore(battlerAtk, battlerDef, STAT_EVASION)); ADJUST_SCORE(IncreaseStatDownScore(battlerAtk, battlerDef, STAT_EVASION));
@ -5155,7 +5162,7 @@ case EFFECT_GUARD_SPLIT:
ADJUST_SCORE(DECENT_EFFECT); ADJUST_SCORE(DECENT_EFFECT);
break; break;
case EFFECT_HEAL_BLOCK: case EFFECT_HEAL_BLOCK:
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && predictedMove != MOVE_NONE && IsHealingMove(predictedMove)) if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) && predictedMove != MOVE_NONE && IsHealingMove(predictedMove))
ADJUST_SCORE(DECENT_EFFECT); // Try to cancel healing move ADJUST_SCORE(DECENT_EFFECT); // Try to cancel healing move
else if (HasHealingEffect(battlerDef) || aiData->holdEffects[battlerDef] == HOLD_EFFECT_LEFTOVERS else if (HasHealingEffect(battlerDef) || aiData->holdEffects[battlerDef] == HOLD_EFFECT_LEFTOVERS
|| (aiData->holdEffects[battlerDef] == HOLD_EFFECT_BLACK_SLUDGE && IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON))) || (aiData->holdEffects[battlerDef] == HOLD_EFFECT_BLACK_SLUDGE && IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON)))
@ -5189,7 +5196,7 @@ case EFFECT_GUARD_SPLIT:
ADJUST_SCORE(BEST_EFFECT); ADJUST_SCORE(BEST_EFFECT);
break; break;
case EFFECT_QUASH: case EFFECT_QUASH:
if (hasPartner && AI_IsSlower(BATTLE_PARTNER(battlerAtk), battlerDef, aiData->partnerMove, predictedMove, CONSIDER_PRIORITY)) if (hasPartner && AI_IsSlower(BATTLE_PARTNER(battlerAtk), battlerDef, aiData->partnerMove, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
ADJUST_SCORE(DECENT_EFFECT); // Attacker partner wouldn't go before target ADJUST_SCORE(DECENT_EFFECT); // Attacker partner wouldn't go before target
break; break;
case EFFECT_TAILWIND: case EFFECT_TAILWIND:
@ -5204,7 +5211,7 @@ case EFFECT_GUARD_SPLIT:
if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC) if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC)
&& !(effectiveness == UQ_4_12(0.0))) // Doesn't resist ground move && !(effectiveness == UQ_4_12(0.0))) // Doesn't resist ground move
{ {
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker goes first if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) // Attacker goes first
{ {
if (predictedType == TYPE_GROUND) if (predictedType == TYPE_GROUND)
ADJUST_SCORE(GOOD_EFFECT); // Cause the enemy's move to fail ADJUST_SCORE(GOOD_EFFECT); // Cause the enemy's move to fail
@ -5219,7 +5226,7 @@ case EFFECT_GUARD_SPLIT:
} }
break; break;
case EFFECT_CAMOUFLAGE: case EFFECT_CAMOUFLAGE:
if (predictedMove != MOVE_NONE && AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) // Attacker goes first if (predictedMove != MOVE_NONE && AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) // Attacker goes first
&& !IsBattleMoveStatus(move) && effectiveness != UQ_4_12(0.0)) && !IsBattleMoveStatus(move) && effectiveness != UQ_4_12(0.0))
ADJUST_SCORE(DECENT_EFFECT); ADJUST_SCORE(DECENT_EFFECT);
break; break;
@ -5246,7 +5253,7 @@ case EFFECT_GUARD_SPLIT:
ADJUST_SCORE(DECENT_EFFECT); ADJUST_SCORE(DECENT_EFFECT);
break; break;
case EFFECT_ENDEAVOR: case EFFECT_ENDEAVOR:
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) && !CanTargetFaintAi(battlerDef, battlerAtk)) if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) && !CanTargetFaintAi(battlerDef, battlerAtk))
ADJUST_SCORE(DECENT_EFFECT); ADJUST_SCORE(DECENT_EFFECT);
break; break;
case EFFECT_REVIVAL_BLESSING: case EFFECT_REVIVAL_BLESSING:
@ -5516,7 +5523,7 @@ case EFFECT_GUARD_SPLIT:
case MOVE_EFFECT_THROAT_CHOP: case MOVE_EFFECT_THROAT_CHOP:
if (IsSoundMove(GetBestDmgMoveFromBattler(battlerDef, battlerAtk, AI_DEFENDING))) if (IsSoundMove(GetBestDmgMoveFromBattler(battlerDef, battlerAtk, AI_DEFENDING)))
{ {
if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
ADJUST_SCORE(GOOD_EFFECT); ADJUST_SCORE(GOOD_EFFECT);
else else
ADJUST_SCORE(DECENT_EFFECT); ADJUST_SCORE(DECENT_EFFECT);
@ -5571,8 +5578,10 @@ static s32 AI_ForceSetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32
|| gBattleResults.battleTurnCounter != 0) || gBattleResults.battleTurnCounter != 0)
return score; return score;
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_SMART_SWITCHING if (gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_SMART_SWITCHING
&& AI_IsSlower(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY) && AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)
&& CanTargetFaintAi(battlerDef, battlerAtk) && CanTargetFaintAi(battlerDef, battlerAtk)
&& GetBattleMovePriority(battlerAtk, gAiLogicData->abilities[battlerAtk], move) == 0) && GetBattleMovePriority(battlerAtk, gAiLogicData->abilities[battlerAtk], move) == 0)
{ {
@ -6162,6 +6171,7 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
struct AiLogicData *aiData = gAiLogicData; struct AiLogicData *aiData = gAiLogicData;
uq4_12_t effectiveness = aiData->effectiveness[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex]; uq4_12_t effectiveness = aiData->effectiveness[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex];
u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData); u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData);
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
// Switch benefit // Switch benefit
switch (moveEffect) switch (moveEffect)
@ -6173,7 +6183,7 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(GOOD_EFFECT); ADJUST_SCORE(GOOD_EFFECT);
else if (hitsToKO == 1) else if (hitsToKO == 1)
ADJUST_SCORE(BEST_EFFECT); ADJUST_SCORE(BEST_EFFECT);
else if (IsSwitchOutEffect(GetMoveEffect(predictedMove)) && AI_WhoStrikesFirst(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) == AI_IS_SLOWER) // Pursuit against fast U-Turn else if (IsSwitchOutEffect(GetMoveEffect(predictedMove)) && AI_WhoStrikesFirst(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) == AI_IS_SLOWER) // Pursuit against fast U-Turn
ADJUST_SCORE(DECENT_EFFECT); ADJUST_SCORE(DECENT_EFFECT);
break; break;
} }

View File

@ -199,6 +199,14 @@ u32 GetIncomingMove(u32 battler, u32 opposingBattler, struct AiLogicData *aiData
return aiData->lastUsedMove[opposingBattler]; return aiData->lastUsedMove[opposingBattler];
} }
// When not predicting, don't want to reference player's previous move; leads to weird behaviour for cases like Fake Out or Protect, especially in doubles
u32 GetIncomingMoveSpeedCheck(u32 battler, u32 opposingBattler, struct AiLogicData *aiData)
{
if (aiData->predictingMove && CanAiPredictMove())
return aiData->predictedMove[opposingBattler];
return MOVE_NONE;
}
void ClearBattlerMoveHistory(u32 battlerId) void ClearBattlerMoveHistory(u32 battlerId)
{ {
memset(gBattleHistory->usedMoves[battlerId], 0, sizeof(gBattleHistory->usedMoves[battlerId])); memset(gBattleHistory->usedMoves[battlerId], 0, sizeof(gBattleHistory->usedMoves[battlerId]));
@ -497,6 +505,7 @@ u32 GetTotalBaseStat(u32 species)
bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler) bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler)
{ {
int i; int i;
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAI, opposingBattler, gAiLogicData);
for (i = 0; i < MAX_MON_MOVES; i++) for (i = 0; i < MAX_MON_MOVES; i++)
{ {
@ -504,7 +513,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler)
enum BattleMoveEffects effect = GetMoveEffect(move); enum BattleMoveEffects effect = GetMoveEffect(move);
if (effect == EFFECT_PROTECT && move != MOVE_ENDURE) if (effect == EFFECT_PROTECT && move != MOVE_ENDURE)
return TRUE; return TRUE;
if (effect == EFFECT_SEMI_INVULNERABLE && AI_IsSlower(battlerAI, opposingBattler, GetAIChosenMove(battlerAI), GetIncomingMove(battlerAI, opposingBattler, gAiLogicData), CONSIDER_PRIORITY)) if (effect == EFFECT_SEMI_INVULNERABLE && AI_IsSlower(battlerAI, opposingBattler, GetAIChosenMove(battlerAI), predictedMoveSpeedCheck, CONSIDER_PRIORITY))
return TRUE; return TRUE;
} }
return FALSE; return FALSE;
@ -2031,8 +2040,9 @@ bool32 CanLowerStat(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData,
if (stat == STAT_SPEED) if (stat == STAT_SPEED)
{ {
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
// If AI is faster and doesn't have any mons left, lowering speed doesn't give any // If AI is faster and doesn't have any mons left, lowering speed doesn't give any
return !(AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY) return !(AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, DONT_CONSIDER_PRIORITY)
&& CountUsablePartyMons(battlerAtk) == 0 && CountUsablePartyMons(battlerAtk) == 0
&& !HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_ELECTRO_BALL)); && !HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_ELECTRO_BALL));
} }
@ -2074,7 +2084,8 @@ u32 IncreaseStatDownScore(u32 battlerAtk, u32 battlerDef, u32 stat)
tempScore += DECENT_EFFECT; tempScore += DECENT_EFFECT;
break; break;
case STAT_SPEED: case STAT_SPEED:
if (AI_IsSlower(battlerAtk, battlerDef, gAiThinkingStruct->moveConsidered, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY)) u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
if (AI_IsSlower(battlerAtk, battlerDef, gAiThinkingStruct->moveConsidered, predictedMoveSpeedCheck, DONT_CONSIDER_PRIORITY))
tempScore += DECENT_EFFECT; tempScore += DECENT_EFFECT;
break; break;
case STAT_SPATK: case STAT_SPATK:
@ -3112,6 +3123,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 mov
{ {
bool32 hasStatBoost = AnyUsefulStatIsRaised(battlerAtk) || gBattleMons[battlerDef].statStages[STAT_EVASION] >= 9; //Significant boost in evasion for any class bool32 hasStatBoost = AnyUsefulStatIsRaised(battlerAtk) || gBattleMons[battlerDef].statStages[STAT_EVASION] >= 9; //Significant boost in evasion for any class
u32 battlerToSwitch; u32 battlerToSwitch;
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
battlerToSwitch = gBattleStruct->AI_monToSwitchIntoId[battlerAtk]; battlerToSwitch = gBattleStruct->AI_monToSwitchIntoId[battlerAtk];
@ -3132,7 +3144,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 mov
if (IsBattlerPredictedToSwitch(battlerDef)) if (IsBattlerPredictedToSwitch(battlerDef))
return SHOULD_PIVOT; // Try pivoting so you can switch to a better matchup to counter your new opponent return SHOULD_PIVOT; // Try pivoting so you can switch to a better matchup to counter your new opponent
if (AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)) // Attacker goes first if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) // Attacker goes first
{ {
if (!CanAIFaintTarget(battlerAtk, battlerDef, 0)) // Can't KO foe otherwise if (!CanAIFaintTarget(battlerAtk, battlerDef, 0)) // Can't KO foe otherwise
{ {
@ -3514,10 +3526,11 @@ bool32 AI_CanBeInfatuated(u32 battlerAtk, u32 battlerDef, u32 defAbility)
u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbility, u32 move) u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbility, u32 move)
{ {
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
if (((!IsMoldBreakerTypeAbility(battlerAtk, gAiLogicData->abilities[battlerAtk]) && (defAbility == ABILITY_SHIELD_DUST || defAbility == ABILITY_INNER_FOCUS)) if (((!IsMoldBreakerTypeAbility(battlerAtk, gAiLogicData->abilities[battlerAtk]) && (defAbility == ABILITY_SHIELD_DUST || defAbility == ABILITY_INNER_FOCUS))
|| gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK
|| DoesSubstituteBlockMove(battlerAtk, battlerDef, move) || DoesSubstituteBlockMove(battlerAtk, battlerDef, move)
|| AI_IsSlower(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY))) // Opponent goes first || AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))) // Opponent goes first
{ {
return 0; return 0;
} }
@ -3525,7 +3538,7 @@ u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbi
|| gBattleMons[battlerDef].status1 & STATUS1_PARALYSIS || gBattleMons[battlerDef].status1 & STATUS1_PARALYSIS
|| gBattleMons[battlerDef].volatiles.infatuation || gBattleMons[battlerDef].volatiles.infatuation
|| gBattleMons[battlerDef].volatiles.confusionTurns > 0) || gBattleMons[battlerDef].volatiles.confusionTurns > 0)
|| ((AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)) && CanTargetFaintAi(battlerDef, battlerAtk))) || ((AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) && CanTargetFaintAi(battlerDef, battlerAtk)))
{ {
return 2; // good idea to flinch return 2; // good idea to flinch
} }
@ -3558,7 +3571,8 @@ bool32 IsFlinchGuaranteed(u32 battlerAtk, u32 battlerDef, u32 move)
if (!MoveHasAdditionalEffect(move, MOVE_EFFECT_FLINCH)) if (!MoveHasAdditionalEffect(move, MOVE_EFFECT_FLINCH))
return FALSE; return FALSE;
if (AI_IsSlower(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)) u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
return FALSE; return FALSE;
u32 i; u32 i;
@ -3710,7 +3724,8 @@ bool32 ShouldUseRecoilMove(u32 battlerAtk, u32 battlerDef, u32 recoilDmg, u32 mo
bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage) bool32 ShouldAbsorb(u32 battlerAtk, u32 battlerDef, u32 move, s32 damage)
{ {
if (move == 0xFFFF || AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)) u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
if (move == 0xFFFF || AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
{ {
// using item or user goes first // using item or user goes first
s32 healDmg = (GetMoveAbsorbPercentage(move) * damage) / 100; s32 healDmg = (GetMoveAbsorbPercentage(move) * damage) / 100;
@ -3738,11 +3753,12 @@ bool32 ShouldRecover(u32 battlerAtk, u32 battlerDef, u32 move, u32 healPercent)
{ {
u32 maxHP = gBattleMons[battlerAtk].maxHP; u32 maxHP = gBattleMons[battlerAtk].maxHP;
u32 healAmount = (healPercent * maxHP) / 100; u32 healAmount = (healPercent * maxHP) / 100;
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
if (healAmount > maxHP) if (healAmount > maxHP)
healAmount = maxHP; healAmount = maxHP;
if (gBattleMons[battlerAtk].volatiles.healBlock) if (gBattleMons[battlerAtk].volatiles.healBlock)
healAmount = 0; healAmount = 0;
if (AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)) if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY))
{ {
if (CanTargetFaintAi(battlerDef, battlerAtk) if (CanTargetFaintAi(battlerDef, battlerAtk)
&& !CanTargetFaintAiWithMod(battlerDef, battlerAtk, healAmount, 0)) && !CanTargetFaintAiWithMod(battlerDef, battlerAtk, healAmount, 0))
@ -4479,7 +4495,8 @@ static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef,
{ {
enum AIScore tempScore = NO_INCREASE; enum AIScore tempScore = NO_INCREASE;
u32 noOfHitsToFaint = NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk); u32 noOfHitsToFaint = NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk);
u32 aiIsFaster = AI_IsFaster(battlerAtk, battlerDef, MOVE_NONE, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY); // Don't care about the priority of our setup move, care about outspeeding otherwise u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
u32 aiIsFaster = AI_IsFaster(battlerAtk, battlerDef, MOVE_NONE, predictedMoveSpeedCheck, DONT_CONSIDER_PRIORITY); // Don't care about the priority of our setup move, care about outspeeding otherwise
u32 shouldSetUp = ((noOfHitsToFaint >= 2 && aiIsFaster) || (noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS); u32 shouldSetUp = ((noOfHitsToFaint >= 2 && aiIsFaster) || (noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS);
u32 i; u32 i;
u32 statId = GetStatBeingChanged(statChange); u32 statId = GetStatBeingChanged(statChange);
@ -5025,8 +5042,9 @@ enum AIConsiderGimmick ShouldTeraFromCalcs(u32 battler, u32 opposingBattler, str
} }
else else
{ {
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battler, opposingBattler, gAiLogicData);
// will we go first? // will we go first?
if (AI_WhoStrikesFirst(battler, opposingBattler, killingMove, GetIncomingMove(battler, opposingBattler, gAiLogicData), CONSIDER_PRIORITY) == AI_IS_FASTER && GetBattleMovePriority(battler, gAiLogicData->abilities[battler], killingMove) >= GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], hardPunishingMove)) if (AI_WhoStrikesFirst(battler, opposingBattler, killingMove, predictedMoveSpeedCheck, CONSIDER_PRIORITY) == AI_IS_FASTER && GetBattleMovePriority(battler, gAiLogicData->abilities[battler], killingMove) >= GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], hardPunishingMove))
return USE_GIMMICK; return USE_GIMMICK;
} }
} }
@ -5149,8 +5167,8 @@ void IncreaseTidyUpScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score)
ADJUST_SCORE_PTR(GOOD_EFFECT); ADJUST_SCORE_PTR(GOOD_EFFECT);
if (AreAnyHazardsOnSide(GetBattlerSide(battlerDef)) && CountUsablePartyMons(battlerDef) != 0) if (AreAnyHazardsOnSide(GetBattlerSide(battlerDef)) && CountUsablePartyMons(battlerDef) != 0)
ADJUST_SCORE_PTR(-2); ADJUST_SCORE_PTR(-2);
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
if (gBattleMons[battlerAtk].volatiles.substitute && AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY)) if (gBattleMons[battlerAtk].volatiles.substitute && AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, DONT_CONSIDER_PRIORITY))
ADJUST_SCORE_PTR(-10); ADJUST_SCORE_PTR(-10);
if (gBattleMons[battlerDef].volatiles.substitute) if (gBattleMons[battlerDef].volatiles.substitute)
ADJUST_SCORE_PTR(GOOD_EFFECT); ADJUST_SCORE_PTR(GOOD_EFFECT);
@ -5188,9 +5206,9 @@ bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, st
default: default:
break; break;
} }
u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, opposingBattler, gAiLogicData);
return (preventsStatLoss return (preventsStatLoss
&& AI_IsFaster(battlerAtk, battlerAtkPartner, MOVE_NONE, GetIncomingMove(battlerAtk, opposingBattler, gAiLogicData), CONSIDER_PRIORITY) && AI_IsFaster(battlerAtk, battlerAtkPartner, MOVE_NONE, predictedMoveSpeedCheck, CONSIDER_PRIORITY)
&& HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL)); && HasMoveWithCategory(battlerAtkPartner, DAMAGE_CATEGORY_PHYSICAL));
} }

View File

@ -661,20 +661,6 @@ AI_SINGLE_BATTLE_TEST("AI stays choice locked into moves in spite of the player'
} }
} }
AI_SINGLE_BATTLE_TEST("AI won't use Sucker Punch if it expects a move of the same priority bracket and the opponent is faster")
{
GIVEN {
ASSUME(GetMovePriority(MOVE_QUICK_ATTACK) == 1);
ASSUME(GetMovePriority(MOVE_SUCKER_PUNCH) == 1);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { Speed(300); Moves(MOVE_QUICK_ATTACK); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(100); Moves(MOVE_SUCKER_PUNCH, MOVE_SCRATCH); }
} WHEN {
TURN { MOVE(player, MOVE_QUICK_ATTACK); EXPECT_MOVE(opponent, MOVE_SUCKER_PUNCH); }
TURN { MOVE(player, MOVE_QUICK_ATTACK); EXPECT_MOVE(opponent, MOVE_SCRATCH); }
}
}
AI_SINGLE_BATTLE_TEST("AI won't use Sucker Punch if it expects a status move a percentage of the time") AI_SINGLE_BATTLE_TEST("AI won't use Sucker Punch if it expects a status move a percentage of the time")
{ {
PASSES_RANDOMLY(SUCKER_PUNCH_CHANCE, 100, RNG_AI_SUCKER_PUNCH); PASSES_RANDOMLY(SUCKER_PUNCH_CHANCE, 100, RNG_AI_SUCKER_PUNCH);
@ -1022,3 +1008,18 @@ AI_SINGLE_BATTLE_TEST("AI has a chance to prioritize last chance priority damage
TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_AQUA_JET); } TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_AQUA_JET); }
} }
} }
AI_DOUBLE_BATTLE_TEST("AI won't be confused by player's previous priority moves when evaluating KOs")
{
PASSES_RANDOMLY(100, 100);
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_BEAUTIFLY) { Speed(1); Moves(MOVE_DETECT, MOVE_SCRATCH); }
PLAYER(SPECIES_MASQUERAIN) { Speed(10); Moves(MOVE_DETECT, MOVE_SCRATCH); }
OPPONENT(SPECIES_CRADILY) { Speed(5); Moves(MOVE_POWER_GEM); }
OPPONENT(SPECIES_ZIGZAGOON) { Speed(4); Moves(MOVE_CELEBRATE); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_DETECT); MOVE(playerRight, MOVE_DETECT); EXPECT_MOVE(opponentLeft, MOVE_POWER_GEM, target:playerLeft); EXPECT_MOVE(opponentRight, MOVE_CELEBRATE); }
TURN { MOVE(playerLeft, MOVE_DETECT); MOVE(playerRight, MOVE_DETECT); EXPECT_MOVE(opponentLeft, MOVE_POWER_GEM, target:playerLeft); EXPECT_MOVE(opponentRight, MOVE_CELEBRATE); }
}
}

View File

@ -27,3 +27,18 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_PREDICT_MOVE: AI will still attack you when it sh
TURN { MOVE(player, MOVE_SURF); EXPECT_MOVE(opponent, MOVE_LEAF_BLADE); } TURN { MOVE(player, MOVE_SURF); EXPECT_MOVE(opponent, MOVE_LEAF_BLADE); }
} }
} }
AI_SINGLE_BATTLE_TEST("AI won't use Sucker Punch if it expects a move of the same priority bracket and the opponent is faster")
{
PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_MOVE);
GIVEN {
ASSUME(GetMovePriority(MOVE_QUICK_ATTACK) == 1);
ASSUME(GetMovePriority(MOVE_SUCKER_PUNCH) == 1);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE);
PLAYER(SPECIES_WOBBUFFET) { Speed(300); Moves(MOVE_QUICK_ATTACK); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(100); Moves(MOVE_SUCKER_PUNCH, MOVE_SCRATCH); }
} WHEN {
TURN { MOVE(player, MOVE_QUICK_ATTACK); EXPECT_MOVE(opponent, MOVE_SCRATCH); }
TURN { MOVE(player, MOVE_QUICK_ATTACK); EXPECT_MOVE(opponent, MOVE_SCRATCH); }
}
}

View File

@ -1385,3 +1385,18 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will consider Hidden Power wh
TURN { MOVE(player, MOVE_HIDDEN_POWER); EXPECT_SWITCH(opponent, 1); } TURN { MOVE(player, MOVE_HIDDEN_POWER); EXPECT_SWITCH(opponent, 1); }
} }
} }
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: Fake Out style moves won't confuse choiced AI into thinking it does no damage")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_CHOICE_SCARF].holdEffect == HOLD_EFFECT_CHOICE_SCARF);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES);
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_FAKE_OUT, MOVE_SCRATCH); }
OPPONENT(SPECIES_INFERNAPE) { Item(ITEM_CHOICE_SCARF); Moves(MOVE_CLOSE_COMBAT); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); }
} WHEN {
TURN { MOVE(player, MOVE_FAKE_OUT); EXPECT_MOVE(opponent, MOVE_CLOSE_COMBAT); }
TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_CLOSE_COMBAT); }
}
}