From 499e90ac4bcc2ce91397fedd4449f9b17dbc000c Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:09:46 +0200 Subject: [PATCH] Fixes Endure and Eject Pack issues (#7687) Co-authored-by: Bassoonian --- include/battle.h | 15 ++++---- include/constants/battle.h | 2 +- src/battle_main.c | 9 ++--- src/battle_script_commands.c | 18 ++++----- src/battle_util.c | 4 +- test/battle/hold_effect/eject_pack.c | 21 +++++++++++ test/battle/hold_effect/life_orb.c | 19 ++++++++++ test/battle/move_effect/endure.c | 27 ++++++++++++++ test/battle/move_effect/protect.c | 55 ++++++++++++++++++++++++++++ 9 files changed, 145 insertions(+), 25 deletions(-) diff --git a/include/battle.h b/include/battle.h index 6276a7098a..60c2d85b6a 100644 --- a/include/battle.h +++ b/include/battle.h @@ -130,16 +130,16 @@ struct DisableStruct u8 iceFaceActivationPrevention:1; // fixes hit escape move edge case u8 unnerveActivated:1; // Unnerve and As One (Unnerve part) activate only once per switch in u8 hazardsDone:1; - u8 padding1:1; + u8 endured:1; u8 octolockedBy:3; - u8 padding2:5; + u8 tryEjectPack:1; + u8 padding:4; }; // Fully Cleared each turn after end turn effects are done. A few things are cleared before end turn effects struct ProtectStruct { u32 protected:7; // 126 protect options - u32 endured:1; u32 noValidMoves:1; u32 helpingHand:1; u32 bounceMove:1; @@ -155,10 +155,9 @@ struct ProtectStruct u32 statRaised:1; u32 usedCustapBerry:1; // also quick claw u32 touchedProtectLike:1; - u32 unused:8; + u32 unused:9; // End of 32-bit bitfield u16 disableEjectPack:1; - u16 tryEjectPack:1; u16 pranksterElevated:1; u16 quickDraw:1; u16 beakBlastCharge:1; @@ -169,12 +168,12 @@ struct ProtectStruct u16 usedAllySwitch:1; u16 lashOutAffected:1; u16 assuranceDoubled:1; - u16 padding:3; + u16 padding:4; // End of 16-bit bitfield u16 physicalDmg; u16 specialDmg; - u8 physicalBattlerId; - u8 specialBattlerId; + u8 physicalBattlerId:4; + u8 specialBattlerId:4; }; // Cleared at the start of HandleAction_ActionFinished diff --git a/include/constants/battle.h b/include/constants/battle.h index 858f2c8785..825487f24e 100644 --- a/include/constants/battle.h +++ b/include/constants/battle.h @@ -363,7 +363,7 @@ enum BattleWeather #define B_WEATHER_LOW_LIGHT (B_WEATHER_FOG | B_WEATHER_ICY_ANY | B_WEATHER_RAIN | B_WEATHER_SANDSTORM) #define B_WEATHER_PRIMAL_ANY (B_WEATHER_RAIN_PRIMAL | B_WEATHER_SUN_PRIMAL | B_WEATHER_STRONG_WINDS) -// Explicit numbers until frostbite because those shouldn't be shifted +// Explicit numbers until frostbite because those shouldn't be shifted enum __attribute__((packed)) MoveEffect { MOVE_EFFECT_NONE = 0, diff --git a/src/battle_main.c b/src/battle_main.c index 3c3b363e86..0fd77bd7cf 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3197,6 +3197,9 @@ void SwitchInClearSetData(u32 battler, struct Volatiles *volatilesCopy) memset(&gDisableStructs[battler], 0, sizeof(struct DisableStruct)); + if (GetProtectType(gProtectStructs[battler].protected) == PROTECT_TYPE_SINGLE) // Side type protects expire at the end of the turn + gProtectStructs[battler].protected = PROTECT_NONE; + if (effect == EFFECT_BATON_PASS) { gDisableStructs[battler].substituteHP = disableStructCopy.substituteHP; @@ -3294,9 +3297,7 @@ const u8* FaintClearSetData(u32 battler) for (i = 0; i < NUM_BATTLE_STATS; i++) gBattleMons[battler].statStages[i] = DEFAULT_STAT_STAGE; - bool32 keepGastroAcid = FALSE; - if (gBattleMons[battler].volatiles.gastroAcid) - keepGastroAcid = TRUE; + bool32 keepGastroAcid = gBattleMons[battler].volatiles.gastroAcid; memset(&gBattleMons[battler].volatiles, 0, sizeof(struct Volatiles)); gBattleMons[battler].volatiles.gastroAcid = keepGastroAcid; // Edge case: Keep Gastro Acid if pokemon's ability can have effect after fainting, for example Innards Out. @@ -3323,7 +3324,6 @@ const u8* FaintClearSetData(u32 battler) gProtectStructs[battler].protected = PROTECT_NONE; gProtectStructs[battler].quash = FALSE; - gProtectStructs[battler].endured = FALSE; gProtectStructs[battler].noValidMoves = FALSE; gProtectStructs[battler].helpingHand = FALSE; gProtectStructs[battler].bounceMove = FALSE; @@ -3335,7 +3335,6 @@ const u8* FaintClearSetData(u32 battler) gProtectStructs[battler].fleeType = 0; gProtectStructs[battler].notFirstStrike = FALSE; gProtectStructs[battler].statRaised = FALSE; - gProtectStructs[battler].tryEjectPack = FALSE; gProtectStructs[battler].pranksterElevated = FALSE; gDisableStructs[battler].isFirstTurn = 2; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index d1aae70c1d..69a0526f45 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1920,7 +1920,7 @@ static void Cmd_adjustdamage(void) { enduredHit |= 1u << battlerDef; } - else if (gProtectStructs[battlerDef].endured) + else if (gDisableStructs[battlerDef].endured) { enduredHit |= 1u << battlerDef; gBattleStruct->moveResultFlags[battlerDef] |= MOVE_RESULT_FOE_ENDURED; @@ -5545,7 +5545,7 @@ static inline bool32 CanEjectButtonTrigger(u32 battlerAtk, u32 battlerDef, enum static inline bool32 CanEjectPackTrigger(u32 battlerAtk, u32 battlerDef, enum BattleMoveEffects moveEffect) { - if (gProtectStructs[battlerDef].tryEjectPack + if (gDisableStructs[battlerDef].tryEjectPack && GetBattlerHoldEffect(battlerDef, TRUE) == HOLD_EFFECT_EJECT_PACK && IsBattlerAlive(battlerDef) && CountUsablePartyMons(battlerDef) > 0 @@ -6565,7 +6565,7 @@ static void Cmd_moveend(void) } for (u32 i = 0; i < gBattlersCount; i++) - gProtectStructs[i].tryEjectPack = FALSE; + gDisableStructs[i].tryEjectPack = FALSE; u8 battlers[4] = {0, 1, 2, 3}; if (numEjectButtonBattlers > 1) @@ -6628,7 +6628,7 @@ static void Cmd_moveend(void) } for (u32 i = 0; i < gBattlersCount; i++) - gProtectStructs[i].tryEjectPack = FALSE; + gDisableStructs[i].tryEjectPack = FALSE; u8 battlers[4] = {0, 1, 2, 3}; if (numEmergencyExitBattlers > 1) @@ -6683,7 +6683,7 @@ static void Cmd_moveend(void) SortBattlersBySpeed(battlers, FALSE); for (i = 0; i < gBattlersCount; i++) - gProtectStructs[i].tryEjectPack = FALSE; + gDisableStructs[i].tryEjectPack = FALSE; for (i = 0; i < gBattlersCount; i++) { @@ -6910,7 +6910,7 @@ static void Cmd_moveend(void) for (i = 0; i < gBattlersCount; i++) { gBattleStruct->battlerState[gBattlerAttacker].targetsDone[i] = FALSE; - gProtectStructs[i].tryEjectPack = FALSE; + gDisableStructs[i].tryEjectPack = FALSE; if (gBattleStruct->battlerState[i].commanderSpecies != SPECIES_NONE && !IsBattlerAlive(i)) { @@ -9533,7 +9533,7 @@ static void Cmd_setprotectlike(void) { if (GetMoveEffect(gCurrentMove) == EFFECT_ENDURE) { - gProtectStructs[gBattlerAttacker].endured = TRUE; + gDisableStructs[gBattlerAttacker].endured = TRUE; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_BRACED_ITSELF; } else if (GetProtectType(protectMethod) == PROTECT_TYPE_SIDE) @@ -10329,7 +10329,7 @@ static u32 ChangeStatBuffs(u32 battler, s8 statValue, u32 statId, union StatChan } else if (!flags.onlyChecking) { - gProtectStructs[battler].tryEjectPack = TRUE; + gDisableStructs[battler].tryEjectPack = TRUE; gProtectStructs[battler].lashOutAffected = TRUE; } } @@ -10930,7 +10930,7 @@ static void Cmd_tryKO(void) if (lands) { - if (gProtectStructs[gBattlerTarget].endured) + if (gDisableStructs[gBattlerTarget].endured) { gBattleStruct->moveDamage[gBattlerTarget] = gBattleMons[gBattlerTarget].hp - 1; gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_FOE_ENDURED; diff --git a/src/battle_util.c b/src/battle_util.c index 52de1e838a..44220727ac 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -11461,7 +11461,7 @@ bool32 TrySwitchInEjectPack(enum ItemCaseId caseID) for (u32 i = 0; i < gBattlersCount; i++) { - if (gProtectStructs[i].tryEjectPack + if (gDisableStructs[i].tryEjectPack && GetBattlerHoldEffect(i, TRUE) == HOLD_EFFECT_EJECT_PACK && IsBattlerAlive(i) && CountUsablePartyMons(i) > 0) @@ -11479,7 +11479,7 @@ bool32 TrySwitchInEjectPack(enum ItemCaseId caseID) SortBattlersBySpeed(battlers, FALSE); for (u32 i = 0; i < gBattlersCount; i++) - gProtectStructs[i].tryEjectPack = FALSE; + gDisableStructs[i].tryEjectPack = FALSE; for (u32 i = 0; i < gBattlersCount; i++) { diff --git a/test/battle/hold_effect/eject_pack.c b/test/battle/hold_effect/eject_pack.c index a4696eb8ff..600af04515 100644 --- a/test/battle/hold_effect/eject_pack.c +++ b/test/battle/hold_effect/eject_pack.c @@ -317,3 +317,24 @@ DOUBLE_BATTLE_TEST("Eject Pack: Only the fastest Eject Pack will activate after } } } + +SINGLE_BATTLE_TEST("Eject Pack does not activate if mon is switched in due to Eject Button") +{ + GIVEN { + PLAYER(SPECIES_DUGTRIO) { Ability(ABILITY_ARENA_TRAP); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_EJECT_PACK); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(player, MOVE_BULLDOZE); + SEND_OUT(opponent, 1); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLDOZE, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + MESSAGE("The opposing Wobbuffet is switched out with the Eject Button!"); + MESSAGE("2 sent out Wobbuffet!"); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + } +} diff --git a/test/battle/hold_effect/life_orb.c b/test/battle/hold_effect/life_orb.c index 2f1514538c..81ddd614db 100644 --- a/test/battle/hold_effect/life_orb.c +++ b/test/battle/hold_effect/life_orb.c @@ -131,3 +131,22 @@ SINGLE_BATTLE_TEST("Life Orb activates if move connected but no damage was dealt MESSAGE("Wobbuffet was hurt by the Life Orb!"); } } + +SINGLE_BATTLE_TEST("Life Orb does not activate on a charge turn") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_FLY); } + TURN { SKIP_TURN(player); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLY, player); + NONE_OF { + HP_BAR(player); + MESSAGE("Wobbuffet was hurt by the Life Orb!"); + } + HP_BAR(opponent); + HP_BAR(player); // Lief Orb + } +} diff --git a/test/battle/move_effect/endure.c b/test/battle/move_effect/endure.c index 5401321162..f332fc28de 100644 --- a/test/battle/move_effect/endure.c +++ b/test/battle/move_effect/endure.c @@ -27,6 +27,33 @@ SINGLE_BATTLE_TEST("Endure does not prevent multiple hits and stat changes occur } } +DOUBLE_BATTLE_TEST("Endure is not transferred to a mon that is switched in due to Eject Button") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { HP(1); Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_SQUIRTLE) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_ENDURE); + MOVE(playerLeft, MOVE_POUND, target: opponentRight); + SEND_OUT(opponentRight, 2); + MOVE(playerRight, MOVE_POUND, target: opponentRight); + SEND_OUT(opponentRight, 3); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ENDURE, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerLeft); + MESSAGE("The opposing Wynaut endured the hit!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerRight); + NOT MESSAGE("The opposing Squirtle endured the hit!"); + } +} + TO_DO_BATTLE_TEST("Endure's success rate decreases for every consecutively used turn"); TO_DO_BATTLE_TEST("Endure uses the same counter as Protect"); TO_DO_BATTLE_TEST("Endure doesn't trigger effects that require damage to be done to the Pokémon (Gen 2-4)"); // Eg. Rough Skin diff --git a/test/battle/move_effect/protect.c b/test/battle/move_effect/protect.c index 2f7cfeba5e..97a09bd922 100644 --- a/test/battle/move_effect/protect.c +++ b/test/battle/move_effect/protect.c @@ -675,3 +675,58 @@ SINGLE_BATTLE_TEST("Protect: Protective Pads protects from secondary effects") } } } + +DOUBLE_BATTLE_TEST("Protect is not transferred to a mon that is switched in due to Eject Button") +{ + GIVEN { + PLAYER(SPECIES_URSHIFU) { Ability(ABILITY_UNSEEN_FIST); }; + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_SQUIRTLE); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_PROTECT); + MOVE(playerLeft, MOVE_POUND, target: opponentRight); + SEND_OUT(opponentRight, 2); + MOVE(playerRight, MOVE_POUND, target: opponentRight); + SEND_OUT(opponentRight, 3); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerRight); + HP_BAR(opponentRight); + } +} + +DOUBLE_BATTLE_TEST("Wide Guard is still activate even if user is switched out due to Eject Button") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { Item(ITEM_EJECT_BUTTON); } + OPPONENT(SPECIES_SQUIRTLE); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { + MOVE(opponentRight, MOVE_WIDE_GUARD); + MOVE(playerLeft, MOVE_POUND, target: opponentRight); + SEND_OUT(opponentRight, 2); + MOVE(playerRight, MOVE_HYPER_VOICE, target: opponentRight); + SEND_OUT(opponentRight, 3); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WIDE_GUARD, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, playerLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerRight); + HP_BAR(opponentLeft); + HP_BAR(opponentRight); + } + } +}