Fixes Emergency Exit and Eject Pack (#6459)

This commit is contained in:
Alex 2025-03-29 14:04:01 +01:00 committed by GitHub
parent 7ead90aeff
commit 79a2ec1ce2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 270 additions and 165 deletions

View File

@ -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

View File

@ -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

View File

@ -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[];

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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!");
}
}
}