diff --git a/include/battle_util.h b/include/battle_util.h index 6f1223b684..eef6a009e0 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -205,6 +205,7 @@ bool32 AreAllMovesUnusable(u32 battler); u8 GetImprisonedMovesCount(u32 battler, u16 move); u8 DoFieldEndTurnEffects(void); s32 GetDrainedBigRootHp(u32 battler, s32 hp); +bool32 IsMagicGuardProtected(u32 battler, u32 ability); u8 DoBattlerEndTurnEffects(void); bool32 HandleWishPerishSongOnTurnEnd(void); bool32 HandleFaintedMonActions(void); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index d5439d97a6..8885c84a66 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -339,7 +339,7 @@ static bool32 SetTargetToNextPursuiter(u32 battlerDef); void ApplyExperienceMultipliers(s32 *expAmount, u8 expGetterMonId, u8 faintedBattler); static void RemoveAllWeather(void); static void RemoveAllTerrains(void); -static bool8 CanAbilityPreventStatLoss(u16 abilityDef); +static bool32 CanAbilityPreventStatLoss(u32 abilityDef); static bool8 CanBurnHitThaw(u16 move); static u32 GetNextTarget(u32 moveTarget, bool32 excludeCurrent); static void TryUpdateEvolutionTracker(u32 evolutionMethod, u32 upAmount, u16 usedMove); @@ -1321,9 +1321,10 @@ static void Cmd_attackcanceler(void) RecordAbilityBattle(gBattlerTarget, gLastUsedAbility); } else if (IsBattlerProtected(gBattlerAttacker, gBattlerTarget, gCurrentMove) - && (gCurrentMove != MOVE_CURSE || IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GHOST)) + && (effect != EFFECT_CURSE || IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_GHOST)) && (!gBattleMoveEffects[effect].twoTurnEffect || (gBattleMons[gBattlerAttacker].status2 & STATUS2_MULTIPLETURNS)) && effect != EFFECT_SUCKER_PUNCH + && effect != EFFECT_COUNTER && effect != EFFECT_UPPER_HAND) { if (IsMoveMakingContact(gCurrentMove, gBattlerAttacker)) @@ -6169,14 +6170,31 @@ static u32 GetNextTarget(u32 moveTarget, bool32 excludeCurrent) return battler; } -static inline bool32 IsProtectivePadsProtected(u32 battler, u32 move) +static inline bool32 IsProtectivePadsProtected(u32 battler, u32 move, u32 holdEffect) { - if (!IsMoveMakingContact(move, battler)) + if (holdEffect != HOLD_EFFECT_PROTECTIVE_PADS) return FALSE; - if (GetBattlerHoldEffect(battler, TRUE) == HOLD_EFFECT_PROTECTIVE_PADS) + RecordItemEffectBattle(battler, holdEffect); + return TRUE; +} + +static inline bool32 IsProtectEffectAffected(u32 battler, u32 move) +{ + u32 holdEffect = GetBattlerHoldEffect(gBattlerAttacker, TRUE); + if (IsProtectivePadsProtected(battler, move, holdEffect)) + return TRUE; + + if (holdEffect == HOLD_EFFECT_CLEAR_AMULET) { - RecordItemEffectBattle(battler, HOLD_EFFECT_PROTECTIVE_PADS); + RecordItemEffectBattle(battler, holdEffect); + return TRUE; + } + + u32 ability = GetBattlerAbility(gBattlerAttacker); + if (CanAbilityPreventStatLoss(ability)) + { + RecordAbilityBattle(battler, ability); return TRUE; } @@ -6219,9 +6237,8 @@ static void Cmd_moveend(void) if (gProtectStructs[gBattlerAttacker].touchedProtectLike) { if (gProtectStructs[gBattlerTarget].spikyShielded - && moveEffect != EFFECT_COUNTER - && !IsProtectivePadsProtected(gBattlerAttacker, gCurrentMove) - && GetBattlerAbility(gBattlerAttacker) != ABILITY_MAGIC_GUARD) + && !IsProtectivePadsProtected(gBattlerAttacker, gCurrentMove, GetBattlerHoldEffect(gBattlerAttacker, TRUE)) + && !IsMagicGuardProtected(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker))) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerAttacker) / 8; @@ -6233,7 +6250,7 @@ static void Cmd_moveend(void) effect = 1; } else if (gProtectStructs[gBattlerTarget].kingsShielded - && !IsProtectivePadsProtected(gBattlerAttacker, gCurrentMove)) + && !IsProtectEffectAffected(gBattlerAttacker, gCurrentMove)) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; i = gBattlerAttacker; @@ -6248,7 +6265,7 @@ static void Cmd_moveend(void) effect = 1; } else if (gProtectStructs[gBattlerTarget].banefulBunkered - && !IsProtectivePadsProtected(gBattlerAttacker, gCurrentMove)) + && !IsProtectivePadsProtected(gBattlerAttacker, gCurrentMove, GetBattlerHoldEffect(gBattlerAttacker, TRUE))) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; gBattleScripting.moveEffect = MOVE_EFFECT_POISON | MOVE_EFFECT_AFFECTS_USER; @@ -6260,7 +6277,7 @@ static void Cmd_moveend(void) else if (gProtectStructs[gBattlerTarget].obstructed && moveEffect != EFFECT_SUCKER_PUNCH && moveEffect != EFFECT_UPPER_HAND - && !IsProtectivePadsProtected(gBattlerAttacker, gCurrentMove)) + && !IsProtectEffectAffected(gBattlerAttacker, gCurrentMove)) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; i = gBattlerAttacker; @@ -6272,7 +6289,7 @@ static void Cmd_moveend(void) effect = 1; } else if (gProtectStructs[gBattlerTarget].silkTrapped - && !IsProtectivePadsProtected(gBattlerAttacker, gCurrentMove)) + && !IsProtectEffectAffected(gBattlerAttacker, gCurrentMove)) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; i = gBattlerAttacker; @@ -6284,7 +6301,7 @@ static void Cmd_moveend(void) effect = 1; } else if (gProtectStructs[gBattlerTarget].burningBulwarked - && !IsProtectivePadsProtected(gBattlerAttacker, gCurrentMove)) + && !IsProtectivePadsProtected(gBattlerAttacker, gCurrentMove, GetBattlerHoldEffect(gBattlerAttacker, TRUE))) { gProtectStructs[gBattlerAttacker].touchedProtectLike = FALSE; gBattleScripting.moveEffect = MOVE_EFFECT_BURN | MOVE_EFFECT_AFFECTS_USER; @@ -16824,7 +16841,7 @@ static bool8 IsFinalStrikeEffect(u32 moveEffect) return FALSE; } -static bool8 CanAbilityPreventStatLoss(u16 abilityDef) +static bool32 CanAbilityPreventStatLoss(u32 abilityDef) { switch (abilityDef) { diff --git a/src/battle_util.c b/src/battle_util.c index 9fb321118f..863bbcb8c7 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2207,7 +2207,8 @@ s32 GetDrainedBigRootHp(u32 battler, s32 hp) return hp * -1; } -static inline bool32 IsBattlerProtectedByMagicGuard(u32 battler, u32 ability) +// This should always be the last check. Otherwise the ability might be recorded when it is not supposed to be +bool32 IsMagicGuardProtected(u32 battler, u32 ability) { if (ability != ABILITY_MAGIC_GUARD) return FALSE; @@ -2343,7 +2344,7 @@ u8 DoBattlerEndTurnEffects(void) if ((gStatuses3[battler] & STATUS3_LEECHSEED) && IsBattlerAlive(gStatuses3[battler] & STATUS3_LEECHSEED_BATTLER) && IsBattlerAlive(battler) - && !IsBattlerProtectedByMagicGuard(battler, ability)) + && !IsMagicGuardProtected(battler, ability)) { gBattlerTarget = gStatuses3[battler] & STATUS3_LEECHSEED_BATTLER; // Notice gBattlerTarget is actually the HP receiver. gBattlerAttacker = battler; @@ -2374,7 +2375,7 @@ u8 DoBattlerEndTurnEffects(void) case ENDTURN_POISON: // poison if ((gBattleMons[battler].status1 & STATUS1_POISON) && IsBattlerAlive(battler) - && !IsBattlerProtectedByMagicGuard(battler, ability)) + && !IsMagicGuardProtected(battler, ability)) { if (ability == ABILITY_POISON_HEAL) { @@ -2402,7 +2403,7 @@ u8 DoBattlerEndTurnEffects(void) case ENDTURN_BAD_POISON: // toxic poison if ((gBattleMons[battler].status1 & STATUS1_TOXIC_POISON) && IsBattlerAlive(battler) - && !IsBattlerProtectedByMagicGuard(battler, ability)) + && !IsMagicGuardProtected(battler, ability)) { if (ability == ABILITY_POISON_HEAL) { @@ -2433,7 +2434,7 @@ u8 DoBattlerEndTurnEffects(void) case ENDTURN_BURN: // burn if ((gBattleMons[battler].status1 & STATUS1_BURN) && IsBattlerAlive(battler) - && !IsBattlerProtectedByMagicGuard(battler, ability)) + && !IsMagicGuardProtected(battler, ability)) { gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / (B_BURN_DAMAGE >= GEN_7 ? 16 : 8); if (ability == ABILITY_HEATPROOF) @@ -2452,7 +2453,7 @@ u8 DoBattlerEndTurnEffects(void) case ENDTURN_FROSTBITE: // burn if ((gBattleMons[battler].status1 & STATUS1_FROSTBITE) && IsBattlerAlive(battler) - && !IsBattlerProtectedByMagicGuard(battler, ability)) + && !IsMagicGuardProtected(battler, ability)) { gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / (B_BURN_DAMAGE >= GEN_7 ? 16 : 8); if (gBattleStruct->moveDamage[battler] == 0) @@ -2465,7 +2466,7 @@ u8 DoBattlerEndTurnEffects(void) case ENDTURN_NIGHTMARES: // spooky nightmares if ((gBattleMons[battler].status2 & STATUS2_NIGHTMARE) && IsBattlerAlive(battler) - && !IsBattlerProtectedByMagicGuard(battler, ability)) + && !IsMagicGuardProtected(battler, ability)) { // R/S does not perform this sleep check, which causes the nightmare effect to // persist even after the affected Pokémon has been awakened by Shed Skin. @@ -2487,7 +2488,7 @@ u8 DoBattlerEndTurnEffects(void) case ENDTURN_CURSE: // curse if ((gBattleMons[battler].status2 & STATUS2_CURSED) && IsBattlerAlive(battler) - && !IsBattlerProtectedByMagicGuard(battler, ability)) + && !IsMagicGuardProtected(battler, ability)) { gBattleStruct->moveDamage[battler] = GetNonDynamaxMaxHP(battler) / 4; if (gBattleStruct->moveDamage[battler] == 0) @@ -2502,7 +2503,7 @@ u8 DoBattlerEndTurnEffects(void) { if (--gDisableStructs[battler].wrapTurns != 0) // damaged by wrap { - if (IsBattlerProtectedByMagicGuard(battler, ability)) + if (IsMagicGuardProtected(battler, ability)) { gBattleStruct->turnEffectsTracker++; break; @@ -2812,7 +2813,7 @@ u8 DoBattlerEndTurnEffects(void) case ENDTURN_SALT_CURE: if (gStatuses4[battler] & STATUS4_SALT_CURE && IsBattlerAlive(battler) - && !IsBattlerProtectedByMagicGuard(battler, ability)) + && !IsMagicGuardProtected(battler, ability)) { gBattlerTarget = battler; if (IS_BATTLER_ANY_TYPE(battler, TYPE_STEEL, TYPE_WATER)) diff --git a/test/battle/ability/clear_body.c b/test/battle/ability/clear_body.c index fd2317a545..4b9273edc1 100644 --- a/test/battle/ability/clear_body.c +++ b/test/battle/ability/clear_body.c @@ -412,3 +412,43 @@ SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke don't prevent S MESSAGE("The opposing Metang used Celebrate!"); } } + +SINGLE_BATTLE_TEST("Clear Body, Full Metal Body, and White Smoke protect from Protect's secondary effects") +{ + u32 move = MOVE_NONE; + u32 species = SPECIES_NONE; + u32 ability = ABILITY_NONE; + + static const u32 moves[] = { + MOVE_SPIKY_SHIELD, + MOVE_KINGS_SHIELD, + MOVE_SILK_TRAP, + MOVE_OBSTRUCT, + }; + + for (u32 j = 0; j < ARRAY_COUNT(moves); j++) + { + PARAMETRIZE{ move = moves[j]; species = SPECIES_METANG; ability = ABILITY_CLEAR_BODY; } + PARAMETRIZE{ move = moves[j]; species = SPECIES_SOLGALEO; ability = ABILITY_FULL_METAL_BODY; } + PARAMETRIZE{ move = moves[j]; species = SPECIES_TORKOAL; ability = ABILITY_WHITE_SMOKE; } + } + + GIVEN { + PLAYER(species) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + if (move == MOVE_KINGS_SHIELD) { + MESSAGE("Wobbuffet's Attack fell!"); + } else if (move == MOVE_SILK_TRAP) { + MESSAGE("Wobbuffet's Speed fell!"); + } else if (move == MOVE_OBSTRUCT) { + MESSAGE("Wobbuffet's Defense harshly fell!"); + } + } + } +} diff --git a/test/battle/hold_effect/clear_amulet.c b/test/battle/hold_effect/clear_amulet.c index cc143c51e1..bbe58f164b 100644 --- a/test/battle/hold_effect/clear_amulet.c +++ b/test/battle/hold_effect/clear_amulet.c @@ -88,3 +88,34 @@ SINGLE_BATTLE_TEST("Clear Amulet prevents secondary effects that reduce stats") } } } + +SINGLE_BATTLE_TEST("Clear Amulet protects from Protect's secondary effects") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SPIKY_SHIELD; } + PARAMETRIZE { move = MOVE_BANEFUL_BUNKER; } + PARAMETRIZE { move = MOVE_BURNING_BULWARK; } + PARAMETRIZE { move = MOVE_KINGS_SHIELD; } + PARAMETRIZE { move = MOVE_SILK_TRAP; } + PARAMETRIZE { move = MOVE_OBSTRUCT; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_CLEAR_AMULET); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + if (move == MOVE_KINGS_SHIELD) { + MESSAGE("Wobbuffet's Attack fell!"); + } else if (move == MOVE_SILK_TRAP) { + MESSAGE("Wobbuffet's Speed fell!"); + } else if (move == MOVE_OBSTRUCT) { + MESSAGE("Wobbuffet's Defense harshly fell!"); + } + } + } +} diff --git a/test/battle/hold_effect/protective_pads.c b/test/battle/hold_effect/protective_pads.c index 41ffc83d4b..8bd206c6a3 100644 --- a/test/battle/hold_effect/protective_pads.c +++ b/test/battle/hold_effect/protective_pads.c @@ -97,9 +97,9 @@ SINGLE_BATTLE_TEST("Protective Pads protects from Protect's secondary effects") if (move == MOVE_SPIKY_SHIELD) { HP_BAR(player); } else if (move == MOVE_BANEFUL_BUNKER) { - STATUS_ICON(player, STATUS1_BURN); - } else if (move == MOVE_BURNING_BULWARK) { STATUS_ICON(player, STATUS1_POISON); + } else if (move == MOVE_BURNING_BULWARK) { + STATUS_ICON(player, STATUS1_BURN); } else if (move == MOVE_KINGS_SHIELD) { MESSAGE("Wobbuffet's Attack fell!"); } else if (move == MOVE_SILK_TRAP) { diff --git a/test/battle/move_effect/counter.c b/test/battle/move_effect/counter.c index f4d3a78114..d45e75b478 100644 --- a/test/battle/move_effect/counter.c +++ b/test/battle/move_effect/counter.c @@ -1,6 +1,43 @@ #include "global.h" #include "test/battle.h" +SINGLE_BATTLE_TEST("Counter is not affected by Protect effects") +{ + u32 move; + + PARAMETRIZE { move = MOVE_SPIKY_SHIELD; } + PARAMETRIZE { move = MOVE_BANEFUL_BUNKER; } + PARAMETRIZE { move = MOVE_BURNING_BULWARK; } + PARAMETRIZE { move = MOVE_KINGS_SHIELD; } + PARAMETRIZE { move = MOVE_SILK_TRAP; } + PARAMETRIZE { move = MOVE_OBSTRUCT; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_COUNTER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_COUNTER, player); + if (move == MOVE_SPIKY_SHIELD) { + HP_BAR(player); + } else if (move == MOVE_BANEFUL_BUNKER) { + STATUS_ICON(player, STATUS1_POISON); + } else if (move == MOVE_BURNING_BULWARK) { + STATUS_ICON(player, STATUS1_BURN); + } else if (move == MOVE_KINGS_SHIELD) { + MESSAGE("Wobbuffet's Attack fell!"); + } else if (move == MOVE_SILK_TRAP) { + MESSAGE("Wobbuffet's Speed fell!"); + } else if (move == MOVE_OBSTRUCT) { + MESSAGE("Wobbuffet's Defense harshly fell!"); + } + } + } +} + TO_DO_BATTLE_TEST("Counter will do twice as much damage received from the opponent"); TO_DO_BATTLE_TEST("Counter cannot affect ally Pokémon"); TO_DO_BATTLE_TEST("Counter hits the last opponent that hit the user"); //Doubles diff --git a/test/battle/move_effect/protect.c b/test/battle/move_effect/protect.c index 84862bbcd4..82a29ef81b 100644 --- a/test/battle/move_effect/protect.c +++ b/test/battle/move_effect/protect.c @@ -563,23 +563,3 @@ DOUBLE_BATTLE_TEST("Crafty Shield does not protect against moves that target all MESSAGE("The opposing Sunflora's Defense rose!"); } } - -SINGLE_BATTLE_TEST("Spiky Shield does not damage users on Counter or Mirror Coat") -{ - u32 move; - PARAMETRIZE { move = MOVE_MIRROR_COAT; } - PARAMETRIZE { move = MOVE_COUNTER; } - GIVEN { - PLAYER(SPECIES_WOBBUFFET); - OPPONENT(SPECIES_WOBBUFFET); - } WHEN { - TURN { MOVE(player, MOVE_SPIKY_SHIELD); MOVE(opponent, move); } - } SCENE { - ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKY_SHIELD, player); - NONE_OF { - ANIMATION(ANIM_TYPE_MOVE, move, opponent); - HP_BAR(opponent); - } - } -} -