From fab4dc1163b48212a13fffb75839c8ae318e0178 Mon Sep 17 00:00:00 2001 From: surskitty Date: Fri, 12 Sep 2025 17:16:45 -0400 Subject: [PATCH] Adjusted AI handling for Gravity; AI for weather/field status additional effects. (#7651) --- include/battle_ai_util.h | 8 +- src/battle_ai_field_statuses.c | 80 ++++++++++---- src/battle_ai_main.c | 170 +++++++++++++++++++++-------- src/battle_ai_util.c | 35 +++--- test/battle/ai/can_use_all_moves.c | 2 +- test/battle/move_effect/gravity.c | 33 ++++++ 6 files changed, 231 insertions(+), 97 deletions(-) diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index ec517d5a27..02e34961c8 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -4,7 +4,10 @@ #include "battle_ai_main.h" #include "battle_ai_field_statuses.h" -#define FOE(battler) ((BATTLE_OPPOSITE(battler)) & BIT_SIDE) +// Left and right are determined by how they're referred to in tests and everywhere else. +// Left is battlers 0 and 1, right 2 and 3; if you assume the battler referencing them is south, left is to the northeast and right to the northwest. +#define LEFT_FOE(battler) ((BATTLE_OPPOSITE(battler)) & BIT_SIDE) +#define RIGHT_FOE(battler) (((BATTLE_OPPOSITE(battler)) & BIT_SIDE) | BIT_FLANK) // Roll boundaries used by AI when scoring. Doesn't affect actual damage dealt. #define MAX_ROLL_PERCENTAGE DMG_ROLL_PERCENT_HI @@ -182,7 +185,7 @@ bool32 HasBattlerSideMoveWithAdditionalEffect(u32 battler, u32 moveEffect); bool32 HasMoveWithCriticalHitChance(u32 battlerId); bool32 HasMoveWithMoveEffectExcept(u32 battlerId, u32 moveEffect, enum BattleMoveEffects exception); bool32 HasMoveThatLowersOwnStats(u32 battlerId); -bool32 HasMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef, u32 accCheck, bool32 ignoreStatus, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect); +bool32 HasMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef, u32 accCheck, bool32 ignoreStatus); bool32 HasAnyKnownMove(u32 battlerId); bool32 IsAromaVeilProtectedEffect(enum BattleMoveEffects moveEffect); bool32 IsNonVolatileStatusMove(u32 moveEffect); @@ -299,7 +302,6 @@ 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_field_statuses.c b/src/battle_ai_field_statuses.c index bd7bdd8e17..968bd31628 100644 --- a/src/battle_ai_field_statuses.c +++ b/src/battle_ai_field_statuses.c @@ -44,6 +44,7 @@ static enum FieldEffectOutcome BenefitsFromElectricTerrain(u32 battler); static enum FieldEffectOutcome BenefitsFromGrassyTerrain(u32 battler); static enum FieldEffectOutcome BenefitsFromMistyTerrain(u32 battler); static enum FieldEffectOutcome BenefitsFromPsychicTerrain(u32 battler); +static enum FieldEffectOutcome BenefitsFromGravity(u32 battler); static enum FieldEffectOutcome BenefitsFromTrickRoom(u32 battler); bool32 WeatherChecker(u32 battler, u32 weather, enum FieldEffectOutcome desiredResult) @@ -108,6 +109,8 @@ bool32 FieldStatusChecker(u32 battler, u32 fieldStatus, enum FieldEffectOutcome result = BenefitsFromPsychicTerrain(battler); // other field statuses + if (fieldStatus & STATUS_FIELD_GRAVITY) + result = BenefitsFromGravity(battler); if (fieldStatus & STATUS_FIELD_TRICK_ROOM) result = BenefitsFromTrickRoom(battler); @@ -248,9 +251,9 @@ static enum FieldEffectOutcome BenefitsFromSandstorm(u32 battler) if (gAiLogicData->holdEffects[battler] == HOLD_EFFECT_SAFETY_GOGGLES || IS_BATTLER_ANY_TYPE(battler, TYPE_ROCK, TYPE_GROUND, TYPE_STEEL)) { - if (!(IS_BATTLER_ANY_TYPE(FOE(battler), TYPE_ROCK, TYPE_GROUND, TYPE_STEEL)) - || gAiLogicData->holdEffects[FOE(battler)] == HOLD_EFFECT_SAFETY_GOGGLES - || DoesAbilityBenefitFromWeather(gAiLogicData->abilities[FOE(battler)], B_WEATHER_SANDSTORM)) + if (!(IS_BATTLER_ANY_TYPE(LEFT_FOE(battler), TYPE_ROCK, TYPE_GROUND, TYPE_STEEL)) + || gAiLogicData->holdEffects[LEFT_FOE(battler)] == HOLD_EFFECT_SAFETY_GOGGLES + || DoesAbilityBenefitFromWeather(gAiLogicData->abilities[LEFT_FOE(battler)], B_WEATHER_SANDSTORM)) return FIELD_EFFECT_POSITIVE; else return FIELD_EFFECT_NEUTRAL; @@ -274,7 +277,7 @@ static enum FieldEffectOutcome BenefitsFromHailOrSnow(u32 battler, u32 weather) if (HasLightSensitiveMove(battler)) return FIELD_EFFECT_NEGATIVE; - if (HasMoveWithFlag(FOE(battler), MoveAlwaysHitsInHailSnow)) + if (HasMoveWithFlag(LEFT_FOE(battler), MoveAlwaysHitsInHailSnow)) return FIELD_EFFECT_NEGATIVE; return FIELD_EFFECT_NEUTRAL; @@ -294,7 +297,7 @@ static enum FieldEffectOutcome BenefitsFromRain(u32 battler) if (HasLightSensitiveMove(battler) || HasDamagingMoveOfType(battler, TYPE_FIRE)) return FIELD_EFFECT_NEGATIVE; - if (HasMoveWithFlag(FOE(battler), MoveAlwaysHitsInRain)) + if (HasMoveWithFlag(LEFT_FOE(battler), MoveAlwaysHitsInRain)) return FIELD_EFFECT_NEGATIVE; return FIELD_EFFECT_NEUTRAL; @@ -309,11 +312,12 @@ static enum FieldEffectOutcome BenefitsFromElectricTerrain(u32 battler) if (HasMoveWithEffect(battler, EFFECT_RISING_VOLTAGE)) return FIELD_EFFECT_POSITIVE; - if (HasMoveWithEffect(FOE(battler), EFFECT_REST) && IsBattlerGrounded(FOE(battler))) + if ((HasMoveWithEffect(LEFT_FOE(battler), EFFECT_REST) && IsBattlerGrounded(LEFT_FOE(battler))) + || (HasMoveWithEffect(RIGHT_FOE(battler), EFFECT_REST) && IsBattlerGrounded(RIGHT_FOE(battler)))) return FIELD_EFFECT_POSITIVE; bool32 grounded = IsBattlerGrounded(battler); - if (grounded && HasBattlerSideMoveWithAdditionalEffect(FOE(battler), MOVE_EFFECT_SLEEP)) + if (grounded && HasBattlerSideMoveWithAdditionalEffect(LEFT_FOE(battler), MOVE_EFFECT_SLEEP)) return FIELD_EFFECT_POSITIVE; if (grounded && ((gBattleMons[battler].status1 & STATUS1_SLEEP) @@ -321,7 +325,7 @@ static enum FieldEffectOutcome BenefitsFromElectricTerrain(u32 battler) || HasDamagingMoveOfType(battler, TYPE_ELECTRIC))) return FIELD_EFFECT_POSITIVE; - if (HasBattlerSideMoveWithEffect(FOE(battler), EFFECT_RISING_VOLTAGE)) + if (HasBattlerSideMoveWithEffect(LEFT_FOE(battler), EFFECT_RISING_VOLTAGE)) return FIELD_EFFECT_NEGATIVE; @@ -334,7 +338,7 @@ static enum FieldEffectOutcome BenefitsFromGrassyTerrain(u32 battler) if (DoesAbilityBenefitFromFieldStatus(gAiLogicData->abilities[battler], STATUS_FIELD_GRASSY_TERRAIN)) return FIELD_EFFECT_POSITIVE; - if (HasMoveWithEffect(battler, EFFECT_GRASSY_GLIDE)) + if (HasBattlerSideMoveWithEffect(battler, EFFECT_GRASSY_GLIDE)) return FIELD_EFFECT_POSITIVE; if (HasMoveWithAdditionalEffect(battler, MOVE_EFFECT_FLORAL_HEALING)) return FIELD_EFFECT_POSITIVE; @@ -342,14 +346,14 @@ static enum FieldEffectOutcome BenefitsFromGrassyTerrain(u32 battler) bool32 grounded = IsBattlerGrounded(battler); // Weaken spamming Earthquake, Magnitude, and Bulldoze. - if (grounded && (HasBattlerSideMoveWithEffect(FOE(battler), EFFECT_EARTHQUAKE) - || HasBattlerSideMoveWithEffect(FOE(battler), EFFECT_MAGNITUDE))) + if (grounded && (HasBattlerSideMoveWithEffect(LEFT_FOE(battler), EFFECT_EARTHQUAKE) + || HasBattlerSideMoveWithEffect(LEFT_FOE(battler), EFFECT_MAGNITUDE))) return FIELD_EFFECT_POSITIVE; if (grounded && HasDamagingMoveOfType(battler, TYPE_GRASS)) return FIELD_EFFECT_POSITIVE; - if (HasBattlerSideMoveWithEffect(FOE(battler), EFFECT_GRASSY_GLIDE)) + if (HasBattlerSideMoveWithEffect(LEFT_FOE(battler), EFFECT_GRASSY_GLIDE)) return FIELD_EFFECT_NEGATIVE; @@ -370,16 +374,17 @@ static enum FieldEffectOutcome BenefitsFromMistyTerrain(u32 battler) if (HasPartner(battler)) allyGrounded = IsBattlerGrounded(BATTLE_PARTNER(battler)); - if (HasMoveWithEffect(FOE(battler), EFFECT_REST) && IsBattlerGrounded(FOE(battler))) + if ((HasMoveWithEffect(LEFT_FOE(battler), EFFECT_REST) && IsBattlerGrounded(LEFT_FOE(battler))) + || (HasMoveWithEffect(RIGHT_FOE(battler), EFFECT_REST) && IsBattlerGrounded(RIGHT_FOE(battler)))) return FIELD_EFFECT_POSITIVE; // harass dragons if ((grounded || allyGrounded) - && (HasDamagingMoveOfType(FOE(battler), TYPE_DRAGON) || HasDamagingMoveOfType(BATTLE_PARTNER(FOE(battler)), TYPE_DRAGON))) + && (HasDamagingMoveOfType(LEFT_FOE(battler), TYPE_DRAGON) || HasDamagingMoveOfType(RIGHT_FOE(battler), TYPE_DRAGON))) return FIELD_EFFECT_POSITIVE; if ((grounded || allyGrounded) - && (HasNonVolatileMoveEffect(FOE(battler), MOVE_EFFECT_SLEEP) || HasNonVolatileMoveEffect(BATTLE_PARTNER(FOE(battler)), MOVE_EFFECT_SLEEP))) + && (HasNonVolatileMoveEffect(LEFT_FOE(battler), MOVE_EFFECT_SLEEP) || HasNonVolatileMoveEffect(RIGHT_FOE(battler), MOVE_EFFECT_SLEEP))) return FIELD_EFFECT_POSITIVE; if (grounded && (gBattleMons[battler].status1 & STATUS1_SLEEP || gBattleMons[battler].volatiles.yawn)) @@ -406,16 +411,16 @@ static enum FieldEffectOutcome BenefitsFromPsychicTerrain(u32 battler) if (grounded || allyGrounded) { // harass priority - if (HasBattlerSideAbility(FOE(battler), ABILITY_GALE_WINGS, gAiLogicData) - || HasBattlerSideAbility(FOE(battler), ABILITY_TRIAGE, gAiLogicData) - || HasBattlerSideAbility(FOE(battler), ABILITY_PRANKSTER, gAiLogicData)) + if (HasBattlerSideAbility(LEFT_FOE(battler), ABILITY_GALE_WINGS, gAiLogicData) + || HasBattlerSideAbility(LEFT_FOE(battler), ABILITY_TRIAGE, gAiLogicData) + || HasBattlerSideAbility(LEFT_FOE(battler), ABILITY_PRANKSTER, gAiLogicData)) return FIELD_EFFECT_POSITIVE; } - if (grounded && (HasDamagingMoveOfType(battler, TYPE_PSYCHIC))) + if (grounded && HasDamagingMoveOfType(battler, TYPE_PSYCHIC)) return FIELD_EFFECT_POSITIVE; - if (HasBattlerSideMoveWithEffect(FOE(battler), EFFECT_EXPANDING_FORCE)) + if (HasBattlerSideMoveWithEffect(LEFT_FOE(battler), EFFECT_EXPANDING_FORCE)) return FIELD_EFFECT_NEGATIVE; if (HasBattlerSideAbility(battler, ABILITY_GALE_WINGS, gAiLogicData) @@ -426,15 +431,44 @@ static enum FieldEffectOutcome BenefitsFromPsychicTerrain(u32 battler) return FIELD_EFFECT_NEUTRAL; } +static enum FieldEffectOutcome BenefitsFromGravity(u32 battler) +{ + if (!IsBattlerGrounded(battler)) + return FIELD_EFFECT_NEGATIVE; + + if (HasBattlerSideAbility(battler, ABILITY_HUSTLE, gAiLogicData)) + return FIELD_EFFECT_POSITIVE; + + if (HasMoveWithFlag(battler, IsMoveGravityBanned)) + return FIELD_EFFECT_NEGATIVE; + + if (IsBattlerAlive(LEFT_FOE(battler))) + { + if (HasMoveWithLowAccuracy(battler, LEFT_FOE(battler), LOW_ACCURACY_THRESHOLD, FALSE) + || (!IsBattlerGrounded(LEFT_FOE(battler)) && HasDamagingMoveOfType(battler, TYPE_GROUND))) + return FIELD_EFFECT_POSITIVE; + } + + if (IsBattlerAlive(RIGHT_FOE(battler))) + { + if (HasMoveWithLowAccuracy(battler, RIGHT_FOE(battler), LOW_ACCURACY_THRESHOLD, FALSE) + || (!IsBattlerGrounded(RIGHT_FOE(battler)) && HasDamagingMoveOfType(battler, TYPE_GROUND))) + return FIELD_EFFECT_POSITIVE; + } + + return FIELD_EFFECT_NEUTRAL; +} + + static enum FieldEffectOutcome BenefitsFromTrickRoom(u32 battler) { // If we're in singles, we literally only care about speed. if (IsBattle1v1()) { - if (gAiLogicData->speedStats[battler] < gAiLogicData->speedStats[FOE(battler)]) + if (gAiLogicData->speedStats[battler] < gAiLogicData->speedStats[LEFT_FOE(battler)]) return FIELD_EFFECT_POSITIVE; // If we tie, we shouldn't change trick room state. - else if (gAiLogicData->speedStats[battler] == gAiLogicData->speedStats[FOE(battler)]) + else if (gAiLogicData->speedStats[battler] == gAiLogicData->speedStats[LEFT_FOE(battler)]) return FIELD_EFFECT_NEUTRAL; else return FIELD_EFFECT_NEGATIVE; @@ -455,7 +489,7 @@ static enum FieldEffectOutcome BenefitsFromTrickRoom(u32 battler) } // If we are faster or tie, we don't want trick room. - if ((gAiLogicData->speedStats[battler] >= gAiLogicData->speedStats[FOE(battler)]) || (gAiLogicData->speedStats[battler] >= gAiLogicData->speedStats[BATTLE_PARTNER(FOE(battler))])) + if ((gAiLogicData->speedStats[battler] >= gAiLogicData->speedStats[LEFT_FOE(battler)]) || (gAiLogicData->speedStats[battler] >= gAiLogicData->speedStats[RIGHT_FOE(battler)])) return FIELD_EFFECT_NEGATIVE; return FIELD_EFFECT_POSITIVE; diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index cedb2db5e7..bd1ebe3a36 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -1,5 +1,3 @@ -// Note that FOE specifically returns the left-side battler; BATTLE_OPPOSITE is the diagonal. - #include "global.h" #include "main.h" #include "malloc.h" @@ -1892,15 +1890,15 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { if (CountUsablePartyMons(battlerAtk) == 0 && aiData->abilities[battlerAtk] != ABILITY_SOUNDPROOF - && aiData->abilities[BATTLE_PARTNER(battlerAtk)] != ABILITY_SOUNDPROOF - && CountUsablePartyMons(FOE(battlerAtk)) >= 1) + && CountUsablePartyMons(battlerDef) >= 1 + && (aiData->abilities[BATTLE_PARTNER(battlerAtk)] != ABILITY_SOUNDPROOF || !IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))) { ADJUST_SCORE(-10); //Don't wipe your team if you're going to lose } - else if ((!IsBattlerAlive(FOE(battlerAtk)) || aiData->abilities[FOE(battlerAtk)] == ABILITY_SOUNDPROOF - || gBattleMons[FOE(battlerAtk)].volatiles.perishSong) - && (!IsBattlerAlive(BATTLE_PARTNER(FOE(battlerAtk))) || aiData->abilities[BATTLE_PARTNER(FOE(battlerAtk))] == ABILITY_SOUNDPROOF - || gBattleMons[BATTLE_PARTNER(FOE(battlerAtk))].volatiles.perishSong)) + else if ((!IsBattlerAlive(LEFT_FOE(battlerAtk)) || aiData->abilities[LEFT_FOE(battlerAtk)] == ABILITY_SOUNDPROOF + || gBattleMons[LEFT_FOE(battlerAtk)].volatiles.perishSong) + && (!IsBattlerAlive(RIGHT_FOE(battlerAtk)) || aiData->abilities[RIGHT_FOE(battlerAtk)] == ABILITY_SOUNDPROOF + || gBattleMons[RIGHT_FOE(battlerAtk)].volatiles.perishSong)) { ADJUST_SCORE(-10); //Both enemies are perish songed } @@ -1915,7 +1913,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) && CountUsablePartyMons(battlerDef) >= 1) ADJUST_SCORE(-10); - if (gBattleMons[FOE(battlerAtk)].volatiles.perishSong || aiData->abilities[FOE(battlerAtk)] == ABILITY_SOUNDPROOF) + if (gBattleMons[battlerDef].volatiles.perishSong || aiData->abilities[battlerDef] == ABILITY_SOUNDPROOF) ADJUST_SCORE(-10); } break; @@ -2345,11 +2343,11 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) ADJUST_SCORE(-9); break; case EFFECT_COURT_CHANGE: - if (gSideStatuses[GetBattlerSide(FOE(battlerAtk))] & SIDE_STATUS_BAD_COURT) + if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_BAD_COURT) ADJUST_SCORE(BAD_EFFECT); if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_GOOD_COURT) ADJUST_SCORE(BAD_EFFECT); - if (AreAnyHazardsOnSide(GetBattlerSide(FOE(battlerAtk))) && CountUsablePartyMons(battlerAtk) != 0) + if (AreAnyHazardsOnSide(GetBattlerSide(battlerDef)) && CountUsablePartyMons(battlerAtk) != 0) ADJUST_SCORE(WORST_EFFECT); if (hasPartner) { @@ -3116,38 +3114,38 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) } else { - u32 ownHitsToKOFoe1 = GetBestNoOfHitsToKO(battlerAtk, BATTLE_OPPOSITE(battlerAtk), AI_ATTACKING); - u32 partnerHitsToKOFoe1 = GetBestNoOfHitsToKO(battlerAtkPartner, BATTLE_OPPOSITE(battlerAtk), AI_ATTACKING); - u32 ownHitsToKOFoe2 = GetBestNoOfHitsToKO(battlerAtk, BATTLE_OPPOSITE(battlerAtkPartner), AI_ATTACKING); - u32 partnerHitsToKOFoe2 = GetBestNoOfHitsToKO(battlerAtkPartner, BATTLE_OPPOSITE(battlerAtkPartner), AI_ATTACKING); + u32 ownHitsToKOFoe1 = GetBestNoOfHitsToKO(battlerAtk, LEFT_FOE(battlerAtk), AI_ATTACKING); + u32 partnerHitsToKOFoe1 = GetBestNoOfHitsToKO(battlerAtkPartner, LEFT_FOE(battlerAtk), AI_ATTACKING); + u32 ownHitsToKOFoe2 = GetBestNoOfHitsToKO(battlerAtk, RIGHT_FOE(battlerAtk), AI_ATTACKING); + u32 partnerHitsToKOFoe2 = GetBestNoOfHitsToKO(battlerAtkPartner, RIGHT_FOE(battlerAtk), AI_ATTACKING); if (hasTwoOpponents) { // Might be about to die - if (CanTargetFaintAi(BATTLE_OPPOSITE(battlerAtk), battlerAtk) && CanTargetFaintAi(BATTLE_OPPOSITE(battlerAtkPartner), battlerAtk) - && AI_IsSlower(battlerAtk, BATTLE_OPPOSITE(battlerAtk), move, predictedMove, DONT_CONSIDER_PRIORITY) - && AI_IsSlower(battlerAtk, BATTLE_OPPOSITE(battlerAtkPartner), move, predictedMove, DONT_CONSIDER_PRIORITY)) + if (CanTargetFaintAi(LEFT_FOE(battlerAtk), battlerAtk) && CanTargetFaintAi(RIGHT_FOE(battlerAtk), battlerAtk) + && AI_IsSlower(battlerAtk, LEFT_FOE(battlerAtk), move, predictedMove, DONT_CONSIDER_PRIORITY) + && AI_IsSlower(battlerAtk, RIGHT_FOE(battlerAtk), move, predictedMove, DONT_CONSIDER_PRIORITY)) ADJUST_SCORE(GOOD_EFFECT); if (ownHitsToKOFoe1 > partnerHitsToKOFoe1 && partnerHitsToKOFoe1 > 1 && ownHitsToKOFoe2 > partnerHitsToKOFoe2 && partnerHitsToKOFoe2 > 1) ADJUST_SCORE(GOOD_EFFECT); } - else if (IsBattlerAlive(BATTLE_OPPOSITE(battlerAtk))) + else if (IsBattlerAlive(LEFT_FOE(battlerAtk))) { // Might be about to die - if (CanTargetFaintAi(BATTLE_OPPOSITE(battlerAtk), battlerAtk) - && AI_IsSlower(battlerAtk, BATTLE_OPPOSITE(battlerAtk), move, predictedMove, DONT_CONSIDER_PRIORITY)) + if (CanTargetFaintAi(LEFT_FOE(battlerAtk), battlerAtk) + && AI_IsSlower(battlerAtk, LEFT_FOE(battlerAtk), move, predictedMove, DONT_CONSIDER_PRIORITY)) ADJUST_SCORE(GOOD_EFFECT); if (ownHitsToKOFoe1 > partnerHitsToKOFoe1 && partnerHitsToKOFoe1 > 1) ADJUST_SCORE(GOOD_EFFECT); } - else if (IsBattlerAlive(BATTLE_OPPOSITE(battlerAtkPartner))) + else if (IsBattlerAlive(RIGHT_FOE(battlerAtk))) { // Might be about to die - if (CanTargetFaintAi(BATTLE_OPPOSITE(battlerAtkPartner), battlerAtk) - && AI_IsSlower(battlerAtk, BATTLE_OPPOSITE(battlerAtkPartner), move, predictedMove, DONT_CONSIDER_PRIORITY)) + if (CanTargetFaintAi(RIGHT_FOE(battlerAtk), battlerAtk) + && AI_IsSlower(battlerAtk, RIGHT_FOE(battlerAtk), move, predictedMove, DONT_CONSIDER_PRIORITY)) ADJUST_SCORE(GOOD_EFFECT); if (ownHitsToKOFoe2 > partnerHitsToKOFoe2 && partnerHitsToKOFoe2 > 1) @@ -3663,8 +3661,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, 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 (AI_IsSlower(battlerAtkPartner, LEFT_FOE(battlerAtk), aiData->partnerMove, predictedMoveSpeedCheck, CONSIDER_PRIORITY) // Opponent mon 1 goes before partner + && AI_IsSlower(battlerAtkPartner, RIGHT_FOE(battlerAtk), 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 @@ -3673,8 +3671,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, predictedMoveSpeedCheck, CONSIDER_PRIORITY) - && AI_IsFaster(battlerAtk, BATTLE_PARTNER(FOE(battlerAtk)), move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) + if (AI_IsFaster(battlerAtk, LEFT_FOE(battlerAtk), move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) + && AI_IsFaster(battlerAtk, RIGHT_FOE(battlerAtk), move, predictedMoveSpeedCheck, CONSIDER_PRIORITY) && gBattleMons[battlerAtkPartner].hp < gBattleMons[battlerAtkPartner].maxHP / 2) RETURN_SCORE_PLUS(WEAK_EFFECT); break; @@ -4260,16 +4258,16 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) ADJUST_SCORE(IncreaseStatUpScore(BATTLE_PARTNER(battlerAtk), battlerDef, STAT_CHANGE_ATK)); ADJUST_SCORE(IncreaseStatUpScore(BATTLE_PARTNER(battlerAtk), battlerDef, STAT_CHANGE_SPATK)); } - if (IS_BATTLER_OF_TYPE(FOE(battlerAtk), TYPE_GRASS) && IsBattlerGrounded(FOE(battlerAtk))) + if (IS_BATTLER_OF_TYPE(LEFT_FOE(battlerAtk), TYPE_GRASS) && IsBattlerGrounded(LEFT_FOE(battlerAtk))) { - if (aiData->abilities[FOE(battlerAtk)] == ABILITY_CONTRARY) + if (aiData->abilities[LEFT_FOE(battlerAtk)] == ABILITY_CONTRARY) ADJUST_SCORE(WEAK_EFFECT); else ADJUST_SCORE(AWFUL_EFFECT); } - if (IS_BATTLER_OF_TYPE(BATTLE_PARTNER(FOE(battlerAtk)), TYPE_GRASS) && IsBattlerGrounded(BATTLE_PARTNER(FOE(battlerAtk)))) + if (IS_BATTLER_OF_TYPE(RIGHT_FOE(battlerAtk), TYPE_GRASS) && IsBattlerGrounded(RIGHT_FOE(battlerAtk))) { - if (aiData->abilities[BATTLE_PARTNER(FOE(battlerAtk))] == ABILITY_CONTRARY) + if (aiData->abilities[RIGHT_FOE(battlerAtk)] == ABILITY_CONTRARY) ADJUST_SCORE(WEAK_EFFECT); else ADJUST_SCORE(AWFUL_EFFECT); @@ -4284,16 +4282,16 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) { ADJUST_SCORE(IncreaseStatUpScore(BATTLE_PARTNER(battlerAtk), battlerDef, STAT_CHANGE_DEF)); } - if (IS_BATTLER_OF_TYPE(FOE(battlerAtk), TYPE_GRASS)) + if (IS_BATTLER_OF_TYPE(LEFT_FOE(battlerAtk), TYPE_GRASS)) { - if (aiData->abilities[FOE(battlerAtk)] == ABILITY_CONTRARY) + if (aiData->abilities[LEFT_FOE(battlerAtk)] == ABILITY_CONTRARY) ADJUST_SCORE(WEAK_EFFECT); else ADJUST_SCORE(AWFUL_EFFECT); } - if (IS_BATTLER_OF_TYPE(BATTLE_PARTNER(FOE(battlerAtk)), TYPE_GRASS)) + if (IS_BATTLER_OF_TYPE(RIGHT_FOE(battlerAtk), TYPE_GRASS)) { - if (aiData->abilities[BATTLE_PARTNER(FOE(battlerAtk))] == ABILITY_CONTRARY) + if (aiData->abilities[RIGHT_FOE(battlerAtk)] == ABILITY_CONTRARY) ADJUST_SCORE(WEAK_EFFECT); else ADJUST_SCORE(AWFUL_EFFECT); @@ -4529,7 +4527,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) case EFFECT_LOCK_ON: if (HasMoveWithEffect(battlerAtk, EFFECT_OHKO) || HasMoveWithEffect(battlerAtk, EFFECT_SHEER_COLD)) ADJUST_SCORE(GOOD_EFFECT); - else if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 85, TRUE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef])) + else if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 85, TRUE)) ADJUST_SCORE(GOOD_EFFECT); break; case EFFECT_DESTINY_BOND: @@ -4818,7 +4816,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move) //ADJUST_SCORE(8); break; case EFFECT_COURT_CHANGE: - if (gSideStatuses[GetBattlerSide(FOE(battlerAtk))] & SIDE_STATUS_GOOD_COURT) + if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_GOOD_COURT) ADJUST_SCORE(WEAK_EFFECT); if (gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_BAD_COURT) ADJUST_SCORE(WEAK_EFFECT); @@ -5294,11 +5292,16 @@ case EFFECT_GUARD_SPLIT: ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_GRAVITY: - if (!(gFieldStatuses & STATUS_FIELD_GRAVITY)) + if (!(gFieldStatuses & STATUS_FIELD_GRAVITY || ShouldClearFieldStatus(battlerAtk, STATUS_FIELD_GRAVITY))) { - if (HasSleepMoveWithLowAccuracy(battlerAtk, battlerDef)) // Has Gravity for a move like Hypnosis + // improve accuracy of Hypnosis + if (HasSleepMoveWithLowAccuracy(battlerAtk, battlerDef) + || HasSleepMoveWithLowAccuracy(BATTLE_PARTNER(battlerAtk), battlerDef)) IncreaseSleepScore(battlerAtk, battlerDef, move, &score); - if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 90, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef])) + if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 90, TRUE) + || HasMoveWithLowAccuracy(BATTLE_PARTNER(battlerAtk), battlerDef, 90, TRUE)) + ADJUST_SCORE(WEAK_EFFECT); + if (ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_GRAVITY)) ADJUST_SCORE(DECENT_EFFECT); } break; @@ -5341,8 +5344,7 @@ case EFFECT_GUARD_SPLIT: ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_TELEKINESIS: - if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 90, FALSE, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], aiData->holdEffects[battlerAtk], aiData->holdEffects[battlerDef]) - || !IsBattlerGrounded(battlerDef)) + if (HasMoveWithLowAccuracy(battlerAtk, battlerDef, 90, FALSE) || !IsBattlerGrounded(battlerDef)) ADJUST_SCORE(DECENT_EFFECT); break; case EFFECT_HEAL_BLOCK: @@ -5404,8 +5406,8 @@ case EFFECT_GUARD_SPLIT: u32 tailwindScore = 0; u32 speed = aiData->speedStats[battlerAtk]; u32 partnerSpeed = aiData->speedStats[BATTLE_PARTNER(battlerAtk)]; - u32 foe1Speed = aiData->speedStats[FOE(battlerAtk)]; - u32 foe2Speed = aiData->speedStats[BATTLE_PARTNER(FOE(battlerAtk))]; + u32 foe1Speed = aiData->speedStats[LEFT_FOE(battlerAtk)]; + u32 foe2Speed = aiData->speedStats[RIGHT_FOE(battlerAtk)]; if (speed <= foe1Speed && (speed * 2) > foe1Speed) tailwindScore += 1; @@ -5795,6 +5797,78 @@ case EFFECT_GUARD_SPLIT: if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_WATER) || IS_BATTLER_OF_TYPE(battlerDef, TYPE_STEEL)) ADJUST_SCORE(DECENT_EFFECT); break; + case MOVE_EFFECT_SUN: + if (ShouldSetWeather(battlerAtk, B_WEATHER_SUN)) + ADJUST_SCORE(DECENT_EFFECT); + if (ShouldClearWeather(battlerAtk, B_WEATHER_SUN)) + ADJUST_SCORE(BAD_EFFECT); + break; + case MOVE_EFFECT_RAIN: + if (ShouldSetWeather(battlerAtk, B_WEATHER_RAIN)) + ADJUST_SCORE(DECENT_EFFECT); + if (ShouldClearWeather(battlerAtk, B_WEATHER_RAIN)) + ADJUST_SCORE(BAD_EFFECT); + break; + case MOVE_EFFECT_SANDSTORM: + if (ShouldSetWeather(battlerAtk, B_WEATHER_SANDSTORM)) + ADJUST_SCORE(DECENT_EFFECT); + if (ShouldClearWeather(battlerAtk, B_WEATHER_SANDSTORM)) + ADJUST_SCORE(BAD_EFFECT); + break; + case MOVE_EFFECT_HAIL: + if (ShouldSetWeather(battlerAtk, B_WEATHER_HAIL)) + ADJUST_SCORE(DECENT_EFFECT); + if (ShouldClearWeather(battlerAtk, B_WEATHER_HAIL)) + ADJUST_SCORE(BAD_EFFECT); + break; + case MOVE_EFFECT_MISTY_TERRAIN: + if (ShouldClearFieldStatus(battlerAtk, STATUS_FIELD_MISTY_TERRAIN)) + { + ADJUST_SCORE(BAD_EFFECT); + break; + } + if (ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_MISTY_TERRAIN) + || ShouldClearFieldStatus(battlerAtk, gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case MOVE_EFFECT_GRASSY_TERRAIN: + if (ShouldClearFieldStatus(battlerAtk, STATUS_FIELD_GRASSY_TERRAIN)) + { + ADJUST_SCORE(BAD_EFFECT); + break; + } + if (ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_GRASSY_TERRAIN) + || ShouldClearFieldStatus(battlerAtk, gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case MOVE_EFFECT_ELECTRIC_TERRAIN: + if (ShouldClearFieldStatus(battlerAtk, STATUS_FIELD_ELECTRIC_TERRAIN)) + { + ADJUST_SCORE(BAD_EFFECT); + break; + } + if (ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_ELECTRIC_TERRAIN) + || ShouldClearFieldStatus(battlerAtk, gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case MOVE_EFFECT_PSYCHIC_TERRAIN: + if (ShouldClearFieldStatus(battlerAtk, STATUS_FIELD_PSYCHIC_TERRAIN)) + { + ADJUST_SCORE(BAD_EFFECT); + break; + } + if (ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_PSYCHIC_TERRAIN) + || ShouldClearFieldStatus(battlerAtk, gFieldStatuses & STATUS_FIELD_TERRAIN_ANY)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case MOVE_EFFECT_GRAVITY: + if (!(gFieldStatuses & STATUS_FIELD_GRAVITY) && ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_GRAVITY)) + ADJUST_SCORE(DECENT_EFFECT); + break; + case MOVE_EFFECT_AURORA_VEIL: + if (ShouldSetScreen(battlerAtk, battlerDef, EFFECT_AURORA_VEIL)) + ADJUST_SCORE(DECENT_EFFECT); + break; default: break; } @@ -6048,7 +6122,7 @@ static s32 AI_AttacksPartner(u32 battlerAtk, u32 battlerDef, u32 move, s32 score u32 hitsToKO = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, gAiThinkingStruct->movesetIndex, AI_ATTACKING); if (GetMoveTarget(move) == MOVE_TARGET_FOES_AND_ALLY && hitsToKO > 0 && - (GetNoOfHitsToKOBattler(battlerAtk, FOE(battlerAtk), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0 || GetNoOfHitsToKOBattler(battlerAtk, FOE(battlerDef), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0)) + (GetNoOfHitsToKOBattler(battlerAtk, LEFT_FOE(battlerAtk), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0 || GetNoOfHitsToKOBattler(battlerAtk, LEFT_FOE(battlerDef), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0)) ADJUST_SCORE(BEST_EFFECT); if (hitsToKO > 0) @@ -6128,8 +6202,8 @@ static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) if (gBattleMons[battlerDef].volatiles.healBlock) return 0; - if (CanTargetFaintAi(FOE(battlerAtk), BATTLE_PARTNER(battlerAtk)) - || (CanTargetFaintAi(BATTLE_PARTNER(FOE(battlerAtk)), BATTLE_PARTNER(battlerAtk)))) + if (CanTargetFaintAi(LEFT_FOE(battlerAtk), BATTLE_PARTNER(battlerAtk)) + || CanTargetFaintAi(RIGHT_FOE(battlerAtk), BATTLE_PARTNER(battlerAtk))) ADJUST_SCORE(-1); if (gAiLogicData->hpPercents[battlerDef] <= 50) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index a43b957a50..c350c21c8f 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2558,7 +2558,7 @@ bool32 HasMoveThatRaisesOwnStats(u32 battlerId) return FALSE; } -bool32 HasMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef, u32 accCheck, bool32 ignoreStatus, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect) +bool32 HasMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef, u32 accCheck, bool32 ignoreStatus) { s32 i; u16 *moves = GetMovesArray(battlerAtk); @@ -3884,7 +3884,7 @@ bool32 IsBattle1v1() bool32 HasTwoOpponents(u32 battler) { if (IsDoubleBattle() - && IsBattlerAlive(FOE(battler)) && IsBattlerAlive(BATTLE_PARTNER(FOE(battler)))) + && IsBattlerAlive(LEFT_FOE(battler)) && IsBattlerAlive(RIGHT_FOE(battler))) return TRUE; return FALSE; } @@ -5262,7 +5262,7 @@ bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef) case STAT_SPATK: return (HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_SPECIAL)); case STAT_ACC: - return (HasLowAccuracyMove(battlerAtk, battlerDef)); + return HasMoveWithLowAccuracy(battlerAtk, battlerDef, LOW_ACCURACY_THRESHOLD, FALSE); case STAT_EVASION: case STAT_SPEED: return TRUE; @@ -5330,7 +5330,7 @@ bool32 AI_ShouldSpicyExtract(u32 battlerAtk, u32 battlerAtkPartner, u32 move, st if (gBattleMons[battlerAtkPartner].statStages[STAT_ATK] == MAX_STAT_STAGE || partnerAbility == ABILITY_CONTRARY || partnerAbility == ABILITY_GOOD_AS_GOLD - || HasBattlerSideMoveWithEffect(FOE(battlerAtk), EFFECT_FOUL_PLAY)) + || HasBattlerSideMoveWithEffect(LEFT_FOE(battlerAtk), EFFECT_FOUL_PLAY)) return FALSE; preventsStatLoss = !CanLowerStat(battlerAtk, battlerAtkPartner, aiData, STAT_DEF); @@ -5393,17 +5393,6 @@ u32 IncreaseSubstituteMoveScore(u32 battlerAtk, u32 battlerDef, u32 move) return scoreIncrease; } -bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef) -{ - int i; - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (gAiLogicData->moveAccuracy[battlerAtk][battlerDef][i] <= LOW_ACCURACY_THRESHOLD) - return TRUE; - } - return FALSE; -} - bool32 IsBattlerItemEnabled(u32 battler) { if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_NEGATE_UNAWARE) @@ -5768,7 +5757,8 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData return GOOD_EFFECT; // Conditional ability logic goes here. case ABILITY_COMPOUND_EYES: - if (HasMoveWithLowAccuracy(battler, FOE(battler), 90, TRUE, aiData->abilities[battler], aiData->abilities[FOE(battler)], aiData->holdEffects[battler], aiData->holdEffects[FOE(battler)])) + if (HasMoveWithLowAccuracy(battler, LEFT_FOE(battler), 90, FALSE) + || HasMoveWithLowAccuracy(battler, RIGHT_FOE(battler), 90, FALSE)) return GOOD_EFFECT; break; case ABILITY_CONTRARY: @@ -5800,7 +5790,7 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData break; case ABILITY_INTIMIDATE: { - u32 abilityDef = aiData->abilities[FOE(battler)]; + u32 abilityDef = aiData->abilities[LEFT_FOE(battler)]; if (DoesIntimidateRaiseStats(abilityDef)) { return AWFUL_EFFECT; @@ -5809,26 +5799,27 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData { if (HasTwoOpponents(battler)) { - abilityDef = aiData->abilities[BATTLE_PARTNER(FOE(battler))]; + abilityDef = aiData->abilities[RIGHT_FOE(battler)]; if (DoesIntimidateRaiseStats(abilityDef)) { return AWFUL_EFFECT; } else { - s32 score1 = IncreaseStatDownScore(battler, FOE(battler), STAT_ATK); - s32 score2 = IncreaseStatDownScore(battler, BATTLE_PARTNER(FOE(battler)), STAT_ATK); + s32 score1 = IncreaseStatDownScore(battler, LEFT_FOE(battler), STAT_ATK); + s32 score2 = IncreaseStatDownScore(battler, RIGHT_FOE(battler), STAT_ATK); if (score1 > score2) return score1; else return score2; } } - return IncreaseStatDownScore(battler, FOE(battler), STAT_ATK); + return IncreaseStatDownScore(battler, LEFT_FOE(battler), STAT_ATK); } } case ABILITY_NO_GUARD: - if (HasLowAccuracyMove(battler, FOE(battler))) + if (HasMoveWithLowAccuracy(battler, LEFT_FOE(battler), LOW_ACCURACY_THRESHOLD, FALSE) + || HasMoveWithLowAccuracy(battler, RIGHT_FOE(battler), LOW_ACCURACY_THRESHOLD, FALSE)) return GOOD_EFFECT; break; // Toxic counter ticks upward while Poison Healed; losing Poison Heal while Toxiced can KO. diff --git a/test/battle/ai/can_use_all_moves.c b/test/battle/ai/can_use_all_moves.c index f91658b3f7..8b6e985a93 100644 --- a/test/battle/ai/can_use_all_moves.c +++ b/test/battle/ai/can_use_all_moves.c @@ -313,7 +313,6 @@ AI_DOUBLE_BATTLE_TEST("AI can use all moves, 301-400") case EFFECT_COPYCAT: case EFFECT_LAST_RESORT: case EFFECT_AQUA_RING: - case EFFECT_GRAVITY: case EFFECT_HEALING_WISH: //TODO: AI TESTS @@ -327,6 +326,7 @@ AI_DOUBLE_BATTLE_TEST("AI can use all moves, 301-400") case EFFECT_MAGNET_RISE: // tests exist elsewhere + case EFFECT_GRAVITY: case EFFECT_HEAL_BELL: case EFFECT_ATTACK_UP_USER_ALLY: diff --git a/test/battle/move_effect/gravity.c b/test/battle/move_effect/gravity.c index baac7a53ea..ec24917f6d 100644 --- a/test/battle/move_effect/gravity.c +++ b/test/battle/move_effect/gravity.c @@ -45,3 +45,36 @@ DOUBLE_BATTLE_TEST("Gravity cancels fly and sky drop if they are in the air") EXPECT_EQ(gLastMoves[0], MOVE_GRAVITY); } } + +AI_DOUBLE_BATTLE_TEST("AI uses Gravity") +{ + u32 move, friendItem, foeItem; + u64 aiFlags = AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT; + + PARAMETRIZE { move = MOVE_THUNDER; friendItem = ITEM_NONE; foeItem = ITEM_NONE; } + PARAMETRIZE { move = MOVE_HEADBUTT; friendItem = ITEM_AIR_BALLOON; foeItem = ITEM_NONE; } + PARAMETRIZE { move = MOVE_HEADBUTT; friendItem = ITEM_AIR_BALLOON; foeItem = ITEM_AIR_BALLOON; } + PARAMETRIZE { move = MOVE_HEADBUTT; friendItem = ITEM_NONE; foeItem = ITEM_AIR_BALLOON; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + move = MOVE_THUNDER; friendItem = ITEM_NONE; foeItem = ITEM_NONE; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + move = MOVE_HEADBUTT; friendItem = ITEM_AIR_BALLOON; foeItem = ITEM_NONE; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + move = MOVE_HEADBUTT; friendItem = ITEM_AIR_BALLOON; foeItem = ITEM_AIR_BALLOON; } + PARAMETRIZE { aiFlags |= AI_FLAG_OMNISCIENT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_PP_STALL_PREVENTION; + move = MOVE_HEADBUTT; friendItem = ITEM_NONE; foeItem = ITEM_AIR_BALLOON; } + + GIVEN { + AI_FLAGS(aiFlags); + PLAYER(SPECIES_WOBBUFFET) { Item(foeItem); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_GRAVITY, MOVE_HEADBUTT, MOVE_TAUNT); } + OPPONENT(SPECIES_WOBBUFFET) { Moves(move, MOVE_EARTH_POWER); Item(friendItem); } + } WHEN { + if (move == MOVE_THUNDER || (foeItem == ITEM_AIR_BALLOON && friendItem != ITEM_AIR_BALLOON)) + TURN { EXPECT_MOVE(opponentLeft, MOVE_GRAVITY); } + else + TURN { NOT_EXPECT_MOVE(opponentLeft, MOVE_GRAVITY); } + } +} +