Adjusted AI handling for Gravity; AI for weather/field status additional effects. (#7651)

This commit is contained in:
surskitty 2025-09-12 17:16:45 -04:00 committed by GitHub
parent 3c94074750
commit fab4dc1163
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 231 additions and 97 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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.

View File

@ -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:

View File

@ -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); }
}
}