diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index c3bbe6221c..ba43dc9d4a 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1884,6 +1884,10 @@ .2byte \species .4byte \ptr .endm + + .macro tryendneutralizinggas battler:req + various \battler, VARIOUS_TRY_END_NEUTRALIZING_GAS + .endm .macro trytoapplymimicry battler:req, ptr:req various \battler, VARIOUS_TRY_TO_APPLY_MIMICRY diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index e67c4e642e..24a3d16eda 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -2187,6 +2187,7 @@ BattleScript_EffectSimpleBeam: trytoclearprimalweather printstring STRINGID_EMPTYSTRING3 waitmessage 1 + tryendneutralizinggas BS_TARGET goto BattleScript_MoveEnd BattleScript_EffectSuckerPunch: @@ -2409,6 +2410,7 @@ BattleScript_EffectGastroAcid: trytoclearprimalweather printstring STRINGID_EMPTYSTRING3 waitmessage 1 + tryendneutralizinggas BS_TARGET goto BattleScript_MoveEnd BattleScript_EffectToxicSpikes: @@ -8383,6 +8385,12 @@ BattleScript_SwitchInAbilityMsg:: waitmessage B_WAIT_TIME_LONG end3 +BattleScript_SwitchInAbilityMsgRet:: + call BattleScript_AbilityPopUp + printfromtable gSwitchInAbilityStringIds + waitmessage B_WAIT_TIME_LONG + return + BattleScript_ActivateAsOne:: call BattleScript_AbilityPopUp printfromtable gSwitchInAbilityStringIds @@ -9237,3 +9245,19 @@ BattleScript_PastelVeilLoopIncrement: goto BattleScript_PastelVeilEnd BattleScript_PastelVeilEnd: end3 + +sByteFour: +.byte MAX_BATTLERS_COUNT + +BattleScript_NeutralizingGasExits:: + savetarget + pause B_WAIT_TIME_SHORT + printstring STRINGID_NEUTRALIZINGGASOVER + waitmessage B_WAIT_TIME_LONG + setbyte gBattlerTarget, 0 +BattleScript_NeutralizingGasExitsLoop: + switchinabilities BS_TARGET + addbyte gBattlerTarget, 1 + jumpifbytenotequal gBattlerTarget, sByteFour, BattleScript_NeutralizingGasExitsLoop @ SOMEHOW, comparing to gBattlersCount is problematic. + restoretarget + return diff --git a/include/battle.h b/include/battle.h index b05da574fb..35628be832 100644 --- a/include/battle.h +++ b/include/battle.h @@ -51,12 +51,13 @@ struct ResourceFlags u32 flags[4]; }; -#define RESOURCE_FLAG_FLASH_FIRE 0x1 -#define RESOURCE_FLAG_ROOST 0x2 -#define RESOURCE_FLAG_UNBURDEN 0x4 -#define RESOURCE_FLAG_INTIMIDATED 0x8 -#define RESOURCE_FLAG_TRACED 0x10 -#define RESOURCE_FLAG_EMERGENCY_EXIT 0x20 +#define RESOURCE_FLAG_FLASH_FIRE 0x1 +#define RESOURCE_FLAG_ROOST 0x2 +#define RESOURCE_FLAG_UNBURDEN 0x4 +#define RESOURCE_FLAG_INTIMIDATED 0x8 +#define RESOURCE_FLAG_TRACED 0x10 +#define RESOURCE_FLAG_EMERGENCY_EXIT 0x20 +#define RESOURCE_FLAG_NEUTRALIZING_GAS 0x40 struct DisableStruct { @@ -173,6 +174,8 @@ struct SpecialStatus u8 damagedMons:4; // Mons that have been damaged directly by using a move, includes substitute. u8 dancerUsedMove:1; u8 dancerOriginalTarget:3; + u8 announceNeutralizingGas:1; // See Cmd_switchineffects + u8 neutralizingGasRemoved:1; // See VARIOUS_TRY_END_NEUTRALIZING_GAS s32 dmg; s32 physicalDmg; s32 specialDmg; @@ -225,7 +228,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_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_scripts.h b/include/battle_scripts.h index 41d4ee91d9..415eaa6bb4 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -261,6 +261,7 @@ extern const u8 BattleScript_AttackerAbilityStatRaiseEnd3[]; extern const u8 BattleScript_PoisonHealActivates[]; extern const u8 BattleScript_BadDreamsActivates[]; extern const u8 BattleScript_SwitchInAbilityMsg[]; +extern const u8 BattleScript_SwitchInAbilityMsgRet[]; extern const u8 BattleScript_ToxicSpikesPoisoned[]; extern const u8 BattleScript_ToxicSpikesAbsorbed[]; extern const u8 BattleScript_StickyWebOnSwitchIn[]; @@ -413,5 +414,6 @@ extern const u8 BattleScript_AttackerFormChangeEnd3NoPopup[]; extern const u8 BattleScript_AttackerFormChangeMoveEffect[]; extern const u8 BattleScript_BothCanNoLongerEscape[]; extern const u8 BattleScript_OctolockEndTurn[]; +extern const u8 BattleScript_NeutralizingGasExits[]; #endif // GUARD_BATTLE_SCRIPTS_H diff --git a/include/battle_util.h b/include/battle_util.h index 3ae031520a..4d216cf81b 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -24,6 +24,7 @@ #define ABILITYEFFECT_TRACE1 12 #define ABILITYEFFECT_TRACE2 13 #define ABILITYEFFECT_MOVE_END_OTHER 14 +#define ABILITYEFFECT_NEUTRALIZINGGAS 15 #define ABILITYEFFECT_SWITCH_IN_TERRAIN 254 #define ABILITYEFFECT_SWITCH_IN_WEATHER 255 diff --git a/include/constants/battle_config.h b/include/constants/battle_config.h index 0b80c9bc66..29b7b57503 100644 --- a/include/constants/battle_config.h +++ b/include/constants/battle_config.h @@ -51,6 +51,7 @@ #define SPECIES_MELOETTA_PIROUETTE 10019 #define SPECIES_MORPEKO 0 #define SPECIES_MORPEKO_HANGRY 10020 + #define SPECIES_SIRFETCHD 10021 #endif // Items with peculiar battle effects. @@ -91,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. @@ -114,6 +118,7 @@ #define B_PAYBACK_SWITCH_BOOST GEN_7 // In Gen5+, if the opponent switches out, Payback's damage will no longer be doubled. #define B_HIDDEN_POWER_DMG GEN_7 // In Gen6+, Hidden Power's base power was set to always be 60. Before, it was determined by the mon's IVs. #define B_ROUGH_SKIN_DMG GEN_7 // In Gen4+, Rough Skin contact damage is 1/8th of max HP instead of 1/16th. This will also affect Iron Barbs. +#define B_KNOCK_OFF_DMG GEN_8 // In Gen6+, Knock Off deals 50% more damage when knocking off an item // Type settings #define B_GHOSTS_ESCAPE GEN_7 // In Gen6+, abilities like Shadow Tag or moves like Mean Look fail on Ghost-type Pokémon. They can also escape any Wild Battle. @@ -154,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. @@ -184,6 +190,7 @@ #define B_HEAVY_BALL_MODIFIER GEN_7 // In Gen7+, Heavy Ball's ranges change. See Cmd_handleballthrow. #define B_DREAM_BALL_MODIFIER GEN_8 // In Gen8, Dream Ball's catch multiplier is x4 when the target is asleep or has the ability Comatose. #define B_SERENE_GRACE_BOOST GEN_7 // In Gen5+, Serene Grace boosts the added flinch chance of King's Rock and Razor Fang. +#define B_LEEK_ALWAYS_CRIT GEN_7 // In Gen6+, if a Farfetch'd or Sirfetch'd holding a Leek use a move with increased Critical Hit ratio, it will always result in a Critical Hit. // Flag settings // To use the following features in scripting, replace the 0s with the flag ID you're assigning it to. diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index a4205fd4bf..4de6271614 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -224,6 +224,7 @@ #define VARIOUS_CHECK_POLTERGEIST 134 #define VARIOUS_SET_OCTOLOCK 135 #define VARIOUS_CUT_1_3_HP_RAISE_STATS 136 +#define VARIOUS_TRY_END_NEUTRALIZING_GAS 137 // Cmd_manipulatedamage #define DMG_CHANGE_SIGN 0 diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index d439e3e7f1..7846fa5218 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -607,8 +607,10 @@ #define STRINGID_PKMNBECAMEWEAKERTOFIRE 604 #define STRINGID_ABOUTTOUSEPOLTERGEIST 605 #define STRINGID_CANTESCAPEBECAUSEOFCURRENTMOVE 606 +#define STRINGID_NEUTRALIZINGGASENTERS 607 +#define STRINGID_NEUTRALIZINGGASOVER 608 -#define BATTLESTRINGS_COUNT 607 +#define BATTLESTRINGS_COUNT 609 // The below IDs are all indexes into battle message tables, // used to determine which of a set of messages to print. @@ -844,6 +846,7 @@ #define B_MSG_SWITCHIN_ASONE 13 #define B_MSG_SWITCHIN_CURIOUS_MEDICINE 14 #define B_MSG_SWITCHIN_PASTEL_VEIL 15 +#define B_MSG_SWITCHIN_NEUTRALIZING_GAS 16 // gMentalHerbCureStringIds #define B_MSG_MENTALHERBCURE_INFATUATION 0 diff --git a/include/constants/hold_effects.h b/include/constants/hold_effects.h index d7d74d3e1b..cba2321ce2 100644 --- a/include/constants/hold_effects.h +++ b/include/constants/hold_effects.h @@ -62,12 +62,12 @@ #define HOLD_EFFECT_FIRE_POWER 58 #define HOLD_EFFECT_DRAGON_POWER 59 #define HOLD_EFFECT_NORMAL_POWER 60 -#define HOLD_EFFECT_UP_GRADE 61 +#define HOLD_EFFECT_UPGRADE 61 #define HOLD_EFFECT_SHELL_BELL 62 #define HOLD_EFFECT_LUCKY_PUNCH 63 #define HOLD_EFFECT_METAL_POWDER 64 #define HOLD_EFFECT_THICK_CLUB 65 -#define HOLD_EFFECT_STICK 66 +#define HOLD_EFFECT_LEEK 66 // Gen4 hold effects. #define HOLD_EFFECT_CHOICE_SCARF 67 diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index fe176371c7..e906d5359b 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -278,6 +278,7 @@ static u8 ChooseMoveOrAction_Singles(void) return AI_CHOICE_WATCH; gActiveBattler = sBattler_AI; + // If can switch. if (CountUsablePartyMons(sBattler_AI) > 0 && !IsAbilityPreventingEscape(sBattler_AI) @@ -318,7 +319,7 @@ static u8 ChooseMoveOrAction_Singles(void) } } } - + numOfBestMoves = 1; currentMoveArray[0] = AI_THINKING_STRUCT->score[0]; consideredMoveArray[0] = 0; @@ -559,7 +560,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 +1259,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 +1279,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 +1393,22 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) } break; case EFFECT_SANDSTORM: - if (gBattleWeather & B_WEATHER_SANDSTORM //TODO | B_WEATHER_PRIMAL_ANY) + if (gBattleWeather & (B_WEATHER_SANDSTORM | B_WEATHER_PRIMAL_ANY) || PartnerMoveEffectIsWeather(AI_DATA->battlerAtkPartner, AI_DATA->partnerMove)) score -= 8; break; case EFFECT_SUNNY_DAY: - if (gBattleWeather & B_WEATHER_SUN //TODO | B_WEATHER_PRIMAL_ANY) + if (gBattleWeather & (B_WEATHER_SUN | B_WEATHER_PRIMAL_ANY) || PartnerMoveEffectIsWeather(AI_DATA->battlerAtkPartner, AI_DATA->partnerMove)) score -= 8; break; case EFFECT_RAIN_DANCE: - if (gBattleWeather & B_WEATHER_RAIN //TODO | B_WEATHER_PRIMAL_ANY) + if (gBattleWeather & (B_WEATHER_RAIN | B_WEATHER_PRIMAL_ANY) || PartnerMoveEffectIsWeather(AI_DATA->battlerAtkPartner, AI_DATA->partnerMove)) score -= 8; break; case EFFECT_HAIL: - if (gBattleWeather & B_WEATHER_HAIL //TODO | B_WEATHER_PRIMAL_ANY) + if (gBattleWeather & (B_WEATHER_HAIL | B_WEATHER_PRIMAL_ANY) || PartnerMoveEffectIsWeather(AI_DATA->battlerAtkPartner, AI_DATA->partnerMove)) score -= 8; break; @@ -1719,7 +1720,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 +1877,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 +1915,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 +2096,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 +2283,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 +2312,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]; @@ -2320,7 +2321,7 @@ static s16 AI_CheckBadMove(u8 battlerAtk, u8 battlerDef, u16 move, s16 score) || IsInstructBannedMove(instructedMove) || MoveRequiresRecharging(instructedMove) || MoveCallsOtherMove(instructedMove) - #ifdef ITEM_Z_RING + #ifdef ITEM_Z_POWER_RING //|| (IsZMove(instructedMove)) #endif || (gLockedMoves[battlerDef] != 0 && gLockedMoves[battlerDef] != 0xFFFF) @@ -2351,21 +2352,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 +2414,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 +2469,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 +2517,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 +2567,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,22 +2834,22 @@ 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]; if (instructedMove != MOVE_NONE && !IS_MOVE_STATUS(instructedMove) - && gBattleMoves[instructedMove].target & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY)) //Use instruct on multi-target moves + && gBattleMoves[instructedMove].target & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY)) // Use instruct on multi-target moves { RETURN_SCORE_PLUS(1); } } 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 +2875,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 +2928,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 +2972,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 +2987,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 +3048,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 +3146,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 +3249,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 +3391,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 +3423,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 +3482,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 +3532,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 +3784,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 +3829,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 +3896,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 +3941,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 +4111,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 +4430,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 +4472,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 +4494,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 +4509,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 +4552,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 +4594,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 +4625,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 7b2cf5f273..39c6689c41 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -83,7 +83,7 @@ static bool8 ShouldSwitchIfWonderGuard(void) opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(gActiveBattler)); - if (gBattleMons[GetBattlerAtPosition(opposingPosition)].ability != ABILITY_WONDER_GUARD) + if (GetBattlerAbility(GetBattlerAtPosition(opposingPosition)) != ABILITY_WONDER_GUARD) return FALSE; // Check if Pokemon has a super effective move. @@ -177,7 +177,7 @@ static bool8 FindMonThatAbsorbsOpponentsMove(void) else return FALSE; - if (gBattleMons[gActiveBattler].ability == absorbingTypeAbility) + if (AI_GetAbility(gActiveBattler) == absorbingTypeAbility) return FALSE; GetAIPartyIndexes(gActiveBattler, &firstId, &lastId); @@ -229,7 +229,7 @@ static bool8 ShouldSwitchIfNaturalCure(void) { if (!(gBattleMons[gActiveBattler].status1 & STATUS1_SLEEP)) return FALSE; - if (gBattleMons[gActiveBattler].ability != ABILITY_NATURAL_CURE) + if (AI_GetAbility(gActiveBattler) != ABILITY_NATURAL_CURE) return FALSE; if (gBattleMons[gActiveBattler].hp < gBattleMons[gActiveBattler].maxHP / 2) return FALSE; @@ -437,6 +437,7 @@ bool32 ShouldSwitch(void) return FALSE; availableToSwitch = 0; + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) { battlerIn1 = gActiveBattler; @@ -852,7 +853,8 @@ static bool8 ShouldUseItem(void) break; if (gBattleMons[gActiveBattler].hp == 0) break; - if (gBattleMons[gActiveBattler].hp < gBattleMons[gActiveBattler].maxHP / 4 || gBattleMons[gActiveBattler].maxHP - gBattleMons[gActiveBattler].hp > itemEffects[paramOffset]) + if (gBattleMons[gActiveBattler].hp < gBattleMons[gActiveBattler].maxHP / 4 + || gBattleMons[gActiveBattler].maxHP - gBattleMons[gActiveBattler].hp > itemEffects[paramOffset]) shouldUse = TRUE; break; case AI_ITEM_CURE_CONDITION: diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 12b860b431..7165ad1521 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; @@ -1121,31 +1125,35 @@ bool32 AI_IsAbilityOnSide(u32 battlerId, u32 ability) // does NOT include ability suppression checks s32 AI_GetAbility(u32 battlerId) { + u32 knownAbility = GetBattlerAbility(battlerId); + // The AI knows its own ability. if (IsBattlerAIControlled(battlerId)) - return gBattleMons[battlerId].ability; + return knownAbility; + + // Check neutralizing gas, gastro acid + if (knownAbility == ABILITY_NONE) + return knownAbility; if (BATTLE_HISTORY->abilities[battlerId] != ABILITY_NONE) return BATTLE_HISTORY->abilities[battlerId]; - // Abilities that prevent fleeing. - if (gBattleMons[battlerId].ability == ABILITY_SHADOW_TAG - || gBattleMons[battlerId].ability == ABILITY_MAGNET_PULL - || gBattleMons[battlerId].ability == ABILITY_ARENA_TRAP) - return gBattleMons[battlerId].ability; + // Abilities that prevent fleeing - treat as always known + if (knownAbility == ABILITY_SHADOW_TAG || knownAbility == ABILITY_MAGNET_PULL || knownAbility == ABILITY_ARENA_TRAP) + return knownAbility; + // 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. } @@ -1703,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 @@ -1719,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 @@ -1735,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 @@ -1749,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 @@ -1764,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 @@ -1779,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 @@ -1793,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 @@ -2408,9 +2416,19 @@ static bool32 AnyUsefulStatIsRaised(u8 battler) return FALSE; } +struct Pokemon *GetPartyBattlerPartyData(u8 battlerId, u8 switchBattler) +{ + struct Pokemon *mon; + if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) + mon = &gPlayerParty[switchBattler]; + else + mon = &gEnemyParty[switchBattler]; + return mon; +} + static bool32 PartyBattlerShouldAvoidHazards(u8 currBattler, u8 switchBattler) { - struct Pokemon *mon = GetBattlerPartyData(switchBattler); + struct Pokemon *mon = GetPartyBattlerPartyData(currBattler, switchBattler); u16 ability = GetMonAbility(mon); // we know our own party data u16 holdEffect = GetBattlerHoldEffect(GetMonData(mon, MON_DATA_HELD_ITEM), TRUE); u32 flags = gSideStatuses[GetBattlerSide(currBattler)] & (SIDE_STATUS_SPIKES | SIDE_STATUS_STEALTH_ROCK | SIDE_STATUS_STICKY_WEB | SIDE_STATUS_TOXIC_SPIKES); @@ -2457,7 +2475,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 { @@ -2817,7 +2835,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 } @@ -2941,7 +2959,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; @@ -2968,7 +2986,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]; @@ -3368,6 +3386,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; @@ -3389,6 +3448,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) { @@ -3414,7 +3476,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; @@ -3461,6 +3523,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)) @@ -3481,6 +3546,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 @@ -3497,6 +3565,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); @@ -3515,6 +3586,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 @@ -3530,6 +3604,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) @@ -3542,3 +3619,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 78b7da9172..1107787960 100644 --- a/src/battle_controller_opponent.c +++ b/src/battle_controller_opponent.c @@ -1542,7 +1542,7 @@ static void OpponentHandlePrintSelectionString(void) static void OpponentHandleChooseAction(void) { - AI_TrySwitchOrUseItem(); + AI_TrySwitchOrUseItem(); // TODO consider move choice first OpponentBufferExecCompleted(); } @@ -1591,7 +1591,9 @@ static void OpponentHandleChooseMove(void) if (gAbsentBattlerFlags & gBitTable[gBattlerTarget]) gBattlerTarget = GetBattlerAtPosition(B_POSITION_PLAYER_RIGHT); } - if (CanMegaEvolve(gActiveBattler)) // If opponent can mega evolve, do it. + + // If opponent can mega evolve, do it. + if (CanMegaEvolve(gActiveBattler)) BtlController_EmitTwoReturnValues(BUFFER_B, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); else BtlController_EmitTwoReturnValues(BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8)); @@ -1599,7 +1601,7 @@ static void OpponentHandleChooseMove(void) } OpponentBufferExecCompleted(); } - else + else // Wild pokemon - use random move { u16 move; do diff --git a/src/battle_controller_player_partner.c b/src/battle_controller_player_partner.c index 1741eb7ca0..0b7a954063 100644 --- a/src/battle_controller_player_partner.c +++ b/src/battle_controller_player_partner.c @@ -1534,10 +1534,12 @@ static void PlayerPartnerHandleChooseMove(void) gBattlerTarget = GetBattlerAtPosition(B_POSITION_OPPONENT_RIGHT); } - if (CanMegaEvolve(gActiveBattler)) // If partner can mega evolve, do it. + // If partner can mega evolve, do it. + if (CanMegaEvolve(gActiveBattler)) BtlController_EmitTwoReturnValues(BUFFER_B, 10, (chosenMoveId) | (RET_MEGA_EVOLUTION) | (gBattlerTarget << 8)); else BtlController_EmitTwoReturnValues(BUFFER_B, 10, (chosenMoveId) | (gBattlerTarget << 8)); + PlayerPartnerBufferExecCompleted(); } diff --git a/src/battle_debug.c b/src/battle_debug.c index 2334d6918b..b565d8681b 100644 --- a/src/battle_debug.c +++ b/src/battle_debug.c @@ -1924,12 +1924,20 @@ static const u8 sText_HoldEffectPsychicPower[] = _("Psychic Power"); static const u8 sText_HoldEffectFirePower[] = _("Fire Power"); static const u8 sText_HoldEffectDragonPower[] = _("Dragon Power"); static const u8 sText_HoldEffectNormalPower[] = _("Normal Power"); -static const u8 sText_HoldEffectUpGrade[] = _("Up Grade"); +#ifdef ITEM_EXPANSION +static const u8 sText_HoldEffectUpgrade[] = _("Upgrade"); +#else +static const u8 sText_HoldEffectUpgrade[] = _("Up Grade"); +#endif static const u8 sText_HoldEffectShellBell[] = _("Shell Bell"); static const u8 sText_HoldEffectLuckyPunch[] = _("Lucky Punch"); static const u8 sText_HoldEffectMetalPowder[] = _("Metal Powder"); static const u8 sText_HoldEffectThickClub[] = _("Thick Club"); -static const u8 sText_HoldEffectStick[] = _("Stick"); +#ifdef ITEM_EXPANSION +static const u8 sText_HoldEffectLeek[] = _("Leek"); +#else +static const u8 sText_HoldEffectLeek[] = _("Stick"); +#endif static const u8 sText_HoldEffectChoiceScarf[] = _("Choice Scarf"); static const u8 sText_HoldEffectChoiceSpecs[] = _("Choice Specs"); static const u8 sText_HoldEffectDampRock[] = _("Damp Rock"); @@ -2064,12 +2072,12 @@ static const u8 *const sHoldEffectNames[] = [HOLD_EFFECT_FIRE_POWER] = sText_HoldEffectFirePower, [HOLD_EFFECT_DRAGON_POWER] = sText_HoldEffectDragonPower, [HOLD_EFFECT_NORMAL_POWER] = sText_HoldEffectNormalPower, - [HOLD_EFFECT_UP_GRADE] = sText_HoldEffectUpGrade, + [HOLD_EFFECT_UPGRADE] = sText_HoldEffectUpgrade, [HOLD_EFFECT_SHELL_BELL] = sText_HoldEffectShellBell, [HOLD_EFFECT_LUCKY_PUNCH] = sText_HoldEffectLuckyPunch, [HOLD_EFFECT_METAL_POWDER] = sText_HoldEffectMetalPowder, [HOLD_EFFECT_THICK_CLUB] = sText_HoldEffectThickClub, - [HOLD_EFFECT_STICK] = sText_HoldEffectStick, + [HOLD_EFFECT_LEEK] = sText_HoldEffectLeek, [HOLD_EFFECT_CHOICE_SCARF] = sText_HoldEffectChoiceScarf, [HOLD_EFFECT_CHOICE_SPECS] = sText_HoldEffectChoiceSpecs, [HOLD_EFFECT_DAMP_ROCK] = sText_HoldEffectDampRock, diff --git a/src/battle_main.c b/src/battle_main.c index b6ab9345bd..5a854b1b48 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -99,6 +99,7 @@ static void RunTurnActionsFunctions(void); static void SetActionsAndBattlersTurnOrder(void); static void UpdateBattlerPartyOrdersOnSwitch(void); static bool8 AllAtActionConfirmed(void); +static void TryChangeTurnOrder(void); static void CheckFocusPunch_ClearVarsBeforeTurnStarts(void); static void CheckMegaEvolutionBeforeTurn(void); static void CheckQuickClaw_CustapBerryActivation(void); @@ -3603,6 +3604,10 @@ static void TryDoEventsBeforeFirstTurn(void) } } + // Check neutralizing gas + if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, 0, 0, 0, 0) != 0) + return; + // Check all switch in abilities happening from the fastest mon to slowest. while (gBattleStruct->switchInAbilitiesCounter < gBattlersCount) { @@ -4732,6 +4737,32 @@ 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; } diff --git a/src/battle_message.c b/src/battle_message.c index 0fd88d0a98..90ecde0868 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -729,9 +729,13 @@ static const u8 sText_CantEscapeDueToUsedMove[] = _("{B_ATK_NAME_WITH_PREFIX} ca static const u8 sText_PkmnBecameWeakerToFire[] = _("{B_DEF_NAME_WITH_PREFIX} became\nweaker to fire!"); static const u8 sText_PkmnAboutToBeAttackedByItsItem[] = _("{B_DEF_NAME_WITH_PREFIX} is about\nto be attacked by its {B_BUFF1}!"); static const u8 sText_CantEscapeBecauseOfCurrentMove[] = _("{B_DEF_NAME_WITH_PREFIX} can no longer escape\nbecause of {B_CURRENT_MOVE}!"); +static const u8 sText_NeutralizingGasEnters[] = _("Neutralizing Gas filled the area!"); +static const u8 sText_NeutralizingGasOver[] = _("The effects of Neutralizing\nGas wore off!"); const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = { + [STRINGID_NEUTRALIZINGGASOVER - 12] = sText_NeutralizingGasOver, + [STRINGID_NEUTRALIZINGGASENTERS - 12] = sText_NeutralizingGasEnters, [STRINGID_BATTLERTYPECHANGEDTO - 12] = sText_BattlerTypeChangedTo, [STRINGID_PASTELVEILENTERS - 12] = sText_PastelVeilEnters, [STRINGID_PASTELVEILPROTECTED -12] = sText_PastelVeilProtected, @@ -1388,6 +1392,7 @@ const u16 gSwitchInAbilityStringIds[] = [B_MSG_SWITCHIN_ASONE] = STRINGID_ASONEENTERS, [B_MSG_SWITCHIN_CURIOUS_MEDICINE] = STRINGID_CURIOUSMEDICINEENTERS, [B_MSG_SWITCHIN_PASTEL_VEIL] = STRINGID_PASTELVEILENTERS, + [B_MSG_SWITCHIN_NEUTRALIZING_GAS] = STRINGID_NEUTRALIZINGGASENTERS, }; const u16 gMissStringIds[] = diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index b9289d01b6..7f1c372b7f 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1870,11 +1870,13 @@ static void Cmd_ppreduce(void) static const u8 sCriticalHitChance[] = {16, 8, 4, 3, 2}; // Gens 2,3,4,5 #endif // B_CRIT_CHANCE +#define BENEFITS_FROM_LEEK(battler, holdEffect)((holdEffect == HOLD_EFFECT_LEEK) && (GET_BASE_SPECIES_ID(gBattleMons[battler].species) == SPECIES_FARFETCHD || gBattleMons[battler].species == SPECIES_SIRFETCHD)) s32 CalcCritChanceStage(u8 battlerAtk, u8 battlerDef, u32 move, bool32 recordAbility) { s32 critChance = 0; u32 abilityAtk = GetBattlerAbility(gBattlerAttacker); u32 abilityDef = GetBattlerAbility(gBattlerTarget); + u32 holdEffectAtk = GetBattlerHoldEffect(battlerAtk, TRUE); if (gSideStatuses[battlerDef] & SIDE_STATUS_LUCKY_CHANT || gStatuses3[gBattlerAttacker] & STATUS3_CANT_SCORE_A_CRIT) @@ -1890,19 +1892,21 @@ s32 CalcCritChanceStage(u8 battlerAtk, u8 battlerDef, u32 move, bool32 recordAbi else if (gStatuses3[battlerAtk] & STATUS3_LASER_FOCUS || gBattleMoves[move].effect == EFFECT_ALWAYS_CRIT || (abilityAtk == ABILITY_MERCILESS && gBattleMons[battlerDef].status1 & STATUS1_PSN_ANY) - || move == MOVE_SURGING_STRIKES) + || move == MOVE_SURGING_STRIKES + #if B_LEEK_ALWAYS_CRIT >= GEN_6 + || ((gBattleMoves[gCurrentMove].flags & FLAG_HIGH_CRIT) && BENEFITS_FROM_LEEK(battlerAtk, holdEffectAtk)) + #endif + ) { critChance = -2; } else { - u32 holdEffectAtk = GetBattlerHoldEffect(battlerAtk, TRUE); - critChance = 2 * ((gBattleMons[gBattlerAttacker].status2 & STATUS2_FOCUS_ENERGY) != 0) + ((gBattleMoves[gCurrentMove].flags & FLAG_HIGH_CRIT) != 0) + (holdEffectAtk == HOLD_EFFECT_SCOPE_LENS) + 2 * (holdEffectAtk == HOLD_EFFECT_LUCKY_PUNCH && gBattleMons[gBattlerAttacker].species == SPECIES_CHANSEY) - + 2 * (holdEffectAtk == HOLD_EFFECT_STICK && gBattleMons[gBattlerAttacker].species == SPECIES_FARFETCHD) + + 2 * BENEFITS_FROM_LEEK(battlerAtk, holdEffectAtk) + (abilityAtk == ABILITY_SUPER_LUCK); if (critChance >= ARRAY_COUNT(sCriticalHitChance)) @@ -1911,6 +1915,7 @@ s32 CalcCritChanceStage(u8 battlerAtk, u8 battlerDef, u32 move, bool32 recordAbi return critChance; } +#undef BENEFITS_FROM_LEEK s8 GetInverseCritChance(u8 battlerAtk, u8 battlerDef, u32 move) { @@ -3174,7 +3179,11 @@ void SetMoveEffect(bool32 primary, u32 certain) RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); } else if (gBattleMons[gBattlerAttacker].item != 0 + #ifdef ITEM_EXPANSION + || gBattleMons[gBattlerTarget].item == ITEM_ENIGMA_BERRY_E_READER + #else || gBattleMons[gBattlerTarget].item == ITEM_ENIGMA_BERRY + #endif || gBattleMons[gBattlerTarget].item == 0) { gBattlescriptCurrInstr++; @@ -5470,6 +5479,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; @@ -6174,7 +6191,16 @@ static void Cmd_switchineffects(void) gHitMarker &= ~HITMARKER_FAINTED(gActiveBattler); gSpecialStatuses[gActiveBattler].faintedHasReplacement = FALSE; - if (!(gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_SPIKES_DAMAGED) + // Neutralizing Gas announces itself before hazards + if (gBattleMons[gActiveBattler].ability == ABILITY_NEUTRALIZING_GAS && gSpecialStatuses[gActiveBattler].announceNeutralizingGas == 0) + { + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_NEUTRALIZING_GAS; + gSpecialStatuses[gActiveBattler].announceNeutralizingGas = TRUE; + gBattlerAbility = gActiveBattler; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_SwitchInAbilityMsgRet; + } + else if (!(gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_SPIKES_DAMAGED) && (gSideStatuses[GetBattlerSide(gActiveBattler)] & SIDE_STATUS_SPIKES) && GetBattlerAbility(gActiveBattler) != ABILITY_MAGIC_GUARD && IsBattlerAffectedByHazards(gActiveBattler, FALSE) @@ -6248,7 +6274,7 @@ static void Cmd_switchineffects(void) { // There is a hack here to ensure the truant counter will be 0 when the battler's next turn starts. // The truant counter is not updated in the case where a mon switches in after a lost judgement in the battle arena. - if (gBattleMons[gActiveBattler].ability == ABILITY_TRUANT + if (GetBattlerAbility(gActiveBattler) == ABILITY_TRUANT && gCurrentActionFuncId != B_ACTION_USE_MOVE && !gDisableStructs[gActiveBattler].truantSwitchInHack) gDisableStructs[gActiveBattler].truantCounter = 1; @@ -7446,7 +7472,7 @@ u32 IsLeafGuardProtected(u32 battler) bool32 IsShieldsDownProtected(u32 battler) { - return (gBattleMons[battler].ability == ABILITY_SHIELDS_DOWN + return (GetBattlerAbility(battler) == ABILITY_SHIELDS_DOWN && GetFormIdFromFormSpeciesId(gBattleMons[battler].species) < GetFormIdFromFormSpeciesId(SPECIES_MINIOR_CORE_RED)); // Minior is not in core form } @@ -7914,6 +7940,7 @@ static void Cmd_various(void) break; case VARIOUS_SWITCHIN_ABILITIES: gBattlescriptCurrInstr += 3; + AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, gActiveBattler, 0, 0, 0); AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, gActiveBattler, 0, 0, 0); AbilityBattleEffects(ABILITYEFFECT_INTIMIDATE2, gActiveBattler, 0, 0, 0); AbilityBattleEffects(ABILITYEFFECT_TRACE2, gActiveBattler, 0, 0, 0); @@ -8094,6 +8121,9 @@ static void Cmd_various(void) } else { + if (gBattleMons[gBattlerTarget].ability == ABILITY_NEUTRALIZING_GAS) + gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = TRUE; + gBattleMons[gBattlerTarget].ability = ABILITY_SIMPLE; gBattlescriptCurrInstr += 7; } @@ -8840,7 +8870,7 @@ static void Cmd_various(void) effect = TryHandleSeed(gActiveBattler, STATUS_FIELD_PSYCHIC_TERRAIN, STAT_SPDEF, item, FALSE); break; } - + if (effect) return; } @@ -8970,11 +9000,11 @@ static void Cmd_various(void) for (i = 0; i < gBattlersCount; i++) { - if (((GetBattlerAbility(i) == ABILITY_DESOLATE_LAND && gBattleWeather & B_WEATHER_SUN_PRIMAL) - || (GetBattlerAbility(i) == ABILITY_PRIMORDIAL_SEA && gBattleWeather & B_WEATHER_RAIN_PRIMAL) - || (GetBattlerAbility(i) == ABILITY_DELTA_STREAM && gBattleWeather & B_WEATHER_STRONG_WINDS)) - && IsBattlerAlive(i) - && !(gStatuses3[i] & STATUS3_GASTRO_ACID)) + u32 ability = GetBattlerAbility(i); + if (((ability == ABILITY_DESOLATE_LAND && gBattleWeather & B_WEATHER_SUN_PRIMAL) + || (ability == ABILITY_PRIMORDIAL_SEA && gBattleWeather & B_WEATHER_RAIN_PRIMAL) + || (ability == ABILITY_DELTA_STREAM && gBattleWeather & B_WEATHER_STRONG_WINDS)) + && IsBattlerAlive(i)) shouldNotClear = TRUE; } if (gBattleWeather & B_WEATHER_SUN_PRIMAL && !shouldNotClear) @@ -8997,6 +9027,15 @@ static void Cmd_various(void) } break; } + case VARIOUS_TRY_END_NEUTRALIZING_GAS: + if (gSpecialStatuses[gActiveBattler].neutralizingGasRemoved) + { + gSpecialStatuses[gActiveBattler].neutralizingGasRemoved = FALSE; + BattleScriptPush(gBattlescriptCurrInstr + 3); + gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; + return; + } + break; case VARIOUS_GET_ROTOTILLER_TARGETS: // Gets the battlers to be affected by rototiller. If there are none, print 'But it failed!' { @@ -11413,11 +11452,11 @@ static void Cmd_healpartystatus(void) u16 ability; if (gBattlerPartyIndexes[gBattlerAttacker] == i) - ability = gBattleMons[gBattlerAttacker].ability; + ability = GetBattlerAbility(gBattlerAttacker); else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE && gBattlerPartyIndexes[gActiveBattler] == i && !(gAbsentBattlerFlags & gBitTable[gActiveBattler])) - ability = gBattleMons[gActiveBattler].ability; + ability = GetBattlerAbility(gActiveBattler); else ability = GetAbilityBySpecies(species, abilityNum); @@ -12320,7 +12359,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 @@ -12329,9 +12368,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 @@ -12369,6 +12411,9 @@ static void Cmd_setgastroacid(void) } else { + if (gBattleMons[gBattlerTarget].ability == ABILITY_NEUTRALIZING_GAS) + gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = TRUE; + gStatuses3[gBattlerTarget] |= STATUS3_GASTRO_ACID; gBattlescriptCurrInstr += 5; } @@ -12649,31 +12694,39 @@ static void Cmd_trygetintimidatetarget(void) static void Cmd_switchoutabilities(void) { gActiveBattler = GetBattlerForBattleScript(gBattlescriptCurrInstr[1]); - - switch (GetBattlerAbility(gActiveBattler)) + if (gBattleMons[gActiveBattler].ability == ABILITY_NEUTRALIZING_GAS) { - case ABILITY_NATURAL_CURE: - gBattleMons[gActiveBattler].status1 = 0; - BtlController_EmitSetMonData(BUFFER_A, REQUEST_STATUS_BATTLE, - gBitTable[*(gBattleStruct->battlerPartyIndexes + gActiveBattler)], - sizeof(gBattleMons[gActiveBattler].status1), - &gBattleMons[gActiveBattler].status1); - MarkBattlerForControllerExec(gActiveBattler); - break; - case ABILITY_REGENERATOR: - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 3; - gBattleMoveDamage += gBattleMons[gActiveBattler].hp; - if (gBattleMoveDamage > gBattleMons[gActiveBattler].maxHP) - gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP; - BtlController_EmitSetMonData(BUFFER_A, REQUEST_HP_BATTLE, - gBitTable[*(gBattleStruct->battlerPartyIndexes + gActiveBattler)], - sizeof(gBattleMoveDamage), - &gBattleMoveDamage); - MarkBattlerForControllerExec(gActiveBattler); - break; + gBattleMons[gActiveBattler].ability = ABILITY_NONE; + BattleScriptPush(gBattlescriptCurrInstr); + gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; } + else + { + switch (GetBattlerAbility(gActiveBattler)) + { + case ABILITY_NATURAL_CURE: + gBattleMons[gActiveBattler].status1 = 0; + BtlController_EmitSetMonData(BUFFER_A, REQUEST_STATUS_BATTLE, + gBitTable[*(gBattleStruct->battlerPartyIndexes + gActiveBattler)], + sizeof(gBattleMons[gActiveBattler].status1), + &gBattleMons[gActiveBattler].status1); + MarkBattlerForControllerExec(gActiveBattler); + break; + case ABILITY_REGENERATOR: + gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP / 3; + gBattleMoveDamage += gBattleMons[gActiveBattler].hp; + if (gBattleMoveDamage > gBattleMons[gActiveBattler].maxHP) + gBattleMoveDamage = gBattleMons[gActiveBattler].maxHP; + BtlController_EmitSetMonData(BUFFER_A, REQUEST_HP_BATTLE, + gBitTable[*(gBattleStruct->battlerPartyIndexes + gActiveBattler)], + sizeof(gBattleMoveDamage), + &gBattleMoveDamage); + MarkBattlerForControllerExec(gActiveBattler); + break; + } - gBattlescriptCurrInstr += 2; + gBattlescriptCurrInstr += 2; + } } static void Cmd_jumpifhasnohp(void) @@ -13098,7 +13151,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 @@ -13169,7 +13223,7 @@ static void Cmd_handleballthrow(void) u8 catchRate; gLastThrownBall = gLastUsedItem; - if (gLastUsedItem == ITEM_SAFARI_BALL) + if (gBattleTypeFlags & BATTLE_TYPE_SAFARI) catchRate = gBattleStruct->safariCatchFactor * 1275 / 100; else catchRate = gBaseStats[gBattleMons[gBattlerTarget].species].catchRate; diff --git a/src/battle_util.c b/src/battle_util.c index 0ba7d1ab5d..6feb00f2c3 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -317,8 +317,8 @@ void HandleAction_UseMove(void) else if ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && gSideTimers[side].followmeTimer == 0 && (gBattleMoves[gCurrentMove].power != 0 || gBattleMoves[gCurrentMove].target != MOVE_TARGET_USER) - && ((gBattleMons[*(gBattleStruct->moveTarget + gBattlerAttacker)].ability != ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC) - || (gBattleMons[*(gBattleStruct->moveTarget + gBattlerAttacker)].ability != ABILITY_STORM_DRAIN && moveType == TYPE_WATER))) + && ((GetBattlerAbility(*(gBattleStruct->moveTarget + gBattlerAttacker)) != ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC) + || (GetBattlerAbility(*(gBattleStruct->moveTarget + gBattlerAttacker)) != ABILITY_STORM_DRAIN && moveType == TYPE_WATER))) { side = GetBattlerSide(gBattlerAttacker); for (gActiveBattler = 0; gActiveBattler < gBattlersCount; gActiveBattler++) @@ -387,9 +387,9 @@ void HandleAction_UseMove(void) { gActiveBattler = gBattlerByTurnOrder[var]; RecordAbilityBattle(gActiveBattler, gBattleMons[gActiveBattler].ability); - if (gBattleMons[gActiveBattler].ability == ABILITY_LIGHTNING_ROD) + if (GetBattlerAbility(gActiveBattler) == ABILITY_LIGHTNING_ROD) gSpecialStatuses[gActiveBattler].lightningRodRedirected = TRUE; - else if (gBattleMons[gActiveBattler].ability == ABILITY_STORM_DRAIN) + else if (GetBattlerAbility(gActiveBattler) == ABILITY_STORM_DRAIN) gSpecialStatuses[gActiveBattler].stormDrainRedirected = TRUE; gBattlerTarget = gActiveBattler; } @@ -627,7 +627,7 @@ bool8 TryRunFromBattle(u8 battler) effect++; } #endif - else if (gBattleMons[battler].ability == ABILITY_RUN_AWAY) + else if (GetBattlerAbility(battler) == ABILITY_RUN_AWAY) { if (InBattlePyramid()) { @@ -2736,7 +2736,7 @@ u8 DoBattlerEndTurnEffects(void) for (gBattlerAttacker = 0; gBattlerAttacker < gBattlersCount; gBattlerAttacker++) { if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP) - && gBattleMons[gBattlerAttacker].ability != ABILITY_SOUNDPROOF) + && GetBattlerAbility(gBattlerAttacker) != ABILITY_SOUNDPROOF) { gBattleMons[gBattlerAttacker].status1 &= ~STATUS1_SLEEP; gBattleMons[gBattlerAttacker].status2 &= ~STATUS2_NIGHTMARE; @@ -2804,6 +2804,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) { @@ -3320,7 +3321,7 @@ u8 AtkCanceller_UnableToUseMove(void) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_TRUANT: // truant - if (gBattleMons[gBattlerAttacker].ability == ABILITY_TRUANT && gDisableStructs[gBattlerAttacker].truantCounter) + if (GetBattlerAbility(gBattlerAttacker) == ABILITY_TRUANT && gDisableStructs[gBattlerAttacker].truantCounter) { CancelMultiTurnMoves(gBattlerAttacker); gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; @@ -3761,7 +3762,7 @@ u8 TryWeatherFormChange(u8 battler) if (gBattleMons[battler].species == SPECIES_CASTFORM) { - if (gBattleMons[battler].ability != ABILITY_FORECAST || gBattleMons[battler].hp == 0) + if (GetBattlerAbility(battler) != ABILITY_FORECAST || gBattleMons[battler].hp == 0) { ret = 0; } @@ -3797,11 +3798,11 @@ u8 TryWeatherFormChange(u8 battler) } else if (gBattleMons[battler].species == SPECIES_CHERRIM) { - if (gBattleMons[battler].ability != ABILITY_FLOWER_GIFT || gBattleMons[battler].hp == 0) + if (GetBattlerAbility(battler) != ABILITY_FLOWER_GIFT || gBattleMons[battler].hp == 0) ret = 0; else if (gBattleMonForms[battler] == 0 && weatherEffect && holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA && gBattleWeather & B_WEATHER_SUN) ret = 2; - else if (gBattleMonForms[battler] != 0 && (!weatherEffect || holdEffect != HOLD_EFFECT_UTILITY_UMBRELLA || !(gBattleWeather & B_WEATHER_SUN))) + else if (gBattleMonForms[battler] != 0 && (!weatherEffect || holdEffect == HOLD_EFFECT_UTILITY_UMBRELLA || !(gBattleWeather & B_WEATHER_SUN))) ret = 1; } @@ -3888,7 +3889,7 @@ static bool32 ShouldChangeFormHpBased(u32 battler) for (i = 0; i < ARRAY_COUNT(forms); i++) { - if (gBattleMons[battler].ability == forms[i][0]) + if (GetBattlerAbility(battler) == forms[i][0]) { if (gBattleMons[battler].species == forms[i][2] && gBattleMons[battler].hp > gBattleMons[battler].maxHP / forms[i][3]) @@ -5383,7 +5384,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move case ABILITYEFFECT_IMMUNITY: // 5 for (battler = 0; battler < gBattlersCount; battler++) { - switch (gBattleMons[battler].ability) + switch (GetBattlerAbility(battler)) { case ABILITY_IMMUNITY: if (gBattleMons[battler].status1 & (STATUS1_POISON | STATUS1_TOXIC_POISON | STATUS1_TOXIC_COUNTER)) @@ -5473,7 +5474,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move case ABILITYEFFECT_FORECAST: // 6 for (battler = 0; battler < gBattlersCount; battler++) { - if (gBattleMons[battler].ability == ABILITY_FORECAST || gBattleMons[battler].ability == ABILITY_FLOWER_GIFT) + if (GetBattlerAbility(battler) == ABILITY_FORECAST || GetBattlerAbility(battler) == ABILITY_FLOWER_GIFT) { effect = TryWeatherFormChange(battler); if (effect) @@ -5534,7 +5535,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move case ABILITYEFFECT_INTIMIDATE2: for (i = 0; i < gBattlersCount; i++) { - if (gBattleMons[i].ability == ABILITY_INTIMIDATE && gBattleResources->flags->flags[i] & RESOURCE_FLAG_INTIMIDATED) + if (GetBattlerAbility(i) == ABILITY_INTIMIDATE && gBattleResources->flags->flags[i] & RESOURCE_FLAG_INTIMIDATED) { gLastUsedAbility = ABILITY_INTIMIDATE; gBattleResources->flags->flags[i] &= ~RESOURCE_FLAG_INTIMIDATED; @@ -5601,6 +5602,23 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move } } break; + case ABILITYEFFECT_NEUTRALIZINGGAS: + // Prints message only. separate from ABILITYEFFECT_ON_SWITCHIN bc activates before entry hazards + for (i = 0; i < gBattlersCount; i++) + { + if (gBattleMons[i].ability == ABILITY_NEUTRALIZING_GAS && !(gBattleResources->flags->flags[i] & RESOURCE_FLAG_NEUTRALIZING_GAS)) + { + gBattleResources->flags->flags[i] |= RESOURCE_FLAG_NEUTRALIZING_GAS; + gBattlerAbility = i; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_NEUTRALIZING_GAS; + BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); + effect++; + } + + if (effect) + break; + } + break; } if (effect && gLastUsedAbility != 0xFF) @@ -5611,11 +5629,51 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move return effect; } +bool32 IsNeutralizingGasBannedAbility(u32 ability) +{ + switch (ability) + { + case ABILITY_MULTITYPE: + case ABILITY_ZEN_MODE: + case ABILITY_STANCE_CHANGE: + case ABILITY_POWER_CONSTRUCT: + case ABILITY_SCHOOLING: + case ABILITY_RKS_SYSTEM: + case ABILITY_SHIELDS_DOWN: + case ABILITY_COMATOSE: + case ABILITY_DISGUISE: + case ABILITY_GULP_MISSILE: + case ABILITY_ICE_FACE: + case ABILITY_AS_ONE_ICE_RIDER: + case ABILITY_AS_ONE_SHADOW_RIDER: + return TRUE; + default: + return FALSE; + } +} + +bool32 IsNeutralizingGasOnField(void) +{ + u32 i; + + for (i = 0; i < gBattlersCount; i++) + { + if (IsBattlerAlive(i) && gBattleMons[i].ability == ABILITY_NEUTRALIZING_GAS && !(gStatuses3[i] & STATUS3_GASTRO_ACID)) + return TRUE; + } + + return FALSE; +} + u32 GetBattlerAbility(u8 battlerId) { if (gStatuses3[battlerId] & STATUS3_GASTRO_ACID) return ABILITY_NONE; - else if ((((gBattleMons[gBattlerAttacker].ability == ABILITY_MOLD_BREAKER + + if (IsNeutralizingGasOnField() && !IsNeutralizingGasBannedAbility(gBattleMons[battlerId].ability)) + return ABILITY_NONE; + + if ((((gBattleMons[gBattlerAttacker].ability == ABILITY_MOLD_BREAKER || gBattleMons[gBattlerAttacker].ability == ABILITY_TERAVOLT || gBattleMons[gBattlerAttacker].ability == ABILITY_TURBOBLAZE) && !(gStatuses3[gBattlerAttacker] & STATUS3_GASTRO_ACID)) @@ -5625,8 +5683,8 @@ u32 GetBattlerAbility(u8 battlerId) && gActionsByTurnOrder[gBattlerByTurnOrder[gBattlerAttacker]] == B_ACTION_USE_MOVE && gCurrentTurnActionNumber < gBattlersCount) return ABILITY_NONE; - else - return gBattleMons[battlerId].ability; + + return gBattleMons[battlerId].ability; } u32 IsAbilityOnSide(u32 battlerId, u32 ability) @@ -5678,7 +5736,7 @@ u32 IsAbilityPreventingEscape(u32 battlerId) return 0; #endif #if B_SHADOW_TAG_ESCAPE >= GEN_4 - if ((id = IsAbilityOnOpposingSide(battlerId, ABILITY_SHADOW_TAG)) && gBattleMons[battlerId].ability != ABILITY_SHADOW_TAG) + if ((id = IsAbilityOnOpposingSide(battlerId, ABILITY_SHADOW_TAG)) && GetBattlerAbility(battlerId) != ABILITY_SHADOW_TAG) #else if (id = IsAbilityOnOpposingSide(battlerId, ABILITY_SHADOW_TAG)) #endif @@ -7226,7 +7284,7 @@ u32 GetMoveTarget(u16 move, u8 setTarget) targetBattler = SetRandomTarget(gBattlerAttacker); if (gBattleMoves[move].type == TYPE_ELECTRIC && IsAbilityOnOpposingSide(gBattlerAttacker, ABILITY_LIGHTNING_ROD) - && gBattleMons[targetBattler].ability != ABILITY_LIGHTNING_ROD) + && GetBattlerAbility(targetBattler) != ABILITY_LIGHTNING_ROD) { targetBattler ^= BIT_FLANK; RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability); @@ -7234,7 +7292,7 @@ u32 GetMoveTarget(u16 move, u8 setTarget) } else if (gBattleMoves[move].type == TYPE_WATER && IsAbilityOnOpposingSide(gBattlerAttacker, ABILITY_STORM_DRAIN) - && gBattleMons[targetBattler].ability != ABILITY_STORM_DRAIN) + && GetBattlerAbility(targetBattler) != ABILITY_STORM_DRAIN) { targetBattler ^= BIT_FLANK; RecordAbilityBattle(targetBattler, gBattleMons[targetBattler].ability); @@ -7412,7 +7470,7 @@ u32 GetBattlerHoldEffect(u8 battlerId, bool32 checkNegating) return HOLD_EFFECT_NONE; if (gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) return HOLD_EFFECT_NONE; - if (gBattleMons[battlerId].ability == ABILITY_KLUTZ && !(gStatuses3[battlerId] & STATUS3_GASTRO_ACID)) + if (GetBattlerAbility(battlerId) == ABILITY_KLUTZ) return HOLD_EFFECT_NONE; } @@ -7467,8 +7525,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; @@ -8081,7 +8144,7 @@ static u32 CalcMoveBasePowerAfterModifiers(u16 move, u8 battlerAtk, u8 battlerDe if (IsAbilityOnField(ABILITY_AURA_BREAK)) MulModifier(&modifier, UQ_4_12(0.75)); else - MulModifier(&modifier, UQ_4_12(1.25)); + MulModifier(&modifier, UQ_4_12(1.33)); } // attacker partner's abilities @@ -8242,8 +8305,11 @@ static u32 CalcMoveBasePowerAfterModifiers(u16 move, u8 battlerAtk, u8 battlerDe MulModifier(&modifier, UQ_4_12(0.5)); break; case EFFECT_KNOCK_OFF: - if (gBattleMons[battlerDef].item != ITEM_NONE && GetBattlerAbility(battlerDef) != ABILITY_STICKY_HOLD) + #if B_KNOCK_OFF_DMG >= GEN_6 + if (gBattleMons[battlerDef].item != ITEM_NONE + && CanBattlerGetOrLoseItem(battlerDef, gBattleMons[battlerDef].item)) MulModifier(&modifier, UQ_4_12(1.5)); + #endif break; } @@ -9055,9 +9121,9 @@ bool32 CanMegaEvolve(u8 battlerId) struct MegaEvolutionData *mega = &(((struct ChooseMoveStruct*)(&gBattleResources->bufferA[gActiveBattler][4]))->mega); #ifdef ITEM_EXPANSION - // Check if Player has a Mega Bracelet + // Check if Player has a Mega Ring if ((GetBattlerPosition(battlerId) == B_POSITION_PLAYER_LEFT || (!(gBattleTypeFlags & BATTLE_TYPE_MULTI) && GetBattlerPosition(battlerId) == B_POSITION_PLAYER_RIGHT)) - && !CheckBagHasItem(ITEM_MEGA_BRACELET, 1)) + && !CheckBagHasItem(ITEM_MEGA_RING, 1)) return FALSE; #endif diff --git a/src/battle_util2.c b/src/battle_util2.c index 4f6c3582f0..c269d86134 100644 --- a/src/battle_util2.c +++ b/src/battle_util2.c @@ -142,7 +142,7 @@ u32 BattlePalace_TryEscapeStatus(u8 battlerId) { u32 toSub; - if (gBattleMons[battlerId].ability == ABILITY_EARLY_BIRD) + if (GetBattlerAbility(battlerId) == ABILITY_EARLY_BIRD) toSub = 2; else toSub = 1; diff --git a/src/data/items.h b/src/data/items.h index ad2d54c74d..4b306ba798 100644 --- a/src/data/items.h +++ b/src/data/items.h @@ -2680,7 +2680,7 @@ const struct Item gItems[] = .name = _("UP-GRADE"), .itemId = ITEM_UP_GRADE, .price = 2100, - .holdEffect = HOLD_EFFECT_UP_GRADE, + .holdEffect = HOLD_EFFECT_UPGRADE, .description = sUpGradeDesc, .pocket = POCKET_ITEMS, .type = ITEM_USE_BAG_MENU, @@ -2767,7 +2767,7 @@ const struct Item gItems[] = .name = _("STICK"), .itemId = ITEM_STICK, .price = 200, - .holdEffect = HOLD_EFFECT_STICK, + .holdEffect = HOLD_EFFECT_LEEK, .description = sStickDesc, .pocket = POCKET_ITEMS, .type = ITEM_USE_BAG_MENU,