Clean up AI code duplication and unify checks (#6348)

This commit is contained in:
Alex 2025-03-20 22:50:01 +01:00 committed by GitHub
parent 4c18e88282
commit c707df358e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 220 additions and 190 deletions

View File

@ -99,7 +99,7 @@ u32 AI_GetBattlerAbility(u32 battler);
// stat stage checks
bool32 AnyStatIsRaised(u32 battlerId);
bool32 ShouldLowerStat(u32 battlerAtk, u32 battlerDef, u32 battlerAbility, u32 stat);
bool32 ShouldLowerStat(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 stat);
bool32 BattlerStatCanRise(u32 battler, u32 battlerAbility, u32 stat);
bool32 AreBattlersStatsMaxed(u32 battler);
u32 CountPositiveStatStages(u32 battlerId);

View File

@ -835,6 +835,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
u32 i;
u32 weather;
u32 predictedMove = aiData->lastUsedMove[battlerDef];
u32 abilityDef = aiData->abilities[battlerDef];
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
return score;
@ -895,145 +896,115 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
}
if (DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move))
abilityDef = ABILITY_NONE;
// check non-user target
if (!(moveTarget & MOVE_TARGET_USER))
{
// target ability checks
if (!DoesBattlerIgnoreAbilityChecks(battlerAtk, aiData->abilities[battlerAtk], move))
if (CanAbilityBlockMove(battlerAtk, battlerDef, move, abilityDef, ABILITY_CHECK_TRIGGER))
RETURN_SCORE_MINUS(20);
if (CanAbilityAbsorbMove(battlerAtk, battlerDef, abilityDef, move, moveType, ABILITY_CHECK_TRIGGER))
RETURN_SCORE_MINUS(20);
switch (abilityDef)
{
if (CanAbilityBlockMove(battlerAtk, battlerDef, move, aiData->abilities[battlerDef], ABILITY_CHECK_TRIGGER))
RETURN_SCORE_MINUS(20);
if (CanAbilityAbsorbMove(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, moveType, ABILITY_CHECK_TRIGGER))
RETURN_SCORE_MINUS(20);
switch (aiData->abilities[battlerDef])
case ABILITY_MAGIC_GUARD:
switch (moveEffect)
{
case ABILITY_MAGIC_GUARD:
switch (moveEffect)
{
case EFFECT_POISON:
case EFFECT_WILL_O_WISP:
case EFFECT_TOXIC:
case EFFECT_LEECH_SEED:
ADJUST_SCORE(-5);
break;
case EFFECT_CURSE:
if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GHOST)) // Don't use Curse if you're a ghost type vs a Magic Guard user, they'll take no damage.
ADJUST_SCORE(-5);
break;
}
case EFFECT_POISON:
case EFFECT_WILL_O_WISP:
case EFFECT_TOXIC:
case EFFECT_LEECH_SEED:
ADJUST_SCORE(-5);
break;
case ABILITY_WONDER_GUARD:
if (effectiveness < UQ_4_12(2.0))
case EFFECT_CURSE:
if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_GHOST)) // Don't use Curse if you're a ghost type vs a Magic Guard user, they'll take no damage.
ADJUST_SCORE(-5);
break;
}
break;
case ABILITY_WONDER_GUARD:
if (effectiveness < UQ_4_12(2.0))
RETURN_SCORE_MINUS(20);
break;
case ABILITY_JUSTIFIED:
if (moveType == TYPE_DARK && !IsBattleMoveStatus(move))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_RATTLED:
if (!IsBattleMoveStatus(move)
&& (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_AROMA_VEIL:
if (IsAromaVeilProtectedEffect(moveEffect))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_SWEET_VEIL:
if (moveEffect == EFFECT_SLEEP || moveEffect == EFFECT_YAWN)
RETURN_SCORE_MINUS(10);
break;
case ABILITY_FLOWER_VEIL:
if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) && (IsNonVolatileStatusMoveEffect(moveEffect)))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_MAGIC_BOUNCE:
if (MoveCanBeBouncedBack(move))
RETURN_SCORE_MINUS(20);
break;
case ABILITY_CONTRARY:
if (IsStatLoweringEffect(moveEffect))
RETURN_SCORE_MINUS(20);
break;
case ABILITY_COMATOSE:
if (IsNonVolatileStatusMoveEffect(moveEffect))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_SHIELDS_DOWN:
if (IsShieldsDownProtected(battlerAtk, aiData->abilities[battlerAtk]) && IsNonVolatileStatusMoveEffect(moveEffect))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_LEAF_GUARD:
if ((AI_GetWeather() & B_WEATHER_SUN)
&& aiData->holdEffects[battlerDef] != HOLD_EFFECT_UTILITY_UMBRELLA
&& IsNonVolatileStatusMoveEffect(moveEffect))
RETURN_SCORE_MINUS(10);
break;
} // def ability checks
// target partner ability checks & not attacking partner
if (isDoubleBattle)
{
switch (aiData->abilities[BATTLE_PARTNER(battlerDef)])
{
case ABILITY_LIGHTNING_ROD:
if (moveType == TYPE_ELECTRIC && !IsMoveRedirectionPrevented(battlerAtk, move, aiData->abilities[battlerAtk]))
RETURN_SCORE_MINUS(20);
break;
case ABILITY_JUSTIFIED:
if (moveType == TYPE_DARK && !IsBattleMoveStatus(move))
RETURN_SCORE_MINUS(10);
case ABILITY_STORM_DRAIN:
if (moveType == TYPE_WATER && !IsMoveRedirectionPrevented(battlerAtk, move, aiData->abilities[battlerAtk]))
RETURN_SCORE_MINUS(20);
break;
case ABILITY_RATTLED:
if (!IsBattleMoveStatus(move)
&& (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG))
case ABILITY_MAGIC_BOUNCE:
if (MoveCanBeBouncedBack(move) && moveTarget & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_OPPONENTS_FIELD))
RETURN_SCORE_MINUS(20);
break;
case ABILITY_SWEET_VEIL:
if (moveEffect == EFFECT_SLEEP || moveEffect == EFFECT_YAWN)
RETURN_SCORE_MINUS(20);
break;
case ABILITY_FLOWER_VEIL:
if ((IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS)) && (IsNonVolatileStatusMoveEffect(moveEffect) || IsStatLoweringEffect(moveEffect)))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_AROMA_VEIL:
if (IsAromaVeilProtectedEffect(moveEffect))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_SWEET_VEIL:
if (moveEffect == EFFECT_SLEEP || moveEffect == EFFECT_YAWN)
RETURN_SCORE_MINUS(10);
break;
case ABILITY_FLOWER_VEIL:
if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS) && (IsNonVolatileStatusMoveEffect(moveEffect) || IsStatLoweringEffect(moveEffect)))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_MAGIC_BOUNCE:
if (MoveCanBeBouncedBack(move))
RETURN_SCORE_MINUS(20);
break;
case ABILITY_CONTRARY:
if (IsStatLoweringEffect(moveEffect))
RETURN_SCORE_MINUS(20);
break;
case ABILITY_CLEAR_BODY:
case ABILITY_FULL_METAL_BODY:
case ABILITY_WHITE_SMOKE:
if (IsStatLoweringEffect(moveEffect))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_HYPER_CUTTER:
if ((moveEffect == EFFECT_ATTACK_DOWN || moveEffect == EFFECT_ATTACK_DOWN_2)
&& move != MOVE_PLAY_NICE && move != MOVE_NOBLE_ROAR && move != MOVE_TEARFUL_LOOK && move != MOVE_VENOM_DRENCH)
RETURN_SCORE_MINUS(10);
break;
case ABILITY_ILLUMINATE:
if (B_ILLUMINATE_EFFECT < GEN_9)
break;
// fallthrough
case ABILITY_KEEN_EYE:
case ABILITY_MINDS_EYE:
if (moveEffect == EFFECT_ACCURACY_DOWN || moveEffect == EFFECT_ACCURACY_DOWN_2)
RETURN_SCORE_MINUS(10);
break;
case ABILITY_BIG_PECKS:
if (moveEffect == EFFECT_DEFENSE_DOWN || moveEffect == EFFECT_DEFENSE_DOWN_2)
RETURN_SCORE_MINUS(10);
break;
case ABILITY_DEFIANT:
case ABILITY_COMPETITIVE:
if (IsStatLoweringEffect(moveEffect) && !IS_TARGETING_PARTNER(battlerAtk, battlerDef))
RETURN_SCORE_MINUS(8);
break;
case ABILITY_COMATOSE:
if (IsNonVolatileStatusMoveEffect(moveEffect))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_SHIELDS_DOWN:
if (IsShieldsDownProtected(battlerAtk, aiData->abilities[battlerAtk]) && IsNonVolatileStatusMoveEffect(moveEffect))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_LEAF_GUARD:
if ((AI_GetWeather() & B_WEATHER_SUN)
&& aiData->holdEffects[battlerDef] != HOLD_EFFECT_UTILITY_UMBRELLA
&& IsNonVolatileStatusMoveEffect(moveEffect))
RETURN_SCORE_MINUS(10);
break;
} // def ability checks
// target partner ability checks & not attacking partner
if (isDoubleBattle)
{
switch (aiData->abilities[BATTLE_PARTNER(battlerDef)])
{
case ABILITY_LIGHTNING_ROD:
if (moveType == TYPE_ELECTRIC && !IsMoveRedirectionPrevented(battlerAtk, move, aiData->abilities[battlerAtk]))
RETURN_SCORE_MINUS(20);
break;
case ABILITY_STORM_DRAIN:
if (moveType == TYPE_WATER && !IsMoveRedirectionPrevented(battlerAtk, move, aiData->abilities[battlerAtk]))
RETURN_SCORE_MINUS(20);
break;
case ABILITY_MAGIC_BOUNCE:
if (MoveCanBeBouncedBack(move) && moveTarget & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_OPPONENTS_FIELD))
RETURN_SCORE_MINUS(20);
break;
case ABILITY_SWEET_VEIL:
if (moveEffect == EFFECT_SLEEP || moveEffect == EFFECT_YAWN)
RETURN_SCORE_MINUS(20);
break;
case ABILITY_FLOWER_VEIL:
if ((IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS)) && (IsNonVolatileStatusMoveEffect(moveEffect) || IsStatLoweringEffect(moveEffect)))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_AROMA_VEIL:
if (IsAromaVeilProtectedEffect(moveEffect))
RETURN_SCORE_MINUS(10);
break;
}
} // def partner ability checks
} // ignore def ability check
}
} // def partner ability checks
// gen7+ dark type mons immune to priority->elevated moves from prankster
if (B_PRANKSTER_DARK_TYPES >= GEN_7 && IS_BATTLER_OF_TYPE(battlerDef, TYPE_DARK)
@ -1108,7 +1079,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
default:
break; // check move damage
case EFFECT_SLEEP:
if (!AI_CanPutToSleep(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove))
if (!AI_CanPutToSleep(battlerAtk, battlerDef, abilityDef, move, aiData->partnerMove))
ADJUST_SCORE(-10);
if (PartnerMoveActivatesSleepClause(aiData->partnerMove))
ADJUST_SCORE(-20);
@ -1360,47 +1331,40 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
// stat lowering effects
case EFFECT_ATTACK_DOWN:
case EFFECT_ATTACK_DOWN_2:
if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_ATK)) //|| !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL))
ADJUST_SCORE(-10);
else if (aiData->abilities[battlerDef] == ABILITY_HYPER_CUTTER)
if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_ATK)) //|| !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL))
ADJUST_SCORE(-10);
break;
case EFFECT_DEFENSE_DOWN:
case EFFECT_DEFENSE_DOWN_2:
if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_DEF))
if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_DEF))
ADJUST_SCORE(-10);
break;
case EFFECT_SPEED_DOWN:
case EFFECT_SPEED_DOWN_2:
if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPEED))
ADJUST_SCORE(-10);
else if (aiData->abilities[battlerDef] == ABILITY_SPEED_BOOST)
if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPEED))
ADJUST_SCORE(-10);
break;
case EFFECT_SPECIAL_ATTACK_DOWN:
case EFFECT_SPECIAL_ATTACK_DOWN_2:
if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPATK)) //|| !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL))
if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPATK)) //|| !HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL))
ADJUST_SCORE(-10);
break;
case EFFECT_SPECIAL_DEFENSE_DOWN:
case EFFECT_SPECIAL_DEFENSE_DOWN_2:
if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPDEF))
if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPDEF))
ADJUST_SCORE(-10);
break;
case EFFECT_ACCURACY_DOWN:
case EFFECT_ACCURACY_DOWN_2:
if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_ACC))
if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_ACC))
ADJUST_SCORE(-10);
else if (aiData->abilities[battlerDef] == ABILITY_KEEN_EYE || aiData->abilities[battlerDef] == ABILITY_MINDS_EYE
|| (B_ILLUMINATE_EFFECT >= GEN_9 && aiData->abilities[battlerDef] == ABILITY_ILLUMINATE))
ADJUST_SCORE(-8);
break;
case EFFECT_EVASION_DOWN:
case EFFECT_EVASION_DOWN_2:
case EFFECT_TICKLE:
if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_ATK))
if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_ATK))
ADJUST_SCORE(-10);
else if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_DEF))
else if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_DEF))
ADJUST_SCORE(-8);
break;
case EFFECT_VENOM_DRENCH:
@ -1410,18 +1374,18 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
else
{
if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPEED))
if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPEED))
ADJUST_SCORE(-10);
else if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPATK))
else if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPATK))
ADJUST_SCORE(-8);
else if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_ATK))
else if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_ATK))
ADJUST_SCORE(-6);
}
break;
case EFFECT_NOBLE_ROAR:
if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPATK))
if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPATK))
ADJUST_SCORE(-10);
else if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_ATK))
else if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_ATK))
ADJUST_SCORE(-8);
break;
case EFFECT_CAPTIVATE:
@ -1455,7 +1419,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (aiData->abilities[battlerDef] == ABILITY_WONDER_GUARD && effectiveness < UQ_4_12(2.0))
ADJUST_SCORE(-10);
if (HasDamagingMove(battlerDef) && !((gBattleMons[battlerAtk].status2 & STATUS2_SUBSTITUTE)
|| IsBattlerIncapacitated(battlerDef, aiData->abilities[battlerDef])
|| IsBattlerIncapacitated(battlerDef, abilityDef)
|| gBattleMons[battlerDef].status2 & (STATUS2_INFATUATION | STATUS2_CONFUSION)))
ADJUST_SCORE(-10);
if (HasMoveEffect(battlerAtk, EFFECT_SUBSTITUTE) && !(gBattleMons[battlerAtk].status2 & STATUS2_SUBSTITUTE))
@ -1482,12 +1446,12 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
break;
case EFFECT_TOXIC_THREAD:
if (!ShouldLowerStat(battlerAtk, battlerDef, aiData->abilities[battlerDef], STAT_SPEED))
if (!ShouldLowerStat(battlerAtk, battlerDef, abilityDef, STAT_SPEED))
ADJUST_SCORE(-1); // may still want to just poison
//fallthrough
case EFFECT_POISON:
case EFFECT_TOXIC:
if (!AI_CanPoison(battlerAtk, battlerDef, aiData->abilities[battlerDef], move, aiData->partnerMove))
if (!AI_CanPoison(battlerAtk, battlerDef, abilityDef, move, aiData->partnerMove))
ADJUST_SCORE(-10);
break;
case EFFECT_LIGHT_SCREEN:

