From f6efc75c1acf98453abf7b4185e6e380db14e8e2 Mon Sep 17 00:00:00 2001 From: Nephrite Date: Fri, 12 Jan 2024 04:01:33 +0900 Subject: [PATCH] Move functions to battle_ai_util.c --- include/battle_ai_util.h | 4 + src/battle_ai_main.c | 277 --------------------------------------- src/battle_ai_util.c | 274 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 277 deletions(-) diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index c9b6f7f691..0c3b71d7ca 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -190,5 +190,9 @@ void IncreaseConfusionScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score void IncreaseFrostbiteScore(u32 battlerAtk, u32 battlerDef, u32 move, s32 *score); s32 AI_CalcPartyMonDamage(u32 move, u32 battlerAtk, u32 battlerDef, struct BattlePokemon switchinCandidate, bool8 isPartyMonAttacker); +s32 AI_CheckMoveEffects(u32 battlerAtk, u32 battlerDef, u32 move, s32 score, struct AiLogicData *aiData, u32 predictedMove, bool32 isDoubleBattle); +s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle); +bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef); +s32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData); #endif //GUARD_BATTLE_AI_UTIL_H diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index f33041bbdf..8465da05b2 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -51,9 +51,6 @@ static s32 AI_Roaming(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 AI_Safari(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 AI_FirstBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score); -static s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle); -static bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef); -static s32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData); static s32 (*const sBattleAiFuncTable[])(u32, u32, u32, s32) = @@ -758,235 +755,6 @@ static inline void BattleAI_DoAIProcessing(struct AI_ThinkingStruct *aiThink, u3 aiThink->movesetIndex = 0; } -static s32 AI_CheckMoveEffects(u32 battlerAtk, u32 battlerDef, u32 move, s32 score, struct AiLogicData *aiData, u32 predictedMove, bool32 isDoubleBattle) -{ - u8 i; - // check move additional effects that are likely to happen - for (i = 0; i < gBattleMoves[move].numAdditionalEffects; i++) - { - // Only consider effects with a guaranteed chance to happen - if (!MoveEffectIsGuaranteed(battlerAtk, aiData->abilities[battlerAtk], &gBattleMoves[move].additionalEffects[i])) - continue; - - // Consider move effects that target self - if (gBattleMoves[move].additionalEffects[i].self) - { - switch (gBattleMoves[move].additionalEffects[i].moveEffect) - { - case MOVE_EFFECT_SPD_PLUS_2: - case MOVE_EFFECT_SPD_PLUS_1: - if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY && !AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) - ADJUST_SCORE(3); - break; - case MOVE_EFFECT_ATK_PLUS_1: - case MOVE_EFFECT_DEF_PLUS_1: - case MOVE_EFFECT_SP_ATK_PLUS_1: - case MOVE_EFFECT_SP_DEF_PLUS_1: - case MOVE_EFFECT_ACC_PLUS_1: - case MOVE_EFFECT_EVS_PLUS_1: - IncreaseStatUpScore( - battlerAtk, - battlerDef, - STAT_ATK + gBattleMoves[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1, - &score - ); - break; - case MOVE_EFFECT_ATK_PLUS_2: - case MOVE_EFFECT_DEF_PLUS_2: - case MOVE_EFFECT_SP_ATK_PLUS_2: - case MOVE_EFFECT_SP_DEF_PLUS_2: - case MOVE_EFFECT_ACC_PLUS_2: - case MOVE_EFFECT_EVS_PLUS_2: - IncreaseStatUpScore( - battlerAtk, - battlerDef, - STAT_ATK + gBattleMoves[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_2, - &score - ); - break; - // Effects that lower stat(s) - only need to consider Contrary - case MOVE_EFFECT_ATK_MINUS_1: - case MOVE_EFFECT_DEF_MINUS_1: - case MOVE_EFFECT_SPD_MINUS_1: - case MOVE_EFFECT_SP_ATK_MINUS_1: - case MOVE_EFFECT_SP_DEF_MINUS_1: - case MOVE_EFFECT_V_CREATE: - case MOVE_EFFECT_DEF_SPDEF_DOWN: - case MOVE_EFFECT_ATK_DEF_DOWN: - case MOVE_EFFECT_SP_ATK_TWO_DOWN: - if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) - ADJUST_SCORE(3); - break; - case MOVE_EFFECT_RAPIDSPIN: - if ((gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerAtk) != 0) - || (gStatuses3[battlerAtk] & STATUS3_LEECHSEED || gBattleMons[battlerAtk].status2 & STATUS2_WRAPPED)) - { - ADJUST_SCORE(3); - break; - } - //Spin checks - if (!(gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY)) - ADJUST_SCORE(-6); - break; - } - } - else // consider move effects that hinder the target - { - switch (gBattleMoves[move].additionalEffects[i].moveEffect) - { - case MOVE_EFFECT_FLINCH: - score += ShouldTryToFlinch(battlerAtk, battlerDef, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], move); - break; - case MOVE_EFFECT_SPD_MINUS_1: - case MOVE_EFFECT_SPD_MINUS_2: - if (!ShouldLowerSpeed(battlerAtk, battlerDef, aiData->abilities[battlerDef])) - break; - case MOVE_EFFECT_ATK_MINUS_1: - case MOVE_EFFECT_DEF_MINUS_1: - case MOVE_EFFECT_SP_ATK_MINUS_1: - case MOVE_EFFECT_SP_DEF_MINUS_1: - case MOVE_EFFECT_ACC_MINUS_1: - case MOVE_EFFECT_EVS_MINUS_1: - if (aiData->abilities[battlerDef] != ABILITY_CONTRARY) - ADJUST_SCORE(2); - break; - case MOVE_EFFECT_ATK_MINUS_2: - case MOVE_EFFECT_DEF_MINUS_2: - case MOVE_EFFECT_SP_ATK_MINUS_2: - case MOVE_EFFECT_SP_DEF_MINUS_2: - case MOVE_EFFECT_ACC_MINUS_2: - case MOVE_EFFECT_EVS_MINUS_2: - if (aiData->abilities[battlerDef] != ABILITY_CONTRARY) - ADJUST_SCORE(3); - break; - case MOVE_EFFECT_POISON: - IncreasePoisonScore(battlerAtk, battlerDef, move, &score); - break; - case MOVE_EFFECT_CLEAR_SMOG: - score += AI_TryToClearStats(battlerAtk, battlerDef, FALSE); - break; - case MOVE_EFFECT_SPECTRAL_THIEF: - score += AI_ShouldCopyStatChanges(battlerAtk, battlerDef); - break; - case MOVE_EFFECT_BUG_BITE: // And pluck - if (gBattleMons[battlerDef].status2 & STATUS2_SUBSTITUTE || aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD) - break; - else if (ItemId_GetPocket(aiData->items[battlerDef]) == POCKET_BERRIES) - ADJUST_SCORE(3); - break; - case MOVE_EFFECT_INCINERATE: - if (gBattleMons[battlerDef].status2 & STATUS2_SUBSTITUTE || aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD) - break; - else if (ItemId_GetPocket(aiData->items[battlerDef]) == POCKET_BERRIES || aiData->holdEffects[battlerDef] == HOLD_EFFECT_GEMS) - ADJUST_SCORE(3); - break; - case MOVE_EFFECT_SMACK_DOWN: - if (!IsBattlerGrounded(battlerDef) && HasDamagingMoveOfType(battlerAtk, TYPE_GROUND)) - ADJUST_SCORE(1); - break; - case MOVE_EFFECT_KNOCK_OFF: - if (CanKnockOffItem(battlerDef, aiData->items[battlerDef])) - { - switch (aiData->holdEffects[battlerDef]) - { - case HOLD_EFFECT_IRON_BALL: - if (HasMoveEffect(battlerDef, EFFECT_FLING)) - ADJUST_SCORE(4); - break; - case HOLD_EFFECT_LAGGING_TAIL: - case HOLD_EFFECT_STICKY_BARB: - break; - default: - ADJUST_SCORE(3); - break; - } - } - break; - case MOVE_EFFECT_STEAL_ITEM: - { - bool32 canSteal = FALSE; - - if (B_TRAINERS_KNOCK_OFF_ITEMS == TRUE) - canSteal = TRUE; - if (gBattleTypeFlags & BATTLE_TYPE_FRONTIER || GetBattlerSide(battlerAtk) == B_SIDE_PLAYER) - canSteal = TRUE; - - if (canSteal && aiData->items[battlerAtk] == ITEM_NONE - && aiData->items[battlerDef] != ITEM_NONE - && CanBattlerGetOrLoseItem(battlerDef, aiData->items[battlerDef]) - && CanBattlerGetOrLoseItem(battlerAtk, aiData->items[battlerDef]) - && !HasMoveEffect(battlerAtk, EFFECT_ACROBATICS) - && aiData->abilities[battlerDef] != ABILITY_STICKY_HOLD) - { - switch (aiData->holdEffects[battlerDef]) - { - case HOLD_EFFECT_NONE: - break; - case HOLD_EFFECT_CHOICE_BAND: - case HOLD_EFFECT_CHOICE_SCARF: - case HOLD_EFFECT_CHOICE_SPECS: - ADJUST_SCORE(2); - break; - case HOLD_EFFECT_TOXIC_ORB: - if (ShouldPoisonSelf(battlerAtk, aiData->abilities[battlerAtk])) - ADJUST_SCORE(2); - break; - case HOLD_EFFECT_FLAME_ORB: - if (ShouldBurnSelf(battlerAtk, aiData->abilities[battlerAtk])) - ADJUST_SCORE(2); - break; - case HOLD_EFFECT_BLACK_SLUDGE: - if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON)) - ADJUST_SCORE(2); - break; - case HOLD_EFFECT_IRON_BALL: - if (HasMoveEffect(battlerAtk, EFFECT_FLING)) - ADJUST_SCORE(2); - break; - case HOLD_EFFECT_LAGGING_TAIL: - case HOLD_EFFECT_STICKY_BARB: - break; - default: - ADJUST_SCORE(1); - break; - } - } - break; - } - break; - case MOVE_EFFECT_STEALTH_ROCK: - case MOVE_EFFECT_SPIKES: - score += AI_ShouldSetUpHazards(battlerAtk, battlerDef, aiData); - break; - case MOVE_EFFECT_FEINT: - if (gBattleMoves[predictedMove].effect == EFFECT_PROTECT) - ADJUST_SCORE(3); - break; - case MOVE_EFFECT_THROAT_CHOP: - if (HasSoundMove(battlerDef) && gBattleMoves[predictedMove].soundMove && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) - ADJUST_SCORE(3); - break; - case MOVE_EFFECT_FLAME_BURST: - if (isDoubleBattle) - { - if (IsBattlerAlive(BATTLE_PARTNER(battlerDef)) - && aiData->hpPercents[BATTLE_PARTNER(battlerDef)] < 12 - && aiData->abilities[BATTLE_PARTNER(battlerDef)] != ABILITY_MAGIC_GUARD - && !IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerDef), TYPE_FIRE)) - ADJUST_SCORE(1); - } - break; - case MOVE_EFFECT_WRAP: - if (!HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPIDSPIN) && !IsBattlerTrapped(battlerDef, TRUE) && ShouldTrap(battlerAtk, battlerDef, move)) - ADJUST_SCORE(5); - break; - } - } - } - - return score; -} - // AI Score Functions // AI_FLAG_CHECK_BAD_MOVE - decreases move scores static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) @@ -2916,51 +2684,6 @@ static s32 AI_TryToFaint(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) return score; } -static s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle) -{ - if (isDoubleBattle) - return min(CountPositiveStatStages(battlerDef) + CountPositiveStatStages(BATTLE_PARTNER(battlerDef)), 7); - else - return min(CountPositiveStatStages(battlerDef), 4); -} - -static bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef) -{ - u8 i; - // Want to copy positive stat changes - for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) - { - if (gBattleMons[battlerDef].statStages[i] > gBattleMons[battlerAtk].statStages[i]) - { - switch (i) - { - case STAT_ATK: - return (HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_PHYSICAL)); - case STAT_SPATK: - return (HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_SPECIAL)); - case STAT_ACC: - case STAT_EVASION: - case STAT_SPEED: - return TRUE; - case STAT_DEF: - case STAT_SPDEF: - return (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_STALL); - } - } - } - - return FALSE; -} - -//TODO - track entire opponent party data to determine hazard effectiveness -static s32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData) -{ - if (aiData->abilities[battlerDef] == ABILITY_MAGIC_BOUNCE || CountUsablePartyMons(battlerDef) == 0) - return 0; - - return 2 * gDisableStructs[battlerAtk].isFirstTurn; -} - // double battle logic static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score) { diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 4bd807dad8..7be8191d26 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -3586,3 +3586,277 @@ bool32 AI_IsBattlerAsleepOrComatose(u32 battlerId) { return (gBattleMons[battlerId].status1 & STATUS1_SLEEP) || AI_DATA->abilities[battlerId] == ABILITY_COMATOSE; } + +s32 AI_CheckMoveEffects(u32 battlerAtk, u32 battlerDef, u32 move, s32 score, struct AiLogicData *aiData, u32 predictedMove, bool32 isDoubleBattle) +{ + u8 i; + // check move additional effects that are likely to happen + for (i = 0; i < gBattleMoves[move].numAdditionalEffects; i++) + { + // Only consider effects with a guaranteed chance to happen + if (!MoveEffectIsGuaranteed(battlerAtk, aiData->abilities[battlerAtk], &gBattleMoves[move].additionalEffects[i])) + continue; + + // Consider move effects that target self + if (gBattleMoves[move].additionalEffects[i].self) + { + switch (gBattleMoves[move].additionalEffects[i].moveEffect) + { + case MOVE_EFFECT_SPD_PLUS_2: + case MOVE_EFFECT_SPD_PLUS_1: + if (aiData->abilities[battlerAtk] != ABILITY_CONTRARY && !AI_STRIKES_FIRST(battlerAtk, battlerDef, move)) + ADJUST_SCORE(3); + break; + case MOVE_EFFECT_ATK_PLUS_1: + case MOVE_EFFECT_DEF_PLUS_1: + case MOVE_EFFECT_SP_ATK_PLUS_1: + case MOVE_EFFECT_SP_DEF_PLUS_1: + case MOVE_EFFECT_ACC_PLUS_1: + case MOVE_EFFECT_EVS_PLUS_1: + IncreaseStatUpScore( + battlerAtk, + battlerDef, + STAT_ATK + gBattleMoves[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_1, + &score + ); + break; + case MOVE_EFFECT_ATK_PLUS_2: + case MOVE_EFFECT_DEF_PLUS_2: + case MOVE_EFFECT_SP_ATK_PLUS_2: + case MOVE_EFFECT_SP_DEF_PLUS_2: + case MOVE_EFFECT_ACC_PLUS_2: + case MOVE_EFFECT_EVS_PLUS_2: + IncreaseStatUpScore( + battlerAtk, + battlerDef, + STAT_ATK + gBattleMoves[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_2, + &score + ); + break; + // Effects that lower stat(s) - only need to consider Contrary + case MOVE_EFFECT_ATK_MINUS_1: + case MOVE_EFFECT_DEF_MINUS_1: + case MOVE_EFFECT_SPD_MINUS_1: + case MOVE_EFFECT_SP_ATK_MINUS_1: + case MOVE_EFFECT_SP_DEF_MINUS_1: + case MOVE_EFFECT_V_CREATE: + case MOVE_EFFECT_DEF_SPDEF_DOWN: + case MOVE_EFFECT_ATK_DEF_DOWN: + case MOVE_EFFECT_SP_ATK_TWO_DOWN: + if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY) + ADJUST_SCORE(3); + break; + case MOVE_EFFECT_RAPIDSPIN: + if ((gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY && CountUsablePartyMons(battlerAtk) != 0) + || (gStatuses3[battlerAtk] & STATUS3_LEECHSEED || gBattleMons[battlerAtk].status2 & STATUS2_WRAPPED)) + { + ADJUST_SCORE(3); + break; + } + //Spin checks + if (!(gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY)) + ADJUST_SCORE(-6); + break; + } + } + else // consider move effects that hinder the target + { + switch (gBattleMoves[move].additionalEffects[i].moveEffect) + { + case MOVE_EFFECT_FLINCH: + score += ShouldTryToFlinch(battlerAtk, battlerDef, aiData->abilities[battlerAtk], aiData->abilities[battlerDef], move); + break; + case MOVE_EFFECT_SPD_MINUS_1: + case MOVE_EFFECT_SPD_MINUS_2: + if (!ShouldLowerSpeed(battlerAtk, battlerDef, aiData->abilities[battlerDef])) + break; + case MOVE_EFFECT_ATK_MINUS_1: + case MOVE_EFFECT_DEF_MINUS_1: + case MOVE_EFFECT_SP_ATK_MINUS_1: + case MOVE_EFFECT_SP_DEF_MINUS_1: + case MOVE_EFFECT_ACC_MINUS_1: + case MOVE_EFFECT_EVS_MINUS_1: + if (aiData->abilities[battlerDef] != ABILITY_CONTRARY) + ADJUST_SCORE(2); + break; + case MOVE_EFFECT_ATK_MINUS_2: + case MOVE_EFFECT_DEF_MINUS_2: + case MOVE_EFFECT_SP_ATK_MINUS_2: + case MOVE_EFFECT_SP_DEF_MINUS_2: + case MOVE_EFFECT_ACC_MINUS_2: + case MOVE_EFFECT_EVS_MINUS_2: + if (aiData->abilities[battlerDef] != ABILITY_CONTRARY) + ADJUST_SCORE(3); + break; + case MOVE_EFFECT_POISON: + IncreasePoisonScore(battlerAtk, battlerDef, move, &score); + break; + case MOVE_EFFECT_CLEAR_SMOG: + score += AI_TryToClearStats(battlerAtk, battlerDef, FALSE); + break; + case MOVE_EFFECT_SPECTRAL_THIEF: + score += AI_ShouldCopyStatChanges(battlerAtk, battlerDef); + break; + case MOVE_EFFECT_BUG_BITE: // And pluck + if (gBattleMons[battlerDef].status2 & STATUS2_SUBSTITUTE || aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD) + break; + else if (ItemId_GetPocket(aiData->items[battlerDef]) == POCKET_BERRIES) + ADJUST_SCORE(3); + break; + case MOVE_EFFECT_INCINERATE: + if (gBattleMons[battlerDef].status2 & STATUS2_SUBSTITUTE || aiData->abilities[battlerDef] == ABILITY_STICKY_HOLD) + break; + else if (ItemId_GetPocket(aiData->items[battlerDef]) == POCKET_BERRIES || aiData->holdEffects[battlerDef] == HOLD_EFFECT_GEMS) + ADJUST_SCORE(3); + break; + case MOVE_EFFECT_SMACK_DOWN: + if (!IsBattlerGrounded(battlerDef) && HasDamagingMoveOfType(battlerAtk, TYPE_GROUND)) + ADJUST_SCORE(1); + break; + case MOVE_EFFECT_KNOCK_OFF: + if (CanKnockOffItem(battlerDef, aiData->items[battlerDef])) + { + switch (aiData->holdEffects[battlerDef]) + { + case HOLD_EFFECT_IRON_BALL: + if (HasMoveEffect(battlerDef, EFFECT_FLING)) + ADJUST_SCORE(4); + break; + case HOLD_EFFECT_LAGGING_TAIL: + case HOLD_EFFECT_STICKY_BARB: + break; + default: + ADJUST_SCORE(3); + break; + } + } + break; + case MOVE_EFFECT_STEAL_ITEM: + { + bool32 canSteal = FALSE; + + if (B_TRAINERS_KNOCK_OFF_ITEMS == TRUE) + canSteal = TRUE; + if (gBattleTypeFlags & BATTLE_TYPE_FRONTIER || GetBattlerSide(battlerAtk) == B_SIDE_PLAYER) + canSteal = TRUE; + + if (canSteal && aiData->items[battlerAtk] == ITEM_NONE + && aiData->items[battlerDef] != ITEM_NONE + && CanBattlerGetOrLoseItem(battlerDef, aiData->items[battlerDef]) + && CanBattlerGetOrLoseItem(battlerAtk, aiData->items[battlerDef]) + && !HasMoveEffect(battlerAtk, EFFECT_ACROBATICS) + && aiData->abilities[battlerDef] != ABILITY_STICKY_HOLD) + { + switch (aiData->holdEffects[battlerDef]) + { + case HOLD_EFFECT_NONE: + break; + case HOLD_EFFECT_CHOICE_BAND: + case HOLD_EFFECT_CHOICE_SCARF: + case HOLD_EFFECT_CHOICE_SPECS: + ADJUST_SCORE(2); + break; + case HOLD_EFFECT_TOXIC_ORB: + if (ShouldPoisonSelf(battlerAtk, aiData->abilities[battlerAtk])) + ADJUST_SCORE(2); + break; + case HOLD_EFFECT_FLAME_ORB: + if (ShouldBurnSelf(battlerAtk, aiData->abilities[battlerAtk])) + ADJUST_SCORE(2); + break; + case HOLD_EFFECT_BLACK_SLUDGE: + if (IS_BATTLER_OF_TYPE(battlerAtk, TYPE_POISON)) + ADJUST_SCORE(2); + break; + case HOLD_EFFECT_IRON_BALL: + if (HasMoveEffect(battlerAtk, EFFECT_FLING)) + ADJUST_SCORE(2); + break; + case HOLD_EFFECT_LAGGING_TAIL: + case HOLD_EFFECT_STICKY_BARB: + break; + default: + ADJUST_SCORE(1); + break; + } + } + break; + } + break; + case MOVE_EFFECT_STEALTH_ROCK: + case MOVE_EFFECT_SPIKES: + score += AI_ShouldSetUpHazards(battlerAtk, battlerDef, aiData); + break; + case MOVE_EFFECT_FEINT: + if (gBattleMoves[predictedMove].effect == EFFECT_PROTECT) + ADJUST_SCORE(3); + break; + case MOVE_EFFECT_THROAT_CHOP: + if (HasSoundMove(battlerDef) && gBattleMoves[predictedMove].soundMove && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER) + ADJUST_SCORE(3); + break; + case MOVE_EFFECT_FLAME_BURST: + if (isDoubleBattle) + { + if (IsBattlerAlive(BATTLE_PARTNER(battlerDef)) + && aiData->hpPercents[BATTLE_PARTNER(battlerDef)] < 12 + && aiData->abilities[BATTLE_PARTNER(battlerDef)] != ABILITY_MAGIC_GUARD + && !IS_BATTLER_OF_TYPE(BATTLE_PARTNER(battlerDef), TYPE_FIRE)) + ADJUST_SCORE(1); + } + break; + case MOVE_EFFECT_WRAP: + if (!HasMoveWithMoveEffect(battlerDef, MOVE_EFFECT_RAPIDSPIN) && !IsBattlerTrapped(battlerDef, TRUE) && ShouldTrap(battlerAtk, battlerDef, move)) + ADJUST_SCORE(5); + break; + } + } + } + + return score; +} + +s32 AI_TryToClearStats(u32 battlerAtk, u32 battlerDef, bool32 isDoubleBattle) +{ + if (isDoubleBattle) + return min(CountPositiveStatStages(battlerDef) + CountPositiveStatStages(BATTLE_PARTNER(battlerDef)), 7); + else + return min(CountPositiveStatStages(battlerDef), 4); +} + +bool32 AI_ShouldCopyStatChanges(u32 battlerAtk, u32 battlerDef) +{ + u8 i; + // Want to copy positive stat changes + for (i = STAT_ATK; i < NUM_BATTLE_STATS; i++) + { + if (gBattleMons[battlerDef].statStages[i] > gBattleMons[battlerAtk].statStages[i]) + { + switch (i) + { + case STAT_ATK: + return (HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_PHYSICAL)); + case STAT_SPATK: + return (HasMoveWithCategory(battlerAtk, BATTLE_CATEGORY_SPECIAL)); + case STAT_ACC: + case STAT_EVASION: + case STAT_SPEED: + return TRUE; + case STAT_DEF: + case STAT_SPDEF: + return (AI_THINKING_STRUCT->aiFlags[battlerAtk] & AI_FLAG_STALL); + } + } + } + + return FALSE; +} + +//TODO - track entire opponent party data to determine hazard effectiveness +s32 AI_ShouldSetUpHazards(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData) +{ + if (aiData->abilities[battlerDef] == ABILITY_MAGIC_BOUNCE || CountUsablePartyMons(battlerDef) == 0) + return 0; + + return 2 * gDisableStructs[battlerAtk].isFirstTurn; +}