diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index 31d6437b97..6c58063350 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -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 diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 2144056cd5..991584a534 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -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); diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 751402af43..bc3c35db84 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -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)) diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 645a34beba..fd08bd7c0c 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -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) diff --git a/test/battle/ai/ai_check_viability.c b/test/battle/ai/ai_check_viability.c index 0a5a9ff3df..47cf2450a9 100644 --- a/test/battle/ai/ai_check_viability.c +++ b/test/battle/ai/ai_check_viability.c @@ -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); } + } +} + +