diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 22efc06d0e..52b5764b00 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -197,7 +197,8 @@ bool32 IsHealingMove(u32 move); bool32 HasHealingEffect(u32 battler); bool32 IsTrappingMove(u32 move); bool32 HasTrappingMoveEffect(u32 battler); -bool32 ShouldFakeOut(u32 battlerAtk, u32 battlerDef, u32 move); +bool32 IsFlinchGuaranteed(u32 battlerAtk, u32 battlerDef, u32 move); +bool32 HasChoiceEffect(u32 battler); bool32 HasThawingMove(u32 battler); bool32 IsStatRaisingEffect(enum BattleMoveEffects effect); bool32 IsStatLoweringEffect(enum BattleMoveEffects effect); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index c6d488211a..2fbe68f0ee 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -1138,6 +1138,12 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (DoesBattlerIgnoreAbilityChecks(battlerAtk, abilityAtk, move)) abilityDef = ABILITY_NONE; + // If a pokemon can be guaranteed flinched, don't target the pokemon that can't be flinched. + 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); + // check non-user target if (!(moveTarget & MOVE_TARGET_USER)) { @@ -1999,6 +2005,8 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_FIRST_TURN_ONLY: if (!gDisableStructs[battlerAtk].isFirstTurn) ADJUST_SCORE(-10); + if (HasChoiceEffect(battlerAtk)) + ADJUST_SCORE(-5); break; case EFFECT_STOCKPILE: if (gDisableStructs[battlerAtk].stockpileCounter >= 3) @@ -3946,6 +3954,10 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(-20); // Force switch if all your attacking moves are physical and you have Natural Cure. } + // check guaranteed flinch, a la Fake Out + if (IsFlinchGuaranteed(battlerAtk, battlerDef, move)) + ADJUST_SCORE(BEST_EFFECT); + // Non-volatile statuses switch(GetMoveNonVolatileStatus(move)) { @@ -4551,9 +4563,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(IncreaseStatUpScore(battlerAtk, battlerDef, STAT_CHANGE_DEF)); break; case EFFECT_FIRST_TURN_ONLY: - if (ShouldFakeOut(battlerAtk, battlerDef, move) && MoveHasAdditionalEffectWithChance(move, MOVE_EFFECT_FLINCH, 100)) - ADJUST_SCORE(GOOD_EFFECT); - else if (gDisableStructs[battlerAtk].isFirstTurn && GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move) + if (gDisableStructs[battlerAtk].isFirstTurn && GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move) ADJUST_SCORE(BEST_EFFECT); break; case EFFECT_STOCKPILE: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index a3598e6800..0f6fc16fb7 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -3502,18 +3502,57 @@ bool32 ShouldTrap(u32 battlerAtk, u32 battlerDef, u32 move) return FALSE; } -bool32 ShouldFakeOut(u32 battlerAtk, u32 battlerDef, u32 move) +bool32 IsFlinchGuaranteed(u32 battlerAtk, u32 battlerDef, u32 move) { - if ((!gDisableStructs[battlerAtk].isFirstTurn && MoveHasAdditionalEffectWithChance(move, MOVE_EFFECT_FLINCH, 100)) - || gAiLogicData->abilities[battlerAtk] == ABILITY_GORILLA_TACTICS - || gAiLogicData->holdEffects[battlerAtk] == HOLD_EFFECT_CHOICE_BAND - || gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK - || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) - || (!IsMoldBreakerTypeAbility(battlerAtk, gAiLogicData->abilities[battlerAtk]) - && (gAiLogicData->abilities[battlerDef] == ABILITY_SHIELD_DUST || gAiLogicData->abilities[battlerDef] == ABILITY_INNER_FOCUS))) + if (!MoveHasAdditionalEffect(move, MOVE_EFFECT_FLINCH)) return FALSE; - return TRUE; + if (AI_IsSlower(battlerAtk, battlerDef, move, GetIncomingMove(battlerAtk, battlerDef, gAiLogicData), CONSIDER_PRIORITY)) + return FALSE; + + u32 i; + u32 additionalEffectCount = GetMoveAdditionalEffectCount(move); + // check move additional effects that are likely to happen + for (i = 0; i < additionalEffectCount; i++) + { + const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(move, i); + // Only consider effects with a guaranteed chance to happen + if (!MoveEffectIsGuaranteed(battlerAtk, gAiLogicData->abilities[battlerAtk], additionalEffect)) + continue; + + if (additionalEffect->moveEffect == MOVE_EFFECT_FLINCH) + { + if (gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK + || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) + || (!IsMoldBreakerTypeAbility(battlerAtk, gAiLogicData->abilities[battlerAtk]) + && (gAiLogicData->abilities[battlerDef] == ABILITY_SHIELD_DUST || gAiLogicData->abilities[battlerDef] == ABILITY_INNER_FOCUS))) + return FALSE; + else + return TRUE; + } + } + return FALSE; +} + +bool32 HasChoiceEffect(u32 battler) +{ + u32 ability = gAiLogicData->abilities[battler]; + if (ability == ABILITY_GORILLA_TACTICS) + return TRUE; + + if (ability == ABILITY_KLUTZ) + return FALSE; + + enum ItemHoldEffect holdEffect = gAiLogicData->holdEffects[battler]; + switch (holdEffect) + { + case HOLD_EFFECT_CHOICE_BAND: + case HOLD_EFFECT_CHOICE_SCARF: + case HOLD_EFFECT_CHOICE_SPECS: + return TRUE; + default: + return FALSE; + } } static u32 FindMoveUsedXTurnsAgo(u32 battlerId, u32 x) diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index 5589f7bb0e..e0eac1eeed 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -648,3 +648,16 @@ AI_DOUBLE_BATTLE_TEST("AI uses Power Split to improve its stats") } } +AI_DOUBLE_BATTLE_TEST("AI prefers to Fake Out the opponent vulnerable to flinching.") +{ + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_DOUBLE_BATTLE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_ZUBAT) { Ability(ABILITY_INNER_FOCUS); } + PLAYER(SPECIES_BRAIXEN) { Ability(ABILITY_BLAZE); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_FAKE_OUT, MOVE_BRANCH_POKE, MOVE_ROCK_SMASH); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { EXPECT_MOVE(opponentLeft, MOVE_FAKE_OUT, target:playerRight); } + } +}