AI handling for similar utility moves (#7513)

This commit is contained in:
surskitty 2025-08-08 16:54:07 -04:00 committed by GitHub
parent 90601792b9
commit fda783b394
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 297 additions and 157 deletions

View File

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

View File

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

View File

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

View File

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

View File

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