Expanding and Refactoring Skill Swap and other ability-changing moves (#7238)

Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
This commit is contained in:
surskitty 2025-07-05 08:49:35 -04:00 committed by GitHub
parent 38a3963ec2
commit 4052ab337c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 535 additions and 178 deletions

View File

@ -38,7 +38,10 @@ enum AIScore
WEAK_EFFECT = 1,
DECENT_EFFECT = 2,
GOOD_EFFECT = 3,
BEST_EFFECT = 4
BEST_EFFECT = 4,
BAD_EFFECT = -1,
AWFUL_EFFECT = -3,
WORST_EFFECT = -5
};
// AI_TryToFaint

View File

@ -177,6 +177,7 @@ bool32 HasThawingMove(u32 battler);
bool32 IsStatRaisingEffect(enum BattleMoveEffects effect);
bool32 IsStatLoweringEffect(enum BattleMoveEffects effect);
bool32 IsSelfStatLoweringEffect(enum BattleMoveEffects effect);
bool32 IsSelfStatRaisingEffect(enum BattleMoveEffects effect);
bool32 IsSwitchOutEffect(enum BattleMoveEffects effect);
bool32 IsChaseEffect(enum BattleMoveEffects effect);
bool32 IsAttackBoostMoveEffect(enum BattleMoveEffects effect);
@ -208,10 +209,14 @@ bool32 AI_IsBattlerAsleepOrComatose(u32 battlerId);
// ability logic
bool32 IsMoxieTypeAbility(u32 ability);
bool32 ShouldTriggerAbility(u32 battler, u32 ability);
bool32 DoesAbilityRaiseStatsWhenLowered(u32 ability);
bool32 ShouldTriggerAbility(u32 battlerAtk, u32 battlerDef, u32 ability);
bool32 CanEffectChangeAbility(u32 battlerAtk, u32 battlerDef, u32 effect, struct AiLogicData *aiData);
void AbilityChangeScore(u32 battlerAtk, u32 battlerDef, u32 effect, s32 *score, struct AiLogicData *aiData);
s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData *aiData);
// partner logic
#define IS_TARGETING_PARTNER(battlerAtk, battlerDef)((battlerAtk) == (battlerDef ^ BIT_FLANK))
bool32 IsTargetingPartner(u32 battlerAtk, u32 battlerDef);
u32 GetAllyChosenMove(u32 battlerId);
bool32 IsValidDoubleBattle(u32 battlerAtk);
bool32 DoesPartnerHaveSameMoveEffect(u32 battlerAtkPartner, u32 battlerDef, u32 move, u32 partnerMove);

View File

