diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 62cba4949e..448fbf71c5 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -168,14 +168,15 @@ bool32 HasMove(u32 battlerId, u32 move); bool32 HasOnlyMovesWithCategory(u32 battlerId, enum DamageCategory category, bool32 onlyOffensive); bool32 HasMoveWithCategory(u32 battler, enum DamageCategory category); bool32 HasMoveWithType(u32 battler, u32 type); -bool32 HasMoveWithEffect(u32 battlerId, enum BattleMoveEffects moveEffect); +bool32 HasMoveWithEffect(u32 battler, enum BattleMoveEffects moveEffect); +bool32 HasMoveWithAIEffect(u32 battler, u32 aiEffect); bool32 HasBattlerSideMoveWithEffect(u32 battler, u32 effect); +bool32 HasBattlerSideMoveWithAIEffect(u32 battler, u32 effect); bool32 HasBattlerSideUsedMoveWithEffect(u32 battler, u32 effect); bool32 HasNonVolatileMoveEffect(u32 battlerId, u32 effect); bool32 IsPowerBasedOnStatus(u32 battlerId, enum BattleMoveEffects effect, u32 argument); bool32 HasMoveWithAdditionalEffect(u32 battlerId, u32 moveEffect); bool32 HasBattlerSideMoveWithAdditionalEffect(u32 battler, u32 moveEffect); -bool32 HasBattlerSideUsedMoveWithAdditionalEffect(u32 battler, u32 moveEffect); bool32 HasMoveWithCriticalHitChance(u32 battlerId); bool32 HasMoveWithMoveEffectExcept(u32 battlerId, u32 moveEffect, enum BattleMoveEffects exception); bool32 HasMoveThatLowersOwnStats(u32 battlerId); @@ -252,11 +253,9 @@ bool32 HasTwoOpponents(u32 battler); bool32 HasPartner(u32 battler); bool32 HasPartnerIgnoreFlags(u32 battler); // HasPartner respects the Attacks Partner AI flag; HasPartnerIgnoreFlags checks only if a live pokemon is adjacent. +bool32 AreMovesEquivalent(u32 battlerAtk, u32 battlerAtkPartner, u32 move, u32 partnerMove); bool32 DoesPartnerHaveSameMoveEffect(u32 battlerAtkPartner, u32 battlerDef, u32 move, u32 partnerMove); -bool32 PartnerHasSameMoveEffectWithoutTarget(u32 battlerAtkPartner, u32 move, u32 partnerMove); bool32 PartnerMoveEffectIsStatusSameTarget(u32 battlerAtkPartner, u32 battlerDef, u32 partnerMove); -bool32 IsMoveEffectWeather(u32 move); -bool32 PartnerMoveEffectIsTerrain(u32 battlerAtkPartner, u32 partnerMove); bool32 PartnerMoveEffectIs(u32 battlerAtkPartner, u32 partnerMove, enum BattleMoveEffects effectCheck); bool32 PartnerMoveIs(u32 battlerAtkPartner, u32 partnerMove, u32 moveCheck); bool32 PartnerMoveIsSameAsAttacker(u32 battlerAtkPartner, u32 battlerDef, u32 move, u32 partnerMove); @@ -301,4 +300,22 @@ bool32 HasBattlerSideAbility(u32 battlerDef, u32 ability, struct AiLogicData *ai u32 GetThinkingBattler(u32 battler); bool32 IsNaturalEnemy(u32 speciesAttacker, u32 speciesTarget); +// These are for the purpose of not doubling up on moves during double battles. +// Used in GetAIEffectGroup for move effects and GetAIEffectGroupFromMove for additional effects +#define AI_EFFECT_NONE 0 +#define AI_EFFECT_WEATHER (1 << 0) +#define AI_EFFECT_TERRAIN (1 << 1) +#define AI_EFFECT_CLEAR_HAZARDS (1 << 2) +#define AI_EFFECT_BREAK_SCREENS (1 << 3) +#define AI_EFFECT_RESET_STATS (1 << 4) +#define AI_EFFECT_FORCE_SWITCH (1 << 5) +#define AI_EFFECT_TORMENT (1 << 6) +#define AI_EFFECT_LIGHT_SCREEN (1 << 7) +#define AI_EFFECT_REFLECT (1 << 8) +#define AI_EFFECT_GRAVITY (1 << 9) +#define AI_EFFECT_CHANGE_ABILITY (1 << 10) + +// As Aurora Veil should almost never be used alongside the other screens, we save the bit. +#define AI_EFFECT_AURORA_VEIL (AI_EFFECT_LIGHT_SCREEN | AI_EFFECT_REFLECT) + #endif //GUARD_BATTLE_AI_UTIL_H diff --git a/src/battle_ai_field_statuses.c b/src/battle_ai_field_statuses.c index d04f00d4b5..d85df3e74a 100644 --- a/src/battle_ai_field_statuses.c +++ b/src/battle_ai_field_statuses.c @@ -375,14 +375,15 @@ static enum FieldEffectOutcome BenefitsFromMistyTerrain(u32 battler) // harass dragons if ((grounded || allyGrounded) - && (HasDamagingMoveOfType(FOE(battler), TYPE_DRAGON) || HasDamagingMoveOfType(BATTLE_PARTNER(FOE(battler)), TYPE_DRAGON))) + && (HasDamagingMoveOfType(FOE(battler), TYPE_DRAGON) || HasDamagingMoveOfType(BATTLE_PARTNER(FOE(battler)), TYPE_DRAGON))) return FIELD_EFFECT_POSITIVE; - if ((grounded || allyGrounded) && HasBattlerSideUsedMoveWithAdditionalEffect(FOE(battler), MOVE_EFFECT_SLEEP)) + if ((grounded || allyGrounded) + && (HasNonVolatileMoveEffect(FOE(battler), MOVE_EFFECT_SLEEP) || HasNonVolatileMoveEffect(BATTLE_PARTNER(FOE(battler)), MOVE_EFFECT_SLEEP))) return FIELD_EFFECT_POSITIVE; if (grounded && ((gBattleMons[battler].status1 & STATUS1_SLEEP) - || (gStatuses3[battler] & STATUS3_YAWN))) + || (gStatuses3[battler] & STATUS3_YAWN))) return FIELD_EFFECT_POSITIVE; return FIELD_EFFECT_NEUTRAL; diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 528bcbd859..5e75f1dfb3 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -1648,7 +1648,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; // other case EFFECT_HAZE: - if (PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + if (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) { ADJUST_SCORE(-10); // partner already using haze } @@ -1707,19 +1707,19 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-1); // may still want to just poison //fallthrough case EFFECT_LIGHT_SCREEN: - if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_LIGHTSCREEN - || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + if (gSideStatuses[GetBattlerSide(battlerAtk)] & (SIDE_STATUS_LIGHTSCREEN | SIDE_STATUS_AURORA_VEIL) + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-10); break; case EFFECT_REFLECT: - if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_REFLECT - || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + if (gSideStatuses[GetBattlerSide(battlerAtk)] & (SIDE_STATUS_REFLECT | SIDE_STATUS_AURORA_VEIL) + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-10); break; case EFFECT_AURORA_VEIL: if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_AURORA_VEIL - || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove) - || !(weather & (B_WEATHER_ICY_ANY))) + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + || !(weather & (B_WEATHER_ICY_ANY))) ADJUST_SCORE(-10); break; case EFFECT_SHEER_COLD: @@ -1734,7 +1734,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_MIST: if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_MIST - || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_FOCUS_ENERGY: @@ -1742,19 +1742,14 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_NON_VOLATILE_STATUS: - if (GetBattlerMoveTargetType(battlerAtk, move) & MOVE_TARGET_FOES_AND_ALLY - && PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) - ADJUST_SCORE(-10); if (DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_CONFUSE: - if (GetBattlerMoveTargetType(battlerAtk, move) & MOVE_TARGET_FOES_AND_ALLY - && PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) - ADJUST_SCORE(-10); case EFFECT_SWAGGER: case EFFECT_FLATTER: - if (!AI_CanConfuse(battlerAtk, battlerDef, aiData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + if (DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) + || !AI_CanConfuse(battlerAtk, battlerDef, aiData->abilities[battlerDef], BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_SUBSTITUTE: @@ -1788,7 +1783,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); else if (gDisableStructs[battlerDef].disableTimer == 0 && (B_MENTAL_HERB < GEN_5 || aiData->holdEffects[battlerDef] != HOLD_EFFECT_MENTAL_HERB) - && !PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + && !DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) { if (AI_IsFaster(battlerAtk, battlerDef, move, predictedMove, CONSIDER_PRIORITY)) // Attacker should go first { @@ -1884,7 +1879,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_STICKY_WEB: if (IsHazardOnSide(GetBattlerSide(battlerDef), HAZARDS_STICKY_WEB)) ADJUST_SCORE(-10); - if (PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + if (DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); // only one mon needs to set up Sticky Web break; case EFFECT_FORESIGHT: @@ -1912,7 +1907,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { ADJUST_SCORE(-10); //Both enemies are perish songed } - else if (PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + else if (DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) { ADJUST_SCORE(-10); } @@ -1929,23 +1924,23 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_SANDSTORM: if (weather & (B_WEATHER_SANDSTORM | B_WEATHER_PRIMAL_ANY) - || (HasPartner(battlerAtk) && IsMoveEffectWeather(aiData->partnerMove))) + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-8); break; case EFFECT_SUNNY_DAY: if (weather & (B_WEATHER_SUN | B_WEATHER_PRIMAL_ANY) - || (HasPartner(battlerAtk) && IsMoveEffectWeather(aiData->partnerMove))) + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-8); break; case EFFECT_RAIN_DANCE: if (weather & (B_WEATHER_RAIN | B_WEATHER_PRIMAL_ANY) - || (HasPartner(battlerAtk) && IsMoveEffectWeather(aiData->partnerMove))) + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-8); break; case EFFECT_HAIL: case EFFECT_SNOWSCAPE: if (weather & (B_WEATHER_ICY_ANY | B_WEATHER_PRIMAL_ANY) - || (HasPartner(battlerAtk) && IsMoveEffectWeather(aiData->partnerMove))) + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-8); break; case EFFECT_ATTRACT: @@ -1954,7 +1949,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_SAFEGUARD: if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_SAFEGUARD - || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_MAGNITUDE: @@ -1980,7 +1975,8 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_CHILLY_RECEPTION: if (CountUsablePartyMons(battlerAtk) == 0) ADJUST_SCORE(-10); - else if (weather & (B_WEATHER_ICY_ANY | B_WEATHER_PRIMAL_ANY) || IsMoveEffectWeather(aiData->partnerMove)) + else if (weather & (B_WEATHER_ICY_ANY | B_WEATHER_PRIMAL_ANY) + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-8); break; case EFFECT_BELLY_DRUM: @@ -2046,7 +2042,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_FOLLOW_ME: case EFFECT_HELPING_HAND: if (!hasPartner - || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove) + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove) || (aiData->partnerMove != MOVE_NONE && IsBattleMoveStatus(aiData->partnerMove)) || gBattleStruct->monToSwitchIntoId[BATTLE_PARTNER(battlerAtk)] != PARTY_SIZE) //Partner is switching out. ADJUST_SCORE(-10); @@ -2096,13 +2092,13 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_MUD_SPORT: if (gFieldStatuses & STATUS_FIELD_MUDSPORT || gBattleMons[battlerAtk].volatiles.mudSport - || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_WATER_SPORT: if (gFieldStatuses & STATUS_FIELD_WATERSPORT || gBattleMons[battlerAtk].volatiles.waterSport - || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_ABSORB: @@ -2239,7 +2235,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_HEAL_BELL: - if (!AnyPartyMemberStatused(battlerAtk, IsSoundMove(move)) || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + if (!AnyPartyMemberStatused(battlerAtk, IsSoundMove(move)) || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_ENDURE: @@ -2336,7 +2332,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (gSideStatuses[GetBattlerSide(battlerDef)] & (SIDE_STATUS_SCREEN_ANY | SIDE_STATUS_SAFEGUARD | SIDE_STATUS_MIST) || AreAnyHazardsOnSide(GetBattlerSide(battlerAtk))) { - if (PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + if (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) { ADJUST_SCORE(-10); //Only need one hazards removal break; @@ -2440,12 +2436,13 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) case EFFECT_SIMPLE_BEAM: case EFFECT_SKILL_SWAP: case EFFECT_WORRY_SEED: - if (!CanEffectChangeAbility(battlerAtk, battlerDef, moveEffect, aiData)) + if (!CanEffectChangeAbility(battlerAtk, battlerDef, moveEffect, aiData) + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_AND_RETURN_SCORE(NO_DAMAGE_OR_FAILS); break; case EFFECT_SNATCH: if (!HasMoveWithFlag(battlerDef, MoveCanBeSnatched) - || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_POWER_TRICK: @@ -2542,23 +2539,28 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-10); break; case EFFECT_GRASSY_TERRAIN: - if (PartnerMoveEffectIsTerrain(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) || gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN) + if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-10); break; case EFFECT_ELECTRIC_TERRAIN: - if (PartnerMoveEffectIsTerrain(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) || gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN) + if (gFieldStatuses & STATUS_FIELD_ELECTRIC_TERRAIN + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-10); break; case EFFECT_PSYCHIC_TERRAIN: - if (PartnerMoveEffectIsTerrain(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) || gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN) + if (gFieldStatuses & STATUS_FIELD_PSYCHIC_TERRAIN + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-10); break; case EFFECT_MISTY_TERRAIN: - if (PartnerMoveEffectIsTerrain(BATTLE_PARTNER(battlerAtk), aiData->partnerMove) || gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN) + if (gFieldStatuses & STATUS_FIELD_MISTY_TERRAIN + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-10); break; case EFFECT_STEEL_ROLLER: - if (!(gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) + if (!(gFieldStatuses & STATUS_FIELD_TERRAIN_ANY) + || (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove))) ADJUST_SCORE(-10); break; case EFFECT_PLEDGE: @@ -2888,7 +2890,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) break; case EFFECT_COURT_CHANGE: case EFFECT_TEATIME: - if (PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + if (DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) ADJUST_SCORE(-10); break; case EFFECT_PLACEHOLDER: @@ -3009,6 +3011,10 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) // check what effect partner is using if (aiData->partnerMove != 0 && hasPartner) { + // This catches weather, terrain, screens, etc + if (AreMovesEquivalent(battlerAtk, battlerAtkPartner, move, aiData->partnerMove)) + ADJUST_SCORE(-10); + switch (partnerEffect) { case EFFECT_HELPING_HAND: @@ -3022,16 +3028,6 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(WEAK_EFFECT); } break; - // Don't change weather if ally already decided to do so. - case EFFECT_SUNNY_DAY: - case EFFECT_HAIL: - case EFFECT_SNOWSCAPE: - case EFFECT_RAIN_DANCE: - case EFFECT_SANDSTORM: - case EFFECT_CHILLY_RECEPTION: - if (IsMoveEffectWeather(move)) - ADJUST_SCORE(-10); - break; case EFFECT_AFTER_YOU: if (effect == EFFECT_TRICK_ROOM && !(gFieldStatuses & STATUS_FIELD_TRICK_ROOM) && ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_TRICK_ROOM)) ADJUST_SCORE(DECENT_EFFECT); @@ -4110,7 +4106,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) break; case EFFECT_HAZE: if (AnyStatIsRaised(BATTLE_PARTNER(battlerAtk)) - || PartnerHasSameMoveEffectWithoutTarget(BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)) + || DoesPartnerHaveSameMoveEffect(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove)) break; score += AI_TryToClearStats(battlerAtk, battlerDef, moveTargetsBothOpponents); break; @@ -5243,6 +5239,7 @@ case EFFECT_GUARD_SPLIT: if ((AreAnyHazardsOnSide(GetBattlerSide(battlerAtk)) && CountUsablePartyMons(battlerAtk) != 0) || (gStatuses3[battlerAtk] & STATUS3_LEECHSEED || gBattleMons[battlerAtk].volatiles.wrapped)) ADJUST_SCORE(GOOD_EFFECT); + break; case EFFECT_SPECTRAL_THIEF: ADJUST_SCORE(AI_ShouldCopyStatChanges(battlerAtk, battlerDef)); break; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 8e6f0d79a2..9e4f4e35fd 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -24,6 +24,9 @@ #include "constants/moves.h" #include "constants/items.h" +static u32 GetAIEffectGroup(enum BattleMoveEffects effect); +static u32 GetAIEffectGroupFromMove(u32 battler, u32 move); + // Functions static bool32 AI_IsDoubleSpreadMove(u32 battlerAtk, u32 move) { @@ -2222,21 +2225,37 @@ bool32 HasMoveWithType(u32 battler, u32 type) return FALSE; } -bool32 HasMoveWithEffect(u32 battlerId, enum BattleMoveEffects effect) +bool32 HasMoveWithEffect(u32 battler, enum BattleMoveEffects effect) { s32 i; - u16 *moves = GetMovesArray(battlerId); + u16 *moves = GetMovesArray(battler); for (i = 0; i < MAX_MON_MOVES; i++) { - if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE - && GetMoveEffect(moves[i]) == effect) + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE && GetMoveEffect(moves[i]) == effect) return TRUE; } return FALSE; } +bool32 HasMoveWithAIEffect(u32 battler, u32 aiEffect) +{ + s32 i; + u16 *moves = GetMovesArray(battler); + + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (moves[i] != MOVE_NONE && moves[i] != MOVE_UNAVAILABLE) + { + if (GetAIEffectGroupFromMove(battler, moves[i]) & aiEffect) + return TRUE; + } + } + + return FALSE; +} + bool32 HasBattlerSideMoveWithEffect(u32 battler, u32 effect) { if (HasMoveWithEffect(battler, effect)) @@ -2246,18 +2265,44 @@ bool32 HasBattlerSideMoveWithEffect(u32 battler, u32 effect) return FALSE; } +bool32 HasBattlerSideMoveWithAIEffect(u32 battler, u32 aiEffect) +{ + if (HasMoveWithAIEffect(battler, aiEffect)) + return TRUE; + if (HasPartnerIgnoreFlags(battler) && HasMoveWithAIEffect(BATTLE_PARTNER(battler), aiEffect)) + return TRUE; + return FALSE; +} + // HasBattlerSideMoveWithEffect checks if the AI knows a side has a move effect, -// while HasBattlerSideUsedMoveWithEffect checks if the side has ever used a move effect. -// The former acts the same way as the latter if AI_FLAG_OMNISCIENT isn't used. +// while HasBattlerSideUsedMoveWithEffect checks if the side has actively USED the move effect. +// It matches both on move effect and on AI move effect; eg, EFFECT_HAZE will also bring up Freezy Frost or Clear Smog, anything with AI_EFFECT_RESET_STATS. bool32 HasBattlerSideUsedMoveWithEffect(u32 battler, u32 effect) { + u32 aiEffect = GetAIEffectGroup(effect); u32 i; for (i = 0; i < MAX_MON_MOVES; i++) { if (GetMoveEffect(gBattleHistory->usedMoves[battler][i]) == effect) return TRUE; - if (HasPartnerIgnoreFlags(battler) && GetMoveEffect(gBattleHistory->usedMoves[BATTLE_PARTNER(battler)][i]) == effect) - return TRUE; + + if (aiEffect != AI_EFFECT_NONE) + { + if (GetAIEffectGroupFromMove(battler, gBattleHistory->usedMoves[battler][i]) & aiEffect) + return TRUE; + } + + if (HasPartnerIgnoreFlags(battler)) + { + if (GetMoveEffect(gBattleHistory->usedMoves[BATTLE_PARTNER(battler)][i]) == effect) + return TRUE; + + if (aiEffect != AI_EFFECT_NONE) + { + if (GetAIEffectGroupFromMove(battler, gBattleHistory->usedMoves[BATTLE_PARTNER(battler)][i]) & aiEffect) + return TRUE; + } + } } return FALSE; } @@ -2316,22 +2361,6 @@ bool32 HasBattlerSideMoveWithAdditionalEffect(u32 battler, u32 moveEffect) return FALSE; } -// HasBattlerSideMoveWithAdditionalEffect checks if the AI knows a side has a move effect, -// while HasBattlerSideUsedMoveWithAdditionalEffect checks if the side has ever used a move effect. -// The former acts the same way as the latter if AI_FLAG_OMNISCIENT isn't used. -bool32 HasBattlerSideUsedMoveWithAdditionalEffect(u32 battler, u32 moveEffect) -{ - u32 i; - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (MoveHasAdditionalEffect(gBattleHistory->usedMoves[battler][i], moveEffect)) - return TRUE; - if (HasPartnerIgnoreFlags(battler) && MoveHasAdditionalEffect(gBattleHistory->usedMoves[BATTLE_PARTNER(battler)][i], moveEffect)) - return TRUE; - } - return FALSE; -} - bool32 HasMoveWithCriticalHitChance(u32 battlerId) { s32 i; @@ -3732,8 +3761,7 @@ bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects mo u32 atkSide = GetBattlerSide(battlerAtk); // Don't waste a turn if screens will be broken - if (HasMoveWithEffect(battlerDef, EFFECT_BRICK_BREAK) - || HasMoveWithEffect(battlerDef, EFFECT_RAGING_BULL)) + if (HasMoveWithAIEffect(battlerDef, AI_EFFECT_BREAK_SCREENS)) return FALSE; switch (moveEffect) @@ -3747,13 +3775,13 @@ bool32 ShouldSetScreen(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects mo case EFFECT_REFLECT: // Use only if the player has a physical move and AI doesn't already have Reflect itself active. if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL) - && !(gSideStatuses[atkSide] & SIDE_STATUS_REFLECT)) + && !(gSideStatuses[atkSide] & (SIDE_STATUS_REFLECT | SIDE_STATUS_AURORA_VEIL))) return TRUE; break; case EFFECT_LIGHT_SCREEN: // Use only if the player has a special move and AI doesn't already have Light Screen itself active. if (HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL) - && !(gSideStatuses[atkSide] & SIDE_STATUS_LIGHTSCREEN)) + && !(gSideStatuses[atkSide] & (SIDE_STATUS_LIGHTSCREEN | SIDE_STATUS_AURORA_VEIL))) return TRUE; break; default: @@ -3821,33 +3849,178 @@ u32 GetAllyChosenMove(u32 battlerId) return gBattleMons[partnerBattler].moves[gBattleStruct->chosenMovePositions[partnerBattler]]; } -//PARTNER_MOVE_EFFECT_IS_SAME +bool32 AreMovesEquivalent(u32 battlerAtk, u32 battlerAtkPartner, u32 move, u32 partnerMove) +{ + if (!IsBattlerAlive(battlerAtkPartner) || partnerMove == MOVE_NONE) + return FALSE; + + u32 battlerDef = gBattleStruct->moveTarget[battlerAtk]; + + // We don't care the effect is basically the same; we would use this move anyway. + if (GetBestDmgMoveFromBattler(battlerAtk, battlerDef, AI_ATTACKING) == move) + return FALSE; + + u32 atkEffect = GetAIEffectGroupFromMove(battlerAtk, move); + u32 partnerEffect = GetAIEffectGroupFromMove(battlerAtkPartner, partnerMove); + + // shared bits indicate they're meaningfully the same in some way + if (atkEffect & partnerEffect) + { + if (gMovesInfo[move].target == MOVE_TARGET_SELECTED && gMovesInfo[partnerMove].target == MOVE_TARGET_SELECTED) + { + if (battlerDef == gBattleStruct->moveTarget[battlerAtkPartner]) + return TRUE; + else + return FALSE; + } + return TRUE; + } + return FALSE; +} + +static u32 GetAIEffectGroup(enum BattleMoveEffects effect) +{ + u32 aiEffect = AI_EFFECT_NONE; + + switch (effect) + { + case EFFECT_SUNNY_DAY: + case EFFECT_RAIN_DANCE: + case EFFECT_SANDSTORM: + case EFFECT_HAIL: + case EFFECT_SNOWSCAPE: + case EFFECT_CHILLY_RECEPTION: + aiEffect |= AI_EFFECT_WEATHER; + break; + case EFFECT_ELECTRIC_TERRAIN: + case EFFECT_GRASSY_TERRAIN: + case EFFECT_MISTY_TERRAIN: + case EFFECT_PSYCHIC_TERRAIN: + case EFFECT_STEEL_ROLLER: + case EFFECT_ICE_SPINNER: + aiEffect |= AI_EFFECT_TERRAIN; + break; + case EFFECT_COURT_CHANGE: + aiEffect |= AI_EFFECT_CLEAR_HAZARDS | AI_EFFECT_AURORA_VEIL | AI_EFFECT_BREAK_SCREENS; + break; + case EFFECT_DEFOG: + aiEffect |= AI_EFFECT_CLEAR_HAZARDS | AI_EFFECT_BREAK_SCREENS; + break; + case EFFECT_RAPID_SPIN: + case EFFECT_TIDY_UP: + aiEffect |= AI_EFFECT_CLEAR_HAZARDS; + break; + case EFFECT_BRICK_BREAK: + case EFFECT_RAGING_BULL: + aiEffect |= AI_EFFECT_BREAK_SCREENS; + break; + case EFFECT_HAZE: + aiEffect |= AI_EFFECT_RESET_STATS; + break; + case EFFECT_HIT_SWITCH_TARGET: + case EFFECT_ROAR: + aiEffect |= AI_EFFECT_FORCE_SWITCH; + break; + case EFFECT_TORMENT: + aiEffect |= AI_EFFECT_TORMENT; + break; + case EFFECT_AURORA_VEIL: + aiEffect |= AI_EFFECT_AURORA_VEIL; + break; + case EFFECT_LIGHT_SCREEN: + aiEffect |= AI_EFFECT_LIGHT_SCREEN; + break; + case EFFECT_REFLECT: + aiEffect |= AI_EFFECT_REFLECT; + break; + case EFFECT_GRAVITY: + aiEffect |= AI_EFFECT_GRAVITY; + break; + case EFFECT_DOODLE: + case EFFECT_ENTRAINMENT: + case EFFECT_GASTRO_ACID: + case EFFECT_ROLE_PLAY: + case EFFECT_SIMPLE_BEAM: + case EFFECT_SKILL_SWAP: + case EFFECT_WORRY_SEED: + aiEffect |= AI_EFFECT_CHANGE_ABILITY; + break; + default: + break; + } + return aiEffect; +} + +static u32 GetAIEffectGroupFromMove(u32 battler, u32 move) +{ + u32 aiEffect = GetAIEffectGroup(GetMoveEffect(move)); + + u32 i; + u32 additionalEffectCount = GetMoveAdditionalEffectCount(move); + for (i = 0; i < additionalEffectCount; i++) + { + switch (GetMoveAdditionalEffectById(move, i)->moveEffect) + { + case MOVE_EFFECT_SUN: + case MOVE_EFFECT_RAIN: + case MOVE_EFFECT_SANDSTORM: + case MOVE_EFFECT_HAIL: + aiEffect |= AI_EFFECT_WEATHER; + break; + case MOVE_EFFECT_ELECTRIC_TERRAIN: + case MOVE_EFFECT_GRASSY_TERRAIN: + case MOVE_EFFECT_MISTY_TERRAIN: + case MOVE_EFFECT_PSYCHIC_TERRAIN: + aiEffect |= AI_EFFECT_TERRAIN; + break; + case MOVE_EFFECT_DEFOG: + aiEffect |= AI_EFFECT_CLEAR_HAZARDS | AI_EFFECT_BREAK_SCREENS; + break; + case MOVE_EFFECT_CLEAR_SMOG: + case MOVE_EFFECT_HAZE: + aiEffect |= AI_EFFECT_RESET_STATS; + break; + case MOVE_EFFECT_TORMENT_SIDE: + aiEffect |= AI_EFFECT_TORMENT; + break; + case MOVE_EFFECT_LIGHT_SCREEN: + aiEffect |= AI_EFFECT_LIGHT_SCREEN; + break; + case MOVE_EFFECT_REFLECT: + aiEffect |= AI_EFFECT_REFLECT; + break; + case MOVE_EFFECT_AURORA_VEIL: + aiEffect |= AI_EFFECT_AURORA_VEIL; + break; + case MOVE_EFFECT_GRAVITY: + aiEffect |= AI_EFFECT_GRAVITY; + break; + default: + break; + } + } + + return aiEffect; +} + +// It matches both on move effect and on AI move effect; eg, EFFECT_HAZE will also bring up Freezy Frost or Clear Smog, anything with AI_EFFECT_RESET_STATS. bool32 DoesPartnerHaveSameMoveEffect(u32 battlerAtkPartner, u32 battlerDef, u32 move, u32 partnerMove) { if (!HasPartner(battlerAtkPartner)) return FALSE; if (GetMoveEffect(move) == GetMoveEffect(partnerMove) - && partnerMove != MOVE_NONE - && gBattleStruct->moveTarget[battlerAtkPartner] == battlerDef) + && partnerMove != MOVE_NONE) { + if (gMovesInfo[move].target == MOVE_TARGET_SELECTED && gMovesInfo[partnerMove].target == MOVE_TARGET_SELECTED) + { + return gBattleStruct->moveTarget[battlerAtkPartner] == battlerDef; + } return TRUE; } return FALSE; } -//PARTNER_MOVE_EFFECT_IS_SAME_NO_TARGET -bool32 PartnerHasSameMoveEffectWithoutTarget(u32 battlerAtkPartner, u32 move, u32 partnerMove) -{ - if (!HasPartner(battlerAtkPartner)) - return FALSE; - - if (GetMoveEffect(move) == GetMoveEffect(partnerMove) - && partnerMove != MOVE_NONE) - return TRUE; - return FALSE; -} - //PARTNER_MOVE_EFFECT_IS_STATUS_SAME_TARGET bool32 PartnerMoveEffectIsStatusSameTarget(u32 battlerAtkPartner, u32 battlerDef, u32 partnerMove) { @@ -3868,37 +4041,6 @@ bool32 PartnerMoveEffectIsStatusSameTarget(u32 battlerAtkPartner, u32 battlerDef return FALSE; } -bool32 IsMoveEffectWeather(u32 move) -{ - enum BattleMoveEffects effect = GetMoveEffect(move); - if (move != MOVE_NONE - && (effect == EFFECT_SUNNY_DAY - || effect == EFFECT_RAIN_DANCE - || effect == EFFECT_SANDSTORM - || effect == EFFECT_HAIL - || effect == EFFECT_SNOWSCAPE - || effect == EFFECT_CHILLY_RECEPTION)) - return TRUE; - return FALSE; -} - -//PARTNER_MOVE_EFFECT_IS_TERRAIN -bool32 PartnerMoveEffectIsTerrain(u32 battlerAtkPartner, u32 partnerMove) -{ - if (!HasPartner(battlerAtkPartner)) - return FALSE; - - enum BattleMoveEffects partnerEffect = GetMoveEffect(partnerMove); - if (partnerMove != MOVE_NONE - && (partnerEffect == EFFECT_GRASSY_TERRAIN - || partnerEffect == EFFECT_MISTY_TERRAIN - || partnerEffect == EFFECT_ELECTRIC_TERRAIN - || partnerEffect == EFFECT_PSYCHIC_TERRAIN)) - return TRUE; - - return FALSE; -} - //PARTNER_MOVE_EFFECT_IS bool32 PartnerMoveEffectIs(u32 battlerAtkPartner, u32 partnerMove, enum BattleMoveEffects effectCheck) { @@ -4363,17 +4505,14 @@ static enum AIScore IncreaseStatUpScoreInternal(u32 battlerAtk, u32 battlerDef, if (HasBattlerSideMoveWithEffect(battlerDef, EFFECT_ENCORE)) return NO_INCREASE; - // Don't increase stats if opposing battler has used Haze effect - if (!RandomPercentage(RNG_AI_BOOST_INTO_HAZE, BOOST_INTO_HAZE_CHANCE) && - (HasBattlerSideUsedMoveWithEffect(battlerDef, EFFECT_HAZE) - || HasBattlerSideUsedMoveWithAdditionalEffect(battlerDef, MOVE_EFFECT_CLEAR_SMOG) - || HasBattlerSideUsedMoveWithAdditionalEffect(battlerDef, MOVE_EFFECT_HAZE))) + // Don't increase stats if opposing battler has used Haze effect or AI effect + if (!RandomPercentage(RNG_AI_BOOST_INTO_HAZE, BOOST_INTO_HAZE_CHANCE) + && HasBattlerSideUsedMoveWithEffect(battlerDef, EFFECT_HAZE)) return NO_INCREASE; // Don't increase if AI is at +1 and opponent has Haze effect - if (gBattleMons[battlerAtk].statStages[statId] >= MAX_STAT_STAGE - 5 && (HasBattlerSideMoveWithEffect(battlerDef, EFFECT_HAZE) - || HasBattlerSideMoveWithAdditionalEffect(battlerDef, MOVE_EFFECT_CLEAR_SMOG) - || HasBattlerSideMoveWithAdditionalEffect(battlerDef, MOVE_EFFECT_HAZE))) + if (gBattleMons[battlerAtk].statStages[statId] >= MAX_STAT_STAGE - 5 + && HasBattlerSideMoveWithAIEffect(battlerDef, AI_EFFECT_RESET_STATS)) return NO_INCREASE; // Don't increase stats if AI could KO target through Sturdy effect, as otherwise it always 2HKOs @@ -4976,11 +5115,7 @@ bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef) bool32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, u32 move, struct AiLogicData *aiData) { if (CountUsablePartyMons(battlerDef) == 0 - || HasBattlerSideMoveWithEffect(battlerDef, EFFECT_COURT_CHANGE) - || HasBattlerSideMoveWithEffect(battlerDef, EFFECT_DEFOG) - || HasBattlerSideMoveWithEffect(battlerDef, EFFECT_RAPID_SPIN) - || HasBattlerSideMoveWithEffect(battlerDef, EFFECT_TIDY_UP) - || HasBattlerSideMoveWithEffect(battlerDef, MOVE_EFFECT_DEFOG)) + || HasBattlerSideMoveWithAIEffect(battlerDef, AI_EFFECT_CLEAR_HAZARDS)) return FALSE; if (IsBattleMoveStatus(move)) @@ -5023,27 +5158,17 @@ void IncreaseTidyUpScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score) bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, struct AiLogicData *aiData) { u32 preventsStatLoss; - u32 partnerAbility; - u32 partnerHoldEffect = aiData->holdEffects[battlerAtkPartner]; + u32 partnerAbility = aiData->abilities[battlerAtkPartner]; u32 opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battlerAtk)); u32 opposingBattler = GetBattlerAtPosition(opposingPosition); - if (DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move)) - partnerAbility = ABILITY_NONE; - else - partnerAbility = aiData->abilities[battlerAtkPartner]; - if (gBattleMons[battlerAtkPartner].statStages[STAT_ATK] == MAX_STAT_STAGE || partnerAbility == ABILITY_CONTRARY || partnerAbility == ABILITY_GOOD_AS_GOLD - || HasMoveWithEffect(BATTLE_OPPOSITE(battlerAtk), EFFECT_FOUL_PLAY) - || HasMoveWithEffect(BATTLE_OPPOSITE(battlerAtkPartner), EFFECT_FOUL_PLAY)) + || HasBattlerSideMoveWithEffect(FOE(battlerAtk), EFFECT_FOUL_PLAY)) return FALSE; - preventsStatLoss = (partnerAbility == ABILITY_CLEAR_BODY - || partnerAbility == ABILITY_FULL_METAL_BODY - || partnerAbility == ABILITY_WHITE_SMOKE - || partnerHoldEffect == HOLD_EFFECT_CLEAR_AMULET); + preventsStatLoss = !CanLowerStat(battlerAtk, battlerAtkPartner, aiData, STAT_DEF); switch (GetMoveEffect(aiData->partnerMove)) { diff --git a/test/battle/ai/ai_doubles.c b/test/battle/ai/ai_doubles.c index e0eac1eeed..ab6b4a4425 100644 --- a/test/battle/ai/ai_doubles.c +++ b/test/battle/ai/ai_doubles.c @@ -90,7 +90,7 @@ TO_DO_BATTLE_TEST("AI understands Instruct") TO_DO_BATTLE_TEST("AI understands Quick Guard") TO_DO_BATTLE_TEST("AI understands Wide Guard") -AI_DOUBLE_BATTLE_TEST("AI will not use the same nondamaging move as its partner for no reason") +AI_DOUBLE_BATTLE_TEST("AI won't use the same nondamaging move as its partner for no reason") { u32 move; PARAMETRIZE { move = MOVE_AROMATHERAPY; }