diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 118fbf5984..5759427f4b 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -296,6 +296,7 @@ u32 IncreaseSubstituteMoveScore(u32 battlerAtk, u32 battlerDef, u32 move); bool32 IsBattlerItemEnabled(u32 battler); bool32 IsBattlerPredictedToSwitch(u32 battler); 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 HasBattlerSideAbility(u32 battlerDef, u32 ability, struct AiLogicData *aiData); bool32 IsNaturalEnemy(u32 speciesAttacker, u32 speciesTarget); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 430412966b..78f1b72b28 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -742,7 +742,10 @@ static u32 ChooseMoveOrAction_Singles(u32 battler) u64 flags = gAiThinkingStruct->aiFlags[battler]; u32 opposingBattler = GetOppositeBattler(battler); + gAiThinkingStruct->aiLogicId = 0; + gAiThinkingStruct->movesetIndex = 0; gAiLogicData->partnerMove = 0; // no ally + while (flags != 0) { if (flags & 1) @@ -1078,6 +1081,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) u32 i; u32 weather; u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData); + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); u32 abilityAtk = aiData->abilities[battlerAtk]; u32 abilityDef = aiData->abilities[battlerDef]; 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])) 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); 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) { 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 break; 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) && !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) 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) && !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) 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; case EFFECT_SPITE: 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 || gLastMoves[battlerDef] == 0xFFFF) @@ -2360,7 +2364,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (hasPartner) { 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); 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; case EFFECT_SEMI_INVULNERABLE: 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) 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: 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 else if (GetMoveEffect(predictedMove) != GetMoveEffect(move)) return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score); @@ -2712,7 +2716,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } break; 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 || PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); @@ -2744,7 +2748,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_INSTRUCT: { u32 instructedMove; - if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) + if (AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) instructedMove = predictedMove; else instructedMove = gLastMoves[battlerDef]; @@ -2781,21 +2785,21 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_QUASH: 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)) ADJUST_SCORE(-10); break; case EFFECT_AFTER_YOU: if (!IsTargetingPartner(battlerAtk, battlerDef) || !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)) ADJUST_SCORE(-10); break; case EFFECT_SUCKER_PUNCH: 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 - || 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); break; case EFFECT_TAILWIND: @@ -2832,7 +2836,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; 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) ADJUST_SCORE(-4); break; @@ -2867,7 +2871,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) else if (CanAIFaintTarget(battlerAtk, battlerDef, 0)) ADJUST_SCORE(-10); else if (CanTargetFaintAi(battlerDef, battlerAtk) - && AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) + && AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) ADJUST_SCORE(-10); break; 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); if (predictedMove == MOVE_NONE || IsBattleMoveStatus(predictedMove) - || AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY) + || AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) || defPrio < 1 || defPrio > 3) // Opponent going first or not using priority move 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) { u32 movesetIndex = gAiThinkingStruct->movesetIndex; + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); if (IsTargetingPartner(battlerAtk, battlerDef)) 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) && 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); else 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 partnerHasBadAbility = (gAbilitiesInfo[atkPartnerAbility].aiRating < 0); u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData); + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); SetTypeBeforeUsingMove(move, battlerAtk); moveType = GetBattleMoveType(move); @@ -3059,7 +3065,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) // Adjust for always crit moves 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 if (IsAttackBoostMoveEffect(effect)) @@ -3232,7 +3238,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case ABILITY_ANGER_POINT: if (MoveAlwaysCrits(move) && BattlerStatCanRise(battlerAtkPartner, atkPartnerAbility, STAT_ATK) - && AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMove, CONSIDER_PRIORITY) + && AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) && isFriendlyFireOK) { if (MoveAlwaysCrits(move)) @@ -3551,7 +3557,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_INSTRUCT: { u16 instructedMove; - if (AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMove, CONSIDER_PRIORITY)) + if (AI_IsFaster(battlerAtk, battlerAtkPartner, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) instructedMove = aiData->partnerMove; else 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)) ADJUST_SCORE(DECENT_EFFECT); - if (AI_IsSlower(battlerAtkPartner, FOE(battlerAtkPartner), aiData->partnerMove, predictedMove, 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 + 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, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) // Opponent mon 2 goes before partner { if (partnerEffect == EFFECT_COUNTER || partnerEffect == EFFECT_MIRROR_COAT) break; // These moves need to go last @@ -3578,8 +3584,8 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_HEAL_PULSE: case EFFECT_HIT_ENEMY_HEAL_ALLY: - if (AI_IsFaster(battlerAtk, FOE(battlerAtk), move, predictedMove, CONSIDER_PRIORITY) - && AI_IsFaster(battlerAtk, BATTLE_PARTNER(FOE(battlerAtk)), move, predictedMove, CONSIDER_PRIORITY) + if (AI_IsFaster(battlerAtk, FOE(battlerAtk), move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) + && AI_IsFaster(battlerAtk, BATTLE_PARTNER(FOE(battlerAtk)), move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) && gBattleMons[battlerAtkPartner].hp < gBattleMons[battlerAtkPartner].maxHP / 2) RETURN_SCORE_PLUS(WEAK_EFFECT); 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) { - u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData); - u32 speed1 = AI_WhoStrikesFirst(battlerAtk, battlerDef, move1, predictedMove, CONSIDER_PRIORITY); - u32 speed2 = AI_WhoStrikesFirst(battlerAtk, battlerDef, move2, predictedMove, CONSIDER_PRIORITY); + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); + u32 speed1 = AI_WhoStrikesFirst(battlerAtk, battlerDef, move1, predictedMoveSpeedCheck, CONSIDER_PRIORITY); + u32 speed2 = AI_WhoStrikesFirst(battlerAtk, battlerDef, move2, predictedMoveSpeedCheck, CONSIDER_PRIORITY); if (speed1 == AI_IS_FASTER && speed2 == AI_IS_SLOWER) return MOVE_WON_COMPARISON; @@ -3937,6 +3943,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) s32 score = 0; u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData); + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); u32 predictedType = GetMoveType(predictedMove); u32 predictedMoveSlot = GetMoveSlot(GetMovesArray(battlerDef), predictedMove); bool32 isBattle1v1 = IsBattle1v1(); @@ -4235,7 +4242,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(IncreaseSubstituteMoveScore(battlerAtk, battlerDef, move)); break; 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 && (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] != 0xFFFF) && (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)) 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: if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX) 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); break; case EFFECT_SPITE: @@ -4564,7 +4571,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) if (predictedMove != MOVE_NONE && isBattle1v1) { 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_MISTY_EXPLOSION || predictedEffect == EFFECT_PROTECT)) @@ -4613,7 +4620,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) break; case EFFECT_ATTRACT: if (isBattle1v1 - && (AI_IsSlower(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) + && (AI_IsSlower(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, CONSIDER_PRIORITY)) && BattlerWillFaintFromSecondaryDamage(battlerDef, aiData->abilities[battlerDef])) break; // Don't use if the attract won't have a change to activate if (gBattleMons[battlerDef].status1 & STATUS1_ANY @@ -4661,7 +4668,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) if (hasPartner) { 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 } ADJUST_SCORE(IncreaseStatDownScore(battlerAtk, battlerDef, STAT_EVASION)); @@ -5155,7 +5162,7 @@ case EFFECT_GUARD_SPLIT: ADJUST_SCORE(DECENT_EFFECT); break; 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 else if (HasHealingEffect(battlerDef) || aiData->holdEffects[battlerDef] == HOLD_EFFECT_LEFTOVERS || (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); break; 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 break; case EFFECT_TAILWIND: @@ -5204,7 +5211,7 @@ case EFFECT_GUARD_SPLIT: if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC) && !(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) ADJUST_SCORE(GOOD_EFFECT); // Cause the enemy's move to fail @@ -5219,7 +5226,7 @@ case EFFECT_GUARD_SPLIT: } break; 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)) ADJUST_SCORE(DECENT_EFFECT); break; @@ -5246,7 +5253,7 @@ case EFFECT_GUARD_SPLIT: ADJUST_SCORE(DECENT_EFFECT); break; 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); break; case EFFECT_REVIVAL_BLESSING: @@ -5516,7 +5523,7 @@ case EFFECT_GUARD_SPLIT: case MOVE_EFFECT_THROAT_CHOP: 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); else ADJUST_SCORE(DECENT_EFFECT); @@ -5571,8 +5578,10 @@ static s32 AI_ForceSetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 || gBattleResults.battleTurnCounter != 0) return score; + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); + 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) && 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; uq4_12_t effectiveness = aiData->effectiveness[battlerAtk][battlerDef][gAiThinkingStruct->movesetIndex]; u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData); + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); // Switch benefit switch (moveEffect) @@ -6173,7 +6183,7 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(GOOD_EFFECT); else if (hitsToKO == 1) 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); break; } diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 8fe1067a19..e6dec3e58d 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -199,6 +199,14 @@ u32 GetIncomingMove(u32 battler, u32 opposingBattler, struct AiLogicData *aiData 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) { memset(gBattleHistory->usedMoves[battlerId], 0, sizeof(gBattleHistory->usedMoves[battlerId])); @@ -497,6 +505,7 @@ u32 GetTotalBaseStat(u32 species) bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler) { int i; + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAI, opposingBattler, gAiLogicData); for (i = 0; i < MAX_MON_MOVES; i++) { @@ -504,7 +513,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler) enum BattleMoveEffects effect = GetMoveEffect(move); if (effect == EFFECT_PROTECT && move != MOVE_ENDURE) 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 FALSE; @@ -2031,8 +2040,9 @@ bool32 CanLowerStat(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData, 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 - 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 && !HasBattlerSideMoveWithEffect(battlerAtk, EFFECT_ELECTRO_BALL)); } @@ -2074,7 +2084,8 @@ u32 IncreaseStatDownScore(u32 battlerAtk, u32 battlerDef, u32 stat) tempScore += DECENT_EFFECT; break; 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; break; 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 u32 battlerToSwitch; + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); battlerToSwitch = gBattleStruct->AI_monToSwitchIntoId[battlerAtk]; @@ -3132,7 +3144,7 @@ enum AIPivot ShouldPivot(u32 battlerAtk, u32 battlerDef, u32 defAbility, u32 mov if (IsBattlerPredictedToSwitch(battlerDef)) 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 { @@ -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 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); if (((!IsMoldBreakerTypeAbility(battlerAtk, gAiLogicData->abilities[battlerAtk]) && (defAbility == ABILITY_SHIELD_DUST || defAbility == ABILITY_INNER_FOCUS)) || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK || 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; } @@ -3525,7 +3538,7 @@ u32 ShouldTryToFlinch(u32 battlerAtk, u32 battlerDef, u32 atkAbility, u32 defAbi || gBattleMons[battlerDef].status1 & STATUS1_PARALYSIS || gBattleMons[battlerDef].volatiles.infatuation || 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 } @@ -3558,7 +3571,8 @@ bool32 IsFlinchGuaranteed(u32 battlerAtk, u32 battlerDef, u32 move) if (!MoveHasAdditionalEffect(move, MOVE_EFFECT_FLINCH)) 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; 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) { - 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 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 healAmount = (healPercent * maxHP) / 100; + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); if (healAmount > maxHP) healAmount = maxHP; if (gBattleMons[battlerAtk].volatiles.healBlock) 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) && !CanTargetFaintAiWithMod(battlerDef, battlerAtk, healAmount, 0)) @@ -4479,7 +4495,8 @@ static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, { enum AIScore tempScore = NO_INCREASE; 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 i; u32 statId = GetStatBeingChanged(statChange); @@ -5025,8 +5042,9 @@ enum AIConsiderGimmick ShouldTeraFromCalcs(u32 battler, u32 opposingBattler, str } else { + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battler, opposingBattler, gAiLogicData); // 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; } } @@ -5149,8 +5167,8 @@ void IncreaseTidyUpScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) ADJUST_SCORE_PTR(GOOD_EFFECT); if (AreAnyHazardsOnSide(GetBattlerSide(battlerDef)) && CountUsablePartyMons(battlerDef) != 0) ADJUST_SCORE_PTR(-2); - - if (gBattleMons[battlerAtk].volatiles.substitute && AI_IsFaster(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), DONT_CONSIDER_PRIORITY)) + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData); + if (gBattleMons[battlerAtk].volatiles.substitute && AI_IsFaster(battlerAtk, battlerDef, move, predictedMoveSpeedCheck, DONT_CONSIDER_PRIORITY)) ADJUST_SCORE_PTR(-10); if (gBattleMons[battlerDef].volatiles.substitute) ADJUST_SCORE_PTR(GOOD_EFFECT); @@ -5188,9 +5206,9 @@ bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, st default: break; } - + u32 predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, opposingBattler, gAiLogicData); 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)); } diff --git a/test/battle/ai/ai.c b/test/battle/ai/ai.c index 4c65a23489..4c86673473 100644 --- a/test/battle/ai/ai.c +++ b/test/battle/ai/ai.c @@ -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") { 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); } } } + +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); } + } +} diff --git a/test/battle/ai/ai_flag_predict_move.c b/test/battle/ai/ai_flag_predict_move.c index 97a7c12afa..6a8554b734 100644 --- a/test/battle/ai/ai_flag_predict_move.c +++ b/test/battle/ai/ai_flag_predict_move.c @@ -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); } } } + +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); } + } +} diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index 79ffccc618..0a41343427 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -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); } } } + +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); } + } +}