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:
parent
38a3963ec2
commit
4052ab337c
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user