From c707df358ef3033bedc51c7cb006ce7bf1ac9dc8 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Thu, 20 Mar 2025 22:50:01 +0100 Subject: [PATCH] Clean up AI code duplication and unify checks (#6348) --- include/battle_ai_util.h | 2 +- src/battle_ai_main.c | 260 ++++++++++++++------------------ src/battle_ai_util.c | 67 +++++--- test/battle/ability/minds_eye.c | 33 ++-- test/battle/ai/check_bad_move.c | 48 ++++++ 5 files changed, 220 insertions(+), 190 deletions(-) create mode 100644 test/battle/ai/check_bad_move.c diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 2cf314c00d..6715cee619 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -99,7 +99,7 @@ u32 AI_GetBattlerAbility(u32 battler); // stat stage checks bool32 AnyStatIsRaised(u32 battlerId); -bool32 ShouldLowerStat(u32 battlerAtk, u32 battlerDef, u32 battlerAbility, u32 stat); +bool32 ShouldLowerStat(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 stat); bool32 BattlerStatCanRise(u32 battler, u32 battlerAbility, u32 stat); bool32 AreBattlersStatsMaxed(u32 battler); u32 CountPositiveStatStages(u32 battlerId); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 1b0fe8b45a..089dd76ec5 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -835,6 +835,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) u32 i; u32 weather; u32 predictedMove = aiData->lastUsedMove[battlerDef]; + u32 abilityDef = aiData->abilities[battlerDef]; if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)) return score; @@ -895,145 +896,115 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } } + if (DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move)) + abilityDef = ABILITY_NONE; + // check non-user target if (!(moveTarget & MOVE_TARGET_USER)) { - // target ability checks - if (!DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move)) + if (CanAbilityBlockMove(battlerAtk, battlerDef, move, abilityDef, ABILITY_CHECK_TRIGGER)) + RETURN_SCORE_MINUS(20); + + if (CanAbilityAbsorbMove(battlerAtk, battlerDef, abilityDef, move, moveType, ABILITY_CHECK_TRIGGER)) + RETURN_SCORE_MINUS(20); + + switch (abilityDef) { - if (CanAbilityBlockMove(battlerAtk, battlerDef, move, aiData->abilities[battlerDef], ABILITY_CHECK_TRIGGER)) - RETURN_SCORE_MINUS(20); - - if (CanAbilityAbsorbMove(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, moveType, ABILITY_CHECK_TRIGGER)) - RETURN_SCORE_MINUS(20); - - switch (aiData->abilities[battlerDef]) + case ABILITY_MAGIC_GUARD: + switch (moveEffect) { - case ABILITY_MAGIC_GUARD: - switch (moveEffect) - { - case EFFECT_POISON: - case EFFECT_WILL_O_WISP: - case EFFECT_TOXIC: - case EFFECT_LEECH_SEED: - ADJUST_SCORE(-5); - break; - case EFFECT_CURSE: - if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GHOST)) // Don't use Curse if you're a ghost type vs a Magic Guard user, they'll take no damage. - ADJUST_SCORE(-5); - break; - } + case EFFECT_POISON: + case EFFECT_WILL_O_WISP: + case EFFECT_TOXIC: + case EFFECT_LEECH_SEED: + ADJUST_SCORE(-5); break; - case ABILITY_WONDER_GUARD: - if (effectiveness < UQ_4_12(2.0)) + case EFFECT_CURSE: + if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GHOST)) // Don't use Curse if you're a ghost type vs a Magic Guard user, they'll take no damage. + ADJUST_SCORE(-5); + break; + } + break; + case ABILITY_WONDER_GUARD: + if (effectiveness < UQ_4_12(2.0)) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_JUSTIFIED: + if (moveType == TYPE_DARK && !IsBattleMoveStatus(move)) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_RATTLED: + if (!IsBattleMoveStatus(move) + && (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG)) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_AROMA_VEIL: + if (IsAromaVeilProtectedEffect(moveEffect)) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_SWEET_VEIL: + if (moveEffect == EFFECT_SLEEP || moveEffect == EFFECT_YAWN) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_FLOWER_VEIL: + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) && (IsNonVolatileStatusMoveEffect(moveEffect))) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_MAGIC_BOUNCE: + if (MoveCanBeBouncedBack(move)) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_CONTRARY: + if (IsStatLoweringEffect(moveEffect)) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_COMATOSE: + if (IsNonVolatileStatusMoveEffect(moveEffect)) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_SHIELDS_DOWN: + if (IsShieldsDownProtected(battlerAtk, aiData->abilities[battlerAtk]) && IsNonVolatileStatusMoveEffect(moveEffect)) + RETURN_SCORE_MINUS(10); + break; + case ABILITY_LEAF_GUARD: + if ((AI_GetWeather() & B_WEATHER_SUN) + && aiData->holdEffects[battlerDef] != HOLD_EFFECT_UTILITY_UMBRELLA + && IsNonVolatileStatusMoveEffect(moveEffect)) + RETURN_SCORE_MINUS(10); + break; + } // def ability checks + + // target partner ability checks & not attacking partner + if (isDoubleBattle) + { + switch (aiData->abilities[BATTLE_PARTNER(battlerDef)]) + { + case ABILITY_LIGHTNING_ROD: + if (moveType == TYPE_ELECTRIC && !IsMoveRedirectionPrevented(battlerAtk, move, aiData->abilities[battlerAtk])) RETURN_SCORE_MINUS(20); break; - case ABILITY_JUSTIFIED: - if (moveType == TYPE_DARK && !IsBattleMoveStatus(move)) - RETURN_SCORE_MINUS(10); + case ABILITY_STORM_DRAIN: + if (moveType == TYPE_WATER && !IsMoveRedirectionPrevented(battlerAtk, move, aiData->abilities[battlerAtk])) + RETURN_SCORE_MINUS(20); break; - case ABILITY_RATTLED: - if (!IsBattleMoveStatus(move) - && (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG)) + case ABILITY_MAGIC_BOUNCE: + if (MoveCanBeBouncedBack(move) && moveTarget & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_OPPONENTS_FIELD)) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_SWEET_VEIL: + if (moveEffect == EFFECT_SLEEP || moveEffect == EFFECT_YAWN) + RETURN_SCORE_MINUS(20); + break; + case ABILITY_FLOWER_VEIL: + if ((IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS)) && (IsNonVolatileStatusMoveEffect(moveEffect) || IsStatLoweringEffect(moveEffect))) RETURN_SCORE_MINUS(10); break; case ABILITY_AROMA_VEIL: if (IsAromaVeilProtectedEffect(moveEffect)) RETURN_SCORE_MINUS(10); break; - case ABILITY_SWEET_VEIL: - if (moveEffect == EFFECT_SLEEP || moveEffect == EFFECT_YAWN) - RETURN_SCORE_MINUS(10); - break; - case ABILITY_FLOWER_VEIL: - if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) && (IsNonVolatileStatusMoveEffect(moveEffect) || IsStatLoweringEffect(moveEffect))) - RETURN_SCORE_MINUS(10); - break; - case ABILITY_MAGIC_BOUNCE: - if (MoveCanBeBouncedBack(move)) - RETURN_SCORE_MINUS(20); - break; - case ABILITY_CONTRARY: - if (IsStatLoweringEffect(moveEffect)) - RETURN_SCORE_MINUS(20); - break; - case ABILITY_CLEAR_BODY: - case ABILITY_FULL_METAL_BODY: - case ABILITY_WHITE_SMOKE: - if (IsStatLoweringEffect(moveEffect)) - RETURN_SCORE_MINUS(10); - break; - case ABILITY_HYPER_CUTTER: - if ((moveEffect == EFFECT_ATTACK_DOWN || moveEffect == EFFECT_ATTACK_DOWN_2) - && move != MOVE_PLAY_NICE && move != MOVE_NOBLE_ROAR && move != MOVE_TEARFUL_LOOK && move != MOVE_VENOM_DRENCH) - RETURN_SCORE_MINUS(10); - break; - case ABILITY_ILLUMINATE: - if (B_ILLUMINATE_EFFECT < GEN_9) - break; - // fallthrough - case ABILITY_KEEN_EYE: - case ABILITY_MINDS_EYE: - if (moveEffect == EFFECT_ACCURACY_DOWN || moveEffect == EFFECT_ACCURACY_DOWN_2) - RETURN_SCORE_MINUS(10); - break; - case ABILITY_BIG_PECKS: - if (moveEffect == EFFECT_DEFENSE_DOWN || moveEffect == EFFECT_DEFENSE_DOWN_2) - RETURN_SCORE_MINUS(10); - break; - case ABILITY_DEFIANT: - case ABILITY_COMPETITIVE: - if (IsStatLoweringEffect(moveEffect) && !IS_TARGETING_PARTNER(battlerAtk, battlerDef)) - RETURN_SCORE_MINUS(8); - break; - case ABILITY_COMATOSE: - if (IsNonVolatileStatusMoveEffect(moveEffect)) - RETURN_SCORE_MINUS(10); - break; - case ABILITY_SHIELDS_DOWN: - if (IsShieldsDownProtected(battlerAtk, aiData->abilities[battlerAtk]) && IsNonVolatileStatusMoveEffect(moveEffect)) - RETURN_SCORE_MINUS(10); - break; - case ABILITY_LEAF_GUARD: - if ((AI_GetWeather() & B_WEATHER_SUN) - && aiData->holdEffects[battlerDef] != HOLD_EFFECT_UTILITY_UMBRELLA - && IsNonVolatileStatusMoveEffect(moveEffect)) - RETURN_SCORE_MINUS(10); - break; - } // def ability checks - - // target partner ability checks & not attacking partner - if (isDoubleBattle) - { - switch (aiData->abilities[BATTLE_PARTNER(battlerDef)]) - { - case ABILITY_LIGHTNING_ROD: - if (moveType == TYPE_ELECTRIC && !IsMoveRedirectionPrevented(battlerAtk, move, aiData->abilities[battlerAtk])) - RETURN_SCORE_MINUS(20); - break; - case ABILITY_STORM_DRAIN: - if (moveType == TYPE_WATER && !IsMoveRedirectionPrevented(battlerAtk, move, aiData->abilities[battlerAtk])) - RETURN_SCORE_MINUS(20); - break; - case ABILITY_MAGIC_BOUNCE: - if (MoveCanBeBouncedBack(move) && moveTarget & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_OPPONENTS_FIELD)) - RETURN_SCORE_MINUS(20); - break; - case ABILITY_SWEET_VEIL: - if (moveEffect == EFFECT_SLEEP || moveEffect == EFFECT_YAWN) - RETURN_SCORE_MINUS(20); - break; - case ABILITY_FLOWER_VEIL: - if ((IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS)) && (IsNonVolatileStatusMoveEffect(moveEffect) || IsStatLoweringEffect(moveEffect))) - RETURN_SCORE_MINUS(10); - break; - case ABILITY_AROMA_VEIL: - if (IsAromaVeilProtectedEffect(moveEffect)) - RETURN_SCORE_MINUS(10); - break; - } - } // def partner ability checks - } // ignore def ability check + } + } // def partner ability checks // gen7+ dark type mons immune to priority->elevated moves from prankster if (B_PRANKSTER_DARK_TYPES >= GEN_7 && IS_BATTLER_OF_TYPE(battlerDef, TYPE_DARK) @@ -1108,7 +1079,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) default: break; // check move damage case EFFECT_SLEEP: - if (!AI_CanPutToSleep(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + if (!AI_CanPutToSleep(battlerAtk, battlerDef, abilityDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); if (PartnerMoveActivatesSleepClause(aiData->partnerMove)) ADJUST_SCORE(-20); @@ -1360,47 +1331,40 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) // stat lowering effects case EFFECT_ATTACK_DOWN: case EFFECT_ATTACK_DOWN_2: - if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_ATK)) //|| !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)) - ADJUST_SCORE(-10); - else if (aiData->abilities[battlerDef] == ABILITY_HYPER_CUTTER) + if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_ATK)) //|| !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL)) ADJUST_SCORE(-10); break; case EFFECT_DEFENSE_DOWN: case EFFECT_DEFENSE_DOWN_2: - if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_DEF)) + if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_DEF)) ADJUST_SCORE(-10); break; case EFFECT_SPEED_DOWN: case EFFECT_SPEED_DOWN_2: - if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPEED)) - ADJUST_SCORE(-10); - else if (aiData->abilities[battlerDef] == ABILITY_SPEED_BOOST) + if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPEED)) ADJUST_SCORE(-10); break; case EFFECT_SPECIAL_ATTACK_DOWN: case EFFECT_SPECIAL_ATTACK_DOWN_2: - if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPATK)) //|| !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)) + if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPATK)) //|| !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL)) ADJUST_SCORE(-10); break; case EFFECT_SPECIAL_DEFENSE_DOWN: case EFFECT_SPECIAL_DEFENSE_DOWN_2: - if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPDEF)) + if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPDEF)) ADJUST_SCORE(-10); break; case EFFECT_ACCURACY_DOWN: case EFFECT_ACCURACY_DOWN_2: - if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_ACC)) + if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_ACC)) ADJUST_SCORE(-10); - else if (aiData->abilities[battlerDef] == ABILITY_KEEN_EYE || aiData->abilities[battlerDef] == ABILITY_MINDS_EYE - || (B_ILLUMINATE_EFFECT >= GEN_9 && aiData->abilities[battlerDef] == ABILITY_ILLUMINATE)) - ADJUST_SCORE(-8); break; case EFFECT_EVASION_DOWN: case EFFECT_EVASION_DOWN_2: case EFFECT_TICKLE: - if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_ATK)) + if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_ATK)) ADJUST_SCORE(-10); - else if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_DEF)) + else if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_DEF)) ADJUST_SCORE(-8); break; case EFFECT_VENOM_DRENCH: @@ -1410,18 +1374,18 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } else { - if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPEED)) + if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPEED)) ADJUST_SCORE(-10); - else if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPATK)) + else if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPATK)) ADJUST_SCORE(-8); - else if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_ATK)) + else if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_ATK)) ADJUST_SCORE(-6); } break; case EFFECT_NOBLE_ROAR: - if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPATK)) + if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPATK)) ADJUST_SCORE(-10); - else if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_ATK)) + else if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_ATK)) ADJUST_SCORE(-8); break; case EFFECT_CAPTIVATE: @@ -1455,7 +1419,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (aiData->abilities[battlerDef] == ABILITY_WONDER_GUARD && effectiveness < UQ_4_12(2.0)) ADJUST_SCORE(-10); if (HasDamagingMove(battlerDef) && !((gBattleMons[battlerAtk].status2 & STATUS2_SUBSTITUTE) - || IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef]) + || IsBattlerIncapacitated(battlerDef, abilityDef) || gBattleMons[battlerDef].status2 & (STATUS2_INFATUATION | STATUS2_CONFUSION))) ADJUST_SCORE(-10); if (HasMoveEffect(battlerAtk, EFFECT_SUBSTITUTE) && !(gBattleMons[battlerAtk].status2 & STATUS2_SUBSTITUTE)) @@ -1482,12 +1446,12 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_TOXIC_THREAD: - if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPEED)) + if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPEED)) ADJUST_SCORE(-1); // may still want to just poison //fallthrough case EFFECT_POISON: case EFFECT_TOXIC: - if (!AI_CanPoison(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove)) + if (!AI_CanPoison(battlerAtk, battlerDef, abilityDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_LIGHT_SCREEN: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index c03ada6d79..1278e873b8 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -1836,34 +1836,53 @@ void ProtectChecks(u32 battlerAtk, u32 battlerDef, u32 move, u32 predictedMove, } // stat stages -bool32 ShouldLowerStat(u32 battlerAtk, u32 battlerDef, u32 battlerAbility, u32 stat) +bool32 ShouldLowerStat(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 stat) { - if (gBattleMons[battlerDef].statStages[stat] > MIN_STAT_STAGE && battlerAbility != ABILITY_CONTRARY) - { - if (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CLEAR_AMULET - || battlerAbility == ABILITY_CLEAR_BODY - || battlerAbility == ABILITY_WHITE_SMOKE - || battlerAbility == ABILITY_FULL_METAL_BODY) - return FALSE; + if (gBattleMons[battlerDef].statStages[stat] == MIN_STAT_STAGE) + return FALSE; - switch (stat) - { - case STAT_ATK: - return !(battlerAbility == ABILITY_HYPER_CUTTER); - case STAT_DEF: - return !(battlerAbility == ABILITY_BIG_PECKS); - case STAT_SPEED: - // If AI is faster and doesn't have any mons left, lowering speed doesn't give any - return !(AI_IsFaster(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) - && CountUsablePartyMons(battlerAtk) == 0 - && !HasMoveEffect(battlerAtk, EFFECT_ELECTRO_BALL)); - case STAT_ACC: - return !(battlerAbility == ABILITY_KEEN_EYE || (B_ILLUMINATE_EFFECT >= GEN_9 && battlerAbility == ABILITY_ILLUMINATE)); - } - return TRUE; + if (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CLEAR_AMULET) + return FALSE; + + switch (abilityDef) + { + case ABILITY_SPEED_BOOST: + if (stat == STAT_SPEED) + return FALSE; + case ABILITY_HYPER_CUTTER: + if (stat == STAT_ATK) + return FALSE; + case ABILITY_BIG_PECKS: + if (stat == STAT_DEF) + return FALSE; + case ABILITY_ILLUMINATE: + if (B_ILLUMINATE_EFFECT < GEN_9) + break; + case ABILITY_KEEN_EYE: + case ABILITY_MINDS_EYE: + if (stat == STAT_ACC) + return FALSE; + case ABILITY_FLOWER_VEIL: + if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS)) + return FALSE; + break; + case ABILITY_CONTRARY: + case ABILITY_CLEAR_BODY: + case ABILITY_WHITE_SMOKE: + case ABILITY_FULL_METAL_BODY: + return FALSE; } - return FALSE; + // This should be a viability check + if (stat == STAT_SPEED) + { + // If AI is faster and doesn't have any mons left, lowering speed doesn't give any + return !(AI_IsFaster(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered) + && CountUsablePartyMons(battlerAtk) == 0 + && !HasMoveEffect(battlerAtk, EFFECT_ELECTRO_BALL)); + } + + return TRUE; } bool32 BattlerStatCanRise(u32 battler, u32 battlerAbility, u32 stat) diff --git a/test/battle/ability/minds_eye.c b/test/battle/ability/minds_eye.c index 59c81746c7..73100cb20e 100644 --- a/test/battle/ability/minds_eye.c +++ b/test/battle/ability/minds_eye.c @@ -44,28 +44,27 @@ SINGLE_BATTLE_TEST("Mind's Eye doesn't bypass a Ghost-type's Wonder Guard") AI_SINGLE_BATTLE_TEST("AI doesn't use accuracy-lowering moves if it knows that the foe has Mind's Eye") { - u32 abilityAI = ABILITY_NONE, moveAI = MOVE_NONE, j = 0; + u32 abilityAI = ABILITY_NONE; - for (j = MOVE_NONE + 1; j < MOVES_COUNT; j++) - { - if (GetMoveEffect(j) == EFFECT_ACCURACY_DOWN || GetMoveEffect(j) == EFFECT_ACCURACY_DOWN_2) { - PARAMETRIZE { moveAI = j; abilityAI = ABILITY_SWIFT_SWIM; } - PARAMETRIZE { moveAI = j; abilityAI = ABILITY_MOLD_BREAKER; } - } - } + PARAMETRIZE { abilityAI = ABILITY_SWIFT_SWIM; } + PARAMETRIZE { abilityAI = ABILITY_MOLD_BREAKER; } GIVEN { + ASSUME(GetMoveEffect(MOVE_SAND_ATTACK) == EFFECT_ACCURACY_DOWN); AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); - PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_MINDS_EYE); } - OPPONENT(SPECIES_BASCULEGION) { Moves(MOVE_CELEBRATE, moveAI); Ability(abilityAI); } + PLAYER(SPECIES_URSALUNA_BLOODMOON) { Ability(ABILITY_MINDS_EYE); } + OPPONENT(SPECIES_BASCULEGION) { Moves(MOVE_CELEBRATE, MOVE_SAND_ATTACK); Ability(abilityAI); } } WHEN { - TURN { MOVE(player, MOVE_TACKLE); } - TURN { MOVE(player, MOVE_TACKLE); - if (abilityAI == ABILITY_MOLD_BREAKER) { SCORE_GT(opponent, moveAI, MOVE_CELEBRATE); } - else { SCORE_EQ(opponent, moveAI, MOVE_CELEBRATE); } - } + TURN { + if (abilityAI == ABILITY_MOLD_BREAKER) { + SCORE_GT(opponent, MOVE_SAND_ATTACK, MOVE_CELEBRATE); + } else { + SCORE_EQ(opponent, MOVE_SAND_ATTACK, MOVE_CELEBRATE); + } + } } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); - if (abilityAI == ABILITY_MOLD_BREAKER) { ANIMATION(ANIM_TYPE_MOVE, moveAI, opponent); } + if (abilityAI == ABILITY_MOLD_BREAKER) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SAND_ATTACK, opponent); + } } } diff --git a/test/battle/ai/check_bad_move.c b/test/battle/ai/check_bad_move.c new file mode 100644 index 0000000000..79b960ec60 --- /dev/null +++ b/test/battle/ai/check_bad_move.c @@ -0,0 +1,48 @@ +#include "global.h" +#include "test/battle.h" +#include "battle_ai_util.h" + +AI_SINGLE_BATTLE_TEST("AI will not try to lower opposing stats if target is protected by it's ability") +{ + u16 ability, species, move; + + PARAMETRIZE { ability = ABILITY_SPEED_BOOST; species = SPECIES_TORCHIC; move = MOVE_SCARY_FACE; } + PARAMETRIZE { ability = ABILITY_HYPER_CUTTER; species = SPECIES_KRABBY; move = MOVE_GROWL; } + PARAMETRIZE { ability = ABILITY_BIG_PECKS; species = SPECIES_PIDGEY; move = MOVE_SCREECH; } + PARAMETRIZE { ability = ABILITY_ILLUMINATE; species = SPECIES_STARYU; move = MOVE_SAND_ATTACK; } + PARAMETRIZE { ability = ABILITY_KEEN_EYE; species = SPECIES_PIDGEY; move = MOVE_SAND_ATTACK; } + PARAMETRIZE { ability = ABILITY_CONTRARY; species = SPECIES_SNIVY; move = MOVE_NOBLE_ROAR; } + PARAMETRIZE { ability = ABILITY_CLEAR_BODY; species = SPECIES_BELDUM; move = MOVE_NOBLE_ROAR; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_OMNISCIENT); + PLAYER(species) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, move); } + } WHEN { + TURN { SCORE_LT_VAL(opponent, move, AI_SCORE_DEFAULT); } + } +} + + +AI_DOUBLE_BATTLE_TEST("AI will not try to lower opposing stats if target is protected by Flower Veil") +{ + u16 move; + + PARAMETRIZE { move = MOVE_SCARY_FACE; } + PARAMETRIZE { move = MOVE_GROWL; } + PARAMETRIZE { move = MOVE_SCREECH; } + PARAMETRIZE { move = MOVE_SAND_ATTACK; } + PARAMETRIZE { move = MOVE_SAND_ATTACK; } + PARAMETRIZE { move = MOVE_NOBLE_ROAR; } + PARAMETRIZE { move = MOVE_NOBLE_ROAR; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_OMNISCIENT); + PLAYER(SPECIES_COMFEY) { Ability(ABILITY_FLOWER_VEIL); } + PLAYER(SPECIES_BULBASAUR); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, move); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SCORE_LT_VAL(opponentLeft, move, AI_SCORE_DEFAULT, target: playerRight); } + } +}