From 3c72ca11586c3ca96ca06bdb6cb2acb4ecec786a Mon Sep 17 00:00:00 2001 From: ghostyboyy97 <106448956+ghostyboyy97@users.noreply.github.com> Date: Tue, 4 Nov 2025 03:35:43 -0500 Subject: [PATCH] fix (AI scoring): shield dust considerations, IsMoveEffectInMinus self effect edge case, hitsToKO zero-case consideration (#8126) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- include/battle_ai_util.h | 1 + src/battle_ai_main.c | 5 ++++- src/battle_ai_util.c | 22 ++++++++++++++++++---- test/battle/ability/shield_dust.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 1b23dd7e1a..1964e45d53 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -155,6 +155,7 @@ bool32 IsAffectedByPowder(u32 battler, u32 ability, enum ItemHoldEffect holdEffe bool32 MovesWithCategoryUnusable(u32 attacker, u32 target, enum DamageCategory category); enum MoveComparisonResult AI_WhichMoveBetter(u32 move1, u32 move2, u32 battlerAtk, u32 battlerDef, s32 noOfHitsToKo); struct SimulatedDamage AI_CalcDamageSaveBattlers(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, enum AIConsiderGimmick considerGimmickAtk, enum AIConsiderGimmick considerGimmickDef); +bool32 IsAdditionalEffectBlocked(u32 battlerAtk, u32 abilityAtk, u32 battlerDef, u32 abilityDef); struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, uq4_12_t *typeEffectiveness, enum AIConsiderGimmick considerGimmickAtk, enum AIConsiderGimmick considerGimmickDef, u32 weather); bool32 AI_IsDamagedByRecoil(u32 battler); u32 GetNoOfHitsToKO(u32 dmg, s32 hp); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 2cc7cfe62c..0c8c0bb1cd 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -5069,7 +5069,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) if (gBattleMons[battlerDef].speed > gBattleMons[battlerAtk].speed) ADJUST_SCORE(DECENT_EFFECT); break; -case EFFECT_GUARD_SPLIT: + case EFFECT_GUARD_SPLIT: { u32 atkDefense = gBattleMons[battlerAtk].defense; u32 defDefense = gBattleMons[battlerDef].defense; @@ -5590,6 +5590,9 @@ case EFFECT_GUARD_SPLIT: } else // consider move effects that hinder the target { + if (IsAdditionalEffectBlocked(battlerAtk, aiData->abilities[battlerAtk], battlerDef, aiData->abilities[battlerDef])) + continue; + switch (additionalEffect->moveEffect) { case MOVE_EFFECT_FLINCH: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 4a397719c0..d866e5b0d0 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -701,6 +701,17 @@ bool32 IsDamageMoveUnusable(struct DamageContext *ctx) return FALSE; } +bool32 IsAdditionalEffectBlocked(u32 battlerAtk, u32 abilityAtk, u32 battlerDef, u32 abilityDef) +{ + if (gAiLogicData->holdEffects[battlerDef] == HOLD_EFFECT_COVERT_CLOAK) + return TRUE; + + if (abilityDef == ABILITY_SHIELD_DUST && !IsMoldBreakerTypeAbility(battlerAtk, abilityAtk)) + return TRUE; + + return FALSE; +} + static inline s32 GetDamageByRollType(s32 dmg, enum DamageRollType rollType) { if (rollType == DMG_ROLL_LOWEST) @@ -1073,6 +1084,9 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 } else // consider move effects that hinder the target { + if (IsAdditionalEffectBlocked(battlerAtk, abilityAtk, battlerDef, abilityDef)) + continue; + switch (additionalEffect->moveEffect) { case MOVE_EFFECT_POISON: @@ -1107,7 +1121,7 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 case MOVE_EFFECT_SP_DEF_MINUS_1: case MOVE_EFFECT_ACC_MINUS_1: case MOVE_EFFECT_EVS_MINUS_1: - if (CanLowerStat(battlerAtk, battlerDef, gAiLogicData, STAT_ATK + (additionalEffect->moveEffect - MOVE_EFFECT_ATK_MINUS_1)) && noOfHitsToKo != 1) + if (CanLowerStat(battlerAtk, battlerDef, gAiLogicData, STAT_ATK + (additionalEffect->moveEffect - MOVE_EFFECT_ATK_MINUS_1)) && noOfHitsToKo > 1) return TRUE; break; case MOVE_EFFECT_ATK_MINUS_2: @@ -1117,7 +1131,7 @@ static bool32 AI_IsMoveEffectInPlus(u32 battlerAtk, u32 battlerDef, u32 move, s3 case MOVE_EFFECT_SP_DEF_MINUS_2: case MOVE_EFFECT_ACC_MINUS_2: case MOVE_EFFECT_EVS_MINUS_2: - if (CanLowerStat(battlerAtk, battlerDef, gAiLogicData, STAT_ATK + (additionalEffect->moveEffect - MOVE_EFFECT_ATK_MINUS_2)) && noOfHitsToKo != 1) + if (CanLowerStat(battlerAtk, battlerDef, gAiLogicData, STAT_ATK + (additionalEffect->moveEffect - MOVE_EFFECT_ATK_MINUS_2)) && noOfHitsToKo > 1) return TRUE; break; default: @@ -1174,7 +1188,7 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s case MOVE_EFFECT_ATK_DEF_DOWN: case MOVE_EFFECT_DEF_SPDEF_DOWN: if ((additionalEffect->self && abilityAtk != ABILITY_CONTRARY) - || (noOfHitsToKo != 1 && abilityDef == ABILITY_CONTRARY && !DoesBattlerIgnoreAbilityChecks(battlerAtk, abilityAtk, move))) + || (noOfHitsToKo > 1 && !additionalEffect->self && abilityDef == ABILITY_CONTRARY && !DoesBattlerIgnoreAbilityChecks(battlerAtk, abilityAtk, move))) return TRUE; break; case MOVE_EFFECT_RECHARGE: @@ -1195,7 +1209,7 @@ static bool32 AI_IsMoveEffectInMinus(u32 battlerAtk, u32 battlerDef, u32 move, s case MOVE_EFFECT_ACC_PLUS_2: case MOVE_EFFECT_ALL_STATS_UP: if ((additionalEffect->self && abilityAtk == ABILITY_CONTRARY) - || (noOfHitsToKo != 1 && !(abilityDef == ABILITY_CONTRARY && !DoesBattlerIgnoreAbilityChecks(battlerAtk, abilityAtk, move)))) + || (noOfHitsToKo > 1 && !additionalEffect->self && !(abilityDef == ABILITY_CONTRARY && !DoesBattlerIgnoreAbilityChecks(battlerAtk, abilityAtk, move)))) return TRUE; break; default: diff --git a/test/battle/ability/shield_dust.c b/test/battle/ability/shield_dust.c index 2d7358335d..2fc4f51d5c 100644 --- a/test/battle/ability/shield_dust.c +++ b/test/battle/ability/shield_dust.c @@ -188,3 +188,31 @@ SINGLE_BATTLE_TEST("Shield Dust does not prevent ability stat changes") MESSAGE("Vivillon's Speed fell!"); } } + +AI_SINGLE_BATTLE_TEST("AI will score secondary effects against shield dust correctly") +{ + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + GIVEN { + PLAYER(SPECIES_DUSTOX){ Ability(ABILITY_SHIELD_DUST); Moves(MOVE_GUST); } + OPPONENT(SPECIES_SUNFLORA){ Ability(ABILITY_EARLY_BIRD); Moves(MOVE_MYSTICAL_FIRE, MOVE_FIERY_DANCE); } + } WHEN { + TURN { + MOVE(player, MOVE_GUST); + EXPECT_MOVE(opponent, MOVE_FIERY_DANCE); + } + } +} + +AI_SINGLE_BATTLE_TEST("AI will score secondary effects against shield dust correctly when it has Mold Breaker") +{ + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT); + GIVEN { + PLAYER(SPECIES_DUSTOX){ Ability(ABILITY_SHIELD_DUST); Moves(MOVE_GUST); } + OPPONENT(SPECIES_SUNFLORA){ Ability(ABILITY_MOLD_BREAKER); Moves(MOVE_MYSTICAL_FIRE, MOVE_FIERY_DANCE); } + } WHEN { + TURN { + MOVE(player, MOVE_GUST); + EXPECT_MOVE(opponent, MOVE_MYSTICAL_FIRE); + } + } +}