From 79a2ec1ce2f7a665519e3fa9e320a99f8c7935a0 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Sat, 29 Mar 2025 14:04:01 +0100 Subject: [PATCH] Fixes Emergency Exit and Eject Pack (#6459) --- data/battle_scripts_1.s | 32 +-- include/battle.h | 6 +- include/battle_scripts.h | 2 - include/battle_util.h | 2 + include/constants/battle_script_commands.h | 12 +- src/battle_script_commands.c | 290 +++++++++++++-------- src/battle_util.c | 42 ++- test/battle/ability/emergency_exit.c | 19 ++ test/battle/hold_effect/eject_pack.c | 23 ++ test/battle/spread_moves.c | 7 +- 10 files changed, 270 insertions(+), 165 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 626964d3ab..bc2b37466a 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -7557,35 +7557,37 @@ BattleScript_MoodyEnd: end3 BattleScript_EmergencyExit:: + .if B_ABILITY_POP_UP == TRUE pause 5 - call BattleScript_AbilityPopUp + call BattleScript_AbilityPopUpScripting pause B_WAIT_TIME_LONG -BattleScript_EmergencyExitNoPopUp:: - playanimation BS_TARGET, B_ANIM_SLIDE_OFFSCREEN + .endif + playanimation BS_SCRIPTING, B_ANIM_SLIDE_OFFSCREEN waitanimation - openpartyscreen BS_TARGET, BattleScript_EmergencyExitRet - switchoutabilities BS_TARGET + openpartyscreen BS_SCRIPTING, BattleScript_EmergencyExitRet + switchoutabilities BS_SCRIPTING waitstate - switchhandleorder BS_TARGET, 2 + switchhandleorder BS_SCRIPTING, 2 returntoball BS_TARGET, FALSE - getswitchedmondata BS_TARGET - switchindataupdate BS_TARGET - hpthresholds BS_TARGET + getswitchedmondata BS_SCRIPTING + switchindataupdate BS_SCRIPTING + hpthresholds BS_SCRIPTING printstring STRINGID_SWITCHINMON - switchinanim BS_TARGET, FALSE, TRUE + switchinanim BS_SCRIPTING, FALSE, TRUE waitstate - switchineffects BS_TARGET + switchineffects BS_SCRIPTING BattleScript_EmergencyExitRet: return BattleScript_EmergencyExitWild:: + .if B_ABILITY_POP_UP == TRUE pause 5 - call BattleScript_AbilityPopUp + call BattleScript_AbilityPopUpScripting pause B_WAIT_TIME_LONG -BattleScript_EmergencyExitWildNoPopUp:: - playanimation BS_TARGET, B_ANIM_SLIDE_OFFSCREEN + .endif + playanimation BS_SCRIPTING, B_ANIM_SLIDE_OFFSCREEN waitanimation - setoutcomeonteleport BS_TARGET + setoutcomeonteleport BS_SCRIPTING finishaction return diff --git a/include/battle.h b/include/battle.h index f6b8dbf35a..6f77ea88cf 100644 --- a/include/battle.h +++ b/include/battle.h @@ -130,10 +130,9 @@ struct DisableStruct u8 boosterEnergyActivates:1; u8 roostActive:1; u8 unburdenActive:1; - u8 startEmergencyExit:1; u8 neutralizingGas:1; u8 iceFaceActivationPrevention:1; // fixes hit escape move edge case - u8 padding:2; + u8 padding:3; }; // Fully Cleared each turn after end turn effects are done. A few things are cleared before end turn effects @@ -814,9 +813,8 @@ struct BattleStruct u32 stellarBoostFlags[NUM_BATTLE_SIDES]; // stored as a bitfield of flags for all types for each side u8 monCausingSleepClause[NUM_BATTLE_SIDES]; // Stores which pokemon on a given side is causing Sleep Clause to be active as the mon's index in the party u8 additionalEffectsCounter:4; // A counter for the additionalEffects applied by the current move in Cmd_setadditionaleffects - u8 redCardActivates:1; u8 cheekPouchActivated:1; - u8 padding2:1; // padding in the middle so pursuit fields are together + u8 padding2:3; u8 pursuitStoredSwitch; // Stored id for the Pursuit target's switch s32 battlerExpReward; u16 prevTurnSpecies[MAX_BATTLERS_COUNT]; // Stores species the AI has in play at start of turn diff --git a/include/battle_scripts.h b/include/battle_scripts.h index 4343d7eeea..d11d2b814e 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -384,9 +384,7 @@ extern const u8 BattleScript_FriskMsg[]; extern const u8 BattleScript_FriskMsgWithPopup[]; extern const u8 BattleScript_MoodyActivates[]; extern const u8 BattleScript_EmergencyExit[]; -extern const u8 BattleScript_EmergencyExitNoPopUp[]; extern const u8 BattleScript_EmergencyExitWild[]; -extern const u8 BattleScript_EmergencyExitWildNoPopUp[]; extern const u8 BattleScript_CheekPouchActivates[]; extern const u8 BattleScript_TotemVar[]; extern const u8 BattleScript_TotemFlaredToLife[]; diff --git a/include/battle_util.h b/include/battle_util.h index eef6a009e0..5fef1b4f60 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -349,5 +349,7 @@ void ClearPursuitValuesIfSet(u32 battler); void ClearPursuitValues(void); bool32 HasWeatherEffect(void); bool32 IsMovePowderBlocked(u32 battlerAtk, u32 battlerDef, u32 move); +bool32 EmergencyExitCanBeTriggered(u32 battler); +u32 RestoreWhiteHerbStats(u32 battler); #endif // GUARD_BATTLE_UTIL_H diff --git a/include/constants/battle_script_commands.h b/include/constants/battle_script_commands.h index 1b302a4347..e2588e46ab 100644 --- a/include/constants/battle_script_commands.h +++ b/include/constants/battle_script_commands.h @@ -271,6 +271,7 @@ enum MoveEndEffects MOVEEND_ITEM_EFFECTS_TARGET, MOVEEND_MOVE_EFFECTS2, MOVEEND_ITEM_EFFECTS_ALL, + MOVEEND_SYMBIOSIS, MOVEEND_HIT_SWITCH_TARGET, MOVEEND_KINGSROCK, // These item effects will occur each strike of a multi-hit move MOVEEND_NUM_HITS, @@ -285,15 +286,16 @@ enum MoveEndEffects MOVEEND_RAPID_SPIN, MOVEEND_ITEM_EFFECTS_ATTACKER, MOVEEND_MAGICIAN, // Occurs after final multi-hit strike, and after other items/abilities would activate + MOVEEND_SHEER_FORCE, // If move is Sheer Force affected, skip until Eject Pack MOVEEND_RED_CARD, // Red Card triggers before Eject Pack - MOVEEND_EJECT_ITEMS, - MOVEEND_WHITE_HERB, + MOVEEND_EJECT_BUTTON, MOVEEND_LIFEORB_SHELLBELL, // Includes shell bell, throat spray, etc - MOVEEND_CHANGED_ITEMS, - MOVEEND_PICKPOCKET, MOVEEND_EMERGENCY_EXIT, - MOVEEND_SYMBIOSIS, + MOVEEND_EJECT_PACK, MOVEEND_OPPORTUNIST, // Occurs after other stat change items/abilities to try and copy the boosts + MOVEEND_PICKPOCKET, + MOVEEND_WHITE_HERB, + MOVEEND_CHANGED_ITEMS, MOVEEND_SAME_MOVE_TURNS, MOVEEND_SET_EVOLUTION_TRACKER, MOVEEND_CLEAR_BITS, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 8885c84a66..b333d9abb0 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6170,7 +6170,7 @@ static u32 GetNextTarget(u32 moveTarget, bool32 excludeCurrent) return battler; } -static inline bool32 IsProtectivePadsProtected(u32 battler, u32 move, u32 holdEffect) +static inline bool32 IsProtectivePadsProtected(u32 battler, u32 holdEffect) { if (holdEffect != HOLD_EFFECT_PROTECTIVE_PADS) return FALSE; @@ -6182,7 +6182,7 @@ static inline bool32 IsProtectivePadsProtected(u32 battler, u32 move, u32 holdEf static inline bool32 IsProtectEffectAffected(u32 battler, u32 move) { u32 holdEffect = GetBattlerHoldEffect(gBattlerAttacker, TRUE); - if (IsProtectivePadsProtected(battler, move, holdEffect)) + if (IsProtectivePadsProtected(battler, holdEffect)) return TRUE; if (holdEffect == HOLD_EFFECT_CLEAR_AMULET) @@ -6201,6 +6201,33 @@ static inline bool32 IsProtectEffectAffected(u32 battler, u32 move) return FALSE; } +static inline bool32 CanEjectButtonTrigger(u32 battlerAtk, u32 battlerDef, u32 moveEffect) +{ + if (GetBattlerHoldEffect(battlerDef, TRUE) == HOLD_EFFECT_EJECT_BUTTON + && battlerAtk != battlerDef + && IsBattlerTurnDamaged(battlerDef) + && IsBattlerAlive(battlerDef) + && CountUsablePartyMons(battlerDef) > 0 + && !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(battlerAtk))) + return TRUE; + + return FALSE; +} + +static inline bool32 CanEjectPackTrigger(u32 battlerAtk, u32 battlerDef, u32 moveEffect) +{ + if (gProtectStructs[battlerDef].statFell + && GetBattlerHoldEffect(battlerDef, TRUE) == HOLD_EFFECT_EJECT_PACK + && IsBattlerAlive(battlerDef) + && CountUsablePartyMons(battlerDef) > 0 + && !gProtectStructs[battlerDef].disableEjectPack + && !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(battlerAtk)) + && !(moveEffect == EFFECT_PARTING_SHOT && CanBattlerSwitch(battlerAtk))) + return TRUE; + + return FALSE; +} + static void Cmd_moveend(void) { CMD_ARGS(u8 endMode, u8 endState); @@ -6237,7 +6264,7 @@ static void Cmd_moveend(void) if (gProtectStructs[gBattlerAttacker].touchedProtectLike) { if (gProtectStructs[gBattlerTarget].spikyShielded - && !IsProtectivePadsProtected(gBattlerAttacker, gCurrentMove, GetBattlerHoldEffect(gBattlerAttacker, TRUE)) + && !IsProtectivePadsProtected(gBattlerAttacker, GetBattlerHoldEffect(gBattlerAttacker, TRUE)) && !IsMagicGuardProtected(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker))) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; @@ -6265,7 +6292,7 @@ static void Cmd_moveend(void) effect = 1; } else if (gProtectStructs[gBattlerTarget].banefulBunkered - && !IsProtectivePadsProtected(gBattlerAttacker, gCurrentMove, GetBattlerHoldEffect(gBattlerAttacker, TRUE))) + && !IsProtectivePadsProtected(gBattlerAttacker, GetBattlerHoldEffect(gBattlerAttacker, TRUE))) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; gBattleScripting.moveEffect = MOVE_EFFECT_POISON | MOVE_EFFECT_AFFECTS_USER; @@ -6301,7 +6328,7 @@ static void Cmd_moveend(void) effect = 1; } else if (gProtectStructs[gBattlerTarget].burningBulwarked - && !IsProtectivePadsProtected(gBattlerAttacker, gCurrentMove, GetBattlerHoldEffect(gBattlerAttacker, TRUE))) + && !IsProtectivePadsProtected(gBattlerAttacker, GetBattlerHoldEffect(gBattlerAttacker, TRUE))) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; gBattleScripting.moveEffect = MOVE_EFFECT_BURN | MOVE_EFFECT_AFFECTS_USER; @@ -6994,13 +7021,6 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; } - // The order of abilities/items activating after moves hitting multiple targets is - // 1. Magician - // 2. The fastest mon gets switched out using Eject Button / Eject Pack - // 3. White Herb activates - // 4. Red Card activates - // 5. Life Orb / Shell Bell - // 6. Pickpocket case MOVEEND_MAGICIAN: if (GetBattlerAbility(gBattlerAttacker) == ABILITY_MAGICIAN && gCurrentMove != MOVE_FLING && gCurrentMove != MOVE_NATURAL_GIFT @@ -7012,7 +7032,6 @@ static void Cmd_moveend(void) && !gSpecialStatuses[gBattlerAttacker].gemBoost // In base game, gems are consumed after magician would activate. && !(gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerTarget)] & (1u << gBattlerPartyIndexes[gBattlerTarget])) && !DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove) - && !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) && (GetBattlerAbility(gBattlerTarget) != ABILITY_STICKY_HOLD || !IsBattlerAlive(gBattlerTarget))) { StealTargetItem(gBattlerAttacker, gBattlerTarget); @@ -7025,92 +7044,121 @@ static void Cmd_moveend(void) } gBattleScripting.moveendState++; break; - case MOVEEND_EJECT_ITEMS: + case MOVEEND_SHEER_FORCE: + if (TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) + gBattleScripting.moveendState = MOVEEND_EJECT_PACK; + else + gBattleScripting.moveendState++; + break; + case MOVEEND_EJECT_BUTTON: { // Because sorting the battlers by speed takes lots of cycles, it's better to just check if any of the battlers has the Eject items. - u32 ejectPackBattlers = 0, ejectButtonBattlers = 0, i; + u32 numEjectButtonBattlers = 0; + u32 ejectButtonBattlers = 0; + for (i = 0; i < gBattlersCount; i++) { - u32 holdEffect; - holdEffect = GetBattlerHoldEffect(i, TRUE); - if (holdEffect == HOLD_EFFECT_EJECT_BUTTON) + if (CanEjectButtonTrigger(gBattlerAttacker, i, moveEffect)) + { ejectButtonBattlers |= 1u << i; - else if (holdEffect == HOLD_EFFECT_EJECT_PACK) - ejectPackBattlers |= 1u << i; + numEjectButtonBattlers++; + } } - if (ejectButtonBattlers || ejectPackBattlers) + + if (numEjectButtonBattlers == 0) { - u8 battlers[4] = {0, 1, 2, 3}; + gBattleScripting.moveendState++; + break; + } + + u8 battlers[4] = {0, 1, 2, 3}; + if (numEjectButtonBattlers > 1) SortBattlersBySpeed(battlers, FALSE); - for (i = 0; i < gBattlersCount; i++) + for (i = 0; i < gBattlersCount; i++) + { + u32 battler = battlers[i]; + + if (!(ejectButtonBattlers & 1u << battler)) + continue; + + gBattleScripting.battler = battler; + gLastUsedItem = gBattleMons[battler].item; + if (moveEffect == EFFECT_HIT_ESCAPE) + gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection + + effect = TRUE; + gBattleScripting.moveendState = MOVEEND_OPPORTUNIST; + gBattleStruct->battlerState[battler].usedEjectItem = TRUE; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_EjectButtonActivates; + AI_DATA->ejectButtonSwitch = TRUE; + break; // Only the fastest Eject Button activates + } + } + if (!effect) + gBattleScripting.moveendState++; + break; + case MOVEEND_EJECT_PACK: + { + // Because sorting the battlers by speed takes lots of cycles, it's better to just check if any of the battlers has the Eject items. + u32 ejectPackBattlers = 0; + u32 numEjectPackBattlers = 0; + + for (i = 0; i < gBattlersCount; i++) + { + if (CanEjectPackTrigger(gBattlerAttacker, i, moveEffect)) { - u32 battler = battlers[i]; - - if (battler != gBattlerAttacker && ejectButtonBattlers & (1u << battler)) - { - if (TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) // Apparently Sheer Force blocks Eject Button, but not Eject Pack - continue; - // Since we check if battler was damaged, we don't need to check move result. - // In fact, doing so actually prevents multi-target moves from activating eject button properly - if (!IsBattlerTurnDamaged(battler)) - continue; - } - else if (ejectPackBattlers & (1u << battler)) - { - if (!gProtectStructs[battler].statFell || gProtectStructs[battler].disableEjectPack) - continue; - } - else - { - continue; - } - - if (IsBattlerAlive(battler) - && CountUsablePartyMons(battler) > 0 // Has mon to switch into - // Does not activate if attacker used Parting Shot and can switch out - && !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(gBattlerAttacker)) - ) - { - gBattleScripting.battler = battler; - gLastUsedItem = gBattleMons[battler].item; - if (moveEffect == EFFECT_HIT_ESCAPE) - gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection - if (ejectButtonBattlers & (1u << battler)) - { - effect = TRUE; - gBattleStruct->battlerState[battler].usedEjectItem = TRUE; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_EjectButtonActivates; - AI_DATA->ejectButtonSwitch = TRUE; - } - else // Eject Pack - { - if (!gDisableStructs[gBattlerTarget].startEmergencyExit - && !(GetMoveEffect(gCurrentMove) == EFFECT_PARTING_SHOT && CanBattlerSwitch(gBattlerAttacker))) - { - effect = TRUE; - gBattleStruct->battlerState[battler].usedEjectItem = TRUE; - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_EjectPackActivates; - AI_DATA->ejectPackSwitch = TRUE; - gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage = TRUE; - } - gProtectStructs[battler].statFell = FALSE; - } - break; // Only the fastest Eject item activates - } + ejectPackBattlers |= 1u << i; + numEjectPackBattlers++; } } + + if (numEjectPackBattlers == 0) + { + gBattleScripting.moveendState++; + break; + } + + u8 battlers[4] = {0, 1, 2, 3}; + if (numEjectPackBattlers > 1) + SortBattlersBySpeed(battlers, FALSE); + + for (i = 0; i < gBattlersCount; i++) + { + u32 battler = battlers[i]; + + if (!(ejectPackBattlers & 1u << battler)) + continue; + + gBattleScripting.battler = battler; + gLastUsedItem = gBattleMons[battler].item; + + if (moveEffect == EFFECT_HIT_ESCAPE) + gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection + + effect = TRUE; + gBattleStruct->battlerState[battler].usedEjectItem = TRUE; + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_EjectPackActivates; + AI_DATA->ejectPackSwitch = TRUE; + gProtectStructs[battler].statFell = FALSE; + break; // Only the fastest Eject item activates + } } gBattleScripting.moveendState++; break; case MOVEEND_WHITE_HERB: for (i = 0; i < gBattlersCount; i++) { - if (IsBattlerAlive(i) - && ItemBattleEffects(ITEMEFFECT_STATS_CHANGED, i, FALSE)) + if (!IsBattlerAlive(i)) + continue; + + if (GetBattlerHoldEffect(i, TRUE) == HOLD_EFFECT_RESTORE_STATS + && RestoreWhiteHerbStats(i)) { + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_WhiteHerbRet; effect = TRUE; break; } @@ -7130,8 +7178,7 @@ static void Cmd_moveend(void) } if (redCardBattlers && (moveEffect != EFFECT_HIT_SWITCH_TARGET || gBattleStruct->hitSwitchTargetFailed) - && IsBattlerAlive(gBattlerAttacker) - && !TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) + && IsBattlerAlive(gBattlerAttacker)) { // Since we check if battler was damaged, we don't need to check move result. // In fact, doing so actually prevents multi-target moves from activating red card properly @@ -7148,33 +7195,29 @@ static void Cmd_moveend(void) && IsBattlerTurnDamaged(battler) && CanBattlerSwitch(gBattlerAttacker)) { + effect = TRUE; + gBattleScripting.moveendState = MOVEEND_OPPORTUNIST; gLastUsedItem = gBattleMons[battler].item; SaveBattlerTarget(battler); // save battler with red card SaveBattlerAttacker(gBattlerAttacker); gBattleStruct->savedMove = gCurrentMove; gBattleScripting.battler = battler; gEffectBattler = gBattlerAttacker; - gBattleStruct->redCardActivates = TRUE; if (moveEffect == EFFECT_HIT_ESCAPE) gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection BattleScriptPushCursor(); if (gBattleStruct->commanderActive[gBattlerAttacker] != SPECIES_NONE || GetBattlerAbility(gBattlerAttacker) == ABILITY_GUARD_DOG) - { gBattlescriptCurrInstr = BattleScript_RedCardActivationNoSwitch; - } else - { gBattlescriptCurrInstr = BattleScript_RedCardActivates; - gSpecialStatuses[gBattlerAttacker].preventLifeOrbDamage = TRUE; - } - effect = TRUE; break; // Only fastest red card activates } } } } - gBattleScripting.moveendState++; + if (!effect) + gBattleScripting.moveendState++; break; case MOVEEND_LIFEORB_SHELLBELL: if (ItemBattleEffects(ITEMEFFECT_LIFEORB_SHELLBELL, 0, FALSE)) @@ -7219,37 +7262,57 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_EMERGENCY_EXIT: // Special case, because moves hitting multiple opponents stop after switching out - for (i = 0; i < gBattlersCount; i++) { - if (gBattleStruct->redCardActivates) + // Because sorting the battlers by speed takes lots of cycles, + // we check if EE can be activated and cound how many. + u32 numEmergencyExitBattlers = 0; + u32 emergencyExitBattlers = 0; + + for (i = 0; i < gBattlersCount; i++) { - gDisableStructs[i].startEmergencyExit = FALSE; - continue; + if (EmergencyExitCanBeTriggered(i)) + { + emergencyExitBattlers |= 1u << i; + numEmergencyExitBattlers++; + } } - if (gDisableStructs[i].startEmergencyExit) + + if (numEmergencyExitBattlers == 0) { - gDisableStructs[i].startEmergencyExit = FALSE; - gSpecialStatuses[i].emergencyExited = TRUE; - gBattlerTarget = gBattlerAbility = i; + gBattleScripting.moveendState++; + break; + } + + u8 battlers[4] = {0, 1, 2, 3}; + if (numEmergencyExitBattlers > 1) + SortBattlersBySpeed(battlers, FALSE); + + for (i = 0; i < gBattlersCount; i++) + { + u32 battler = battlers[i]; + + if (!(emergencyExitBattlers & 1u << battler)) + continue; + + if (moveEffect == EFFECT_HIT_ESCAPE) + gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection + + effect = TRUE; + gBattleScripting.moveendState = MOVEEND_OPPORTUNIST; + gSpecialStatuses[battler].emergencyExited = TRUE; + gBattleScripting.battler = battler; BattleScriptPushCursor(); - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER || GetBattlerSide(i) == B_SIDE_PLAYER) - { - if (B_ABILITY_POP_UP == TRUE) - gBattlescriptCurrInstr = BattleScript_EmergencyExit; - else - gBattlescriptCurrInstr = BattleScript_EmergencyExitNoPopUp; - } + + if (gBattleTypeFlags & BATTLE_TYPE_TRAINER || GetBattlerSide(battler) == B_SIDE_PLAYER) + gBattlescriptCurrInstr = BattleScript_EmergencyExit; else - { - if (B_ABILITY_POP_UP == TRUE) - gBattlescriptCurrInstr = BattleScript_EmergencyExitWild; - else - gBattlescriptCurrInstr = BattleScript_EmergencyExitWildNoPopUp; - } - return; + gBattlescriptCurrInstr = BattleScript_EmergencyExitWild; + + break; // Only the fastest Emergency Exit / Wimp Out activates } } - gBattleScripting.moveendState++; + if (!effect) + gBattleScripting.moveendState++; break; case MOVEEND_SYMBIOSIS: for (i = 0; i < gBattlersCount; i++) @@ -7326,7 +7389,6 @@ static void Cmd_moveend(void) gBattleStruct->additionalEffectsCounter = 0; gBattleStruct->poisonPuppeteerConfusion = FALSE; gBattleStruct->fickleBeamBoosted = FALSE; - gBattleStruct->redCardActivates = FALSE; gBattleStruct->battlerState[gBattlerAttacker].usedMicleBerry = FALSE; gBattleStruct->noTargetPresent = FALSE; if (gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) diff --git a/src/battle_util.c b/src/battle_util.c index 982aa926b6..4404e14922 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5683,25 +5683,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 effect++; } break; - case ABILITY_EMERGENCY_EXIT: - case ABILITY_WIMP_OUT: - if (!(gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_NO_EFFECT) - && IsBattlerTurnDamaged(gBattlerTarget) - && IsBattlerAlive(battler) - // Had more than half of hp before, now has less - && HadMoreThanHalfHpNowDoesnt(battler) - && (gMultiHitCounter == 0 || gMultiHitCounter == 1) - && !(TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) - && (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER)) - && !(gBattleTypeFlags & BATTLE_TYPE_ARENA) - && CountUsablePartyMons(battler) > 0 - // Not currently held by Sky Drop - && !(gStatuses3[battler] & STATUS3_SKY_DROPPED)) - { - gDisableStructs[battler].startEmergencyExit = TRUE; - effect++; - } - break; case ABILITY_WEAK_ARMOR: if (!(gBattleStruct->moveResultFlags[battler] & MOVE_RESULT_NO_EFFECT) && IsBattlerTurnDamaged(gBattlerTarget) @@ -7228,8 +7209,6 @@ static u32 ItemHealHp(u32 battler, u32 itemId, enum ItemCaseId caseID, bool32 pe BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_ItemHealHP_RemoveItemRet; } - if (gDisableStructs[battler].startEmergencyExit && GetNonDynamaxHP(battler) >= GetNonDynamaxMaxHP(battler) / 2) - gDisableStructs[battler].startEmergencyExit = FALSE; return ITEM_HP_CHANGE; } @@ -7350,7 +7329,7 @@ static inline u32 TryBoosterEnergy(u32 battler, enum ItemCaseId caseID) return ITEM_NO_EFFECT; } -static u32 RestoreWhiteHerbStats(u32 battler) +u32 RestoreWhiteHerbStats(u32 battler) { u32 i, effect = 0; @@ -12302,3 +12281,22 @@ bool32 IsMovePowderBlocked(u32 battlerAtk, u32 battlerDef, u32 move) return effect; } + +bool32 EmergencyExitCanBeTriggered(u32 battler) +{ + u32 ability = GetBattlerAbility(battler); + + if (ability != ABILITY_EMERGENCY_EXIT && ability != ABILITY_WIMP_OUT) + return FALSE; + + if (IsBattlerTurnDamaged(battler) + && IsBattlerAlive(battler) + && HadMoreThanHalfHpNowDoesnt(battler) + && (CanBattlerSwitch(battler) || !(gBattleTypeFlags & BATTLE_TYPE_TRAINER)) + && !(gBattleTypeFlags & BATTLE_TYPE_ARENA) + && CountUsablePartyMons(battler) > 0 + && !(gStatuses3[battler] & STATUS3_SKY_DROPPED)) + return TRUE; + + return FALSE; +} diff --git a/test/battle/ability/emergency_exit.c b/test/battle/ability/emergency_exit.c index 68724450e1..0dbecb95e0 100644 --- a/test/battle/ability/emergency_exit.c +++ b/test/battle/ability/emergency_exit.c @@ -47,3 +47,22 @@ SINGLE_BATTLE_TEST("Emergency Exit switches out when going below 50% max-HP but ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); } } + +DOUBLE_BATTLE_TEST("Only the fastest Wimp Out (Emergency Exit) user switches out") +{ + GIVEN { + PLAYER(SPECIES_ZAPDOS) { Speed(10); } + PLAYER(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WIMPOD) { Speed(1); Ability(ABILITY_WIMP_OUT); Item(ITEM_FOCUS_SASH); }; + OPPONENT(SPECIES_WIMPOD) { Speed(2); Ability(ABILITY_WIMP_OUT); Item(ITEM_FOCUS_SASH); }; + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_HYPER_VOICE); SEND_OUT(opponentRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + ABILITY_POPUP(opponentRight, ABILITY_WIMP_OUT); + } +} diff --git a/test/battle/hold_effect/eject_pack.c b/test/battle/hold_effect/eject_pack.c index 3d674fe9e8..da053ec5e6 100644 --- a/test/battle/hold_effect/eject_pack.c +++ b/test/battle/hold_effect/eject_pack.c @@ -137,3 +137,26 @@ DOUBLE_BATTLE_TEST("Eject Pack will not trigger if the conditions are not met") } } + +SINGLE_BATTLE_TEST("Eject Pack will miss timing to switch out user if Eject Button was activated on target") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(10); Item(ITEM_EJECT_PACK); } + PLAYER(SPECIES_WYNAUT) { Speed(10); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(5); Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WYNAUT) { Speed(10); } + } WHEN { + TURN { MOVE(player, MOVE_OVERHEAT); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OVERHEAT, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player); + MESSAGE("Wobbuffet is switched out with the Eject Pack!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } THEN { + EXPECT(player->species == SPECIES_WOBBUFFET); + EXPECT(opponent->species == SPECIES_WYNAUT); + } +} diff --git a/test/battle/spread_moves.c b/test/battle/spread_moves.c index 2791ca0ea5..f617bd69c1 100644 --- a/test/battle/spread_moves.c +++ b/test/battle/spread_moves.c @@ -16,15 +16,16 @@ DOUBLE_BATTLE_TEST("Spread Moves: Ability and Item effects activate correctly af MOVE(opponentRight, MOVE_HEAT_WAVE); MOVE(playerLeft, MOVE_HYPER_VOICE); SEND_OUT(opponentRight, 3); - SEND_OUT(opponentLeft, 2); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); MESSAGE("2 sent out Pikachu!"); - ABILITY_POPUP(opponentLeft, ABILITY_EMERGENCY_EXIT); - MESSAGE("2 sent out Wynaut!"); + NONE_OF { + ABILITY_POPUP(opponentLeft, ABILITY_EMERGENCY_EXIT); + MESSAGE("2 sent out Wynaut!"); + } } }