View File

@ -1836,34 +1836,53 @@ void ProtectChecks(u32 battlerAtk, u32 battlerDef, u32 move, u32 predictedMove,
}
// stat stages
bool32 ShouldLowerStat(u32 battlerAtk, u32 battlerDef, u32 battlerAbility, u32 stat)
bool32 ShouldLowerStat(u32 battlerAtk, u32 battlerDef, u32 abilityDef, u32 stat)
{
if (gBattleMons[battlerDef].statStages[stat] > MIN_STAT_STAGE && battlerAbility != ABILITY_CONTRARY)
{
if (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CLEAR_AMULET
|| battlerAbility == ABILITY_CLEAR_BODY
|| battlerAbility == ABILITY_WHITE_SMOKE
|| battlerAbility == ABILITY_FULL_METAL_BODY)
return FALSE;
if (gBattleMons[battlerDef].statStages[stat] == MIN_STAT_STAGE)
return FALSE;
switch (stat)
{
case STAT_ATK:
return !(battlerAbility == ABILITY_HYPER_CUTTER);
case STAT_DEF:
return !(battlerAbility == ABILITY_BIG_PECKS);
case STAT_SPEED:
// If AI is faster and doesn't have any mons left, lowering speed doesn't give any
return !(AI_IsFaster(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered)
&& CountUsablePartyMons(battlerAtk) == 0
&& !HasMoveEffect(battlerAtk, EFFECT_ELECTRO_BALL));
case STAT_ACC:
return !(battlerAbility == ABILITY_KEEN_EYE || (B_ILLUMINATE_EFFECT >= GEN_9 && battlerAbility == ABILITY_ILLUMINATE));
}
return TRUE;
if (AI_DATA->holdEffects[battlerDef] == HOLD_EFFECT_CLEAR_AMULET)
return FALSE;
switch (abilityDef)
{
case ABILITY_SPEED_BOOST:
if (stat == STAT_SPEED)
return FALSE;
case ABILITY_HYPER_CUTTER:
if (stat == STAT_ATK)
return FALSE;
case ABILITY_BIG_PECKS:
if (stat == STAT_DEF)
return FALSE;
case ABILITY_ILLUMINATE:
if (B_ILLUMINATE_EFFECT < GEN_9)
break;
case ABILITY_KEEN_EYE:
case ABILITY_MINDS_EYE:
if (stat == STAT_ACC)
return FALSE;
case ABILITY_FLOWER_VEIL:
if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_GRASS))
return FALSE;
break;
case ABILITY_CONTRARY:
case ABILITY_CLEAR_BODY:
case ABILITY_WHITE_SMOKE:
case ABILITY_FULL_METAL_BODY:
return FALSE;
}
return FALSE;
// This should be a viability check
if (stat == STAT_SPEED)
{
// If AI is faster and doesn't have any mons left, lowering speed doesn't give any
return !(AI_IsFaster(battlerAtk, battlerDef, AI_THINKING_STRUCT->moveConsidered)
&& CountUsablePartyMons(battlerAtk) == 0
&& !HasMoveEffect(battlerAtk, EFFECT_ELECTRO_BALL));
}
return TRUE;
}
bool32 BattlerStatCanRise(u32 battler, u32 battlerAbility, u32 stat)

