From 3c9f05b3670934e40b012232bde1ddacc6243778 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sat, 27 Sep 2025 18:47:10 +0200 Subject: [PATCH] Fixes activation order for a couple abilities (#7732) --- asm/macros/battle_script.inc | 3 +- data/battle_scripts_1.s | 68 +++-- include/battle.h | 8 +- include/battle_main.h | 4 +- include/battle_scripts.h | 2 + include/battle_util.h | 5 + include/constants/battle_script_commands.h | 8 +- src/battle_end_turn.c | 29 +-- src/battle_main.c | 38 +-- src/battle_message.c | 2 +- src/battle_script_commands.c | 127 +++++++--- src/battle_util.c | 274 ++++++++++++--------- test/battle/ability/anger_shell.c | 20 ++ test/battle/ability/berserk.c | 61 +++++ test/battle/ability/color_change.c | 13 + test/battle/ability/opportunist.c | 21 ++ 16 files changed, 464 insertions(+), 219 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 798c8d1bd4..4cb30b27f1 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1011,8 +1011,9 @@ .4byte \jumpInstr .endm - .macro tryrestorehpberry + .macro tryrestorehpberry battler:req .byte 0xbb + .byte \battler .endm .macro halvehp failInstr:req diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index f85a077c8a..4953b48aae 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -4966,7 +4966,7 @@ BattleScript_MagicRoomEnds:: setbyte gBattlerTarget, 0 BattleScript_MagicRoomHealingItemsLoop: copyarraywithindex gBattlerAttacker, gBattlerByTurnOrder, gBattlerTarget, 1 - tryrestorehpberry + tryrestorehpberry BS_ATTACKER addbyte gBattlerTarget, 1 jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_MagicRoomHealingItemsLoop end2 @@ -5404,27 +5404,36 @@ BattleScript_SeedSowerActivates:: call BattleScript_ActivateTerrainEffects return -BattleScript_AngerShellActivates:: +BattleScript_BerserkActivates:: saveattacker - copybyte gBattlerAttacker, gBattlerTarget + copybyte gBattlerAttacker, gEffectBattler call BattleScript_AbilityPopUp - jumpifstat BS_TARGET, CMP_LESS_THAN, STAT_ATK, MAX_STAT_STAGE, BattleScript_AngerShellTryDef - jumpifstat BS_TARGET, CMP_LESS_THAN, STAT_SPATK, MAX_STAT_STAGE, BattleScript_AngerShellTryDef - jumpifstat BS_TARGET, CMP_LESS_THAN, STAT_SPEED, MAX_STAT_STAGE, BattleScript_AngerShellTryDef - jumpifstat BS_TARGET, CMP_GREATER_THAN, STAT_DEF, MIN_STAT_STAGE, BattleScript_AngerShellTryDef - jumpifstat BS_TARGET, CMP_EQUAL, STAT_SPDEF, MIN_STAT_STAGE, BattleScript_RestoreAttackerButItFailed -BattleScript_AngerShellTryDef:: - modifybattlerstatstage BS_ATTACKER, STAT_DEF, DECREASE, 1, BattleScript_AngerShellTrySpDef, ANIM_ON -BattleScript_AngerShellTrySpDef: - modifybattlerstatstage BS_ATTACKER, STAT_SPDEF, DECREASE, 1, BattleScript_AngerShellTryAttack, ANIM_ON -BattleScript_AngerShellTryAttack: - modifybattlerstatstage BS_ATTACKER, STAT_ATK, INCREASE, 1, BattleScript_AngerShellTrySpAtk, ANIM_ON -BattleScript_AngerShellTrySpAtk: - modifybattlerstatstage BS_ATTACKER, STAT_SPATK, INCREASE, 1, BattleScript_AngerShellTrySpeed, ANIM_ON -BattleScript_AngerShellTrySpeed: - modifybattlerstatstage BS_ATTACKER, STAT_SPEED, INCREASE, 1, BattleScript_AngerShellRet, ANIM_ON -BattleScript_AngerShellRet: + statbuffchange BS_EFFECT_BATTLER, STAT_CHANGE_CERTAIN, BattleScript_BerserkActivatesTryBerry + call BattleScript_StatUp +BattleScript_BerserkActivatesTryBerry: restoreattacker + tryrestorehpberry BS_EFFECT_BATTLER + return + +BattleScript_AngerShellActivates:: + call BattleScript_AbilityPopUp + jumpifstat BS_EFFECT_BATTLER, CMP_LESS_THAN, STAT_ATK, MAX_STAT_STAGE, BattleScript_AngerShellTryDef + jumpifstat BS_EFFECT_BATTLER, CMP_LESS_THAN, STAT_SPATK, MAX_STAT_STAGE, BattleScript_AngerShellTryDef + jumpifstat BS_EFFECT_BATTLER, CMP_LESS_THAN, STAT_SPEED, MAX_STAT_STAGE, BattleScript_AngerShellTryDef + jumpifstat BS_EFFECT_BATTLER, CMP_GREATER_THAN, STAT_DEF, MIN_STAT_STAGE, BattleScript_AngerShellTryDef + jumpifstat BS_EFFECT_BATTLER, CMP_EQUAL, STAT_SPDEF, MIN_STAT_STAGE, BattleScript_RestoreAttackerButItFailed +BattleScript_AngerShellTryDef:: + modifybattlerstatstage BS_EFFECT_BATTLER, STAT_DEF, DECREASE, 1, BattleScript_AngerShellTrySpDef, ANIM_ON +BattleScript_AngerShellTrySpDef: + modifybattlerstatstage BS_EFFECT_BATTLER, STAT_SPDEF, DECREASE, 1, BattleScript_AngerShellTryAttack, ANIM_ON +BattleScript_AngerShellTryAttack: + modifybattlerstatstage BS_EFFECT_BATTLER, STAT_ATK, INCREASE, 1, BattleScript_AngerShellTrySpAtk, ANIM_ON +BattleScript_AngerShellTrySpAtk: + modifybattlerstatstage BS_EFFECT_BATTLER, STAT_SPATK, INCREASE, 1, BattleScript_AngerShellTrySpeed, ANIM_ON +BattleScript_AngerShellTrySpeed: + modifybattlerstatstage BS_EFFECT_BATTLER, STAT_SPEED, INCREASE, 1, BattleScript_AngerShellRet, ANIM_ON +BattleScript_AngerShellRet: + tryrestorehpberry BS_EFFECT_BATTLER return BattleScript_WindPowerActivates:: @@ -5557,8 +5566,10 @@ BattleScript_DoFutureAttackResult: tryfaintmon BS_TARGET checkteamslost BattleScript_FutureAttackEnd BattleScript_FutureAttackEnd:: + moveendcase MOVEEND_SET_VALUES moveendcase MOVEEND_RAGE moveendcase MOVEEND_ABILITIES + moveendcase MOVEEND_COLOR_CHANGE moveendfromto MOVEEND_ITEM_EFFECTS_ALL, MOVEEND_UPDATE_LAST_MOVES setmoveresultflags 0 end2 @@ -6142,7 +6153,7 @@ BattleScript_DoTurnDmg: datahpupdate BS_ATTACKER tryfaintmon BS_ATTACKER checkteamslost BattleScript_DoTurnDmgEnd - tryrestorehpberry + tryrestorehpberry BS_ATTACKER BattleScript_DoTurnDmgEnd: end2 @@ -6384,7 +6395,7 @@ BattleScript_YawnEnd: BattleScript_EmbargoEndTurn:: printstring STRINGID_EMBARGOENDS waitmessage B_WAIT_TIME_LONG - tryrestorehpberry + tryrestorehpberry BS_ATTACKER end2 BattleScript_TelekinesisEndTurn:: @@ -6674,7 +6685,7 @@ BattleScript_PickupActivates:: call BattleScript_AbilityPopUp printstring STRINGID_XFOUNDONEY waitmessage B_WAIT_TIME_LONG - tryrestorehpberry + tryrestorehpberry BS_ATTACKER BattleScript_PickupActivatesEnd: end2 @@ -6684,7 +6695,7 @@ BattleScript_HarvestActivates:: call BattleScript_AbilityPopUp printstring STRINGID_HARVESTBERRY waitmessage B_WAIT_TIME_LONG - tryrestorehpberry + tryrestorehpberry BS_ATTACKER BattleScript_HarvestActivatesEnd: end2 @@ -8174,6 +8185,10 @@ BattleScript_OpportunistStartCopyStats: goto BattleScript_OpportunistStartCopyStats BattleScript_OpportunistCopyStatChangeEnd: setbyte sSTAT_ANIM_PLAYED, FALSE + return + +BattleScript_OpportunistCopyStatChangeEnd3:: + call BattleScript_OpportunistCopyStatChange end3 BattleScript_TotemVar:: @@ -8593,14 +8608,13 @@ BattleScript_SymbiosisActivates:: return BattleScript_TargetAbilityStatRaiseRet:: - copybyte sSAVED_BATTLER, gBattlerAttacker - copybyte gBattlerAbility, gEffectBattler - copybyte gBattlerAttacker, gBattlerTarget + saveattacker + copybyte gBattlerAttacker, gEffectBattler call BattleScript_AbilityPopUp statbuffchange BS_ATTACKER, STAT_CHANGE_CERTAIN, BattleScript_TargetAbilityStatRaiseRet_End call BattleScript_StatUp BattleScript_TargetAbilityStatRaiseRet_End: - copybyte gBattlerAttacker, sSAVED_BATTLER + restoreattacker return @@@ MAX MOVES @@@ diff --git a/include/battle.h b/include/battle.h index db3e69821e..3d3b38a407 100644 --- a/include/battle.h +++ b/include/battle.h @@ -607,8 +607,9 @@ struct BattleStruct struct BattlerState battlerState[MAX_BATTLERS_COUNT]; struct PartyState partyState[NUM_BATTLE_SIDES][PARTY_SIZE]; u8 eventBlockCounter; - u8 turnEffectsBattlerId; u8 endTurnEventsCounter; + u8 turnEffectsBattlerId:4; + u8 moveEndBattlerId:4; u16 wrappedMove[MAX_BATTLERS_COUNT]; u16 moveTarget[MAX_BATTLERS_COUNT]; u32 expShareExpValue; @@ -779,7 +780,8 @@ struct BattleStruct u8 numHazards[NUM_BATTLE_SIDES]; u8 hazardsCounter:4; // Counter for applying hazard on switch in enum SubmoveState submoveAnnouncement:2; - u8 padding2:2; + u8 toxicChainPriority:1; // If Toxic Chain will trigger on target, all other non volatiles will be blocked + u8 padding2:1; }; struct AiBattleData @@ -901,7 +903,7 @@ struct BattleScripting s32 savedDmg; u16 savedMoveEffect; // For moves hitting multiple targets. u16 moveEffect; - u16 multihitMoveEffect; + u16 unused_0x30; u8 illusionNickHack; // To properly display nick in STRINGID_ENEMYABOUTTOSWITCHPKMN. bool8 fixedPopup; // Force ability popup to stick until manually called back u16 abilityPopupOverwrite; diff --git a/include/battle_main.h b/include/battle_main.h index bf8e88fc5d..70934124e0 100644 --- a/include/battle_main.h +++ b/include/battle_main.h @@ -57,9 +57,9 @@ enum FirstTurnEventsStates FIRST_TURN_EVENTS_TOTEM_BOOST, FIRST_TURN_EVENTS_NEUTRALIZING_GAS, FIRST_TURN_EVENTS_SWITCH_IN_ABILITIES, - FIRST_TURN_EVENTS_OPPORTUNIST_1, FIRST_TURN_EVENTS_ITEM_EFFECTS, - FIRST_TURN_EVENTS_OPPORTUNIST_2, + FIRST_TURN_EVENTS_OPPORTUNIST, + FIRST_TURN_EVENTS_MIRROR_HERB, FIRST_TURN_EVENTS_EJECT_PACK, FIRST_TURN_EVENTS_END, }; diff --git a/include/battle_scripts.h b/include/battle_scripts.h index c1b83c5f70..de01888a6d 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -3,6 +3,7 @@ extern const u8 BattleScript_SupersweetSyrupActivates[]; extern const u8 BattleScript_OpportunistCopyStatChange[]; +extern const u8 BattleScript_OpportunistCopyStatChangeEnd3[]; extern const u8 BattleScript_MirrorHerbCopyStatChange[]; extern const u8 BattleScript_MirrorHerbCopyStatChangeEnd2[]; extern const u8 BattleScript_NotAffected[]; @@ -461,6 +462,7 @@ extern const u8 BattleScript_RemoveFireType[]; extern const u8 BattleScript_TargetAbilityStatRaiseRet[]; extern const u8 BattleScript_RemoveElectricType[]; extern const u8 BattleScript_SeedSowerActivates[]; +extern const u8 BattleScript_BerserkActivates[]; extern const u8 BattleScript_AngerShellActivates[]; extern const u8 BattleScript_WellBakedBodyActivates[]; extern const u8 BattleScript_WindRiderActivatesMoveEnd[]; diff --git a/include/battle_util.h b/include/battle_util.h index 0ce1a9b7d7..b06619e6ba 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -43,6 +43,7 @@ enum { ABILITYEFFECT_ON_SWITCHIN, ABILITYEFFECT_ENDTURN, ABILITYEFFECT_MOVE_END_ATTACKER, + ABILITYEFFECT_COLOR_CHANGE, // Color Change, Berserk, Anger Shell ABILITYEFFECT_MOVE_END, ABILITYEFFECT_IMMUNITY, ABILITYEFFECT_SYNCHRONIZE, @@ -55,6 +56,7 @@ enum { ABILITYEFFECT_SWITCH_IN_TERRAIN, ABILITYEFFECT_SWITCH_IN_WEATHER, ABILITYEFFECT_OPPORTUNIST, + ABILITYEFFECT_OPPORTUNIST_FIRST_TURN, ABILITYEFFECT_SWITCH_IN_STATUSES, }; @@ -64,6 +66,7 @@ enum ItemCaseId ITEMEFFECT_NONE, ITEMEFFECT_ON_SWITCH_IN, ITEMEFFECT_ON_SWITCH_IN_FIRST_TURN, + ITEMEFFECT_LEFTOVERS, // Leftovers, Black Sludge ITEMEFFECT_NORMAL, ITEMEFFECT_TRY_HEALING, ITEMEFFECT_MOVE_END, @@ -74,6 +77,8 @@ enum ItemCaseId ITEMEFFECT_USE_LAST_ITEM, // move end effects for just the battler, not whole field ITEMEFFECT_WHITE_HERB, ITEMEFFECT_WHITE_HERB_ENDTURN, + ITEMEFFECT_MIRROR_HERB, + ITEMEFFECT_MIRROR_HERB_FIRST_TURN, }; enum ItemEffect diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 78c2200018..06115abe88 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -33,7 +33,7 @@ #define sSAVED_DMG (gBattleScripting + 0x28) // savedDmg #define sSAVED_MOVE_EFFECT (gBattleScripting + 0x2C) // savedMoveEffect #define sMOVE_EFFECT (gBattleScripting + 0x2E) // moveEffect -#define sMULTIHIT_EFFECT (gBattleScripting + 0x30) // multihitMoveEffect +#define sUNUSED_0x30 (gBattleScripting + 0x30) // unused_0x30 #define sILLUSION_NICK_HACK (gBattleScripting + 0x32) // illusionNickHack #define sFIXED_ABILITY_POPUP (gBattleScripting + 0x33) // fixedPopup #define sABILITY_OVERWRITE (gBattleScripting + 0x34) // abilityPopupOverwrite @@ -128,7 +128,7 @@ enum CmdVarious // cases for Cmd_moveend - Order matters! enum MoveEndEffects { - MOVEEND_SUM_DAMAGE, + MOVEEND_SET_VALUES, MOVEEND_PROTECT_LIKE_EFFECT, MOVEEND_ABSORB, MOVEEND_RAGE, @@ -155,14 +155,16 @@ enum MoveEndEffects MOVEEND_ITEM_EFFECTS_ATTACKER, MOVEEND_ABILITY_BLOCK, MOVEEND_SHEER_FORCE, // If move is Sheer Force affected, skip until Opportunist + MOVEEND_COLOR_CHANGE, // Color Change / Berserk / Anger Shell MOVEEND_RED_CARD, // Red Card triggers before Eject Pack MOVEEND_EJECT_BUTTON, - MOVEEND_LIFEORB_SHELLBELL, // Includes shell bell, throat spray, etc + MOVEEND_LIFEORB_SHELLBELL, // Shell Bell / Life Orb / Throat Spray MOVEEND_FORM_CHANGE, MOVEEND_EMERGENCY_EXIT, MOVEEND_EJECT_PACK, MOVEEND_HIT_ESCAPE, MOVEEND_OPPORTUNIST, // Occurs after other stat change items/abilities to try and copy the boosts + MOVEEND_MIRROR_HERB, MOVEEND_PICKPOCKET, MOVEEND_WHITE_HERB, MOVEEND_THIRD_MOVE_BLOCK, diff --git a/src/battle_end_turn.c b/src/battle_end_turn.c index f0382b3877..e7485da64d 100644 --- a/src/battle_end_turn.c +++ b/src/battle_end_turn.c @@ -425,17 +425,22 @@ static bool32 HandleEndTurnWish(u32 battler) static bool32 HandleEndTurnFirstEventBlock(u32 battler) { bool32 effect = FALSE; - u32 side; + if (!IsBattlerAlive(battler)) + { + gBattleStruct->eventBlockCounter = 0; + gBattleStruct->turnEffectsBattlerId++; + return effect; + } + switch (gBattleStruct->eventBlockCounter) { case FIRST_EVENT_BLOCK_GMAX_MOVE_RESIDUAL: // TODO: Has to be split into 3 statuses and needs a queue side = GetBattlerSide(battler); if (gSideStatuses[side] & SIDE_STATUS_DAMAGE_NON_TYPES) { - if (IsBattlerAlive(battler) - && !IS_BATTLER_OF_TYPE(battler, gSideTimers[side].damageNonTypesType) + if (!IS_BATTLER_OF_TYPE(battler, gSideTimers[side].damageNonTypesType) && !IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) { gBattlerAttacker = battler; @@ -448,7 +453,7 @@ static bool32 HandleEndTurnFirstEventBlock(u32 battler) gBattleStruct->eventBlockCounter++; break; case FIRST_EVENT_BLOCK_SEA_OF_FIRE_DAMAGE: - if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SEA_OF_FIRE && IsBattlerAlive(battler)) + if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_SEA_OF_FIRE) { gBattlerAttacker = battler; gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / 8; @@ -484,7 +489,6 @@ static bool32 HandleEndTurnFirstEventBlock(u32 battler) break; case FIRST_EVENT_BLOCK_GRASSY_TERRAIN_HEAL: if (gFieldStatuses & STATUS_FIELD_GRASSY_TERRAIN - && IsBattlerAlive(battler) && !IsBattlerAtMaxHp(battler) && !gBattleMons[battler].volatiles.healBlock && !IsSemiInvulnerable(battler, CHECK_ALL) @@ -515,23 +519,12 @@ static bool32 HandleEndTurnFirstEventBlock(u32 battler) break; } case FIRST_EVENT_BLOCK_HEAL_ITEMS: - { - enum ItemHoldEffect holdEffect = GetBattlerHoldEffect(battler, TRUE); - switch (holdEffect) - { - case HOLD_EFFECT_LEFTOVERS: - case HOLD_EFFECT_BLACK_SLUDGE: - if (ItemBattleEffects(ITEMEFFECT_NORMAL, battler)) - effect = TRUE; - break; - default: - break; - } + if (ItemBattleEffects(ITEMEFFECT_LEFTOVERS, battler)) + effect = TRUE; gBattleStruct->eventBlockCounter = 0; gBattleStruct->turnEffectsBattlerId++; break; } - } return effect; } diff --git a/src/battle_main.c b/src/battle_main.c index de7b9417f5..27f414e6ec 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3864,23 +3864,18 @@ static void TryDoEventsBeforeFirstTurn(void) case FIRST_TURN_EVENTS_SWITCH_IN_ABILITIES: while (gBattleStruct->switchInBattlerCounter < gBattlersCount) // From fastest to slowest { - i = gBattlerByTurnOrder[gBattleStruct->switchInBattlerCounter++]; + u32 battler = gBattlerByTurnOrder[gBattleStruct->switchInBattlerCounter++]; - if (TryPrimalReversion(i)) + if (TryPrimalReversion(battler)) return; - if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, i, 0, 0, 0) != 0) + if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, battler, 0, 0, 0)) return; - if (TryClearIllusion(i, ABILITYEFFECT_ON_SWITCHIN)) + if (TryClearIllusion(battler, ABILITYEFFECT_ON_SWITCHIN)) return; } gBattleStruct->switchInBattlerCounter = 0; gBattleStruct->eventsBeforeFirstTurnState++; break; - case FIRST_TURN_EVENTS_OPPORTUNIST_1: - if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0)) - return; - gBattleStruct->eventsBeforeFirstTurnState++; - break; case FIRST_TURN_EVENTS_ITEM_EFFECTS: while (gBattleStruct->switchInBattlerCounter < gBattlersCount) // From fastest to slowest { @@ -3890,9 +3885,23 @@ static void TryDoEventsBeforeFirstTurn(void) gBattleStruct->switchInBattlerCounter = 0; gBattleStruct->eventsBeforeFirstTurnState++; break; - case FIRST_TURN_EVENTS_OPPORTUNIST_2: - if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0)) - return; + case FIRST_TURN_EVENTS_OPPORTUNIST: + while (gBattleStruct->switchInBattlerCounter < gBattlersCount) // From fastest to slowest + { + u32 battler = gBattlerByTurnOrder[gBattleStruct->switchInBattlerCounter++]; + if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST_FIRST_TURN, battler, GetBattlerAbility(battler), 0, 0)) + return; + } + gBattleStruct->switchInBattlerCounter = 0; + gBattleStruct->eventsBeforeFirstTurnState++; + break; + case FIRST_TURN_EVENTS_MIRROR_HERB: + while (gBattleStruct->switchInBattlerCounter < gBattlersCount) // From fastest to slowest + { + if (ItemBattleEffects(ITEMEFFECT_MIRROR_HERB_FIRST_TURN, gBattlerByTurnOrder[gBattleStruct->switchInBattlerCounter++])) + return; + } + gBattleStruct->switchInBattlerCounter = 0; gBattleStruct->eventsBeforeFirstTurnState++; break; case FIRST_TURN_EVENTS_EJECT_PACK: @@ -3925,8 +3934,8 @@ static void TryDoEventsBeforeFirstTurn(void) gBattleStruct->appearedInBattle |= 1u << gBattlerPartyIndexes[i]; } - *(&gBattleStruct->eventBlockCounter) = 0; - *(&gBattleStruct->turnEffectsBattlerId) = 0; + gBattleStruct->eventBlockCounter = 0; + gBattleStruct->turnEffectsBattlerId = 0; gBattleScripting.moveendState = 0; gBattleStruct->faintedActionsState = 0; gBattleStruct->endTurnEventsCounter = 0; @@ -5354,7 +5363,6 @@ static void CheckChangingTurnOrderEffects(void) gBattleMainFunc = RunTurnActionsFunctions; gBattleCommunication[3] = 0; gBattleCommunication[4] = 0; - gBattleScripting.multihitMoveEffect = 0; gBattleResources->battleScriptsStack->size = 0; } diff --git a/src/battle_message.c b/src/battle_message.c index d79e7bf573..d3ba9427ef 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -352,7 +352,7 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] = [STRINGID_PKMNPROTECTEDBY] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} was protected by {B_DEF_ABILITY}!"), //not in gen 5+, ability popup [STRINGID_PKMNPREVENTSUSAGE] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}'s {B_DEF_ABILITY} prevents {B_ATK_NAME_WITH_PREFIX2} from using {B_CURRENT_MOVE}!"), //I don't see this in SV text [STRINGID_PKMNRESTOREDHPUSING] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} restored HP using its {B_DEF_ABILITY}!"), //not in gen 5+, ability popup - [STRINGID_PKMNCHANGEDTYPEWITH] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}'s {B_DEF_ABILITY} made it the {B_BUFF1} type!"), //not in gen 5+, ability popup + [STRINGID_PKMNCHANGEDTYPEWITH] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX}'s {B_EFF_ABILITY} made it the {B_BUFF1} type!"), //not in gen 5+, ability popup [STRINGID_PKMNPREVENTSROMANCEWITH] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}'s {B_DEF_ABILITY} prevents romance!"), //not in gen 5+, ability popup [STRINGID_PKMNPREVENTSCONFUSIONWITH] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}'s {B_DEF_ABILITY} prevents confusion!"), //not in gen 5+, ability popup [STRINGID_PKMNRAISEDFIREPOWERWITH] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}'s {B_DEF_ABILITY} raised the power of Fire-type moves!"), //not in gen 5+, ability popup diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 66f65b9efd..36638b8aac 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -962,6 +962,26 @@ static const struct PickupItem sPickupTable[] = #undef _ +static void ValidateSavedBattlerCounts(void) +{ + if (gBattleStruct->savedAttackerCount > 0) + { + // #if TESTING + // Test_ExitWithResult(TEST_RESULT_ERROR, "savedAttackerCount is greater than 0! More calls to SaveBattlerAttacker than RestoreBattlerAttacker!"); + // #else + DebugPrintfLevel(MGBA_LOG_WARN, "savedAttackerCount is greater than 0! More calls to SaveBattlerAttacker than RestoreBattlerAttacker!"); + // #endif + } + if (gBattleStruct->savedTargetCount > 0) + { + // #if TESTING + // Test_ExitWithResult(TEST_RESULT_ERROR, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!"); + // #else + DebugPrintfLevel(MGBA_LOG_WARN, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!"); + // #endif + } +} + static bool32 NoTargetPresent(u8 battler, u32 move) { if (!IsBattlerAlive(gBattlerTarget)) @@ -4005,6 +4025,10 @@ void SetMoveEffect(u32 battler, u32 effectBattler, bool32 primary, bool32 certai static bool32 CanApplyAdditionalEffect(const struct AdditionalEffect *additionalEffect) { + // If Toxic Chain will activate it blocks all other non volatile effects + if (gBattleStruct->toxicChainPriority && additionalEffect->moveEffect <= MOVE_EFFECT_FROSTBITE) + return FALSE; + if (additionalEffect->self && NumAffectedSpreadMoveTargets() > 1 && GetNextTarget(GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove), TRUE) != MAX_BATTLERS_COUNT) @@ -4021,6 +4045,20 @@ static bool32 CanApplyAdditionalEffect(const struct AdditionalEffect *additional return TRUE; } +static void SetToxicChainPriority(void) +{ + if (gBattleStruct->toxicChainPriority) + return; + + u32 abilityAtk = GetBattlerAbility(gBattlerAttacker); + if (abilityAtk == ABILITY_TOXIC_CHAIN + && IsBattlerAlive(gBattlerTarget) + && CanBePoisoned(gBattlerAttacker, gBattlerTarget, abilityAtk, GetBattlerAbility(gBattlerTarget)) + && IsBattlerTurnDamaged(gBattlerTarget) + && RandomWeighted(RNG_TOXIC_CHAIN, 7, 3)) + gBattleStruct->toxicChainPriority = TRUE; +} + static void Cmd_setadditionaleffects(void) { CMD_ARGS(); @@ -4028,6 +4066,7 @@ static void Cmd_setadditionaleffects(void) if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) { u32 numAdditionalEffects = GetMoveAdditionalEffectCount(gCurrentMove); + SetToxicChainPriority(); if (numAdditionalEffects > gBattleStruct->additionalEffectsCounter) { u32 percentChance; @@ -4076,8 +4115,6 @@ static void Cmd_setadditionaleffects(void) gBattleScripting.moveEffect = 0; gBattlescriptCurrInstr = cmd->nextInstr; } - - gBattleScripting.multihitMoveEffect = 0; } static void Cmd_seteffectprimary(void) @@ -4111,7 +4148,6 @@ static void Cmd_clearvolatile(void) gProtectStructs[battler].chargingTurn = FALSE; gBattlescriptCurrInstr = cmd->nextInstr; - gBattleScripting.multihitMoveEffect = 0; } static void Cmd_tryfaintmon(void) @@ -5849,8 +5885,9 @@ static void Cmd_moveend(void) { switch (gBattleScripting.moveendState) { - case MOVEEND_SUM_DAMAGE: // Sum and store damage dealt for multi strike recoil + case MOVEEND_SET_VALUES: gBattleScripting.savedDmg += gBattleStruct->moveDamage[gBattlerTarget]; + gBattleStruct->moveEndBattlerId = 0; gBattleScripting.moveendState++; break; case MOVEEND_PROTECT_LIKE_EFFECT: @@ -6417,6 +6454,18 @@ static void Cmd_moveend(void) else gBattleScripting.moveendState++; break; + case MOVEEND_COLOR_CHANGE: + while (gBattleStruct->moveEndBattlerId < gBattlersCount) + { + u32 battler = gBattleStruct->moveEndBattlerId++; + if (battler == gBattlerAttacker) + continue; + if (AbilityBattleEffects(ABILITYEFFECT_COLOR_CHANGE, battler, GetBattlerAbility(battler), 0, 0)) + return; + } + gBattleStruct->moveEndBattlerId = 0; + gBattleScripting.moveendState++; + break; case MOVEEND_RED_CARD: { u32 redCardBattlers = 0, i; @@ -6648,10 +6697,24 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_OPPORTUNIST: - if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, 0, 0, 0, 0)) - effect = TRUE; // it loops through all battlers, so we increment after its done with all battlers - else - gBattleScripting.moveendState++; + while (gBattleStruct->moveEndBattlerId < gBattlersCount) + { + u32 battler = gBattleStruct->moveEndBattlerId++; + if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, GetBattlerAbility(battler), 0, 0)) + return; + } + gBattleStruct->moveEndBattlerId = 0; + gBattleScripting.moveendState++; + break; + case MOVEEND_MIRROR_HERB: + while (gBattleStruct->moveEndBattlerId < gBattlersCount) + { + u32 battler = gBattleStruct->moveEndBattlerId++; + if (ItemBattleEffects(ITEMEFFECT_MIRROR_HERB, battler)) + return; + } + gBattleStruct->moveEndBattlerId = 0; + gBattleScripting.moveendState++; break; case MOVEEND_PICKPOCKET: if (IsBattlerAlive(gBattlerAttacker) @@ -6782,25 +6845,7 @@ static void Cmd_moveend(void) && gBattleMons[gBattlerAttacker].volatiles.lockConfusionTurns != 1) // And won't end this turn CancelMultiTurnMoves(gBattlerAttacker, SKY_DROP_IGNORE); // Cancel it - if (gBattleStruct->savedAttackerCount > 0) - { - // #if TESTING - // Test_ExitWithResult(TEST_RESULT_ERROR, "savedAttackerCount is greater than 0! More calls to SaveBattlerAttacker than RestoreBattlerAttacker!"); - // #else - DebugPrintfLevel(MGBA_LOG_WARN, "savedAttackerCount is greater than 0! More calls to SaveBattlerAttacker than RestoreBattlerAttacker!"); - // #endif - } - if (gBattleStruct->savedTargetCount > 0) - { - // #if TESTING - // Test_ExitWithResult(TEST_RESULT_ERROR, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!"); - // #else - DebugPrintfLevel(MGBA_LOG_WARN, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!"); - // #endif - } - - if (gBattleMons[gBattlerAttacker].volatiles.destinyBond > 0) - gBattleMons[gBattlerAttacker].volatiles.destinyBond--; + ValidateSavedBattlerCounts(); gProtectStructs[gBattlerAttacker].shellTrap = FALSE; gBattleStruct->battlerState[gBattlerAttacker].ateBoost = FALSE; gSpecialStatuses[gBattlerAttacker].gemBoost = FALSE; @@ -6814,12 +6859,15 @@ static void Cmd_moveend(void) gBattleStruct->fickleBeamBoosted = FALSE; gBattleStruct->battlerState[gBattlerAttacker].usedMicleBerry = FALSE; gBattleStruct->noTargetPresent = FALSE; + gBattleStruct->toxicChainPriority = FALSE; if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) gBattleStruct->pledgeMove = FALSE; if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE) SetActiveGimmick(gBattlerAttacker, GIMMICK_NONE); if (B_CHARGE >= GEN_9 && moveType == TYPE_ELECTRIC && (IsBattlerTurnDamaged(gBattlerTarget) || gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) gBattleMons[gBattlerAttacker].volatiles.charge = FALSE; + if (gBattleMons[gBattlerAttacker].volatiles.destinyBond > 0) + gBattleMons[gBattlerAttacker].volatiles.destinyBond--; // check if Stellar type boost should be used up if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA && GetBattlerTeraType(gBattlerAttacker) == TYPE_STELLAR @@ -7732,25 +7780,24 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) if (DoSwitchInAbilities(battler) || ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, battler)) return TRUE; - else if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0)) - return TRUE; for (i = 0; i < gBattlersCount; i++) { if (i == battler) continue; - switch (GetBattlerAbility(i)) + u32 ability = GetBattlerAbility(i); + switch (ability) { case ABILITY_TRACE: case ABILITY_COMMANDER: - if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, i, 0, 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_ON_SWITCHIN, i, ability, 0, 0)) return TRUE; break; case ABILITY_FORECAST: case ABILITY_FLOWER_GIFT: case ABILITY_PROTOSYNTHESIS: - if (AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, i, 0, 0, 0)) + if (AbilityBattleEffects(ABILITYEFFECT_ON_WEATHER, i, ability, 0, 0)) return TRUE; break; } @@ -7758,6 +7805,17 @@ static bool32 DoSwitchInEffectsForBattler(u32 battler) return TRUE; } + for (i = 0; i < gBattlersCount; i++) + { + if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, i, GetBattlerAbility(i), 0, 0)) + return TRUE; + } + for (i = 0; i < gBattlersCount; i++) + { + if (ItemBattleEffects(ITEMEFFECT_MIRROR_HERB, battler)) + return TRUE; + } + for (i = 0; i < gBattlersCount; i++) { if (gBattlerByTurnOrder[i] == battler) @@ -11988,8 +12046,9 @@ static void Cmd_jumpifnopursuitswitchdmg(void) static void Cmd_tryrestorehpberry(void) { - CMD_ARGS(); - if (TryRestoreHPBerries(gBattlerAttacker, ITEMEFFECT_TRY_HEALING)) + CMD_ARGS(u8 battler); + u32 battler = GetBattlerForBattleScript(cmd->battler); + if (TryRestoreHPBerries(battler, ITEMEFFECT_TRY_HEALING)) return; gBattlescriptCurrInstr = cmd->nextInstr; } diff --git a/src/battle_util.c b/src/battle_util.c index 0ed081cf90..ef3799e28b 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -910,7 +910,6 @@ void HandleAction_ActionFinished(void) gBattleScripting.moveendState = 0; gBattleCommunication[3] = 0; gBattleCommunication[4] = 0; - gBattleScripting.multihitMoveEffect = 0; gBattleResources->battleScriptsStack->size = 0; gBattleStruct->synchronizeMoveEffect = MOVE_EFFECT_NONE; @@ -4727,6 +4726,48 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } } break; + case ABILITYEFFECT_COLOR_CHANGE: + switch (gLastUsedAbility) + { + case ABILITY_COLOR_CHANGE: + if (IsBattlerTurnDamaged(battler) + && IsBattlerAlive(battler) + && !IS_BATTLER_OF_TYPE(battler, moveType) + && move != MOVE_STRUGGLE + && moveType != TYPE_STELLAR + && moveType != TYPE_MYSTERY) + { + gEffectBattler = gBattlerAbility = battler; + SET_BATTLER_TYPE(battler, moveType); + PREPARE_TYPE_BUFFER(gBattleTextBuff1, moveType); + BattleScriptCall(BattleScript_ColorChangeActivates); + effect++; + } + break; + case ABILITY_BERSERK: + if (IsBattlerTurnDamaged(battler) + && IsBattlerAlive(battler) + && HadMoreThanHalfHpNowDoesnt(battler) + && CompareStat(battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) + { + gEffectBattler = gBattlerAbility = battler; + SET_STATCHANGER(STAT_SPATK, 1, FALSE); + BattleScriptCall(BattleScript_BerserkActivates); + effect++; + } + break; + case ABILITY_ANGER_SHELL: + if (IsBattlerTurnDamaged(battler) + && IsBattlerAlive(battler) + && HadMoreThanHalfHpNowDoesnt(battler)) + { + gEffectBattler = gBattlerAbility = battler; + BattleScriptCall(BattleScript_AngerShellActivates); + effect++; + } + break; + } + break; case ABILITYEFFECT_MOVE_END: // Think contact abilities. switch (gLastUsedAbility) { @@ -4746,7 +4787,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && moveType == TYPE_DARK && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)) { - gEffectBattler = battler; + gEffectBattler = gBattlerAbility = battler; SET_STATCHANGER(STAT_ATK, 1, FALSE); BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); effect++; @@ -4758,7 +4799,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && (moveType == TYPE_DARK || moveType == TYPE_BUG || moveType == TYPE_GHOST) && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN)) { - gEffectBattler = battler; + gEffectBattler = gBattlerAbility = battler; SET_STATCHANGER(STAT_SPEED, 1, FALSE); BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); effect++; @@ -4770,7 +4811,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && moveType == TYPE_WATER && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN)) { - gEffectBattler = battler; + gEffectBattler = gBattlerAbility = battler; SET_STATCHANGER(STAT_DEF, 2, FALSE); BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); effect++; @@ -4782,26 +4823,12 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && IsBattlerAlive(battler) && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN)) { - gEffectBattler = battler; + gEffectBattler = gBattlerAbility = battler; SET_STATCHANGER(STAT_DEF, 1, FALSE); BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); effect++; } break; - case ABILITY_BERSERK: - if (IsBattlerTurnDamaged(battler) - && IsBattlerAlive(battler) - && HadMoreThanHalfHpNowDoesnt(battler) - && (gMultiHitCounter == 0 || gMultiHitCounter == 1) - && !(TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) - && CompareStat(battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)) - { - gEffectBattler = battler; - SET_STATCHANGER(STAT_SPATK, 1, FALSE); - BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); - effect++; - } - break; case ABILITY_WEAK_ARMOR: if (IsBattlerTurnDamaged(battler) && IsBattlerAlive(battler) @@ -4894,21 +4921,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect++; } break; - case ABILITY_COLOR_CHANGE: - if (move != MOVE_STRUGGLE - && !IsBattleMoveStatus(move) - && IsBattlerTurnDamaged(battler) - && !IS_BATTLER_OF_TYPE(battler, moveType) - && moveType != TYPE_STELLAR - && moveType != TYPE_MYSTERY - && IsBattlerAlive(battler)) - { - SET_BATTLER_TYPE(battler, moveType); - PREPARE_TYPE_BUFFER(gBattleTextBuff1, moveType); - BattleScriptCall(BattleScript_ColorChangeActivates); - effect++; - } - break; case ABILITY_GOOEY: case ABILITY_TANGLING_HAIR: if (IsBattlerAlive(gBattlerAttacker) @@ -5123,7 +5135,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN) && (moveType == TYPE_FIRE || moveType == TYPE_WATER)) { - gEffectBattler = battler; + gEffectBattler = gBattlerAbility = battler; SET_STATCHANGER(STAT_SPEED, 6, FALSE); BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); effect++; @@ -5209,24 +5221,12 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN) && moveType == TYPE_FIRE) { - gEffectBattler = gBattlerTarget; + gEffectBattler = gBattlerAbility = gBattlerTarget; SET_STATCHANGER(STAT_ATK, 1, FALSE); BattleScriptCall(BattleScript_TargetAbilityStatRaiseRet); effect++; } break; - case ABILITY_ANGER_SHELL: - if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg - && IsBattlerTurnDamaged(gBattlerTarget) - && (gMultiHitCounter == 0 || gMultiHitCounter == 1) // Activates after all hits from a multi-hit move. - && IsBattlerAlive(gBattlerTarget) - && HadMoreThanHalfHpNowDoesnt(gBattlerTarget) - && !(TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove))) - { - BattleScriptCall(BattleScript_AngerShellActivates); - effect++; - } - break; case ABILITY_WIND_POWER: if (!IsWindMove(gCurrentMove)) break; @@ -5274,13 +5274,9 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITY_TOXIC_CHAIN: - if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) - && IsBattlerAlive(gBattlerTarget) - && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && CanBePoisoned(gBattlerAttacker, gBattlerTarget, gLastUsedAbility, GetBattlerAbility(gBattlerTarget)) - && IsBattlerTurnDamaged(gBattlerTarget) // Need to actually hit the target - && RandomWeighted(RNG_TOXIC_CHAIN, 7, 3)) + if (gBattleStruct->toxicChainPriority) { + gBattleStruct->toxicChainPriority = FALSE; gEffectBattler = gBattlerTarget; gBattleScripting.battler = gBattlerAttacker; gBattleScripting.moveEffect = MOVE_EFFECT_TOXIC; @@ -5356,25 +5352,22 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITYEFFECT_OPPORTUNIST: - /* Similar to ABILITYEFFECT_IMMUNITY in that it loops through all battlers. - * Is called after ABILITYEFFECT_ON_SWITCHIN to copy any boosts - * from switch in abilities e.g. intrepid sword, as - */ - for (battler = 0; battler < gBattlersCount; battler++) + case ABILITYEFFECT_OPPORTUNIST_FIRST_TURN: + switch (ability) { - switch (GetBattlerAbility(battler)) + case ABILITY_OPPORTUNIST: + if (gProtectStructs[battler].activateOpportunist == 2) { - case ABILITY_OPPORTUNIST: - if (gProtectStructs[battler].activateOpportunist == 2) - { - gBattleScripting.battler = battler; - gProtectStructs[battler].activateOpportunist--; - ChooseStatBoostAnimation(battler); - BattleScriptPushCursorAndCallback(BattleScript_OpportunistCopyStatChange); - effect = 1; - } - break; + gBattleScripting.battler = battler; + gProtectStructs[battler].activateOpportunist--; + ChooseStatBoostAnimation(battler); + if (caseID == ABILITYEFFECT_OPPORTUNIST_FIRST_TURN) + BattleScriptPushCursorAndCallback(BattleScript_OpportunistCopyStatChangeEnd3); + else + BattleScriptCall(BattleScript_OpportunistCopyStatChange); + effect = 1; } + break; } break; case ABILITYEFFECT_IMMUNITY: @@ -6527,7 +6520,7 @@ static u32 TryConsumeMirrorHerb(u32 battler, enum ItemCaseId caseID) gBattleScripting.battler = battler; gProtectStructs[battler].eatMirrorHerb = 0; ChooseStatBoostAnimation(battler); - if (caseID == ITEMEFFECT_ON_SWITCH_IN_FIRST_TURN || caseID == ITEMEFFECT_NORMAL) + if (caseID == ITEMEFFECT_MIRROR_HERB_FIRST_TURN) BattleScriptExecute(BattleScript_MirrorHerbCopyStatChangeEnd2); else BattleScriptCall(BattleScript_MirrorHerbCopyStatChange); @@ -6767,9 +6760,6 @@ static u8 ItemEffectMoveEnd(u32 battler, enum ItemHoldEffect holdEffect) case HOLD_EFFECT_BERSERK_GENE: effect = ConsumeBerserkGene(battler, ITEMEFFECT_NONE); break; - case HOLD_EFFECT_MIRROR_HERB: - effect = TryConsumeMirrorHerb(battler, ITEMEFFECT_NONE); - break; default: break; } @@ -6832,6 +6822,43 @@ static inline bool32 TryCureStatus(u32 battler, enum ItemCaseId caseId) return effect; } +// This is hacky and could potentially be improved because ideally this function wouldn't exist but there is some inconsitency with timings. +static bool32 ShouldWaitWithActivation(u32 battler, enum ItemHoldEffect holdEffect) +{ + if (holdEffect != HOLD_EFFECT_RESTORE_HP + && holdEffect != HOLD_EFFECT_RESTORE_PCT_HP + && holdEffect != HOLD_EFFECT_RESTORE_PP + && holdEffect != HOLD_EFFECT_CONFUSE_SPICY + && holdEffect != HOLD_EFFECT_CONFUSE_DRY + && holdEffect != HOLD_EFFECT_CONFUSE_SWEET + && holdEffect != HOLD_EFFECT_CONFUSE_BITTER + && holdEffect != HOLD_EFFECT_CONFUSE_SOUR) + return FALSE; + + // Restore HP berries can be knocked off + if (gBattleStruct->battlerState[battler].itemCanBeKnockedOff) + return TRUE; + + if (IsBattlerTurnDamaged(battler) + && gMultiHitCounter == 0 // Non multi hit moves don't wait for Berserk / Anger Shell activation + && IsBattlerAlive(battler) + && !TestIfSheerForceAffected(battler, gCurrentMove) + && HadMoreThanHalfHpNowDoesnt(battler)) + { + switch (GetBattlerAbility(battler)) + { + case ABILITY_BERSERK: + return CompareStat(battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN); + case ABILITY_ANGER_SHELL: + return TRUE; + default: + return FALSE; + } + } + + return FALSE; +} + u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler) { u32 moveType = 0; @@ -7037,9 +7064,6 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler) case HOLD_EFFECT_BERSERK_GENE: effect = ConsumeBerserkGene(battler, caseID); break; - case HOLD_EFFECT_MIRROR_HERB: - effect = TryConsumeMirrorHerb(battler, caseID); - break; case HOLD_EFFECT_BOOSTER_ENERGY: effect = TryBoosterEnergy(battler, GetBattlerAbility(battler), caseID); break; @@ -7058,9 +7082,48 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler) } } break; + case ITEMEFFECT_LEFTOVERS: + switch (battlerHoldEffect) + { + case HOLD_EFFECT_BLACK_SLUDGE: + if (IS_BATTLER_OF_TYPE(battler, TYPE_POISON)) + { + goto LEFTOVERS; + } + else if (!IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) + { + gBattleScripting.battler = battler; + gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / 8; + if (gBattleStruct->moveDamage[battler] == 0) + gBattleStruct->moveDamage[battler] = 1; + BattleScriptExecute(BattleScript_ItemHurtEnd2); + effect = ITEM_HP_CHANGE; + RecordItemEffectBattle(battler, battlerHoldEffect); + PREPARE_ITEM_BUFFER(gBattleTextBuff1, gLastUsedItem); + } + break; + case HOLD_EFFECT_LEFTOVERS: + LEFTOVERS: + if (gBattleMons[battler].hp < gBattleMons[battler].maxHP + && (B_HEAL_BLOCKING < GEN_5 || !gBattleMons[battler].volatiles.healBlock)) + { + gBattleScripting.battler = battler; + gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / 16; + if (gBattleStruct->moveDamage[battler] == 0) + gBattleStruct->moveDamage[battler] = 1; + gBattleStruct->moveDamage[battler] *= -1; + BattleScriptExecute(BattleScript_ItemHealHP_End2); + effect = ITEM_HP_CHANGE; + RecordItemEffectBattle(battler, battlerHoldEffect); + } + break; + default: + break; + } + break; case ITEMEFFECT_NORMAL: case ITEMEFFECT_TRY_HEALING: - if (gBattleMons[battler].hp) + if (IsBattlerAlive(battler)) { switch (battlerHoldEffect) { @@ -7073,35 +7136,10 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler) case HOLD_EFFECT_RESTORE_PP: effect = ItemRestorePp(battler, gLastUsedItem, caseID); break; - case HOLD_EFFECT_BLACK_SLUDGE: - if (IS_BATTLER_OF_TYPE(battler, TYPE_POISON)) - { - goto LEFTOVERS; - } - else if (!IsAbilityAndRecord(battler, GetBattlerAbility(battler), ABILITY_MAGIC_GUARD)) - { - gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / 8; - if (gBattleStruct->moveDamage[battler] == 0) - gBattleStruct->moveDamage[battler] = 1; - BattleScriptExecute(BattleScript_ItemHurtEnd2); - effect = ITEM_HP_CHANGE; - RecordItemEffectBattle(battler, battlerHoldEffect); - PREPARE_ITEM_BUFFER(gBattleTextBuff1, gLastUsedItem); - } - break; - case HOLD_EFFECT_LEFTOVERS: - LEFTOVERS: - if (gBattleMons[battler].hp < gBattleMons[battler].maxHP - && (B_HEAL_BLOCKING < GEN_5 || !gBattleMons[battler].volatiles.healBlock)) - { - gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / 16; - if (gBattleStruct->moveDamage[battler] == 0) - gBattleStruct->moveDamage[battler] = 1; - gBattleStruct->moveDamage[battler] *= -1; - BattleScriptExecute(BattleScript_ItemHealHP_End2); - effect = ITEM_HP_CHANGE; - RecordItemEffectBattle(battler, battlerHoldEffect); - } + case HOLD_EFFECT_WHITE_HERB: + effect = RestoreWhiteHerbStats(battler); + if (effect != 0) + BattleScriptExecute(BattleScript_WhiteHerbEnd2); break; case HOLD_EFFECT_CONFUSE_SPICY: effect = HealConfuseBerry(battler, gLastUsedItem, FLAVOR_SPICY, caseID); @@ -7220,9 +7258,6 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler) case HOLD_EFFECT_BERSERK_GENE: effect = ConsumeBerserkGene(battler, caseID); break; - case HOLD_EFFECT_MIRROR_HERB: - effect = TryConsumeMirrorHerb(battler, caseID); - break; default: break; } @@ -7255,14 +7290,12 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler) case ITEMEFFECT_MOVE_END: for (battler = 0; battler < gBattlersCount; battler++) { - // If item can be knocked off berry activation will be blocked, but not other items - if (gBattleStruct->battlerState[battler].itemCanBeKnockedOff - && (GetItemPocket(gBattleMons[battler].item) == POCKET_BERRIES - || GetBattlerHoldEffect(battler, TRUE) == HOLD_EFFECT_RESTORE_HP)) + enum ItemHoldEffect holdEffect = GetBattlerHoldEffect(battler, TRUE); + if (ShouldWaitWithActivation(battler, holdEffect)) continue; gLastUsedItem = gBattleMons[battler].item; - effect = ItemEffectMoveEnd(battler, GetBattlerHoldEffect(battler, TRUE)); + effect = ItemEffectMoveEnd(battler, holdEffect); if (effect) { gPotentialItemEffectBattler = gBattleScripting.battler = battler; @@ -7594,6 +7627,17 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler) break; } break; + case ITEMEFFECT_MIRROR_HERB: + case ITEMEFFECT_MIRROR_HERB_FIRST_TURN: + switch (battlerHoldEffect) + { + case HOLD_EFFECT_MIRROR_HERB: + effect = TryConsumeMirrorHerb(battler, caseID); + break; + default: + break; + } + break; } // Berry was successfully used on a Pokemon. diff --git a/test/battle/ability/anger_shell.c b/test/battle/ability/anger_shell.c index 875088c945..6c9064ee0d 100644 --- a/test/battle/ability/anger_shell.c +++ b/test/battle/ability/anger_shell.c @@ -93,3 +93,23 @@ SINGLE_BATTLE_TEST("Anger Shell activates after all hits from a multi-hit move") EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE + 1); } } + +SINGLE_BATTLE_TEST("Anger Shell does not activate if move is boosted by Sheer Force") +{ + u16 maxHp = 500; + GIVEN { + PLAYER(SPECIES_KLAWF) { Ability(ABILITY_ANGER_SHELL); MaxHP(maxHp); HP(maxHp / 2 + 1); } + OPPONENT(SPECIES_NIDOKING) { Ability(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + NOT ABILITY_POPUP(player, ABILITY_ANGER_SHELL); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} diff --git a/test/battle/ability/berserk.c b/test/battle/ability/berserk.c index 80e418e3c5..fe356bc671 100644 --- a/test/battle/ability/berserk.c +++ b/test/battle/ability/berserk.c @@ -73,3 +73,64 @@ SINGLE_BATTLE_TEST("Berserk activates after all hits from a multi-hit move") EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); } } + +SINGLE_BATTLE_TEST("Berserk does not activate if move is boosted by Sheer Force") +{ + u16 maxHp = 500; + GIVEN { + PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_BERSERK); MaxHP(maxHp); HP(maxHp / 2 + 1); } + OPPONENT(SPECIES_NIDOKING) { Ability(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + NOT ABILITY_POPUP(player, ABILITY_BERSERK); + } THEN { + EXPECT_EQ(player->statStages[STAT_DEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + EXPECT_EQ(player->statStages[STAT_SPEED], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Berserk will not activate if the last multi hit move activates a restore berry") +{ + u32 j; + GIVEN { + ASSUME(GetMoveEffect(MOVE_DOUBLE_SLAP) == EFFECT_MULTI_HIT); + PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_BERSERK); Item(ITEM_SITRUS_BERRY); MaxHP(100); HP(90); } + OPPONENT(SPECIES_SHELLDER) { Ability(ABILITY_SKILL_LINK); } // Always hits 5 times. + } WHEN { + TURN { MOVE(opponent, MOVE_DOUBLE_SLAP); } + } SCENE { + for (j = 0; j < 4; j++) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent); + NOT ABILITY_POPUP(player, ABILITY_BERSERK); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_DOUBLE_SLAP, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + NOT ABILITY_POPUP(player, ABILITY_BERSERK); + + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE); + } +} + +SINGLE_BATTLE_TEST("Berserk activates before the hp can be restored on non multi hit moves") +{ + u16 maxHp = 500; + GIVEN { + ASSUME(!IsBattleMoveStatus(MOVE_SCRATCH)); + PLAYER(SPECIES_DRAMPA) { Ability(ABILITY_BERSERK); Item(ITEM_SITRUS_BERRY); MaxHP(maxHp); HP(maxHp / 2 + 1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + ABILITY_POPUP(player, ABILITY_BERSERK); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + } THEN { + EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1); + } +} diff --git a/test/battle/ability/color_change.c b/test/battle/ability/color_change.c index e097035f62..4c07926502 100644 --- a/test/battle/ability/color_change.c +++ b/test/battle/ability/color_change.c @@ -153,3 +153,16 @@ SINGLE_BATTLE_TEST("Color Change changes the type to Normal when a Pokemon is hi MESSAGE("The opposing Kecleon's Color Change made it the Normal type!"); } } + +SINGLE_BATTLE_TEST("Color Change does not activate if move is boosted by Sheer Force") +{ + GIVEN { + PLAYER(SPECIES_KECLEON) { Ability(ABILITY_COLOR_CHANGE); } + OPPONENT(SPECIES_NIDOKING) { Ability(ABILITY_SHEER_FORCE); } + } WHEN { + TURN { MOVE(opponent, MOVE_EMBER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent); + NOT ABILITY_POPUP(player, ABILITY_COLOR_CHANGE); + } +} diff --git a/test/battle/ability/opportunist.c b/test/battle/ability/opportunist.c index 3fac02b5b7..8695f2d00e 100644 --- a/test/battle/ability/opportunist.c +++ b/test/battle/ability/opportunist.c @@ -294,3 +294,24 @@ SINGLE_BATTLE_TEST("Opportunist and Mirror Herb stack stat increases") EXPECT_EQ(opponent->statStages[STAT_SPDEF], DEFAULT_STAT_STAGE); } } + +DOUBLE_BATTLE_TEST("Opportunist and Mirror Herb resolve correctly") +{ + GIVEN { + PLAYER(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); } + PLAYER(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); } + OPPONENT(SPECIES_MEOWSCARADA) { Item(ITEM_MIRROR_HERB); } + OPPONENT(SPECIES_ESPATHRA) { Ability(ABILITY_OPPORTUNIST); Item(ITEM_MIRROR_HERB); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTREPID_SWORD); + ABILITY_POPUP(opponentRight, ABILITY_OPPORTUNIST); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + } THEN { + EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 2); + } +}