@ -1030,7 +1030,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
u32 abilityDef = aiData->abilities[battlerDef];
s32 atkPriority = GetBattleMovePriority(battlerAtk, abilityAtk, move);
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
SetTypeBeforeUsingMove(move, battlerAtk);
@ -1142,7 +1142,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
RETURN_SCORE_MINUS(20);
break;
case ABILITY_JUSTIFIED:
if (moveType == TYPE_DARK && !IsBattleMoveStatus(move) && !IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (moveType == TYPE_DARK && !IsBattleMoveStatus(move) && !IsTargetingPartner(battlerAtk, battlerDef))
RETURN_SCORE_MINUS(10);
break;
case ABILITY_RATTLED:
@ -2312,7 +2312,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
// evasion check
if (gBattleMons[battlerDef].statStages[STAT_EVASION] == MIN_STAT_STAGE
|| ((aiData->abilities[battlerDef] == ABILITY_CONTRARY) && !IS_TARGETING_PARTNER(battlerAtk, battlerDef))) // don't want to raise target stats unless its your partner
|| ((aiData->abilities[battlerDef] == ABILITY_CONTRARY) && !IsTargetingPartner(battlerAtk, battlerDef))) // don't want to raise target stats unless its your partner
ADJUST_SCORE(-10);
break;
case EFFECT_PSYCH_UP: // haze stats check
@ -2361,26 +2361,6 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
|| !CanBattlerGetOrLoseItem(battlerAtk, gBattleMons[battlerAtk].item)) // AI knows its own item
ADJUST_SCORE(-10);
break;
case EFFECT_ROLE_PLAY:
if (aiData->abilities[battlerAtk] == aiData->abilities[battlerDef]
|| aiData->abilities[battlerDef] == ABILITY_NONE
|| gAbilitiesInfo[aiData->abilities[battlerAtk]].cantBeSuppressed
|| gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeCopied)
ADJUST_SCORE(-10);
else if (IsAbilityOfRating(aiData->abilities[battlerAtk], 5))
ADJUST_SCORE(-4);
break;
case EFFECT_DOODLE: // Same as Role Play, but also check if the partner's ability should be replaced
if (aiData->abilities[battlerAtk] == aiData->abilities[battlerDef]
|| aiData->abilities[BATTLE_PARTNER(battlerAtk)] == aiData->abilities[battlerDef]
|| aiData->abilities[battlerDef] == ABILITY_NONE
|| gAbilitiesInfo[aiData->abilities[battlerAtk]].cantBeSuppressed
|| gAbilitiesInfo[aiData->abilities[BATTLE_PARTNER(battlerAtk)]].cantBeSuppressed
|| gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeCopied)
ADJUST_SCORE(-10);
else if (IsAbilityOfRating(aiData->abilities[battlerAtk], 5) || IsAbilityOfRating(aiData->abilities[BATTLE_PARTNER(battlerAtk)], 5))
ADJUST_SCORE(-4);
break;
case EFFECT_WISH:
if (gWishFutureKnock.wishCounter[battlerAtk] > gBattleTurnCounter)
ADJUST_SCORE(-10);
@ -2401,40 +2381,15 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
if (PartnerMoveActivatesSleepClause(aiData->partnerMove))
ADJUST_SCORE(-20);
break;
case EFFECT_SKILL_SWAP:
if (aiData->abilities[battlerAtk] == ABILITY_NONE || aiData->abilities[battlerDef] == ABILITY_NONE
|| gAbilitiesInfo[aiData->abilities[battlerAtk]].cantBeSwapped
|| gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeSwapped
|| aiData->holdEffects[battlerDef] == HOLD_EFFECT_ABILITY_SHIELD)
ADJUST_SCORE(-10);
else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
ADJUST_SCORE(-10);
break;
case EFFECT_WORRY_SEED:
if (aiData->abilities[battlerDef] == ABILITY_INSOMNIA
|| gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeOverwritten
|| aiData->holdEffects[battlerDef] == HOLD_EFFECT_ABILITY_SHIELD)
ADJUST_SCORE(-10);
break;
case EFFECT_GASTRO_ACID:
if (gStatuses3[battlerDef] & STATUS3_GASTRO_ACID
|| gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeSuppressed)
ADJUST_SCORE(-10);
break;
case EFFECT_DOODLE:
case EFFECT_ENTRAINMENT:
if (aiData->abilities[battlerAtk] == ABILITY_NONE
|| gAbilitiesInfo[aiData->abilities[battlerAtk]].cantBeCopied
|| gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeOverwritten
|| aiData->holdEffects[battlerAtk] == HOLD_EFFECT_ABILITY_SHIELD)
ADJUST_SCORE(-10);
else if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
ADJUST_SCORE(-10);
break;
case EFFECT_GASTRO_ACID:
case EFFECT_ROLE_PLAY:
case EFFECT_SIMPLE_BEAM:
if (aiData->abilities[battlerDef] == ABILITY_SIMPLE
|| gAbilitiesInfo[aiData->abilities[battlerDef]].cantBeOverwritten
|| aiData->holdEffects[battlerDef] == HOLD_EFFECT_ABILITY_SHIELD)
ADJUST_SCORE(-10);
case EFFECT_SKILL_SWAP:
case EFFECT_WORRY_SEED:
if (!CanEffectChangeAbility(battlerAtk, battlerDef, moveEffect, aiData))
ADJUST_AND_RETURN_SCORE(NO_DAMAGE_OR_FAILS);
break;
case EFFECT_SNATCH:
if (!HasMoveWithFlag(battlerDef, MoveCanBeSnatched)
@ -2442,27 +2397,27 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
break;
case EFFECT_POWER_TRICK:
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
ADJUST_SCORE(-10);
else if (gBattleMons[battlerAtk].defense >= gBattleMons[battlerAtk].attack && !HasMoveWithCategory(battlerAtk, DAMAGE_CATEGORY_PHYSICAL))
ADJUST_SCORE(-10);
break;
case EFFECT_POWER_SWAP: // Don't use if attacker's stat stages are higher than opponents
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
ADJUST_SCORE(-10);
else if (gBattleMons[battlerAtk].statStages[STAT_ATK] >= gBattleMons[battlerDef].statStages[STAT_ATK]
&& gBattleMons[battlerAtk].statStages[STAT_SPATK] >= gBattleMons[battlerDef].statStages[STAT_SPATK])
ADJUST_SCORE(-10);
break;
case EFFECT_GUARD_SWAP: // Don't use if attacker's stat stages are higher than opponents
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
ADJUST_SCORE(-10);
else if (gBattleMons[battlerAtk].statStages[STAT_DEF] >= gBattleMons[battlerDef].statStages[STAT_DEF]
&& gBattleMons[battlerAtk].statStages[STAT_SPDEF] >= gBattleMons[battlerDef].statStages[STAT_SPDEF])
ADJUST_SCORE(-10);
break;
case EFFECT_SPEED_SWAP:
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
{
ADJUST_SCORE(-10);
}
@ -2475,7 +2430,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
break;
case EFFECT_HEART_SWAP:
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
{
ADJUST_SCORE(-10);
}
@ -2492,7 +2447,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
break;
case EFFECT_POWER_SPLIT:
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
{
ADJUST_SCORE(-10);
}
@ -2509,7 +2464,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
break;
case EFFECT_GUARD_SPLIT:
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
{
ADJUST_SCORE(-10);
}
@ -2674,14 +2629,14 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
break;
case EFFECT_HEAL_PULSE: // and floral healing
if (!IS_TARGETING_PARTNER(battlerAtk, battlerDef)) // Don't heal enemies
if (!IsTargetingPartner(battlerAtk, battlerDef)) // Don't heal enemies
{
ADJUST_SCORE(-10);
break;
}
// fallthrough
case EFFECT_HIT_ENEMY_HEAL_ALLY: // pollen puff
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
{
if (gStatuses3[battlerDef] & STATUS3_HEAL_BLOCK)
return 0; // cannot even select
@ -2698,7 +2653,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
break;
case EFFECT_TOPSY_TURVY:
if (!IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (!IsTargetingPartner(battlerAtk, battlerDef))
{
u32 targetPositiveStages = CountPositiveStatStages(battlerDef);
u32 targetNegativeStages = CountNegativeStatStages(battlerDef);
@ -2743,7 +2698,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
else if (isDoubleBattle)
{
if (!IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (!IsTargetingPartner(battlerAtk, battlerDef))
ADJUST_SCORE(-10);
}
else
@ -2766,7 +2721,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
break;
case EFFECT_AFTER_YOU:
if (!IS_TARGETING_PARTNER(battlerAtk, battlerDef)
if (!IsTargetingPartner(battlerAtk, battlerDef)
|| !isDoubleBattle
|| AI_IsSlower(battlerAtk, battlerDef, move)
|| PartnerMoveIsSameAsAttacker(BATTLE_PARTNER(battlerAtk), battlerDef, move, aiData->partnerMove))
@ -2943,7 +2898,7 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{
u32 movesetIndex = gAiThinkingStruct->movesetIndex;
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
if (IsBattleMoveStatus(move))
@ -2984,7 +2939,6 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
u32 atkPartnerHoldEffect = aiData->holdEffects[BATTLE_PARTNER(battlerAtk)];
enum BattleMoveEffects partnerEffect = GetMoveEffect(aiData->partnerMove);
bool32 partnerProtecting = IsAllyProtectingFromMove(battlerAtk, move, aiData->partnerMove) && !MoveIgnoresProtect(move);
bool32 attackerHasBadAbility = (gAbilitiesInfo[aiData->abilities[battlerAtk]].aiRating < 0);
bool32 partnerHasBadAbility = (gAbilitiesInfo[atkPartnerAbility].aiRating < 0);
u32 predictedMove = GetIncomingMove(battlerAtk, battlerDef, gAiLogicData);
@ -3174,7 +3128,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
else
{
ADJUST_SCORE(-DECENT_EFFECT);
ADJUST_SCORE(AWFUL_EFFECT);
}
}
// No reason to kill partner has been found.
@ -3203,7 +3157,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
}
// check specific target
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
{
bool32 isMoveAffectedByPartnerAbility = TRUE;
@ -3252,7 +3206,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{
ADJUST_SCORE(DECENT_EFFECT);
}
else if (ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
else if (ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility))
{
RETURN_SCORE_PLUS(WEAK_EFFECT);
}
@ -3298,7 +3252,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{
ADJUST_SCORE(DECENT_EFFECT);
}
else if (ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
else if (ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility))
{
RETURN_SCORE_PLUS(WEAK_EFFECT);
}
@ -3314,7 +3268,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
case ABILITY_WATER_COMPACTION:
if (moveType == TYPE_WATER && isFriendlyFireOK
&& ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
&& ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility))
{
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
{
@ -3333,7 +3287,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
break;
case ABILITY_STEAM_ENGINE:
if (isFriendlyFireOK && (moveType == TYPE_WATER || moveType == TYPE_FIRE)
&& ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
&& ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility))
{
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
{
@ -3349,7 +3303,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case ABILITY_THERMAL_EXCHANGE:
if (moveType == TYPE_FIRE && isFriendlyFireOK
&& !IsBattleMoveStatus(move)
&& ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
&& ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility))
{
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
{
@ -3376,7 +3330,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{
ADJUST_SCORE(DECENT_EFFECT);
}
if (ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
if (ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility))
{
RETURN_SCORE_PLUS(WEAK_EFFECT);
}
@ -3394,7 +3348,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(DECENT_EFFECT);
}
if (ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
if (ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility))
{
RETURN_SCORE_PLUS(WEAK_EFFECT);
}
@ -3407,7 +3361,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case ABILITY_JUSTIFIED:
if (moveType == TYPE_DARK && isFriendlyFireOK
&& !IsBattleMoveStatus(move)
&& ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
&& ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility))
{
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
{
@ -3429,7 +3383,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case ABILITY_RATTLED:
if (!IsBattleMoveStatus(move) && isFriendlyFireOK
&& (moveType == TYPE_DARK || moveType == TYPE_GHOST || moveType == TYPE_BUG)
&& ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
&& ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility))
{
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
{
@ -3445,7 +3399,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case ABILITY_CONTRARY:
case ABILITY_DEFIANT:
case ABILITY_COMPETITIVE:
if (IsStatLoweringEffect(effect) && isFriendlyFireOK && ShouldTriggerAbility(battlerAtkPartner, atkPartnerAbility))
if (IsStatLoweringEffect(effect) && isFriendlyFireOK && ShouldTriggerAbility(battlerAtk, battlerAtkPartner, atkPartnerAbility))
{
if (moveTarget == MOVE_TARGET_FOES_AND_ALLY)
{
@ -3471,6 +3425,15 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
switch (effect)
{
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:
AbilityChangeScore(battlerAtk, battlerAtkPartner, effect, &score, aiData);
return score;
case EFFECT_SPICY_EXTRACT:
if (AI_ShouldSpicyExtract(battlerAtk, battlerAtkPartner, move, aiData))
{
@ -3519,51 +3482,6 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
RETURN_SCORE_PLUS(WEAK_EFFECT);
}
break;
case EFFECT_SKILL_SWAP:
if (aiData->abilities[battlerAtk] != aiData->abilities[BATTLE_PARTNER(battlerAtk)] && !attackerHasBadAbility)
{
// Partner abilities
if (aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_TRUANT)
{
ADJUST_SCORE(10);
}
else if (aiData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_INTIMIDATE)
{
ADJUST_SCORE(DECENT_EFFECT);
}
// Active mon abilities
if (aiData->abilities[battlerAtk] == ABILITY_COMPOUND_EYES
&& HasMoveWithLowAccuracy(battlerAtkPartner, FOE(battlerAtkPartner), 90, TRUE, atkPartnerAbility, aiData->abilities[FOE(battlerAtkPartner)], atkPartnerHoldEffect, aiData->holdEffects[FOE(battlerAtkPartner)]))
{
ADJUST_SCORE(GOOD_EFFECT);
}
else if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY && HasMoveThatLowersOwnStats(battlerAtkPartner))
{
ADJUST_SCORE(GOOD_EFFECT);
}
return score;
}
break;
case EFFECT_ROLE_PLAY:
if (attackerHasBadAbility && !partnerHasBadAbility)
{
RETURN_SCORE_PLUS(WEAK_EFFECT);
}
break;
case EFFECT_WORRY_SEED:
case EFFECT_GASTRO_ACID:
case EFFECT_SIMPLE_BEAM:
if (partnerHasBadAbility)
{
RETURN_SCORE_PLUS(DECENT_EFFECT);
}
break;
case EFFECT_ENTRAINMENT:
if (partnerHasBadAbility && IsAbilityOfRating(aiData->abilities[battlerAtk], 0))
{
RETURN_SCORE_PLUS(DECENT_EFFECT);
}
break;
case EFFECT_SOAK:
if (atkPartnerAbility == ABILITY_WONDER_GUARD
&& !IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_WATER)
@ -3619,6 +3537,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
else // checking opponent
{
// these checks mostly handled in AI_CheckBadMove and AI_CheckViability
/*
switch (effect)
{
case EFFECT_SKILL_SWAP:
@ -3630,7 +3549,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
default:
break;
}
*/
// lightning rod, flash fire against enemy handled in AI_CheckBadMove
}
@ -3800,7 +3719,7 @@ static u32 AI_CalcHoldEffectMoveScore(u32 battlerAtk, u32 battlerDef, u32 move)
}
else
{
ADJUST_SCORE(-DECENT_EFFECT);
ADJUST_SCORE(AWFUL_EFFECT);
}
}
break;
@ -4622,11 +4541,6 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
}
}
break;
case EFFECT_ROLE_PLAY:
case EFFECT_DOODLE:
if (IsAbilityOfRating(aiData->abilities[battlerDef], 5))
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_INGRAIN:
ADJUST_SCORE(WEAK_EFFECT);
if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_BIG_ROOT)
@ -4663,29 +4577,15 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_AURORA_VEIL)
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_SKILL_SWAP:
if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
break;
else if (gAbilitiesInfo[aiData->abilities[battlerDef]].aiRating > gAbilitiesInfo[aiData->abilities[battlerAtk]].aiRating)
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_WORRY_SEED:
case EFFECT_GASTRO_ACID:
case EFFECT_SIMPLE_BEAM:
if (IsAbilityOfRating(aiData->abilities[battlerDef], 5))
ADJUST_SCORE(DECENT_EFFECT);
break;
case EFFECT_DOODLE:
case EFFECT_ENTRAINMENT:
if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
break;
if (aiData->abilities[battlerDef] != aiData->abilities[battlerAtk] && !(gStatuses3[battlerDef] & STATUS3_GASTRO_ACID))
{
if (gAbilitiesInfo[aiData->abilities[battlerAtk]].aiRating <= 0)
ADJUST_SCORE(DECENT_EFFECT);
else if (IsAbilityOfRating(aiData->abilities[battlerDef], 5) && gAbilitiesInfo[aiData->abilities[battlerAtk]].aiRating <= 3)
ADJUST_SCORE(WEAK_EFFECT);
}
break;
case EFFECT_GASTRO_ACID:
case EFFECT_ROLE_PLAY:
case EFFECT_SIMPLE_BEAM:
case EFFECT_SKILL_SWAP:
case EFFECT_WORRY_SEED:
AbilityChangeScore(battlerAtk, battlerDef, moveEffect, &score, aiData);
return score;
case EFFECT_IMPRISON:
if (predictedMove != MOVE_NONE && HasMove(battlerAtk, predictedMove))
ADJUST_SCORE(DECENT_EFFECT);
@ -5301,7 +5201,7 @@ static u32 AI_CalcMoveEffectScore(u32 battlerAtk, u32 battlerDef, u32 move)
static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{
// Targeting partner, check benefits of doing that instead
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
if (GetMovePower(move) != 0)
@ -5328,7 +5228,7 @@ static s32 AI_CheckViability(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
static s32 AI_ForceSetupFirstTurn(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{
u8 i;
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)
if (IsTargetingPartner(battlerAtk, battlerDef)
|| gBattleResults.battleTurnCounter != 0)
return score;
@ -5459,7 +5359,7 @@ static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
u8 i;
struct AiLogicData *aiData = gAiLogicData;
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
if (GetMoveCriticalHitStage(move) > 0)
@ -5532,7 +5432,7 @@ static s32 AI_Risky(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
// Adds score bonus to OHKOs and 2HKOs
static s32 AI_TryTo2HKO(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
return score;
if (GetNoOfHitsToKOBattler(battlerAtk, battlerDef, gAiThinkingStruct->movesetIndex, AI_ATTACKING) == 1)
@ -5546,7 +5446,7 @@ static s32 AI_TryTo2HKO(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
// Prefers moves that are good for baton pass
static s32 AI_PreferBatonPass(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
{
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef)
if (IsTargetingPartner(battlerAtk, battlerDef)
|| CountUsablePartyMons(battlerAtk) == 0
|| !IsBattleMoveStatus(move)
|| !HasMoveWithEffect(battlerAtk, EFFECT_BATON_PASS)
@ -5604,7 +5504,7 @@ static s32 AI_HPAware(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
SetTypeBeforeUsingMove(move, battlerAtk);
moveType = GetBattleMoveType(move);
if (IS_TARGETING_PARTNER(battlerAtk, battlerDef))
if (IsTargetingPartner(battlerAtk, battlerDef))
{
if ((effect == EFFECT_HEAL_PULSE || effect == EFFECT_HIT_ENEMY_HEAL_ALLY)
|| (moveType == TYPE_ELECTRIC && gAiLogicData->abilities[BATTLE_PARTNER(battlerAtk)] == ABILITY_VOLT_ABSORB)
@ -6002,7 +5902,7 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_ION_DELUGE:
case EFFECT_MAGIC_COAT:
case EFFECT_SNATCH:
ADJUST_SCORE(-BEST_EFFECT);
ADJUST_SCORE(WORST_EFFECT);
break;
// Get stuck in bad matchup
@ -6012,7 +5912,7 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_INGRAIN:
case EFFECT_NO_RETREAT:
case EFFECT_MEAN_LOOK:
ADJUST_SCORE(-GOOD_EFFECT);
ADJUST_SCORE(AWFUL_EFFECT);
break;
default:
@ -6025,17 +5925,17 @@ static s32 AI_PredictSwitch(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
switch (GetMoveAdditionalEffectById(move, i)->moveEffect)
{
case MOVE_EFFECT_WRAP:
ADJUST_SCORE(-GOOD_EFFECT);
ADJUST_SCORE(AWFUL_EFFECT);
break;
case MOVE_EFFECT_FEINT:
ADJUST_SCORE(-BEST_EFFECT);
ADJUST_SCORE(WORST_EFFECT);
break;
}
}
// Take advantage of ability damage bonus
if ((ability == ABILITY_STAKEOUT || ability == ABILITY_ANALYTIC) && IsBattleMoveStatus(move))
ADJUST_SCORE(-WEAK_EFFECT);
ADJUST_SCORE(BAD_EFFECT);
// This must be last or the player can gauge whether the AI is predicting based on how long it thinks
if (!IsBattlerPredictedToSwitch(battlerDef))

View File

@ -1982,6 +1982,9 @@ u32 IncreaseStatDownScore(u32 battlerAtk, u32 battlerDef, u32 stat)
// Don't decrese stat if opposing battler has Encore
if (HasBattlerSideMoveWithEffect(battlerDef, EFFECT_ENCORE))
return NO_INCREASE;
if (DoesAbilityRaiseStatsWhenLowered(gAiLogicData->abilities[battlerDef]))
return NO_INCREASE;
// TODO: Avoid decreasing stat if
// player can kill ai in 2 hits with decreased attack / sp atk stages
@ -2358,6 +2361,28 @@ bool32 HasMoveThatLowersOwnStats(u32 battlerId)
return FALSE;
}
bool32 HasMoveThatRaisesOwnStats(u32 battlerId)
{
s32 i, j;
u32 aiMove;
u16 *moves = GetMovesArray(battlerId);
for (i = 0; i < MAX_MON_MOVES; i++)
{
aiMove = moves[i];
if (aiMove != MOVE_NONE && aiMove != MOVE_UNAVAILABLE)
{
u32 additionalEffectCount = GetMoveAdditionalEffectCount(aiMove);
for (j = 0; j < additionalEffectCount; j++)
{
const struct AdditionalEffect *additionalEffect = GetMoveAdditionalEffectById(aiMove, j);
if (IsSelfStatRaisingEffect(additionalEffect->moveEffect) && additionalEffect->self)
return TRUE;
}
}
}
return FALSE;
}
bool32 HasMoveWithLowAccuracy(u32 battlerAtk, u32 battlerDef, u32 accCheck, bool32 ignoreStatus, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect)
{
s32 i;
@ -2588,6 +2613,31 @@ bool32 IsSelfStatLoweringEffect(enum BattleMoveEffects effect)
}
}
bool32 IsSelfStatRaisingEffect(enum BattleMoveEffects effect)
{
// Self stat lowering moves like Power Up Punch or Charge Beam
switch (effect)
{
case MOVE_EFFECT_ATK_PLUS_1:
case MOVE_EFFECT_DEF_PLUS_1:
case MOVE_EFFECT_SPD_PLUS_1:
case MOVE_EFFECT_SP_ATK_PLUS_1:
case MOVE_EFFECT_SP_DEF_PLUS_1:
case MOVE_EFFECT_EVS_PLUS_1:
case MOVE_EFFECT_ACC_PLUS_1:
case MOVE_EFFECT_ATK_PLUS_2:
case MOVE_EFFECT_DEF_PLUS_2:
case MOVE_EFFECT_SPD_PLUS_2:
case MOVE_EFFECT_SP_ATK_PLUS_2:
case MOVE_EFFECT_SP_DEF_PLUS_2:
case MOVE_EFFECT_EVS_PLUS_2:
case MOVE_EFFECT_ACC_PLUS_2:
return TRUE;
default:
return FALSE;
}
}
bool32 IsSwitchOutEffect(enum BattleMoveEffects effect)
{
// Switch out effects like U-Turn, Volt Switch, etc.
@ -3618,6 +3668,12 @@ bool32 IsValidDoubleBattle(u32 battlerAtk)
return FALSE;
}
// TODO: Handling for when the 'partner' is not actually a partner, a la Battle Royale or B_WILD_NATURAL_ENEMIES
bool32 IsTargetingPartner(u32 battlerAtk, u32 battlerDef)
{
return ((battlerAtk) == (battlerDef ^ BIT_FLANK));
}
u32 GetAllyChosenMove(u32 battlerId)
{
u32 partnerBattler = BATTLE_PARTNER(battlerId);
@ -4887,50 +4943,411 @@ bool32 IsMoxieTypeAbility(u32 ability)
}
}
// Should the AI use a spread move to deliberately activate its partner's ability?
bool32 ShouldTriggerAbility(u32 battler, u32 ability)
bool32 DoesAbilityRaiseStatsWhenLowered(u32 ability)
{
switch (ability)
{
case ABILITY_CONTRARY:
case ABILITY_COMPETITIVE:
case ABILITY_DEFIANT:
return TRUE;
default:
return FALSE;
}
}
bool32 DoesIntimidateRaiseStats(u32 ability)
{
switch (ability)
{
case ABILITY_COMPETITIVE:
case ABILITY_CONTRARY:
case ABILITY_DEFIANT:
case ABILITY_GUARD_DOG:
case ABILITY_RATTLED:
return TRUE;
default:
return FALSE;
}
}
// TODO: work out when to attack into the player's contextually 'beneficial' ability
bool32 ShouldTriggerAbility(u32 battlerAtk, u32 battlerDef, u32 ability)
{
if (IsTargetingPartner(battlerAtk, battlerDef))
{
switch (ability)
{
case ABILITY_LIGHTNING_ROD:
case ABILITY_STORM_DRAIN:
if (B_REDIRECT_ABILITY_IMMUNITY < GEN_5)
return FALSE;
else
return (BattlerStatCanRise(battler, ability, STAT_SPATK) && HasMoveWithCategory(battler, DAMAGE_CATEGORY_SPECIAL));
return (BattlerStatCanRise(battlerDef, ability, STAT_SPATK) && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL));
case ABILITY_DEFIANT:
case ABILITY_JUSTIFIED:
case ABILITY_MOXIE:
case ABILITY_SAP_SIPPER:
case ABILITY_THERMAL_EXCHANGE:
return (BattlerStatCanRise(battler, ability, STAT_ATK) && HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL));
return (BattlerStatCanRise(battlerDef, ability, STAT_ATK) && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_PHYSICAL));
case ABILITY_COMPETITIVE:
return (BattlerStatCanRise(battler, ability, STAT_SPATK) && HasMoveWithCategory(battler, DAMAGE_CATEGORY_SPECIAL));
return (BattlerStatCanRise(battlerDef, ability, STAT_SPATK) && HasMoveWithCategory(battlerDef, DAMAGE_CATEGORY_SPECIAL));
// TODO: logic for when to trigger Contrary
case ABILITY_CONTRARY:
return TRUE;
case ABILITY_DRY_SKIN:
case ABILITY_VOLT_ABSORB:
case ABILITY_WATER_ABSORB:
return (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_HP_AWARE);
return (gAiThinkingStruct->aiFlags[battlerDef] & AI_FLAG_HP_AWARE);
case ABILITY_RATTLED:
case ABILITY_STEAM_ENGINE:
return BattlerStatCanRise(battler, ability, STAT_SPEED);
return BattlerStatCanRise(battlerDef, ability, STAT_SPEED);
case ABILITY_FLASH_FIRE:
return (HasMoveWithType(battler, TYPE_FIRE) && !gDisableStructs[battler].flashFireBoosted);
return (HasMoveWithType(battlerDef, TYPE_FIRE) && !gDisableStructs[battlerDef].flashFireBoosted);
case ABILITY_WATER_COMPACTION:
case ABILITY_WELL_BAKED_BODY:
return (BattlerStatCanRise(battler, ability, STAT_DEF));
return (BattlerStatCanRise(battlerDef, ability, STAT_DEF));
default:
return FALSE;
}
}
else
{
return FALSE;
}
}
// Used by CheckBadMove; this is determining purely if the effect CAN change an ability, not if it SHOULD.
// At the moment, the parts about Mummy and Wandering Spirit are not actually used.
bool32 CanEffectChangeAbility(u32 battlerAtk, u32 battlerDef, u32 effect, struct AiLogicData *aiData)
{
// Dynamaxed Pokemon are immune to some ability-changing effects.
if (GetActiveGimmick(battlerDef) == GIMMICK_DYNAMAX)
{
switch (effect)
{
case EFFECT_ENTRAINMENT:
case EFFECT_SKILL_SWAP:
return FALSE;
default:
break;
}
}
if (gStatuses3[battlerDef] & STATUS3_GASTRO_ACID)
return FALSE;
u32 atkAbility = aiData->abilities[battlerAtk];
u32 defAbility = aiData->abilities[battlerDef];
bool32 hasSameAbility = (atkAbility == defAbility);
if (defAbility == ABILITY_NONE)
return FALSE;
if (atkAbility == ABILITY_NONE)
{
switch (effect)
{
case EFFECT_DOODLE:
case EFFECT_ENTRAINMENT:
case EFFECT_ROLE_PLAY:
case EFFECT_SKILL_SWAP:
return FALSE;
default:
break;
}
}
// Checking for Ability-specific immunities.
switch (effect)
{
case EFFECT_DOODLE:
if (hasSameAbility || gAbilitiesInfo[atkAbility].cantBeSuppressed || gAbilitiesInfo[defAbility].cantBeCopied)
return FALSE;
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))
{
u32 partnerAbility = aiData->abilities[BATTLE_PARTNER(battlerAtk)];
if (gAbilitiesInfo[partnerAbility].cantBeSuppressed)
return FALSE;
if (partnerAbility == defAbility)
return FALSE;
}
break;
case EFFECT_ROLE_PLAY:
if (hasSameAbility || gAbilitiesInfo[atkAbility].cantBeSuppressed || gAbilitiesInfo[defAbility].cantBeCopied)
return FALSE;
break;
case EFFECT_SKILL_SWAP:
if (hasSameAbility || gAbilitiesInfo[atkAbility].cantBeSwapped || gAbilitiesInfo[defAbility].cantBeSwapped)
return FALSE;
break;
case EFFECT_GASTRO_ACID:
if (gAbilitiesInfo[defAbility].cantBeSuppressed)
return FALSE;
break;
case EFFECT_ENTRAINMENT:
if (hasSameAbility || gAbilitiesInfo[defAbility].cantBeOverwritten || gAbilitiesInfo[atkAbility].cantBeCopied)
return FALSE;
break;
case EFFECT_SIMPLE_BEAM:
if (defAbility == ABILITY_SIMPLE || gAbilitiesInfo[defAbility].cantBeOverwritten)
return FALSE;
break;
case EFFECT_WORRY_SEED:
if (defAbility == ABILITY_INSOMNIA || gAbilitiesInfo[defAbility].cantBeOverwritten)
return FALSE;
break;
default:
return FALSE;
}
if (aiData->holdEffects[battlerDef] == HOLD_EFFECT_ABILITY_SHIELD)
{
switch (effect)
{
case EFFECT_ENTRAINMENT:
case EFFECT_GASTRO_ACID:
case EFFECT_ROLE_PLAY:
case EFFECT_SIMPLE_BEAM:
case EFFECT_SKILL_SWAP:
case EFFECT_WORRY_SEED:
return FALSE;
default:
break;
}
}
if (aiData->holdEffects[battlerAtk] == HOLD_EFFECT_ABILITY_SHIELD)
{
switch (effect)
{
case EFFECT_DOODLE:
case EFFECT_ROLE_PLAY:
case EFFECT_SKILL_SWAP:
return FALSE;
default:
break;
}
}
return TRUE;
}
bool32 DoesEffectReplaceTargetAbility(u32 effect)
{
switch (effect)
{
case EFFECT_ENTRAINMENT:
case EFFECT_GASTRO_ACID:
case EFFECT_SIMPLE_BEAM:
case EFFECT_SKILL_SWAP:
case EFFECT_WORRY_SEED:
return TRUE;
default:
return FALSE;
}
}
void AbilityChangeScore(u32 battlerAtk, u32 battlerDef, u32 effect, s32 *score, struct AiLogicData *aiData)
{
bool32 isTargetingPartner = IsTargetingPartner(battlerAtk, battlerDef);
u32 abilityAtk = aiData->abilities[battlerAtk];
u32 abilityDef = aiData->abilities[battlerDef];
bool32 partnerHasBadAbility = FALSE;
u32 partnerAbility = ABILITY_NONE;
bool32 attackerHasBadAbility = (gAbilitiesInfo[abilityAtk].aiRating < 0);
s32 currentAbilityScore, transferredAbilityScore = 0;
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battlerAtk)))
{
partnerAbility = aiData->abilities[BATTLE_PARTNER(battlerAtk)];
if (!(gAbilitiesInfo[partnerAbility].cantBeSuppressed) && (gAbilitiesInfo[partnerAbility].aiRating < 0))
partnerHasBadAbility = TRUE;
}
if (effect == EFFECT_GASTRO_ACID)
abilityAtk = ABILITY_NONE;
else if (effect == EFFECT_SIMPLE_BEAM)
abilityAtk = ABILITY_SIMPLE;
else if (effect == EFFECT_WORRY_SEED)
abilityAtk = ABILITY_INSOMNIA;
if (effect == EFFECT_DOODLE || effect == EFFECT_ROLE_PLAY || effect == EFFECT_SKILL_SWAP)
{
if (partnerHasBadAbility && effect == EFFECT_DOODLE)
ADJUST_SCORE_PTR(DECENT_EFFECT);
if (attackerHasBadAbility)
ADJUST_SCORE_PTR(DECENT_EFFECT);
currentAbilityScore = BattlerBenefitsFromAbilityScore(battlerAtk, abilityAtk, aiData);
transferredAbilityScore = BattlerBenefitsFromAbilityScore(battlerAtk, abilityDef, aiData);
ADJUST_SCORE_PTR(transferredAbilityScore - currentAbilityScore);
}
if (isTargetingPartner)
{
if (DoesEffectReplaceTargetAbility(effect))
{
if (partnerHasBadAbility)
ADJUST_SCORE_PTR(BEST_EFFECT);
currentAbilityScore = BattlerBenefitsFromAbilityScore(battlerDef, abilityDef, aiData);
transferredAbilityScore = BattlerBenefitsFromAbilityScore(battlerDef, abilityAtk, aiData);
ADJUST_SCORE_PTR(transferredAbilityScore - currentAbilityScore);
}
else // This is only Role Play as Doodle can't target the partner
{
ADJUST_SCORE_PTR(-20);
}
// Trigger Plus or Minus in modern gens. This is not in the overarching function because Skill Swap is rarely beneficial here.
if (B_PLUS_MINUS_INTERACTION >= GEN_5)
{
if (((effect == EFFECT_ENTRAINMENT) && (abilityAtk == ABILITY_PLUS || abilityAtk == ABILITY_MINUS)) || ((effect == EFFECT_ROLE_PLAY) && (abilityDef == ABILITY_PLUS || abilityDef == ABILITY_MINUS)))
ADJUST_SCORE_PTR(DECENT_EFFECT);
}
}
// Targeting an opponent.
else
{
// We already checked if we want their ability, so now we look to see if we want them to lose their ability.
if (DoesEffectReplaceTargetAbility(effect))
{
currentAbilityScore = BattlerBenefitsFromAbilityScore(battlerDef, abilityDef, aiData);
transferredAbilityScore = BattlerBenefitsFromAbilityScore(battlerDef, abilityAtk, aiData);
ADJUST_SCORE_PTR(currentAbilityScore - transferredAbilityScore);
}
}
}
s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData *aiData)
{
if (gAbilitiesInfo[ability].aiRating < 0)
return WORST_EFFECT;
switch (ability)
{
// Transferrable abilities that can be assumed to be always beneficial.
case ABILITY_CLEAR_BODY:
case ABILITY_GOOD_AS_GOLD:
case ABILITY_MAGIC_GUARD:
case ABILITY_MOODY:
case ABILITY_PURIFYING_SALT:
case ABILITY_SPEED_BOOST:
case ABILITY_WHITE_SMOKE:
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)]))
return GOOD_EFFECT;
break;
case ABILITY_CONTRARY:
if (HasMoveThatLowersOwnStats(battler))
return BEST_EFFECT;
if (HasMoveThatRaisesOwnStats(battler))
return AWFUL_EFFECT;
break;
case ABILITY_FRIEND_GUARD:
case ABILITY_POWER_SPOT:
case ABILITY_VICTORY_STAR:
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler)) && aiData->abilities[BATTLE_PARTNER(battler)] != ability)
return GOOD_EFFECT;
break;
case ABILITY_GUTS:
if (HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL) && gBattleMons[battler].status1 & (STATUS1_REFRESH))
return GOOD_EFFECT;
break;
case ABILITY_HUGE_POWER:
case ABILITY_PURE_POWER:
if (HasMoveWithCategory(battler, DAMAGE_CATEGORY_PHYSICAL))
return BEST_EFFECT;
break;
// Also used to Worry Seed WORRY_SEED
case ABILITY_INSOMNIA:
case ABILITY_VITAL_SPIRIT:
if (HasMoveWithEffect(battler, EFFECT_REST))
return WORST_EFFECT;
return NO_INCREASE;
case ABILITY_INTIMIDATE:
u32 abilityDef = aiData->abilities[FOE(battler)];
if (DoesIntimidateRaiseStats(abilityDef))
{
return AWFUL_EFFECT;
}
else
{
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(FOE(battler))))
{
abilityDef = aiData->abilities[BATTLE_PARTNER(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);
if (score1 > score2)
return score1;
else
return score2;
}
}
return IncreaseStatDownScore(battler, FOE(battler), STAT_ATK);
}
case ABILITY_NO_GUARD:
if (HasLowAccuracyMove(battler, FOE(battler)))
return GOOD_EFFECT;
break;
// Toxic counter ticks upward while Poison Healed; losing Poison Heal while Toxiced can KO.
case ABILITY_POISON_HEAL:
if (gBattleMons[battler].status1 & (STATUS1_POISON))
return WEAK_EFFECT;
if (gBattleMons[battler].status1 & (STATUS1_TOXIC_POISON))
return BEST_EFFECT;
break;
// Also used to Simple Beam SIMPLE_BEAM.
case ABILITY_SIMPLE:
// Prioritize moves like Metal Claw, Charge Beam, or Power up Punch
if (HasMoveThatRaisesOwnStats(battler))
return GOOD_EFFECT;
return NO_INCREASE;
case ABILITY_BEADS_OF_RUIN:
case ABILITY_SWORD_OF_RUIN:
case ABILITY_TABLETS_OF_RUIN:
case ABILITY_VESSEL_OF_RUIN:
if (IsDoubleBattle() && IsBattlerAlive(BATTLE_PARTNER(battler)))
{
if (aiData->abilities[BATTLE_PARTNER(battler)] != ability)
return GOOD_EFFECT;
else
return NO_INCREASE;
}
return GOOD_EFFECT;
default:
break;
}
return NO_INCREASE;
}
u32 GetThinkingBattler(u32 battler)