View File

@ -44,28 +44,27 @@ SINGLE_BATTLE_TEST("Mind's Eye doesn't bypass a Ghost-type's Wonder Guard")
AI_SINGLE_BATTLE_TEST("AI doesn't use accuracy-lowering moves if it knows that the foe has Mind's Eye")
{
u32 abilityAI = ABILITY_NONE, moveAI = MOVE_NONE, j = 0;
u32 abilityAI = ABILITY_NONE;
for (j = MOVE_NONE + 1; j < MOVES_COUNT; j++)
{
if (GetMoveEffect(j) == EFFECT_ACCURACY_DOWN || GetMoveEffect(j) == EFFECT_ACCURACY_DOWN_2) {
PARAMETRIZE { moveAI = j; abilityAI = ABILITY_SWIFT_SWIM; }
PARAMETRIZE { moveAI = j; abilityAI = ABILITY_MOLD_BREAKER; }
}
}
PARAMETRIZE { abilityAI = ABILITY_SWIFT_SWIM; }
PARAMETRIZE { abilityAI = ABILITY_MOLD_BREAKER; }
GIVEN {
ASSUME(GetMoveEffect(MOVE_SAND_ATTACK) == EFFECT_ACCURACY_DOWN);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { Ability(ABILITY_MINDS_EYE); }
OPPONENT(SPECIES_BASCULEGION) { Moves(MOVE_CELEBRATE, moveAI); Ability(abilityAI); }
PLAYER(SPECIES_URSALUNA_BLOODMOON) { Ability(ABILITY_MINDS_EYE); }
OPPONENT(SPECIES_BASCULEGION) { Moves(MOVE_CELEBRATE, MOVE_SAND_ATTACK); Ability(abilityAI); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
TURN { MOVE(player, MOVE_TACKLE);
if (abilityAI == ABILITY_MOLD_BREAKER) { SCORE_GT(opponent, moveAI, MOVE_CELEBRATE); }
else { SCORE_EQ(opponent, moveAI, MOVE_CELEBRATE); }
}
TURN {
if (abilityAI == ABILITY_MOLD_BREAKER) {
SCORE_GT(opponent, MOVE_SAND_ATTACK, MOVE_CELEBRATE);
} else {
SCORE_EQ(opponent, MOVE_SAND_ATTACK, MOVE_CELEBRATE);
}
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
if (abilityAI == ABILITY_MOLD_BREAKER) { ANIMATION(ANIM_TYPE_MOVE, moveAI, opponent); }
if (abilityAI == ABILITY_MOLD_BREAKER) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SAND_ATTACK, opponent);
}
}
}

