Fixes clear body type effect and clear amulet against protect effects (#6482)

This commit is contained in:
Alex 2025-03-28 11:35:06 +01:00 committed by GitHub
parent 92cb9ab642
commit ce3d6a924a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 154 additions and 47 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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