View File

@ -278,3 +278,35 @@ AI_SINGLE_BATTLE_TEST("AI uses Wide Guard against Earthquake when opponent would
TURN { MOVE(player, MOVE_EARTHQUAKE); EXPECT_MOVE(opponent, MOVE_WIDE_GUARD); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Worry Seed against Rest")
{
GIVEN {
PLAYER(SPECIES_ZUBAT) { Moves(MOVE_REST, MOVE_SLEEP_TALK, MOVE_AIR_CUTTER); }
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE);
OPPONENT(SPECIES_BUDEW) { Moves(MOVE_WORRY_SEED, MOVE_SLUDGE_BOMB); }
} WHEN {
TURN { MOVE(player, MOVE_AIR_CUTTER); EXPECT_MOVE(opponent, MOVE_WORRY_SEED); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Simple Beam against Contrary Leaf Storm")
{
u32 ability, move;
PARAMETRIZE { ability = ABILITY_CONTRARY; move = MOVE_LEAF_STORM; }
PARAMETRIZE { ability = ABILITY_CONTRARY; move = MOVE_CHARGE_BEAM; }
PARAMETRIZE { ability = ABILITY_OVERGROW; move = MOVE_CHARGE_BEAM; }
GIVEN {
PLAYER(SPECIES_SERPERIOR) { Moves(move); Ability(ability); }
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_OMNISCIENT);
OPPONENT(SPECIES_SWOOBAT) { Moves(MOVE_GUST, MOVE_SIMPLE_BEAM); }
} WHEN {
if (ability == ABILITY_CONTRARY && move == MOVE_LEAF_STORM)
TURN { MOVE(player, move); EXPECT_MOVE(opponent, MOVE_SIMPLE_BEAM); }
else
TURN { MOVE(player, move); NOT_EXPECT_MOVE(opponent, MOVE_SIMPLE_BEAM); }
}
}