From 6d397f9867a9c899a1afe7ecad73ed0bdab57d26 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Thu, 2 May 2024 14:23:46 +0200 Subject: [PATCH] Fixes Magic Bounce in double battles (#4464) * Fixes Magic Bounce in double battles * Add Double Battle check --- include/battle.h | 4 +-- src/battle_main.c | 1 - src/battle_script_commands.c | 48 ++++++++++++++++++---------- src/battle_util.c | 5 +-- test/battle/ability/magic_bounce.c | 51 ++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 23 deletions(-) diff --git a/include/battle.h b/include/battle.h index b537be776e..dc771a5d77 100644 --- a/include/battle.h +++ b/include/battle.h @@ -157,7 +157,6 @@ struct ProtectStruct u32 flinchImmobility:1; u32 notFirstStrike:1; u32 palaceUnableToUseMove:1; - u32 usesBouncedMove:1; u32 usedHealBlockedMove:1; u32 usedGravityPreventedMove:1; u32 powderSelfDmg:1; @@ -736,9 +735,10 @@ struct BattleStruct u8 quickClawBattlerId; struct LostItem itemLost[PARTY_SIZE]; // Player's team that had items consumed or stolen (two bytes per party member) u8 forcedSwitch:4; // For each battler + u8 additionalEffectsCounter:4; // A counter for the additionalEffects applied by the current move in Cmd_setadditionaleffects u8 blunderPolicy:1; // should blunder policy activate u8 swapDamageCategory:1; // Photon Geyser, Shell Side Arm, Light That Burns the Sky - u8 additionalEffectsCounter:4; // A counter for the additionalEffects applied by the current move in Cmd_setadditionaleffects + u8 bouncedMoveIsUsed:1; u8 ballSpriteIds[2]; // item gfx, window gfx u8 appearedInBattle; // Bitfield to track which Pokemon appeared in battle. Used for Burmy's form change u8 skyDropTargets[MAX_BATTLERS_COUNT]; // For Sky Drop, to account for if multiple Pokemon use Sky Drop in a double battle. diff --git a/src/battle_main.c b/src/battle_main.c index e45b43d099..e8f825676c 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3581,7 +3581,6 @@ const u8* FaintClearSetData(u32 battler) gProtectStructs[battler].flinchImmobility = FALSE; gProtectStructs[battler].notFirstStrike = FALSE; gProtectStructs[battler].usedHealBlockedMove = FALSE; - gProtectStructs[battler].usesBouncedMove = FALSE; gProtectStructs[battler].usedGravityPreventedMove = FALSE; gProtectStructs[battler].usedThroatChopPreventedMove = FALSE; gProtectStructs[battler].statRaised = FALSE; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index f07c560877..53b9cbb024 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1394,9 +1394,9 @@ static void Cmd_attackcanceler(void) if (gProtectStructs[gBattlerTarget].bounceMove && gMovesInfo[gCurrentMove].magicCoatAffected - && !gProtectStructs[gBattlerAttacker].usesBouncedMove) + && !gBattleStruct->bouncedMoveIsUsed) { - gProtectStructs[gBattlerTarget].usesBouncedMove = TRUE; + gBattleStruct->bouncedMoveIsUsed = TRUE; gBattleCommunication[MULTISTRING_CHOOSER] = 0; // Edge case for bouncing a powder move against a grass type pokemon. SetAtkCancellerForCalledMove(); @@ -1413,18 +1413,33 @@ static void Cmd_attackcanceler(void) } return; } - else if (GetBattlerAbility(gBattlerTarget) == ABILITY_MAGIC_BOUNCE - && gMovesInfo[gCurrentMove].magicCoatAffected - && !gProtectStructs[gBattlerAttacker].usesBouncedMove) + else if (gMovesInfo[gCurrentMove].magicCoatAffected && !gBattleStruct->bouncedMoveIsUsed) { - gProtectStructs[gBattlerTarget].usesBouncedMove = TRUE; - gBattleCommunication[MULTISTRING_CHOOSER] = 1; - // Edge case for bouncing a powder move against a grass type pokemon. - SetAtkCancellerForCalledMove(); - BattleScriptPushCursor(); - gBattlescriptCurrInstr = BattleScript_MagicCoatBounce; - gBattlerAbility = gBattlerTarget; - return; + u32 battler = gBattlerTarget; + + if (GetBattlerAbility(gBattlerTarget) == ABILITY_MAGIC_BOUNCE) + { + battler = gBattlerTarget; + gBattleStruct->bouncedMoveIsUsed = TRUE; + } + else if (IsDoubleBattle() + && gMovesInfo[gCurrentMove].target == MOVE_TARGET_OPPONENTS_FIELD + && GetBattlerAbility(BATTLE_PARTNER(gBattlerTarget)) == ABILITY_MAGIC_BOUNCE) + { + gBattlerTarget = battler = BATTLE_PARTNER(gBattlerTarget); + gBattleStruct->bouncedMoveIsUsed = TRUE; + } + + if (gBattleStruct->bouncedMoveIsUsed) + { + gBattleCommunication[MULTISTRING_CHOOSER] = 1; + // Edge case for bouncing a powder move against a grass type pokemon. + SetAtkCancellerForCalledMove(); + BattleScriptPushCursor(); + gBattlescriptCurrInstr = BattleScript_MagicCoatBounce; + gBattlerAbility = battler; + return; + } } // Z-moves and Max Moves bypass protection, but deal reduced damage (factored in AccumulateOtherModifiers) @@ -5920,9 +5935,10 @@ static void Cmd_moveend(void) return; } // Check if the move used was actually a bounced move. If so, we need to go back to the original attacker and make sure, its move hits all 2 or 3 pokemon. - else if (gProtectStructs[gBattlerAttacker].usesBouncedMove) + else if (gBattleStruct->bouncedMoveIsUsed) { u8 originalBounceTarget = gBattlerAttacker; + gBattleStruct->bouncedMoveIsUsed = FALSE; gBattlerAttacker = gBattleStruct->attackerBeforeBounce; gBattleStruct->targetsDone[gBattlerAttacker] |= gBitTable[originalBounceTarget]; gBattleStruct->targetsDone[originalBounceTarget] = 0; @@ -6221,7 +6237,7 @@ static void Cmd_moveend(void) if (!(gBattleStruct->lastMoveFailed & gBitTable[gBattlerAttacker] || (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove - && gProtectStructs[gBattlerAttacker].usesBouncedMove))) + && gBattleStruct->bouncedMoveIsUsed))) { // Dance move succeeds // Set target for other Dancer mons; set bit so that mon cannot activate Dancer off of its own move if (!gSpecialStatuses[gBattlerAttacker].dancerUsedMove) @@ -6316,7 +6332,6 @@ static void Cmd_moveend(void) CancelMultiTurnMoves(gBattlerAttacker); // Cancel it gBattleStruct->targetsDone[gBattlerAttacker] = 0; - gProtectStructs[gBattlerAttacker].usesBouncedMove = FALSE; gProtectStructs[gBattlerAttacker].targetAffected = FALSE; gProtectStructs[gBattlerAttacker].shellTrap = FALSE; gBattleStruct->ateBoost[gBattlerAttacker] = 0; @@ -6333,6 +6348,7 @@ static void Cmd_moveend(void) gBattleStruct->hitSwitchTargetFailed = FALSE; gBattleStruct->isAtkCancelerForCalledMove = FALSE; gBattleStruct->swapDamageCategory = FALSE; + gBattleStruct->bouncedMoveIsUsed = FALSE; gBattleStruct->enduredDamage = 0; gBattleStruct->additionalEffectsCounter = 0; gBattleScripting.moveendState++; diff --git a/src/battle_util.c b/src/battle_util.c index e4885c665b..ecdd8db266 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3489,10 +3489,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) if (effect != 0) gBattlescriptCurrInstr = BattleScript_PowderMoveNoEffect; } - if (gProtectStructs[gBattlerAttacker].usesBouncedMove) // Edge case for bouncing a powder move against a grass type pokemon. - gBattleStruct->atkCancellerTracker = CANCELLER_END; - else - gBattleStruct->atkCancellerTracker++; + gBattleStruct->atkCancellerTracker++; break; case CANCELLER_POWDER_STATUS: if (gBattleMons[gBattlerAttacker].status2 & STATUS2_POWDER) diff --git a/test/battle/ability/magic_bounce.c b/test/battle/ability/magic_bounce.c index 95eaf382e6..8d583b154d 100644 --- a/test/battle/ability/magic_bounce.c +++ b/test/battle/ability/magic_bounce.c @@ -81,3 +81,54 @@ DOUBLE_BATTLE_TEST("Magic Bounce bounces back moves hitting both foes at two foe MESSAGE("Foe Wynaut's Defense fell!"); } } + +DOUBLE_BATTLE_TEST("Magic Bounce bounces back moves hitting foes field") +{ + u32 battlerOne, battlerTwo, abilityBattlerOne, abilityBattlerTwo; + + PARAMETRIZE { battlerOne = SPECIES_NATU; abilityBattlerOne = ABILITY_MAGIC_BOUNCE; + battlerTwo = SPECIES_ESPEON; abilityBattlerTwo = ABILITY_SYNCHRONIZE; } + PARAMETRIZE { battlerOne = SPECIES_NATU; abilityBattlerOne = ABILITY_KEEN_EYE; + battlerTwo = SPECIES_ESPEON; abilityBattlerTwo = ABILITY_MAGIC_BOUNCE; } + + GIVEN { + ASSUME(gMovesInfo[MOVE_STEALTH_ROCK].target == MOVE_TARGET_OPPONENTS_FIELD); + PLAYER(SPECIES_ABRA); + PLAYER(SPECIES_KADABRA); + OPPONENT(battlerOne) { Ability(abilityBattlerOne); } + OPPONENT(battlerTwo) { Ability(abilityBattlerTwo); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_STEALTH_ROCK); } + } SCENE { + if (abilityBattlerOne == ABILITY_MAGIC_BOUNCE) + ABILITY_POPUP(opponentLeft, ABILITY_MAGIC_BOUNCE); + else + ABILITY_POPUP(opponentRight, ABILITY_MAGIC_BOUNCE); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, playerLeft); + if (abilityBattlerOne == ABILITY_MAGIC_BOUNCE) { + MESSAGE("Abra's Stealth Rock was bounced back by Foe Natu's Magic Bounce!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponentLeft); + } else { + MESSAGE("Abra's Stealth Rock was bounced back by Foe Espeon's Magic Bounce!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, opponentRight); + } + } +} + +SINGLE_BATTLE_TEST("Magic Bounce bounced back status moves can not be bounced back by Magic Bounce") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TOXIC].effect == EFFECT_TOXIC); + PLAYER(SPECIES_ESPEON) { Ability(ABILITY_MAGIC_BOUNCE); } + OPPONENT(SPECIES_ESPEON) { Ability(ABILITY_MAGIC_BOUNCE); } + } WHEN { + TURN { MOVE(player, MOVE_TOXIC); } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_MAGIC_BOUNCE); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + MESSAGE("Espeon's Toxic was bounced back by Foe Espeon's Magic Bounce!"); + NOT ABILITY_POPUP(player, ABILITY_MAGIC_BOUNCE); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, opponent); + STATUS_ICON(player, badPoison: TRUE); + } +}