diff --git a/include/battle.h b/include/battle.h index 9192d4ed33..f534f416bc 100644 --- a/include/battle.h +++ b/include/battle.h @@ -242,7 +242,7 @@ struct WishFutureKnock u8 futureSightAttacker[MAX_BATTLERS_COUNT]; u16 futureSightMove[MAX_BATTLERS_COUNT]; u8 wishCounter[MAX_BATTLERS_COUNT]; - u8 wishMonId[MAX_BATTLERS_COUNT]; + u8 wishPartyId[MAX_BATTLERS_COUNT]; u8 weatherDuration; u8 knockedOffMons[2]; // Each battler is represented by a bit. The array entry is dependent on the battler's side. }; diff --git a/include/battle_ai_main.h b/include/battle_ai_main.h index 66c614f78c..c98cedbd96 100644 --- a/include/battle_ai_main.h +++ b/include/battle_ai_main.h @@ -3,9 +3,10 @@ // return values for BattleAI_ChooseMoveOrAction // 0 - 3 are move idx -#define AI_CHOICE_FLEE 4 -#define AI_CHOICE_WATCH 5 -#define AI_CHOICE_SWITCH 7 +#define AI_CHOICE_FLEE 4 +#define AI_CHOICE_WATCH 5 +#define AI_CHOICE_SWITCH 7 +#define AI_CHOICE_USE_ITEM 8 #define RETURN_SCORE_PLUS(val) \ { \ diff --git a/include/battle_ai_switch_items.h b/include/battle_ai_switch_items.h index 70dc41b34b..6a8042cc96 100644 --- a/include/battle_ai_switch_items.h +++ b/include/battle_ai_switch_items.h @@ -32,7 +32,7 @@ enum { }; void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId); -void AI_TrySwitchOrUseItem(void); +u8 AI_TrySwitchOrUseItem(u8 currAction); u8 GetMostSuitableMonToSwitchInto(void); bool32 ShouldSwitch(void); diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 1b2591899b..5ccf68c58e 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -1,9 +1,9 @@ #ifndef GUARD_BATTLE_AI_UTIL_H #define GUARD_BATTLE_AI_UTIL_H -// for IsAiFaster -#define AI_CHECK_FASTER 0 // if_user_faster -#define AI_CHECK_SLOWER 1 // if_target_faster +// for AI_WhoStrikesFirst +#define AI_IS_FASTER 0 +#define AI_IS_SLOWER 1 #define FOE(battler) ((battler ^ BIT_SIDE) & BIT_SIDE) @@ -21,12 +21,13 @@ void SaveBattlerData(u8 battlerId); void SetBattlerData(u8 battlerId); void RestoreBattlerData(u8 battlerId); +bool32 WillAIStrikeFirst(void); u32 GetTotalBaseStat(u32 species); bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler); bool32 AtMaxHp(u8 battler); u32 GetHealthPercentage(u8 battler); bool32 IsBattlerTrapped(u8 battler, bool8 switching); -bool32 IsAiFaster(u8 battler); +u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2); bool32 CanTargetFaintAi(u8 battlerDef, u8 battlerAtk); bool32 CanMoveFaintBattler(u16 move, u8 battlerDef, u8 battlerAtk, u8 nHits); bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgMod); @@ -52,10 +53,13 @@ bool32 ShouldRecover(u8 battlerAtk, u8 battlerDef, u16 move, u8 healPercent); bool32 ShouldSetScreen(u8 battlerAtk, u8 battlerDef, u16 moveEffect); bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 moveIndex); bool32 IsRecycleEncouragedItem(u16 item); +bool32 ShouldRestoreHpBerry(u8 battlerAtk, u16 item); +bool32 IsStatBoostingBerry(u16 item); bool32 CanKnockOffItem(u8 battler, u16 item); bool32 IsAbilityOfRating(u16 ability, s8 rating); s8 GetAbilityRating(u16 ability); bool32 AI_IsAbilityOnSide(u32 battlerId, u32 ability); +bool32 AI_MoveMakesContact(u32 ability, u32 holdEffect, u16 move); // stat stage checks bool32 AnyStatIsRaised(u8 battlerId); diff --git a/include/battle_main.h b/include/battle_main.h index bf95ca1568..184786ea57 100644 --- a/include/battle_main.h +++ b/include/battle_main.h @@ -21,6 +21,8 @@ struct UnknownPokemonStruct4 /*0x1D*/ u8 language; }; +struct ChooseMoveStruct; + #define TYPE_NAME_LENGTH 6 #define ABILITY_NAME_LENGTH 12 @@ -70,6 +72,7 @@ void RunBattleScriptCommands(void); bool8 TryRunFromBattle(u8 battlerId); void SpecialStatusesClear(void); void SetTypeBeforeUsingMove(u16 move, u8 battlerAtk); +void FillChooseMoveStruct(struct ChooseMoveStruct *moveInfo); extern struct UnknownPokemonStruct4 gMultiPartnerParty[MULTI_PARTY_SIZE]; diff --git a/include/constants/battle_config.h b/include/constants/battle_config.h index 5e6c7d7293..29b7b57503 100644 --- a/include/constants/battle_config.h +++ b/include/constants/battle_config.h @@ -92,6 +92,9 @@ #define GEN_8 5 #endif +// Mega Evolution settings +#define B_MEGA_EVO_TURN_ORDER GEN_7 // In Gen7, a Pokémon's Speed after Mega Evolution is used to determine turn order, not its Speed before. + // Calculation settings #define B_CRIT_CHANCE GEN_7 // Chances of a critical hit landing. See CalcCritChanceStage. #define B_CRIT_MULTIPLIER GEN_7 // In Gen6+, critical hits multiply damage by 1.5 instead of 2. @@ -156,6 +159,7 @@ #define B_SKILL_SWAP GEN_7 // In Gen4+, Skill Swap triggers switch-in abilities after use. #define B_BRICK_BREAK GEN_7 // In Gen4+, you can destroy your own side's screens. In Gen 5+, screens are not removed if the target is immune. #define B_WISH_HP_SOURCE GEN_7 // In Gen5+, Wish heals half of the user's max HP instead of the target's. +#define B_RAMPAGE_CANCELLING GEN_7 // In Gen5+, a failed Thrash, etc, will cancel except on its last turn. // Ability settings #define B_ABILITY_WEATHER GEN_7 // In Gen6+, ability-induced weather lasts 5 turns. Before, it lasted until the battle ended or until it was changed by a move or a different weather-affecting ability. diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index dda1ff9cc4..3b67164856 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -209,6 +209,10 @@ u8 BattleAI_ChooseMoveOrAction(void) ret = ChooseMoveOrAction_Singles(); else ret = ChooseMoveOrAction_Doubles(); + + AI_THINKING_STRUCT->movesetIndex = ret; + AI_THINKING_STRUCT->moveConsidered = gBattleMons[sBattler_AI].moves[AI_THINKING_STRUCT->movesetIndex]; + ret = AI_TrySwitchOrUseItem(ret); // Clear protect structures, some flags may be set during AI calcs // e.g. pranksterElevated from GetMovePriority @@ -278,47 +282,6 @@ static u8 ChooseMoveOrAction_Singles(void) return AI_CHOICE_WATCH; gActiveBattler = sBattler_AI; - // If can switch. - if (CountUsablePartyMons(sBattler_AI) > 0 - && !IsAbilityPreventingEscape(sBattler_AI) - && !(gBattleMons[gActiveBattler].status2 & (STATUS2_WRAPPED | STATUS2_ESCAPE_PREVENTION)) - && !(gStatuses3[gActiveBattler] & STATUS3_ROOTED) - && !(gBattleTypeFlags & (BATTLE_TYPE_ARENA | BATTLE_TYPE_PALACE)) - && AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS)) - { - // Consider switching if all moves are worthless to use. - if (GetTotalBaseStat(gBattleMons[sBattler_AI].species) >= 310 // Mon is not weak. - && gBattleMons[sBattler_AI].hp >= gBattleMons[sBattler_AI].maxHP / 2) - { - s32 cap = AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93; - for (i = 0; i < MAX_MON_MOVES; i++) - { - if (AI_THINKING_STRUCT->score[i] > cap) - break; - } - - if (i == MAX_MON_MOVES && GetMostSuitableMonToSwitchInto() != PARTY_SIZE) - { - AI_THINKING_STRUCT->switchMon = TRUE; - return AI_CHOICE_SWITCH; - } - } - - // Consider switching if your mon with truant is bodied by Protect spam. - // Or is using a double turn semi invulnerable move(such as Fly) and is faster. - if (GetBattlerAbility(sBattler_AI) == ABILITY_TRUANT - && IsTruantMonVulnerable(sBattler_AI, gBattlerTarget) - && gDisableStructs[sBattler_AI].truantCounter - && gBattleMons[sBattler_AI].hp >= gBattleMons[sBattler_AI].maxHP / 2) - { - if (GetMostSuitableMonToSwitchInto() != PARTY_SIZE) - { - AI_THINKING_STRUCT->switchMon = TRUE; - return AI_CHOICE_SWITCH; - } - } - } - numOfBestMoves = 1; currentMoveArray[0] = AI_THINKING_STRUCT->score[0]; consideredMoveArray[0] = 0; @@ -559,7 +522,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) } // check off screen - if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) != 1) + if (IsSemiInvulnerable(battlerDef, move) && moveEffect != EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) RETURN_SCORE_MINUS(20); // if target off screen and we go first, don't use move // check if negates type @@ -1258,7 +1221,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB) && !PartnerHasSameMoveEffectWithoutTarget(AI_DATA->battlerAtkPartner, move, AI_DATA->partnerMove)) { - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // attacker should go first + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first { if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF) score -= 10; // no anticipated move to disable @@ -1278,7 +1241,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB) && !DoesPartnerHaveSameMoveEffect(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove)) { - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // attacker should go first + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first { if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF) score -= 10; // no anticipated move to encore @@ -1392,22 +1355,22 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) } break; case EFFECT_SANDSTORM: - if (gBattleWeather & WEATHER_SANDSTORM_ANY //TODO | WEATHER_PRIMAL_ANY) + if (gBattleWeather & (WEATHER_SANDSTORM_ANY | WEATHER_PRIMAL_ANY) || PartnerMoveEffectIsWeather(AI_DATA->battlerAtkPartner, AI_DATA->partnerMove)) score -= 8; break; case EFFECT_SUNNY_DAY: - if (gBattleWeather & WEATHER_SUN_ANY //TODO | WEATHER_PRIMAL_ANY) + if (gBattleWeather & (WEATHER_SUN_ANY | WEATHER_PRIMAL_ANY) || PartnerMoveEffectIsWeather(AI_DATA->battlerAtkPartner, AI_DATA->partnerMove)) score -= 8; break; case EFFECT_RAIN_DANCE: - if (gBattleWeather & WEATHER_RAIN_ANY //TODO | WEATHER_PRIMAL_ANY) + if (gBattleWeather & (WEATHER_RAIN_ANY | WEATHER_PRIMAL_ANY) || PartnerMoveEffectIsWeather(AI_DATA->battlerAtkPartner, AI_DATA->partnerMove)) score -= 8; break; case EFFECT_HAIL: - if (gBattleWeather & WEATHER_HAIL_ANY //TODO | WEATHER_PRIMAL_ANY) + if (gBattleWeather & (WEATHER_HAIL_ANY | WEATHER_PRIMAL_ANY) || PartnerMoveEffectIsWeather(AI_DATA->battlerAtkPartner, AI_DATA->partnerMove)) score -= 8; break; @@ -1719,7 +1682,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_SPITE: case EFFECT_MIMIC: - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // attacker should go first + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first { if (gLastMoves[battlerDef] == MOVE_NONE || gLastMoves[battlerDef] == 0xFFFF) @@ -1876,7 +1839,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) if (isDoubleBattle) { if (IsHazardMoveEffect(gBattleMoves[AI_DATA->partnerMove].effect) // partner is going to set up hazards - && GetWhoStrikesFirst(AI_DATA->battlerAtkPartner, battlerAtk, FALSE)) // partner is going to set up before the potential Defog + && AI_WhoStrikesFirst(AI_DATA->battlerAtkPartner, battlerAtk) == AI_IS_FASTER) // partner is going to set up before the potential Defog { score -= 10; break; // Don't use Defog if partner is going to set up hazards @@ -1914,7 +1877,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_SEMI_INVULNERABLE: if (predictedMove != MOVE_NONE - && GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1 + && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER && gBattleMoves[predictedMove].effect == EFFECT_SEMI_INVULNERABLE) score -= 10; // Don't Fly/dig/etc if opponent is going to fly/dig/etc after you @@ -2095,7 +2058,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) case EFFECT_ME_FIRST: if (predictedMove != MOVE_NONE) { - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) score -= 10; // Target is predicted to go first, Me First will fail else return AI_CheckBadMove(battlerAtk, battlerDef, predictedMove, score); @@ -2282,7 +2245,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) } break; case EFFECT_ELECTRIFY: - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER //|| GetMoveTypeSpecial(battlerDef, predictedMove) == TYPE_ELECTRIC // Move will already be electric type || PartnerMoveIsSameAsAttacker(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove)) score -= 10; @@ -2311,7 +2274,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) case EFFECT_INSTRUCT: { u16 instructedMove; - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) instructedMove = predictedMove; else instructedMove = gLastMoves[battlerDef]; @@ -2351,21 +2314,21 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_QUASH: if (!isDoubleBattle - || GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1 + || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER || PartnerMoveIsSameAsAttacker(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove)) score -= 10; break; case EFFECT_AFTER_YOU: if (!IsTargetingPartner(battlerAtk, battlerDef) || !isDoubleBattle - || GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1 + || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER || PartnerMoveIsSameAsAttacker(AI_DATA->battlerAtkPartner, battlerDef, move, AI_DATA->partnerMove)) score -= 10; break; case EFFECT_SUCKER_PUNCH: if (predictedMove != MOVE_NONE) { - if (IS_MOVE_STATUS(predictedMove) || GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) // opponent going first + if (IS_MOVE_STATUS(predictedMove) || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent going first score -= 10; } break; @@ -2413,11 +2376,11 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score--; break; case EFFECT_VITAL_THROW: - if (IsAiFaster(AI_CHECK_FASTER) && GetHealthPercentage(battlerAtk) < 40) + if (WillAIStrikeFirst() && GetHealthPercentage(battlerAtk) < 40) score--; // don't want to move last break; case EFFECT_FLAIL: - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1 // opponent should go first + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER // Opponent should go first || GetHealthPercentage(battlerAtk) > 50) score -= 4; break; @@ -2468,7 +2431,7 @@ static s16 AI_TryToFaint(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0) && gBattleMoves[move].effect != EFFECT_EXPLOSION) { // this move can faint the target - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 || GetMovePriority(battlerAtk, move) > 0) + if (!WillAIStrikeFirst() || GetMovePriority(battlerAtk, move) > 0) score += 4; // we go first or we're using priority move else score += 2; @@ -2516,7 +2479,7 @@ static s16 AI_TryToFaint(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) } //AI_TryToFaint_CheckIfDanger - if (!IsAiFaster(AI_CHECK_FASTER) && CanTargetFaintAi(battlerDef, battlerAtk)) + if (!WillAIStrikeFirst() && CanTargetFaintAi(battlerDef, battlerAtk)) { // AI_TryToFaint_Danger if (GetMoveDamageResult(move) != MOVE_POWER_BEST) score--; @@ -2566,7 +2529,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) // Ally decided to use Frost Breath on us. we must have Anger Point as our ability if (AI_DATA->atkAbility == ABILITY_ANGER_POINT) { - if (GetWhoStrikesFirst(battlerAtk, battlerAtkPartner, TRUE) == 1) // partner moving first + if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_SLOWER) // Partner moving first { // discourage raising our attack since it's about to be maxed out if (IsAttackBoostMoveEffect(effect)) @@ -2833,7 +2796,7 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) case EFFECT_INSTRUCT: { u16 instructedMove; - if (GetWhoStrikesFirst(battlerAtk, battlerAtkPartner, TRUE) == 0) + if (AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_FASTER) instructedMove = AI_DATA->partnerMove; else instructedMove = gLastMoves[battlerAtkPartner]; @@ -2847,8 +2810,8 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) } break; case EFFECT_AFTER_YOU: - if (GetWhoStrikesFirst(battlerAtkPartner, FOE(battlerAtkPartner), TRUE) == 1 // opponent mon 1 goes before partner - || GetWhoStrikesFirst(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)), TRUE) == 1) // opponent mon 2 goes before partner + if (AI_WhoStrikesFirst(battlerAtkPartner, FOE(battlerAtkPartner) == AI_IS_SLOWER) // Opponent mon 1 goes before partner + || AI_WhoStrikesFirst(battlerAtkPartner, BATTLE_PARTNER(FOE(battlerAtkPartner)) == AI_IS_SLOWER)) // Opponent mon 2 goes before partner { if (gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_COUNTER || gBattleMoves[AI_DATA->partnerMove].effect == EFFECT_MIRROR_COAT) break; // These moves need to go last @@ -2874,7 +2837,9 @@ static s16 AI_DoubleBattle(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) case EFFECT_EARTHQUAKE: case EFFECT_MAGNITUDE: if (!IsBattlerGrounded(battlerAtkPartner) - || (GetWhoStrikesFirst(battlerAtk, battlerAtkPartner, TRUE) == 1 && IsUngroundingEffect(gBattleMoves[AI_DATA->partnerMove].effect))) + || (IsBattlerGrounded(battlerAtkPartner) + && AI_WhoStrikesFirst(battlerAtk, battlerAtkPartner) == AI_IS_SLOWER + && IsUngroundingEffect(gBattleMoves[AI_DATA->partnerMove].effect))) score += 2; else if (IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_FIRE) || IS_BATTLER_OF_TYPE(battlerAtkPartner, TYPE_ELECTRIC) @@ -2925,9 +2890,9 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) // check already dead if (!IsBattlerIncapacitated(battlerDef, AI_DATA->defAbility) && CanTargetFaintAi(battlerAtk, battlerDef) - && GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) // opponent should go first + && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent should go first { - if (atkPriority > 0) + if (atkPriority > 0) score++; else score--; @@ -2969,7 +2934,11 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) { case ABILITY_MOXIE: case ABILITY_BEAST_BOOST: - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // attacker should go first + case ABILITY_CHILLING_NEIGH: + case ABILITY_GRIM_NEIGH: + case ABILITY_AS_ONE_ICE_RIDER: + case ABILITY_AS_ONE_SHADOW_RIDER: + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker should go first { if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) score += 8; // prioritize killing target for stat boost @@ -2980,7 +2949,6 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) // move effect checks switch (moveEffect) { - case EFFECT_HIT: break; case EFFECT_SLEEP: @@ -3042,7 +3010,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_SPEED_UP: case EFFECT_SPEED_UP_2: - if (IsAiFaster(AI_CHECK_SLOWER)) + if (!WillAIStrikeFirst()) { if (!AI_RandLessThan(70)) score += 3; @@ -3140,7 +3108,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_SPEED_DOWN: case EFFECT_SPEED_DOWN_2: - if (IsAiFaster(AI_CHECK_FASTER)) + if (WillAIStrikeFirst()) score -= 3; else if (!AI_RandLessThan(70)) score += 2; @@ -3243,6 +3211,10 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) case EFFECT_MULTI_HIT: case EFFECT_DOUBLE_HIT: case EFFECT_TRIPLE_KICK: + if (AI_MoveMakesContact(AI_DATA->atkAbility, AI_DATA->atkHoldEffect, move) + && AI_DATA->atkAbility != ABILITY_MAGIC_GUARD + && AI_DATA->defHoldEffect == HOLD_EFFECT_ROCKY_HELMET) + score -= 2; break; case EFFECT_CONVERSION: if (!IS_BATTLER_OF_TYPE(battlerAtk, gBattleMoves[gBattleMons[battlerAtk].moves[0]].type)) @@ -3381,7 +3353,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score += 2; break; case EFFECT_SPEED_DOWN_HIT: - if (IsAiFaster(AI_CHECK_FASTER)) + if (WillAIStrikeFirst()) score -= 2; else if (!AI_RandLessThan(70)) score++; @@ -3413,7 +3385,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score++; break; case EFFECT_MIMIC: - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) { if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF) return AI_CheckViability(battlerAtk, battlerDef, gLastMoves[battlerDef], score); @@ -3472,7 +3444,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) if (gDisableStructs[battlerDef].disableTimer == 0 && (B_MENTAL_HERB >= GEN_5 && AI_DATA->defHoldEffect != HOLD_EFFECT_MENTAL_HERB)) // mental herb { - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // AI goes first + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // AI goes first { if (gLastMoves[battlerDef] != MOVE_NONE && gLastMoves[battlerDef] != 0xFFFF) @@ -3522,11 +3494,11 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score++; break; case EFFECT_SPEED_UP_HIT: - if (AI_DATA->atkAbility == ABILITY_SERENE_GRACE && AI_DATA->defAbility != ABILITY_CONTRARY && IsAiFaster(AI_CHECK_SLOWER)) + if (AI_DATA->atkAbility == ABILITY_SERENE_GRACE && AI_DATA->defAbility != ABILITY_CONTRARY && !WillAIStrikeFirst()) score += 3; break; case EFFECT_DESTINY_BOND: - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 && CanTargetFaintAi(battlerDef, battlerAtk)) + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER && CanTargetFaintAi(battlerDef, battlerAtk)) score += 3; break; case EFFECT_SPITE: @@ -3774,7 +3746,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) && AI_DATA->atkAbility != ABILITY_CONTRARY && CanIndexMoveFaintTarget(battlerAtk, battlerDef, AI_THINKING_STRUCT->movesetIndex, 0)) { - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Attacker goes first + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first score += 9; else score += 3; @@ -3819,7 +3791,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score++; if (predictedMove != MOVE_NONE && !isDoubleBattle) { - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Attacker goes first + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first { if (gBattleMoves[predictedMove].effect == EFFECT_EXPLOSION || gBattleMoves[predictedMove].effect == EFFECT_PROTECT) @@ -3886,7 +3858,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_ATTRACT: if (!isDoubleBattle && BattlerWillFaintFromSecondaryDamage(battlerDef, AI_DATA->defAbility) - && GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) // Target goes first + && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Target goes first break; // Don't use if the attract won't have a change to activate if (gBattleMons[battlerDef].status1 & STATUS1_ANY @@ -3931,7 +3903,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) if (isDoubleBattle) { if (IsHazardMoveEffect(gBattleMoves[AI_DATA->partnerMove].effect) // Partner is going to set up hazards - && GetWhoStrikesFirst(battlerAtk, AI_DATA->battlerAtkPartner, TRUE) == 1) // Partner going first + && AI_WhoStrikesFirst(battlerAtk, AI_DATA->battlerAtkPartner) == AI_IS_SLOWER) // Partner going first break; // Don't use Defog if partner is going to set up hazards } @@ -4101,6 +4073,18 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score++; if (IsRecycleEncouragedItem(GetUsedHeldItem(battlerAtk))) score++; + if (AI_DATA->atkAbility == ABILITY_RIPEN) + { + u16 item = GetUsedHeldItem(battlerAtk); + u16 toHeal = (ItemId_GetHoldEffectParam(item) == 10) ? 10 : gBattleMons[battlerAtk].maxHP / ItemId_GetHoldEffectParam(item); + + if (IsStatBoostingBerry(item) && atkHpPercent > 60) + score++; + else if (ShouldRestoreHpBerry(battlerAtk, item) && !CanAIFaintTarget(battlerAtk, battlerDef, 0) + && ((GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 && CanTargetFaintAiWithMod(battlerDef, battlerAtk, 0, 0)) + || !CanTargetFaintAiWithMod(battlerDef, battlerAtk, toHeal, 0))) + score++; // Recycle healing berry if we can't otherwise faint the target and the target wont kill us after we activate the berry + } break; case EFFECT_BRICK_BREAK: if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_REFLECT) @@ -4408,13 +4392,13 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score++; break; case EFFECT_THROAT_CHOP: - if (predictedMove != MOVE_NONE && TestMoveFlags(predictedMove, FLAG_SOUND) && GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) + if (predictedMove != MOVE_NONE && TestMoveFlags(predictedMove, FLAG_SOUND) && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) score += 3; // Ai goes first and predicts the target will use a sound move else if (TestMoveFlagsInMoveset(battlerDef, FLAG_SOUND)) score += 3; break; case EFFECT_HEAL_BLOCK: - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 && predictedMove != MOVE_NONE && IsHealingMoveEffect(gBattleMoves[predictedMove].effect)) + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER && predictedMove != MOVE_NONE && IsHealingMoveEffect(gBattleMoves[predictedMove].effect)) score += 3; // Try to cancel healing move else if (HasHealingEffect(battlerDef) || AI_DATA->defHoldEffect == HOLD_EFFECT_LEFTOVERS || (AI_DATA->defHoldEffect == HOLD_EFFECT_BLACK_SLUDGE && IS_BATTLER_OF_TYPE(battlerDef, TYPE_POISON))) @@ -4450,7 +4434,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) break; case EFFECT_QUASH: if (isDoubleBattle - && GetWhoStrikesFirst(AI_DATA->battlerAtkPartner, battlerDef, TRUE) == 1) // Attacker partner wouldn't go before target + && AI_WhoStrikesFirst(AI_DATA->battlerAtkPartner, battlerDef) == AI_IS_SLOWER) // Attacker partner wouldn't go before target score++; break; case EFFECT_TAILWIND: @@ -4472,8 +4456,8 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) if (IsBattlerGrounded(battlerAtk) && HasDamagingMoveOfType(battlerDef, TYPE_ELECTRIC) && !(AI_GetTypeEffectiveness(MOVE_EARTHQUAKE, battlerDef, battlerAtk) == AI_EFFECTIVENESS_x0)) // Doesn't resist ground move { - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Attacker goes first - { + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first + { if (gBattleMoves[predictedMove].type == TYPE_GROUND) score += 3; // Cause the enemy's move to fail break; @@ -4487,7 +4471,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) } break; case EFFECT_CAMOUFLAGE: - if (predictedMove != MOVE_NONE && GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0 // Attacker goes first + if (predictedMove != MOVE_NONE && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER // Attacker goes first && !IS_MOVE_STATUS(move) && AI_GetTypeEffectiveness(predictedMove, battlerDef, battlerAtk) != AI_EFFECTIVENESS_x0) score++; break; @@ -4530,7 +4514,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) } break; case EFFECT_FLAIL: - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Ai goes first + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Ai goes first { if (GetHealthPercentage(battlerAtk) < 20) score++; @@ -4572,7 +4556,7 @@ static s16 AI_CheckViability(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) score += 2; break; case EFFECT_ENDEAVOR: - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) // Opponent faster + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent faster { if (GetHealthPercentage(battlerAtk) < 40) score++; @@ -4603,7 +4587,7 @@ static s16 AI_SetupFirstTurn(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) return score; if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING - && GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1 + && AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER && CanTargetFaintAi(battlerDef, battlerAtk) && GetMovePriority(battlerAtk, move) == 0) { diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 400876cc9b..f40677bd9b 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -12,11 +12,82 @@ #include "constants/item_effects.h" #include "constants/items.h" #include "constants/moves.h" +#include "constants/battle_ai.h" // this file's functions static bool8 HasSuperEffectiveMoveAgainstOpponents(bool8 noRng); static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent); static bool8 ShouldUseItem(void); +static bool32 AI_ShouldHeal(u8 healAmount); + +// Functions +u8 AI_TrySwitchOrUseItem(u8 currAction) +{ + struct Pokemon *party; + u8 battlerIn1, battlerIn2; + s32 firstId; + s32 lastId; // + 1 + u8 battlerIdentity = GetBattlerPosition(gActiveBattler); + + if (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER)) + return currAction; + + if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER) + party = gPlayerParty; // Player's partner + else + party = gEnemyParty; // Enemy trainer + + // Switching logic + if (ShouldSwitch()) + { + if (*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) == PARTY_SIZE) + { + s32 monToSwitchId = GetMostSuitableMonToSwitchInto(); + if (monToSwitchId == PARTY_SIZE) + { + if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) + { + battlerIn1 = GetBattlerAtPosition(battlerIdentity); + battlerIn2 = battlerIn1; + } + else + { + battlerIn1 = GetBattlerAtPosition(battlerIdentity); + battlerIn2 = GetBattlerAtPosition(battlerIdentity ^ BIT_FLANK); + } + + GetAIPartyIndexes(gActiveBattler, &firstId, &lastId); + + for (monToSwitchId = firstId; monToSwitchId < lastId; monToSwitchId++) + { + if (GetMonData(&party[monToSwitchId], MON_DATA_HP) == 0) + continue; + if (monToSwitchId == gBattlerPartyIndexes[battlerIn1]) + continue; + if (monToSwitchId == gBattlerPartyIndexes[battlerIn2]) + continue; + if (monToSwitchId == *(gBattleStruct->monToSwitchIntoId + battlerIn1)) + continue; + if (monToSwitchId == *(gBattleStruct->monToSwitchIntoId + battlerIn2)) + continue; + + break; + } + } + + *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = monToSwitchId; + } + + *(gBattleStruct->monToSwitchIntoId + gActiveBattler) = *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler); + return AI_CHOICE_SWITCH; + } + + // Item Logic + if (ShouldUseItem()) + return AI_CHOICE_USE_ITEM; + + return currAction; +} void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId) { @@ -39,11 +110,52 @@ void GetAIPartyIndexes(u32 battlerId, s32 *firstId, s32 *lastId) static bool8 ShouldSwitchIfAllBadMoves(void) { - if (gBattleResources->ai->switchMon) + u32 i; + + if (AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY | AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_PREFER_BATON_PASS)) { - gBattleResources->ai->switchMon = 0; + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + { + // TODO double battle bad move switching logic + } + else + { + // Single battle. gActiveBattler is the enemy's battler id + if (GetTotalBaseStat(gBattleMons[gActiveBattler].species) >= 310 // Mon is not weak. + && gBattleMons[gActiveBattler].hp >= gBattleMons[gActiveBattler].maxHP / 2) + { + s32 cap = AI_THINKING_STRUCT->aiFlags & (AI_FLAG_CHECK_VIABILITY) ? 95 : 93; + for (i = 0; i < MAX_MON_MOVES; i++) + { + if (AI_THINKING_STRUCT->score[i] > cap) + break; + } + + if (i == MAX_MON_MOVES && GetMostSuitableMonToSwitchInto() != PARTY_SIZE) + { + AI_THINKING_STRUCT->switchMon = TRUE; + } + } + + // Consider switching if your mon with truant is bodied by Protect spam. + // Or is using a double turn semi invulnerable move(such as Fly) and is faster. + if (GetBattlerAbility(gActiveBattler) == ABILITY_TRUANT + && IsTruantMonVulnerable(gActiveBattler, gBattlerTarget) + && gDisableStructs[gActiveBattler].truantCounter + && gBattleMons[gActiveBattler].hp >= gBattleMons[gActiveBattler].maxHP / 2) + { + if (GetMostSuitableMonToSwitchInto() != PARTY_SIZE) + { + AI_THINKING_STRUCT->switchMon = TRUE; + } + } + } + } + + if (AI_THINKING_STRUCT->switchMon) + { + AI_THINKING_STRUCT->switchMon = FALSE; *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; - BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0); return TRUE; } else @@ -58,7 +170,6 @@ static bool8 ShouldSwitchIfPerishSong(void) && gDisableStructs[gActiveBattler].perishSongTimer == 0) { *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; - BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0); return TRUE; } else @@ -125,7 +236,6 @@ static bool8 ShouldSwitchIfWonderGuard(void) { // We found a mon. *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = i; - BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0); return TRUE; } } @@ -216,7 +326,6 @@ static bool8 FindMonThatAbsorbsOpponentsMove(void) { // we found a mon. *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = i; - BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0); return TRUE; } } @@ -236,13 +345,11 @@ static bool8 ShouldSwitchIfNaturalCure(void) if ((gLastLandedMoves[gActiveBattler] == 0 || gLastLandedMoves[gActiveBattler] == 0xFFFF) && Random() & 1) { *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; - BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0); return TRUE; } else if (gBattleMoves[gLastLandedMoves[gActiveBattler]].power == 0 && Random() & 1) { *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; - BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0); return TRUE; } @@ -254,7 +361,6 @@ static bool8 ShouldSwitchIfNaturalCure(void) if (Random() & 1) { *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = PARTY_SIZE; - BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0); return TRUE; } @@ -407,7 +513,6 @@ static bool8 FindMonWithFlagsAndSuperEffective(u16 flags, u8 moduloPercent) if (AI_GetTypeEffectiveness(move, gActiveBattler, battlerIn1) >= UQ_4_12(2.0) && Random() % moduloPercent == 0) { *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = i; - BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0); return TRUE; } } @@ -436,6 +541,11 @@ bool32 ShouldSwitch(void) return FALSE; availableToSwitch = 0; + AI_THINKING_STRUCT->switchMon = FALSE; + + if (CountUsablePartyMons(gActiveBattler) == 0) // No pokemon to switch to + return FALSE; + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) { battlerIn1 = gActiveBattler; @@ -500,73 +610,6 @@ bool32 ShouldSwitch(void) return FALSE; } -void AI_TrySwitchOrUseItem(void) -{ - struct Pokemon *party; - u8 battlerIn1, battlerIn2; - s32 firstId; - s32 lastId; // + 1 - u8 battlerIdentity = GetBattlerPosition(gActiveBattler); - - if (GetBattlerSide(gActiveBattler) == B_SIDE_PLAYER) - party = gPlayerParty; - else - party = gEnemyParty; - - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER) - { - if (ShouldSwitch()) - { - if (*(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) == PARTY_SIZE) - { - s32 monToSwitchId = GetMostSuitableMonToSwitchInto(); - if (monToSwitchId == PARTY_SIZE) - { - if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) - { - battlerIn1 = GetBattlerAtPosition(battlerIdentity); - battlerIn2 = battlerIn1; - } - else - { - battlerIn1 = GetBattlerAtPosition(battlerIdentity); - battlerIn2 = GetBattlerAtPosition(battlerIdentity ^ BIT_FLANK); - } - - GetAIPartyIndexes(gActiveBattler, &firstId, &lastId); - - for (monToSwitchId = firstId; monToSwitchId < lastId; monToSwitchId++) - { - if (GetMonData(&party[monToSwitchId], MON_DATA_HP) == 0) - continue; - if (monToSwitchId == gBattlerPartyIndexes[battlerIn1]) - continue; - if (monToSwitchId == gBattlerPartyIndexes[battlerIn2]) - continue; - if (monToSwitchId == *(gBattleStruct->monToSwitchIntoId + battlerIn1)) - continue; - if (monToSwitchId == *(gBattleStruct->monToSwitchIntoId + battlerIn2)) - continue; - - break; - } - } - - *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler) = monToSwitchId; - } - - *(gBattleStruct->monToSwitchIntoId + gActiveBattler) = *(gBattleStruct->AI_monToSwitchIntoId + gActiveBattler); - return; - } - else if (ShouldUseItem()) - { - return; - } - } - - BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (gActiveBattler ^ BIT_SIDE) << 8); -} - // If there are two(or more) mons to choose from, always choose one that has baton pass // as most often it can't do much on its own. static u32 GetBestMonBatonPass(struct Pokemon *party, int firstId, int lastId, u8 invalidMons, int aliveCount) @@ -839,20 +882,10 @@ static bool8 ShouldUseItem(void) switch (*(gBattleStruct->AI_itemType + gActiveBattler / 2)) { case AI_ITEM_FULL_RESTORE: - if (gBattleMons[gActiveBattler].hp >= gBattleMons[gActiveBattler].maxHP / 4) - break; - if (gBattleMons[gActiveBattler].hp == 0) - break; - shouldUse = TRUE; + shouldUse = AI_ShouldHeal(0); break; case AI_ITEM_HEAL_HP: - paramOffset = GetItemEffectParamOffset(item, 4, 4); - if (paramOffset == 0) - break; - if (gBattleMons[gActiveBattler].hp == 0) - break; - if (gBattleMons[gActiveBattler].hp < gBattleMons[gActiveBattler].maxHP / 4 || gBattleMons[gActiveBattler].maxHP - gBattleMons[gActiveBattler].hp > itemEffects[paramOffset]) - shouldUse = TRUE; + shouldUse = AI_ShouldHeal(itemEffects[GetItemEffectParamOffset(item, 4, 4)]); break; case AI_ITEM_CURE_CONDITION: *(gBattleStruct->AI_itemFlags + gActiveBattler / 2) = 0; @@ -934,7 +967,6 @@ static bool8 ShouldUseItem(void) if (shouldUse) { - BtlController_EmitTwoReturnValues(1, B_ACTION_USE_ITEM, 0); *(gBattleStruct->chosenItem + (gActiveBattler / 2) * 2) = item; gBattleResources->battleHistory->trainerItems[i] = 0; return shouldUse; @@ -943,3 +975,41 @@ static bool8 ShouldUseItem(void) return FALSE; } + +static bool32 AI_ShouldHeal(u8 healAmount) +{ + bool32 shouldHeal = FALSE; + u32 i; + + if (gBattleMons[gActiveBattler].hp < gBattleMons[gActiveBattler].maxHP / 4 + || gBattleMons[gActiveBattler].hp == 0 + || (healAmount != 0 && gBattleMons[gActiveBattler].maxHP - gBattleMons[gActiveBattler].hp > healAmount)) + { + // We have low enough HP to consider healing + shouldHeal = TRUE; + + // Check special cases to NOT heal + for (i = 0; i < gBattlersCount; i++) + { + if (GetBattlerSide(i) == B_SIDE_PLAYER) + { + if (CanTargetFaintAiWithMod(i, gActiveBattler, healAmount, 0)) + { + // Target is expected to faint us even after we heal. So why bother. + shouldHeal = FALSE; + break; + } + + // AI_THINKING_STRUCT->movesetIndex is the array index of the AI's chosen move + if (CanIndexMoveFaintTarget(gActiveBattler, i, AI_THINKING_STRUCT->movesetIndex, 0) && AI_WhoStrikesFirst(gActiveBattler, i) == AI_IS_FASTER) + { + // We can faint the target and move first -> don't heal + shouldHeal = FALSE; + break; + } + } + } + } + + return shouldHeal; +} diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index ef6b0679ff..664078e559 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -439,6 +439,11 @@ static const u16 sOtherMoveCallingMoves[] = }; // Functions +bool32 WillAIStrikeFirst(void) +{ + return (AI_WhoStrikesFirst(sBattler_AI, gBattlerTarget) == AI_IS_FASTER); +} + bool32 AI_RandLessThan(u8 val) { if ((Random() % 0xFF) < val) @@ -632,7 +637,7 @@ bool32 IsTruantMonVulnerable(u32 battlerAI, u32 opposingBattler) u32 move = gBattleResources->battleHistory->usedMoves[opposingBattler][i]; if (gBattleMoves[move].effect == EFFECT_PROTECT && move != MOVE_ENDURE) return TRUE; - if (gBattleMoves[move].effect == EFFECT_SEMI_INVULNERABLE && GetWhoStrikesFirst(battlerAI, opposingBattler, TRUE) == 1) + if (gBattleMoves[move].effect == EFFECT_SEMI_INVULNERABLE && AI_WhoStrikesFirst(battlerAI, opposingBattler) == AI_IS_SLOWER) return TRUE; } return FALSE; @@ -981,48 +986,43 @@ u8 AI_GetMoveEffectiveness(u16 move, u8 battlerAtk, u8 battlerDef) return damageVar; } -// AI_CHECK_FASTER: is user(ai) faster -// AI_CHECK_SLOWER: is target faster -bool32 IsAiFaster(u8 battler) +/* Checks to see if AI will move ahead of another battler + * Output: + * AI_IS_FASTER: is user(ai) faster + * AI_IS_SLOWER: is target faster +*/ +u8 AI_WhoStrikesFirst(u8 battlerAI, u8 battler2) { u32 fasterAI = 0, fasterPlayer = 0, i; - s8 prioAI, prioPlayer; + s8 prioAI = 0; + s8 prioPlayer = 0; // Check move priorities first. - prioAI = GetMovePriority(sBattler_AI, AI_THINKING_STRUCT->moveConsidered); - SaveBattlerData(gBattlerTarget); - SetBattlerData(gBattlerTarget); + prioAI = GetMovePriority(battlerAI, AI_THINKING_STRUCT->moveConsidered); for (i = 0; i < MAX_MON_MOVES; i++) { - if (gBattleMons[gBattlerTarget].moves[i] == 0 || gBattleMons[gBattlerTarget].moves[i] == 0xFFFF) + if (gBattleMons[battler2].moves[i] == 0 || gBattleMons[battler2].moves[i] == 0xFFFF) continue; - prioPlayer = GetMovePriority(gBattlerTarget, gBattleMons[gBattlerTarget].moves[i]); + prioPlayer = GetMovePriority(battler2, gBattleMons[battler2].moves[i]); if (prioAI > prioPlayer) fasterAI++; else if (prioPlayer > prioAI) fasterPlayer++; } - RestoreBattlerData(gBattlerTarget); if (fasterAI > fasterPlayer) { - if (battler == 0) // is user (ai) faster - return TRUE; - else - return FALSE; + return AI_IS_FASTER; } else if (fasterAI < fasterPlayer) { - if (battler == 1) // is target (player) faster - return TRUE; - else - return FALSE; + return AI_IS_SLOWER; } else { // Priorities are the same(at least comparing to moves the AI is aware of), decide by speed. - if (GetWhoStrikesFirst(sBattler_AI, gBattlerTarget, TRUE) == battler) + if (GetWhoStrikesFirst(battlerAI, battler2, TRUE) == 0) return TRUE; else return FALSE; @@ -1090,12 +1090,16 @@ bool32 CanTargetFaintAiWithMod(u8 battlerDef, u8 battlerAtk, s32 hpMod, s32 dmgM { u32 i; u32 unusable = CheckMoveLimitations(battlerDef, 0, 0xFF); + s32 dmg; u16 *moves = gBattleResources->battleHistory->usedMoves[battlerDef]; + u32 hpCheck = gBattleMons[battlerAtk].hp + hpMod; + + if (hpCheck > gBattleMons[battlerAtk].maxHP) + hpCheck = gBattleMons[battlerAtk].maxHP; for (i = 0; i < MAX_MON_MOVES; i++) { - u32 dmg = AI_CalcDamage(moves[i], battlerDef, battlerAtk); - u32 hpCheck = gBattleMons[battlerAtk].hp + hpMod; + dmg = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][i]; if (dmgMod) dmg *= dmgMod; @@ -1141,15 +1145,13 @@ s32 AI_GetAbility(u32 battlerId) // Else, guess the ability if (gBaseStats[gBattleMons[battlerId].species].abilities[0] != ABILITY_NONE) { - if (gBaseStats[gBattleMons[battlerId].species].abilities[1] != ABILITY_NONE) + u16 abilityGuess = ABILITY_NONE; + while (abilityGuess == ABILITY_NONE) { - // AI has no knowledge of opponent, so it guesses which ability. - return gBaseStats[gBattleMons[battlerId].species].abilities[Random() & 1]; - } - else - { - return gBaseStats[gBattleMons[battlerId].species].abilities[0]; // It's definitely ability 1. + abilityGuess = gBaseStats[gBattleMons[battlerId].species].abilities[Random() % NUM_ABILITY_SLOTS]; } + + return abilityGuess; } return ABILITY_NONE; // Unknown. @@ -1709,7 +1711,7 @@ u32 CountNegativeStatStages(u8 battlerId) bool32 ShouldLowerAttack(u8 battlerAtk, u8 battlerDef, u16 defAbility) { - if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_ATK] > 4 @@ -1725,7 +1727,7 @@ bool32 ShouldLowerAttack(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerDefense(u8 battlerAtk, u8 battlerDef, u16 defAbility) { - if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_DEF] > 4 @@ -1741,10 +1743,10 @@ bool32 ShouldLowerDefense(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerSpeed(u8 battlerAtk, u8 battlerDef, u16 defAbility) { - if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. - if (IsAiFaster(AI_CHECK_SLOWER) + if (!WillAIStrikeFirst() && defAbility != ABILITY_CONTRARY && defAbility != ABILITY_CLEAR_BODY && defAbility != ABILITY_FULL_METAL_BODY @@ -1755,7 +1757,7 @@ bool32 ShouldLowerSpeed(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerSpAtk(u8 battlerAtk, u8 battlerDef, u16 defAbility) { - if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_SPATK] > 4 @@ -1770,7 +1772,7 @@ bool32 ShouldLowerSpAtk(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerSpDef(u8 battlerAtk, u8 battlerDef, u16 defAbility) { - if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_SPDEF] > 4 @@ -1785,7 +1787,7 @@ bool32 ShouldLowerSpDef(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerAccuracy(u8 battlerAtk, u8 battlerDef, u16 defAbility) { - if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (defAbility != ABILITY_CONTRARY @@ -1799,7 +1801,7 @@ bool32 ShouldLowerAccuracy(u8 battlerAtk, u8 battlerDef, u16 defAbility) bool32 ShouldLowerEvasion(u8 battlerAtk, u8 battlerDef, u16 defAbility) { - if (IsAiFaster(AI_CHECK_FASTER) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + if (WillAIStrikeFirst() && (AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) return FALSE; // Don't bother lowering stats if can kill enemy. if (gBattleMons[battlerDef].statStages[STAT_EVASION] > DEFAULT_STAT_STAGE @@ -2472,7 +2474,7 @@ bool32 ShouldPivot(u8 battlerAtk, u8 battlerDef, u16 defAbility, u16 move, u8 mo /*if (IsPredictedToSwitch(battlerDef, battlerAtk) && !hasStatBoost) return PIVOT; // Try pivoting so you can switch to a better matchup to counter your new opponent*/ - if (GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 0) // Attacker goes first + if (AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) // Attacker goes first { if (!CanAIFaintTarget(battlerAtk, battlerDef, 0)) // Can't KO foe otherwise { @@ -2832,7 +2834,7 @@ u32 ShouldTryToFlinch(u8 battlerAtk, u8 battlerDef, u16 atkAbility, u16 defAbili { if (defAbility == ABILITY_INNER_FOCUS || DoesSubstituteBlockMove(battlerAtk, battlerDef, move) - || GetWhoStrikesFirst(battlerAtk, battlerDef, TRUE) == 1) // opponent goes first + || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_SLOWER) // Opponent goes first { return 0; // don't try to flinch } @@ -2956,7 +2958,7 @@ bool32 ShouldUseRecoilMove(u8 battlerAtk, u8 battlerDef, u32 recoilDmg, u8 moveI bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage) { - if (move == 0xFFFF || GetWhoStrikesFirst(battlerAtk, gBattlerTarget, TRUE) == 0) + if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) { // using item or user goes first u8 healPercent = (gBattleMoves[move].argument == 0) ? 50 : gBattleMoves[move].argument; @@ -2983,7 +2985,7 @@ bool32 ShouldAbsorb(u8 battlerAtk, u8 battlerDef, u16 move, s32 damage) bool32 ShouldRecover(u8 battlerAtk, u8 battlerDef, u16 move, u8 healPercent) { - if (move == 0xFFFF || GetWhoStrikesFirst(battlerAtk, gBattlerTarget, TRUE) == 0) + if (move == 0xFFFF || AI_WhoStrikesFirst(battlerAtk, battlerDef) == AI_IS_FASTER) { // using item or user going first s32 damage = AI_THINKING_STRUCT->simulatedDmg[battlerAtk][battlerDef][AI_THINKING_STRUCT->movesetIndex]; @@ -3383,6 +3385,47 @@ static const u16 sRecycleEncouragedItems[] = // TODO expand this }; +// Its assumed that the berry is strategically given, so no need to check benefits of the berry +bool32 IsStatBoostingBerry(u16 item) +{ + switch (item) + { + case ITEM_LIECHI_BERRY: + case ITEM_GANLON_BERRY: + case ITEM_SALAC_BERRY: + case ITEM_PETAYA_BERRY: + case ITEM_APICOT_BERRY: + //case ITEM_LANSAT_BERRY: + case ITEM_STARF_BERRY: + #ifdef ITEM_EXPANSION + case ITEM_MICLE_BERRY: + #endif + return TRUE; + default: + return FALSE; + } +} + +bool32 ShouldRestoreHpBerry(u8 battlerAtk, u16 item) +{ + switch (item) + { + case ITEM_ORAN_BERRY: + if (gBattleMons[battlerAtk].maxHP <= 50) + return TRUE; // Only worth it in the early game + return FALSE; + case ITEM_SITRUS_BERRY: + case ITEM_FIGY_BERRY: + case ITEM_WIKI_BERRY: + case ITEM_MAGO_BERRY: + case ITEM_AGUAV_BERRY: + case ITEM_IAPAPA_BERRY: + return TRUE; + default: + return FALSE; + } +} + bool32 IsRecycleEncouragedItem(u16 item) { u32 i; @@ -3404,6 +3447,9 @@ void IncreaseStatUpScore(u8 battlerAtk, u8 battlerDef, u8 statId, s16 *score) if (GetHealthPercentage(battlerAtk) < 80 && AI_RandLessThan(128)) return; + + if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return; // Damaging moves would get a score boost from AI_TryToFaint or PreferStrongestMove so we don't consider them here switch (statId) { @@ -3429,7 +3475,7 @@ void IncreaseStatUpScore(u8 battlerAtk, u8 battlerDef, u8 statId, s16 *score) } break; case STAT_SPEED: - if (IsAiFaster(AI_CHECK_SLOWER)) + if (!WillAIStrikeFirst()) { if (gBattleMons[battlerAtk].statStages[STAT_SPEED] < STAT_UP_2_STAGE) *score += 2; @@ -3476,6 +3522,9 @@ void IncreaseStatUpScore(u8 battlerAtk, u8 battlerDef, u8 statId, s16 *score) void IncreasePoisonScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) { + if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return; + if (AI_CanPoison(battlerAtk, battlerDef, AI_DATA->defAbility, move, AI_DATA->partnerMove) && GetHealthPercentage(battlerDef) > 20) { if (!HasDamagingMove(battlerDef)) @@ -3496,6 +3545,9 @@ void IncreasePoisonScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) void IncreaseBurnScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) { + if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return; + if (AI_CanBurn(battlerAtk, battlerDef, AI_DATA->defAbility, AI_DATA->battlerAtkPartner, move, AI_DATA->partnerMove)) { (*score)++; // burning is good @@ -3512,6 +3564,9 @@ void IncreaseBurnScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) void IncreaseParalyzeScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) { + if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return; + if (AI_CanParalyze(battlerAtk, battlerDef, AI_DATA->defAbility, move, AI_DATA->partnerMove)) { u8 atkSpeed = GetBattlerTotalSpeedStat(battlerAtk); @@ -3530,6 +3585,9 @@ void IncreaseParalyzeScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) void IncreaseSleepScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) { + if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return; + if (AI_CanPutToSleep(battlerAtk, battlerDef, AI_DATA->defAbility, move, AI_DATA->partnerMove)) *score += 2; else @@ -3545,6 +3603,9 @@ void IncreaseSleepScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) void IncreaseConfusionScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) { + if ((AI_THINKING_STRUCT->aiFlags & AI_FLAG_TRY_TO_FAINT) && CanAIFaintTarget(battlerAtk, battlerDef, 0)) + return; + if (AI_CanConfuse(battlerAtk, battlerDef, AI_DATA->defAbility, AI_DATA->battlerAtkPartner, move, AI_DATA->partnerMove) && AI_DATA->defHoldEffect != HOLD_EFFECT_CURE_CONFUSION && AI_DATA->defHoldEffect != HOLD_EFFECT_CURE_STATUS) @@ -3557,3 +3618,12 @@ void IncreaseConfusionScore(u8 battlerAtk, u8 battlerDef, u16 move, s16 *score) *score += 2; } } + +bool32 AI_MoveMakesContact(u32 ability, u32 holdEffect, u16 move) +{ + if (TestMoveFlags(move, FLAG_MAKES_CONTACT) + && ability != ABILITY_LONG_REACH + && holdEffect != HOLD_EFFECT_PROTECTIVE_PADS) + return TRUE; + return FALSE; +} diff --git a/src/battle_controller_opponent.c b/src/battle_controller_opponent.c index ff253730f4..4482c175d0 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -1545,7 +1545,70 @@ static void OpponentHandlePrintSelectionString(void) static void OpponentHandleChooseAction(void) { - AI_TrySwitchOrUseItem(); + if (gBattleTypeFlags & BATTLE_TYPE_PALACE) + { + BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, ChooseMoveAndTargetInBattlePalace()); + } + else + { + u8 chosenMoveId; + struct ChooseMoveStruct moveInfo; + + FillChooseMoveStruct(&moveInfo); + if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER)) + { + BattleAI_SetupAIData(0xF); + chosenMoveId = BattleAI_ChooseMoveOrAction(); + switch (chosenMoveId) + { + case AI_CHOICE_USE_ITEM: + BtlController_EmitTwoReturnValues(1, B_ACTION_USE_ITEM, 0); + break; + case AI_CHOICE_SWITCH: + BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0); + break; + case AI_CHOICE_WATCH: + BtlController_EmitTwoReturnValues(1, B_ACTION_SAFARI_WATCH_CAREFULLY, 0); + break; + case AI_CHOICE_FLEE: + BtlController_EmitTwoReturnValues(1, B_ACTION_RUN, 0); + break; + default: + if (GetMoveTargetType(gActiveBattler, moveInfo.moves[chosenMoveId]) & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) + gBattlerTarget = gActiveBattler; + if (GetMoveTargetType(gActiveBattler, moveInfo.moves[chosenMoveId]) & MOVE_TARGET_BOTH) + { + gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT); + if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) + gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); + } + + if (CanMegaEvolve(gActiveBattler)) { // If opponent can mega evolve, do it. + BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); + } else { + BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (gBattlerTarget << 8)); + } + break; + } + } + else // Wild pokemon - use random move + { + u16 move; + do + { + chosenMoveId = Random() & 3; + move = moveInfo.moves[chosenMoveId]; + } while (move == MOVE_NONE); + + if (GetMoveTargetType(gActiveBattler, move) & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) + BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (gActiveBattler << 8)); + else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (GetBattlerAtPosition(Random() & 2) << 8)); + else + BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (GetBattlerAtPosition(B_POSITION_PLAYER_LEFT) << 8)); + } + } + OpponentBufferExecCompleted(); } @@ -1556,71 +1619,9 @@ static void OpponentHandleYesNoBox(void) static void OpponentHandleChooseMove(void) { - if (gBattleTypeFlags & BATTLE_TYPE_PALACE) - { - BtlController_EmitTwoReturnValues(1, 10, ChooseMoveAndTargetInBattlePalace()); - OpponentBufferExecCompleted(); - } - else - { - u8 chosenMoveId; - struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct*)(&gBattleResources->bufferA[gActiveBattler][4]); - - if (gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_FIRST_BATTLE | BATTLE_TYPE_SAFARI | BATTLE_TYPE_ROAMER)) - { - BattleAI_SetupAIData(0xF); - chosenMoveId = BattleAI_ChooseMoveOrAction(); - - switch (chosenMoveId) - { - case AI_CHOICE_WATCH: - BtlController_EmitTwoReturnValues(1, B_ACTION_SAFARI_WATCH_CAREFULLY, 0); - break; - case AI_CHOICE_FLEE: - BtlController_EmitTwoReturnValues(1, B_ACTION_RUN, 0); - break; - case AI_CHOICE_SWITCH: - BtlController_EmitTwoReturnValues(1, 10, 0xFFFF); - break; - case 6: - BtlController_EmitTwoReturnValues(1, 15, gBattlerTarget); - break; - default: - if (GetMoveTargetType(gActiveBattler, moveInfo->moves[chosenMoveId]) & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) - gBattlerTarget = gActiveBattler; - if (GetMoveTargetType(gActiveBattler, moveInfo->moves[chosenMoveId]) & MOVE_TARGET_BOTH) - { - gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_LEFT); - if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) - gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); - } - if (CanMegaEvolve(gActiveBattler)) // If opponent can mega evolve, do it. - BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); - else - BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (gBattlerTarget << 8)); - break; - } - OpponentBufferExecCompleted(); - } - else - { - u16 move; - do - { - chosenMoveId = Random() & 3; - move = moveInfo->moves[chosenMoveId]; - } while (move == MOVE_NONE); - - if (GetMoveTargetType(gActiveBattler, move) & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) - BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (gActiveBattler << 8)); - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) - BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (GetBattlerAtPosition(Random() & 2) << 8)); - else - BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (GetBattlerAtPosition(B_POSITION_PLAYER_LEFT) << 8)); - - OpponentBufferExecCompleted(); - } - } + u8 *bufferB = gBattleResources->bufferB[gActiveBattler]; + BtlController_EmitTwoReturnValues(1, 10, bufferB[2] | (bufferB[3] << 8)); + OpponentBufferExecCompleted(); } static void OpponentHandleChooseItem(void) diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index a72f1029b7..b4052402e2 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -1509,7 +1509,37 @@ static void PlayerPartnerHandlePrintSelectionString(void) static void PlayerPartnerHandleChooseAction(void) { - AI_TrySwitchOrUseItem(); + u8 chosenMoveId; + struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct*)(&gBattleResources->bufferA[gActiveBattler][4]); + + BattleAI_SetupAIData(0xF); + chosenMoveId = BattleAI_ChooseMoveOrAction(); + + switch (chosenMoveId) + { + case AI_CHOICE_USE_ITEM: + BtlController_EmitTwoReturnValues(1, B_ACTION_USE_ITEM, 0); + break; + case AI_CHOICE_SWITCH: + BtlController_EmitTwoReturnValues(1, B_ACTION_SWITCH, 0); + break; + default: + if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & (MOVE_TARGET_USER | MOVE_TARGET_USER_OR_SELECTED)) + gBattlerTarget = gActiveBattler; + if (gBattleMoves[moveInfo->moves[chosenMoveId]].target & MOVE_TARGET_BOTH) + { + gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); + if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) + gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); + } + + if (CanMegaEvolve(gActiveBattler)) // If partner can mega evolve, do it. + BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); + else + BtlController_EmitTwoReturnValues(1, B_ACTION_USE_MOVE, (chosenMoveId) | (gBattlerTarget << 8)); + break; + } + PlayerPartnerBufferExecCompleted(); } @@ -1520,25 +1550,6 @@ static void PlayerPartnerHandleYesNoBox(void) static void PlayerPartnerHandleChooseMove(void) { - u8 chosenMoveId; - struct ChooseMoveStruct *moveInfo = (struct ChooseMoveStruct*)(&gBattleResources->bufferA[gActiveBattler][4]); - - BattleAI_SetupAIData(0xF); - chosenMoveId = BattleAI_ChooseMoveOrAction(); - - if (GetMoveTargetType(gActiveBattler, moveInfo->moves[chosenMoveId]) & (MOVE_TARGET_USER_OR_SELECTED | MOVE_TARGET_USER)) - gBattlerTarget = gActiveBattler; - if (GetMoveTargetType(gActiveBattler, moveInfo->moves[chosenMoveId]) & MOVE_TARGET_BOTH) - { - gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_LEFT); - if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) - gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); - } - - if (CanMegaEvolve(gActiveBattler)) // If partner can mega evolve, do it. - BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); - else - BtlController_EmitTwoReturnValues(1, 10, (chosenMoveId) | (gBattlerTarget << 8)); PlayerPartnerBufferExecCompleted(); } diff --git a/src/battle_main.c b/src/battle_main.c index 1c2d37dbc5..f4c29366f7 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -104,6 +104,7 @@ static void RunTurnActionsFunctions(void); static void SetActionsAndBattlersTurnOrder(void); static void sub_803CDF8(void); static bool8 AllAtActionConfirmed(void); +static void TryChangeTurnOrder(void); static void CheckFocusPunch_ClearVarsBeforeTurnStarts(void); static void CheckMegaEvolutionBeforeTurn(void); static void CheckQuickClaw_CustapBerryActivation(void); @@ -3876,22 +3877,7 @@ static void HandleTurnActionSelectionState(void) { struct ChooseMoveStruct moveInfo; - moveInfo.mega = gBattleStruct->mega; - moveInfo.species = gBattleMons[gActiveBattler].species; - moveInfo.monType1 = gBattleMons[gActiveBattler].type1; - moveInfo.monType2 = gBattleMons[gActiveBattler].type2; - moveInfo.monType3 = gBattleMons[gActiveBattler].type3; - - for (i = 0; i < MAX_MON_MOVES; i++) - { - moveInfo.moves[i] = gBattleMons[gActiveBattler].moves[i]; - moveInfo.currentPp[i] = gBattleMons[gActiveBattler].pp[i]; - moveInfo.maxPp[i] = CalculatePPWithBonus( - gBattleMons[gActiveBattler].moves[i], - gBattleMons[gActiveBattler].ppBonuses, - i); - } - + FillChooseMoveStruct(&moveInfo); BtlController_EmitChooseMove(0, (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) != 0, FALSE, &moveInfo); MarkBattlerForControllerExec(gActiveBattler); } @@ -4688,10 +4674,37 @@ static void CheckMegaEvolutionBeforeTurn(void) } } + #if B_MEGA_EVO_TURN_ORDER <= GEN_6 + gBattleMainFunc = CheckFocusPunch_ClearVarsBeforeTurnStarts; + gBattleStruct->focusPunchBattlerId = 0; + #else + gBattleMainFunc = TryChangeTurnOrder; // This will just do nothing if no mon has mega evolved + #endif +} + +// In gen7, priority and speed are recalculated during the turn in which a pokemon mega evolves +static void TryChangeTurnOrder(void) +{ + s32 i, j; + for (i = 0; i < gBattlersCount - 1; i++) + { + for (j = i + 1; j < gBattlersCount; j++) + { + u8 battler1 = gBattlerByTurnOrder[i]; + u8 battler2 = gBattlerByTurnOrder[j]; + if (gActionsByTurnOrder[i] == B_ACTION_USE_MOVE + && gActionsByTurnOrder[j] == B_ACTION_USE_MOVE) + { + if (GetWhoStrikesFirst(battler1, battler2, FALSE)) + SwapTurnOrder(i, j); + } + } + } gBattleMainFunc = CheckFocusPunch_ClearVarsBeforeTurnStarts; gBattleStruct->focusPunchBattlerId = 0; } + static void CheckFocusPunch_ClearVarsBeforeTurnStarts(void) { u32 i; @@ -5295,3 +5308,24 @@ void SetTotemBoost(void) } } } + +void FillChooseMoveStruct(struct ChooseMoveStruct * moveInfo) +{ + int i; + + moveInfo->mega = gBattleStruct->mega; + moveInfo->species = gBattleMons[gActiveBattler].species; + moveInfo->monType1 = gBattleMons[gActiveBattler].type1; + moveInfo->monType2 = gBattleMons[gActiveBattler].type2; + moveInfo->monType3 = gBattleMons[gActiveBattler].type3; + + for (i = 0; i < MAX_MON_MOVES; i++) + { + moveInfo->moves[i] = gBattleMons[gActiveBattler].moves[i]; + moveInfo->currentPp[i] = gBattleMons[gActiveBattler].pp[i]; + moveInfo->maxPp[i] = CalculatePPWithBonus( + gBattleMons[gActiveBattler].moves[i], + gBattleMons[gActiveBattler].ppBonuses, + i); + } +} diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 437733f1e4..db3a030ae3 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -5493,6 +5493,14 @@ static void Cmd_moveend(void) *(gBattleStruct->moveTarget + gBattlerAttacker) = gSpecialStatuses[gBattlerAttacker].instructedChosenTarget & 0x3; if (gSpecialStatuses[gBattlerAttacker].dancerOriginalTarget) *(gBattleStruct->moveTarget + gBattlerAttacker) = gSpecialStatuses[gBattlerAttacker].dancerOriginalTarget & 0x3; + + #if B_RAMPAGE_CANCELLING >= GEN_5 + if (gBattleMoves[gCurrentMove].effect == EFFECT_RAMPAGE // If we're rampaging + && (gMoveResultFlags & MOVE_RESULT_NO_EFFECT) // And it is unusable + && (gBattleMons[gBattlerAttacker].status2 & STATUS2_LOCK_CONFUSE) != STATUS2_LOCK_CONFUSE_TURN(1)) // And won't end this turn + CancelMultiTurnMoves(gBattlerAttacker); // Cancel it + #endif + gProtectStructs[gBattlerAttacker].usesBouncedMove = FALSE; gProtectStructs[gBattlerAttacker].targetAffected = FALSE; gBattleStruct->ateBoost[gBattlerAttacker] = 0; @@ -12418,7 +12426,7 @@ static void Cmd_trywish(void) if (gWishFutureKnock.wishCounter[gBattlerAttacker] == 0) { gWishFutureKnock.wishCounter[gBattlerAttacker] = 2; - gWishFutureKnock.wishMonId[gBattlerAttacker] = gBattlerPartyIndexes[gBattlerAttacker]; + gWishFutureKnock.wishPartyId[gBattlerAttacker] = gBattlerPartyIndexes[gBattlerAttacker]; gBattlescriptCurrInstr += 6; } else @@ -12427,9 +12435,12 @@ static void Cmd_trywish(void) } break; case 1: // heal effect - PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, gBattlerTarget, gWishFutureKnock.wishMonId[gBattlerTarget]) + PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, gBattlerTarget, gWishFutureKnock.wishPartyId[gBattlerTarget]) #if B_WISH_HP_SOURCE >= GEN_5 - gBattleMoveDamage = max(1, gBattleMons[gWishFutureKnock.wishMonId[gBattlerTarget]].maxHP / 2); + if (GetBattlerSide(gBattlerTarget) == B_SIDE_PLAYER) + gBattleMoveDamage = max(1, GetMonData(&gPlayerParty[gWishFutureKnock.wishPartyId[gBattlerTarget]], MON_DATA_MAX_HP) / 2); + else + gBattleMoveDamage = max(1, GetMonData(&gEnemyParty[gWishFutureKnock.wishPartyId[gBattlerTarget]], MON_DATA_MAX_HP) / 2); #else gBattleMoveDamage = max(1, gBattleMons[gBattlerTarget].maxHP / 2); #endif @@ -13200,7 +13211,8 @@ static void Cmd_removelightscreenreflect(void) // brick break bool32 failed; #if B_BRICK_BREAK >= GEN_4 - side = GetBattlerSide(gBattlerAttacker); + // From Gen 4 onwards, Brick Break can remove screens on the user's side if used on an ally + side = GetBattlerSide(gBattlerTarget); #else side = GetBattlerSide(gBattlerAttacker) ^ BIT_SIDE; #endif diff --git a/src/battle_util.c b/src/battle_util.c index dcde7b8ced..0ddff34835 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2796,6 +2796,7 @@ u8 DoBattlerEndTurnEffects(void) case ENDTURN_FLINCH: // reset flinch gBattleMons[gActiveBattler].status2 &= ~(STATUS2_FLINCHED); gBattleStruct->turnEffectsTracker++; + break; case ENDTURN_DISABLE: // disable if (gDisableStructs[gActiveBattler].disableTimer != 0) { @@ -7521,8 +7522,13 @@ bool32 IsBattlerProtected(u8 battlerId, u16 move) else if (gProtectStructs[battlerId].protected) return FALSE; } - - if (!(gBattleMoves[move].flags & FLAG_PROTECT_AFFECTED)) + + // Protective Pads doesn't stop Unseen Fist from bypassing Protect effects, so IsMoveMakingContact() isn't used here. + // This means extra logic is needed to handle Shell Side Arm. + if (GetBattlerAbility(gBattlerAttacker) == ABILITY_UNSEEN_FIST + && (gBattleMoves[move].flags & FLAG_MAKES_CONTACT || (gBattleMoves[move].effect == EFFECT_SHELL_SIDE_ARM && gSwapDamageCategory))) + return FALSE; + else if (!(gBattleMoves[move].flags & FLAG_PROTECT_AFFECTED)) return FALSE; else if (gBattleMoves[move].effect == MOVE_EFFECT_FEINT) return FALSE;