View File

@ -0,0 +1,48 @@
#include "global.h"
#include "test/battle.h"
#include "battle_ai_util.h"
AI_SINGLE_BATTLE_TEST("AI will not try to lower opposing stats if target is protected by it's ability")
{
u16 ability, species, move;
PARAMETRIZE { ability = ABILITY_SPEED_BOOST; species = SPECIES_TORCHIC; move = MOVE_SCARY_FACE; }
PARAMETRIZE { ability = ABILITY_HYPER_CUTTER; species = SPECIES_KRABBY; move = MOVE_GROWL; }
PARAMETRIZE { ability = ABILITY_BIG_PECKS; species = SPECIES_PIDGEY; move = MOVE_SCREECH; }
PARAMETRIZE { ability = ABILITY_ILLUMINATE; species = SPECIES_STARYU; move = MOVE_SAND_ATTACK; }
PARAMETRIZE { ability = ABILITY_KEEN_EYE; species = SPECIES_PIDGEY; move = MOVE_SAND_ATTACK; }
PARAMETRIZE { ability = ABILITY_CONTRARY; species = SPECIES_SNIVY; move = MOVE_NOBLE_ROAR; }
PARAMETRIZE { ability = ABILITY_CLEAR_BODY; species = SPECIES_BELDUM; move = MOVE_NOBLE_ROAR; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_OMNISCIENT);
PLAYER(species) { Ability(ability); }
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, move); }
} WHEN {
TURN { SCORE_LT_VAL(opponent, move, AI_SCORE_DEFAULT); }
}
}
AI_DOUBLE_BATTLE_TEST("AI will not try to lower opposing stats if target is protected by Flower Veil")
{
u16 move;
PARAMETRIZE { move = MOVE_SCARY_FACE; }
PARAMETRIZE { move = MOVE_GROWL; }
PARAMETRIZE { move = MOVE_SCREECH; }
PARAMETRIZE { move = MOVE_SAND_ATTACK; }
PARAMETRIZE { move = MOVE_SAND_ATTACK; }
PARAMETRIZE { move = MOVE_NOBLE_ROAR; }
PARAMETRIZE { move = MOVE_NOBLE_ROAR; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_COMFEY) { Ability(ABILITY_FLOWER_VEIL); }
PLAYER(SPECIES_BULBASAUR);
OPPONENT(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, move); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { SCORE_LT_VAL(opponentLeft, move, AI_SCORE_DEFAULT, target: playerRight); }
}
}