From 44c132a752cfda636fbc4a2e00d64b7e3a461239 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Tue, 10 Mar 2026 18:33:58 +0100 Subject: [PATCH] Fix various stuff not working when hitting substitutes (#9487) Co-authored-by: Hedara --- include/battle.h | 10 ++- src/battle_hold_effects.c | 26 +++---- src/battle_move_resolution.c | 46 ++++++------ src/battle_script_commands.c | 26 ++++++- src/battle_util.c | 70 +++++++++---------- test/battle/gimmick/zmove.c | 37 ++++++++++ test/battle/move_effect/absorb.c | 19 ++++- test/battle/move_effect/ceaseless_edge.c | 17 +++++ test/battle/move_effect/chloroblast.c | 15 ++++ test/battle/move_effect/core_enforcer.c | 54 ++++++++++++++ test/battle/move_effect/hit_escape.c | 14 ++++ test/battle/move_effect/ice_spinner.c | 17 +++++ test/battle/move_effect/max_hp_50_recoil.c | 15 ++++ test/battle/move_effect/rapid_spin.c | 24 +++++++ test/battle/move_effect/steel_roller.c | 16 +++++ test/battle/move_effect/stone_axe.c | 17 +++++ .../move_effect_secondary/break_screens.c | 22 ++++++ .../battle/move_effect_secondary/ion_deluge.c | 19 +++++ test/battle/move_flags/recoil.c | 20 ++++++ 19 files changed, 407 insertions(+), 77 deletions(-) create mode 100644 test/battle/move_effect/core_enforcer.c diff --git a/include/battle.h b/include/battle.h index bcfbeb6c91..d01238a68e 100644 --- a/include/battle.h +++ b/include/battle.h @@ -1067,9 +1067,15 @@ static inline bool32 IsBattlerAlive(enum BattlerId battler) return TRUE; } -static inline bool32 IsBattlerTurnDamaged(enum BattlerId battler) +enum SubCheck { - return gSpecialStatuses[battler].damagedByAttack; + EXCLUDING_SUBSTITUTES, + INCLUDING_SUBSTITUTES +}; + +static inline bool32 IsBattlerTurnDamaged(enum BattlerId battler, enum SubCheck subCheck) +{ + return gSpecialStatuses[battler].damagedByAttack || ((subCheck == INCLUDING_SUBSTITUTES) && gBattleStruct->moveDamage[battler] > 0); } static inline bool32 IsBattlerAtMaxHp(enum BattlerId battler) diff --git a/src/battle_hold_effects.c b/src/battle_hold_effects.c index 9011b7a68d..fc9579d252 100644 --- a/src/battle_hold_effects.c +++ b/src/battle_hold_effects.c @@ -189,7 +189,7 @@ static enum ItemEffect TryKingsRock(enum BattlerId battlerAtk, enum BattlerId ba enum ItemEffect effect = ITEM_NO_EFFECT; if (!IsBattlerAlive(battlerDef) - || !IsBattlerTurnDamaged(battlerDef) + || !IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) || MoveIgnoresKingsRock(gCurrentMove) || MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_FLINCH)) return effect; @@ -216,7 +216,7 @@ static enum ItemEffect TryAirBalloon(enum BattlerId battler, ActivationTiming ti if (timing == IsOnTargetHitActivation) { - if (IsBattlerTurnDamaged(battler)) + if (IsBattlerTurnDamaged(battler, EXCLUDING_SUBSTITUTES)) { BattleScriptCall(BattleScript_AirBalloonMsgPop); effect = ITEM_EFFECT_OTHER; @@ -237,7 +237,7 @@ static enum ItemEffect TryRockyHelmet(enum BattlerId battlerDef, enum BattlerId enum ItemEffect effect = ITEM_NO_EFFECT; enum Ability ability = GetBattlerAbility(battlerAtk); - if (IsBattlerTurnDamaged(battlerDef) + if (IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(battlerAtk) && !CanBattlerAvoidContactEffects(battlerAtk, battlerDef, ability, GetBattlerHoldEffect(battlerAtk), gCurrentMove) && !IsAbilityAndRecord(battlerAtk, ability, ABILITY_MAGIC_GUARD)) @@ -256,7 +256,7 @@ static enum ItemEffect TryWeaknessPolicy(enum BattlerId battlerDef) enum ItemEffect effect = ITEM_NO_EFFECT; if (IsBattlerAlive(battlerDef) - && IsBattlerTurnDamaged(battlerDef) + && IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_SUPER_EFFECTIVE) { BattleScriptCall(BattleScript_WeaknessPolicy); @@ -271,7 +271,7 @@ static enum ItemEffect TrySnowball(enum BattlerId battlerDef) enum ItemEffect effect = ITEM_NO_EFFECT; if (IsBattlerAlive(battlerDef) - && IsBattlerTurnDamaged(battlerDef) + && IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && GetBattleMoveType(gCurrentMove) == TYPE_ICE) { BattleScriptCall(BattleScript_TargetItemStatRaise); @@ -287,7 +287,7 @@ static enum ItemEffect TryLuminousMoss(enum BattlerId battlerDef) enum ItemEffect effect = ITEM_NO_EFFECT; if (IsBattlerAlive(battlerDef) - && IsBattlerTurnDamaged(battlerDef) + && IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && GetBattleMoveType(gCurrentMove) == TYPE_WATER) { BattleScriptCall(BattleScript_TargetItemStatRaise); @@ -303,7 +303,7 @@ static enum ItemEffect TryCellBattery(enum BattlerId battlerDef) enum ItemEffect effect = ITEM_NO_EFFECT; if (IsBattlerAlive(battlerDef) - && IsBattlerTurnDamaged(battlerDef) + && IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && GetBattleMoveType(gCurrentMove) == TYPE_ELECTRIC) { BattleScriptCall(BattleScript_TargetItemStatRaise); @@ -319,7 +319,7 @@ static enum ItemEffect TryAbsorbBulb(enum BattlerId battlerDef) enum ItemEffect effect = ITEM_NO_EFFECT; if (IsBattlerAlive(battlerDef) - && IsBattlerTurnDamaged(battlerDef) + && IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && GetBattleMoveType(gCurrentMove) == TYPE_WATER) { effect = ITEM_STATS_CHANGE; @@ -335,7 +335,7 @@ static enum ItemEffect TryJabocaBerry(enum BattlerId battlerDef, enum BattlerId enum ItemEffect effect = ITEM_NO_EFFECT; if (IsBattlerAlive(battlerAtk) - && IsBattlerTurnDamaged(battlerDef) + && IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && !DoesSubstituteBlockMove(battlerAtk, battlerDef, gCurrentMove) && IsBattleMovePhysical(gCurrentMove) && !IsAbilityAndRecord(battlerAtk, GetBattlerAbility(battlerAtk), ABILITY_MAGIC_GUARD)) @@ -357,7 +357,7 @@ static enum ItemEffect TryRowapBerry(enum BattlerId battlerDef, enum BattlerId b enum ItemEffect effect = ITEM_NO_EFFECT; if (IsBattlerAlive(battlerAtk) - && IsBattlerTurnDamaged(battlerDef) + && IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && !DoesSubstituteBlockMove(battlerAtk, battlerDef, gCurrentMove) && IsBattleMoveSpecial(gCurrentMove) && !IsAbilityAndRecord(battlerAtk, GetBattlerAbility(battlerAtk), ABILITY_MAGIC_GUARD)) @@ -380,7 +380,7 @@ static enum ItemEffect TrySetEnigmaBerry(enum BattlerId battlerDef, enum Battler if (IsBattlerAlive(battlerDef) && !DoesSubstituteBlockMove(battlerAtk, battlerDef, gCurrentMove) - && ((IsBattlerTurnDamaged(battlerDef) && gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_SUPER_EFFECTIVE) || gBattleScripting.overrideBerryRequirements) + && ((IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_SUPER_EFFECTIVE) || gBattleScripting.overrideBerryRequirements) && !(gBattleScripting.overrideBerryRequirements && gBattleMons[battlerDef].hp == gBattleMons[battlerDef].maxHP) && !(B_HEAL_BLOCKING >= GEN_5 && gBattleMons[battlerDef].volatiles.healBlock)) { @@ -502,7 +502,7 @@ static enum ItemEffect DamagedStatBoostBerryEffect(enum BattlerId battlerDef, en if (gBattleScripting.overrideBerryRequirements || (!DoesSubstituteBlockMove(battlerAtk, battlerDef, gCurrentMove) && GetBattleMoveCategory(gCurrentMove) == category - && IsBattlerTurnDamaged(battlerDef))) + && IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES))) { if (GetBattlerAbility(battlerDef) == ABILITY_RIPEN) SET_STATCHANGER(statId, 2, FALSE); @@ -562,7 +562,7 @@ static enum ItemEffect TryStickyBarbOnTargetHit(enum BattlerId battlerDef, enum { enum ItemEffect effect = ITEM_NO_EFFECT; - if (IsBattlerTurnDamaged(battlerDef) + if (IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && !CanBattlerAvoidContactEffects(battlerAtk, battlerDef, GetBattlerAbility(battlerAtk), GetBattlerHoldEffect(battlerAtk), gCurrentMove) && !DoesSubstituteBlockMove(battlerAtk, battlerDef, gCurrentMove) && IsBattlerAlive(battlerAtk) diff --git a/src/battle_move_resolution.c b/src/battle_move_resolution.c index d8ef7de821..e01c2d5079 100644 --- a/src/battle_move_resolution.c +++ b/src/battle_move_resolution.c @@ -2141,7 +2141,7 @@ static enum MoveEndResult MoveEndProtectLikeEffect(void) // Not strictly a protect effect, but works the same way if (IsBattlerUsingBeakBlast(gBattlerTarget) - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && CanBeBurned(gBattlerAttacker, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker))) { gBattleMons[gBattlerAttacker].status1 = STATUS1_BURN; @@ -2208,7 +2208,7 @@ static enum MoveEndResult MoveEndAbsorb(void) case EFFECT_ABSORB: case EFFECT_DREAM_EATER: if (gBattleStruct->moveDamage[gBattlerTarget] > 0 - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, INCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerAttacker)) { s32 healAmount = (gBattleStruct->moveDamage[gBattlerTarget] * GetMoveAbsorbPercentage(gCurrentMove) / 100); @@ -2248,7 +2248,7 @@ static enum MoveEndResult MoveEndRage(void) && IsBattlerAlive(gBattlerTarget) && gBattlerAttacker != gBattlerTarget && !IsBattlerAlly(gBattlerAttacker, gBattlerTarget) - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && !IsBattleMoveStatus(gCurrentMove) && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerTarget))) { @@ -2520,7 +2520,7 @@ static enum MoveEndResult MoveEndFaintBlock(void) break; case FAINT_BLOCK_TRY_DESTINY_BOND: // Checked before FAINT_BLOCK_FAINT_TARGET but occurs after since volatiles are cleared on faint if (gBattleMons[gBattlerTarget].volatiles.destinyBond - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerAttacker) && GetActiveGimmick(gBattlerAttacker) != GIMMICK_DYNAMAX && !IsBattlerAlly(gBattlerAttacker, gBattlerTarget)) @@ -2531,7 +2531,7 @@ static enum MoveEndResult MoveEndFaintBlock(void) break; case FAINT_BLOCK_TRY_GRUDGE: // Checked before FAINT_BLOCK_FAINT_TARGET but occurs after since volatiles are cleared on faint if (gBattleMons[gBattlerTarget].volatiles.grudge - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerAttacker) && !IsBattlerAlly(gBattlerAttacker, gBattlerTarget) && !IsZMove(gCurrentMove) @@ -2882,7 +2882,7 @@ static enum MoveEndResult MoveEndDefrost(void) continue; if (!(gBattleMons[battler].status1 & STATUS1_ICY_ANY) - || !IsBattlerTurnDamaged(battler) + || !IsBattlerTurnDamaged(battler, EXCLUDING_SUBSTITUTES) || !IsBattlerAlive(battler)) continue; @@ -2947,7 +2947,7 @@ static enum MoveEndResult MoveEndMoveBlock(void) if (gBattleMons[gBattlerTarget].item != ITEM_NONE && IsBattlerAlive(gBattlerAttacker) && !(B_KNOCK_OFF_REMOVAL >= GEN_5 && side == B_SIDE_PLAYER && !(gBattleTypeFlags & BATTLE_TYPE_TRAINER)) - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && !DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove) && CanBattlerGetOrLoseItem(gBattlerTarget, gBattlerAttacker, gBattleMons[gBattlerTarget].item) && !NoAliveMonsForEitherParty()) @@ -2986,7 +2986,7 @@ static enum MoveEndResult MoveEndMoveBlock(void) } break; case EFFECT_STEAL_ITEM: - if (!IsBattlerTurnDamaged(gBattlerTarget) + if (!IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) || gBattleMons[gBattlerAttacker].item != ITEM_NONE || gBattleMons[gBattlerTarget].item == ITEM_NONE || !IsBattlerAlive(gBattlerAttacker) @@ -3016,7 +3016,7 @@ static enum MoveEndResult MoveEndMoveBlock(void) } break; case EFFECT_HIT_SWITCH_TARGET: - if (IsBattlerTurnDamaged(gBattlerTarget) + if (IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker) && gBattleMons[BATTLE_PARTNER(gBattlerTarget)].volatiles.semiInvulnerable != STATE_COMMANDER) @@ -3047,7 +3047,7 @@ static enum MoveEndResult MoveEndMoveBlock(void) break; case EFFECT_SMACK_DOWN: if (!IsBattlerGrounded(gBattlerTarget, GetBattlerAbility(gBattlerTarget), GetBattlerHoldEffect(gBattlerTarget)) - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerTarget) && !DoesSubstituteBlockMove(gBattlerAttacker, gBattlerTarget, gCurrentMove)) { @@ -3094,7 +3094,7 @@ static enum MoveEndResult MoveEndMoveBlock(void) } break; case EFFECT_RECOIL: - if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker) && gBattleStruct->moveDamage[gBattlerTarget] > 0) + if (IsBattlerTurnDamaged(gBattlerTarget, INCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerAttacker) && gBattleStruct->moveDamage[gBattlerTarget] > 0) { enum Ability ability = GetBattlerAbility(gBattlerAttacker); if (IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_ROCK_HEAD) @@ -3108,7 +3108,7 @@ static enum MoveEndResult MoveEndMoveBlock(void) } break; case EFFECT_CHLOROBLAST: - if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) + if (IsBattlerTurnDamaged(gBattlerTarget, INCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerAttacker)) { enum Ability ability = GetBattlerAbility(gBattlerAttacker); if (IsAbilityAndRecord(gBattlerAttacker, ability, ABILITY_ROCK_HEAD) @@ -3123,7 +3123,7 @@ static enum MoveEndResult MoveEndMoveBlock(void) } break; case EFFECT_RAPID_SPIN: - if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) + if (IsBattlerTurnDamaged(gBattlerTarget, INCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerAttacker)) { BattleScriptCall(BattleScript_RapidSpinAway); result = MOVEEND_RESULT_RUN_SCRIPT; @@ -3132,7 +3132,7 @@ static enum MoveEndResult MoveEndMoveBlock(void) case EFFECT_FELL_STINGER: if (IsBattlerAlive(gBattlerAttacker) && !IsBattlerAlive(gBattlerTarget) - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && !NoAliveMonsForEitherParty() && CompareStat(gBattlerAttacker, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, GetBattlerAbility(gBattlerAttacker))) { @@ -3145,7 +3145,7 @@ static enum MoveEndResult MoveEndMoveBlock(void) break; case EFFECT_STONE_AXE: if (!IsHazardOnSide(side, HAZARDS_STEALTH_ROCK) - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, INCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerAttacker)) { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_POINTEDSTONESFLOAT; @@ -3156,7 +3156,7 @@ static enum MoveEndResult MoveEndMoveBlock(void) break; case EFFECT_CEASELESS_EDGE: if (gSideTimers[side].spikesAmount < 3 - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, INCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerAttacker)) { gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SPIKESSCATTERED; @@ -3216,7 +3216,7 @@ static enum MoveEndResult MoveEndShellTrap(void) // Set ShellTrap to activate after the attacker's turn if target was hit by a physical move. if (GetMoveEffect(gChosenMoveByBattler[battlerDef]) == EFFECT_SHELL_TRAP && IsBattleMovePhysical(gCurrentMove) - && IsBattlerTurnDamaged(battlerDef) + && IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && gProtectStructs[battlerDef].physicalBattlerId == gBattlerAttacker) { gProtectStructs[battlerDef].shellTrap = TRUE; @@ -3265,7 +3265,7 @@ static enum MoveEndResult MoveEndKeeMarangaHpThresholdItemTarget(void) static bool32 TryRedCard(enum BattlerId battlerAtk, enum BattlerId redCardBattler, enum Move move) { if (!IsBattlerAlive(redCardBattler) - || !IsBattlerTurnDamaged(redCardBattler) + || !IsBattlerTurnDamaged(redCardBattler, EXCLUDING_SUBSTITUTES) || DoesSubstituteBlockMove(battlerAtk, redCardBattler, move) || !CanBattlerSwitch(battlerAtk)) return FALSE; @@ -3287,7 +3287,7 @@ static bool32 TryRedCard(enum BattlerId battlerAtk, enum BattlerId redCardBattle static bool32 TryEjectButton(enum BattlerId battlerAtk, u32 ejectButtonBattler) { - if (!IsBattlerTurnDamaged(ejectButtonBattler) + if (!IsBattlerTurnDamaged(ejectButtonBattler, EXCLUDING_SUBSTITUTES) || !IsBattlerAlive(ejectButtonBattler) || !CanBattlerSwitch(ejectButtonBattler)) return FALSE; @@ -3434,7 +3434,7 @@ static enum MoveEndResult MoveEndHitEscape(void) if (GetMoveEffect(gCurrentMove) == EFFECT_HIT_ESCAPE && !gBattleStruct->unableToUseMove - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, INCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerAttacker) && !NoAliveMonsForBattlerSide(gBattlerTarget)) { @@ -3463,7 +3463,7 @@ static enum MoveEndResult MoveEndPickpocket(void) && !IsBattlerUnaffectedByMove(battlerDef) && GetBattlerAbility(battlerDef) == ABILITY_PICKPOCKET && IsMoveMakingContact(gBattlerAttacker, battlerDef, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), gCurrentMove) - && IsBattlerTurnDamaged(battlerDef) + && IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && !DoesSubstituteBlockMove(gBattlerAttacker, battlerDef, gCurrentMove) && IsBattlerAlive(battlerDef) && gBattleMons[battlerDef].item == ITEM_NONE @@ -3560,7 +3560,7 @@ static enum MoveEndResult MoveEndThirdMoveBlock(void) switch (moveEffect) { case EFFECT_STEEL_ROLLER: - if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && IsBattlerTurnDamaged(gBattlerTarget)) + if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && IsBattlerTurnDamaged(gBattlerTarget, INCLUDING_SUBSTITUTES)) { BattleScriptCall(BattleScript_RemoveTerrain); result = MOVEEND_RESULT_RUN_SCRIPT; @@ -3570,7 +3570,7 @@ static enum MoveEndResult MoveEndThirdMoveBlock(void) if (gFieldStatuses & STATUS_FIELD_TERRAIN_ANY && gLastPrintedMoves[gBattlerAttacker] == gCurrentMove && IsBattlerAlive(gBattlerAttacker) - && IsBattlerTurnDamaged(gBattlerTarget)) + && IsBattlerTurnDamaged(gBattlerTarget, INCLUDING_SUBSTITUTES)) { BattleScriptCall(BattleScript_RemoveTerrain); result = MOVEEND_RESULT_RUN_SCRIPT; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 7736a57ae3..47db114160 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2423,6 +2423,26 @@ static inline bool32 IgnoreTargetingForMoveEffect(enum MoveEffect moveEffect) // } } +static bool32 DoesSubstituteBlockMoveEffectOnTarget(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum MoveEffect moveEffect) +{ + if (battlerAtk == battlerDef) + return FALSE; + + if (moveEffect != MOVE_EFFECT_BUG_BITE && IgnoreTargetingForMoveEffect(moveEffect)) + return FALSE; + + if (moveEffect == MOVE_EFFECT_CORE_ENFORCER) + return FALSE; + + if (moveEffect == MOVE_EFFECT_BREAK_SCREEN) + return FALSE; + + if (DoesSubstituteBlockMove(battlerAtk, battlerDef, gCurrentMove)) + return TRUE; + + return FALSE; +} + void SetMoveEffect(enum BattlerId battlerAtk, enum BattlerId effectBattler, enum MoveEffect moveEffect, const u8 *battleScript, enum SetMoveEffectFlags effectFlags) { enum Ability abilities[MAX_BATTLERS_COUNT] = {ABILITY_NONE}; @@ -2455,7 +2475,7 @@ void SetMoveEffect(enum BattlerId battlerAtk, enum BattlerId effectBattler, enum moveEffect = MOVE_EFFECT_NONE; else if (!IsBattlerAlive(gEffectBattler) && !IgnoreTargetingForMoveEffect(moveEffect)) moveEffect = MOVE_EFFECT_NONE; - else if (DoesSubstituteBlockMove(gBattlerAttacker, gEffectBattler, gCurrentMove) && !affectsUser) + else if (DoesSubstituteBlockMoveEffectOnTarget(gBattlerAttacker, gEffectBattler, moveEffect)) moveEffect = MOVE_EFFECT_NONE; gBattleScripting.moveEffect = moveEffect; // ChangeStatBuffs still needs the global moveEffect @@ -2824,7 +2844,7 @@ void SetMoveEffect(enum BattlerId battlerAtk, enum BattlerId effectBattler, enum if (gBattleMons[gEffectBattler].statStages[i] != DEFAULT_STAT_STAGE) break; } - if (IsBattlerTurnDamaged(gEffectBattler) && i != NUM_BATTLE_STATS) + if (IsBattlerTurnDamaged(gEffectBattler, EXCLUDING_SUBSTITUTES) && i != NUM_BATTLE_STATS) { for (i = 0; i < NUM_BATTLE_STATS; i++) gBattleMons[gEffectBattler].statStages[i] = DEFAULT_STAT_STAGE; @@ -3714,7 +3734,7 @@ static void SetToxicChainPriority(void) if (abilityAtk == ABILITY_TOXIC_CHAIN && IsBattlerAlive(gBattlerTarget) && CanBePoisoned(gBattlerAttacker, gBattlerTarget, abilityAtk, GetBattlerAbility(gBattlerTarget)) - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && RandomWeighted(RNG_TOXIC_CHAIN, 7, 3)) gBattleStruct->toxicChainPriority = TRUE; } diff --git a/src/battle_util.c b/src/battle_util.c index 120a2b19af..03df1960cc 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2313,7 +2313,7 @@ u32 NumFaintedBattlersByAttacker(enum BattlerId battlerAtk) if (battler == battlerAtk) continue; - if (IsBattlerTurnDamaged(battler) && !IsBattlerAlive(battler)) + if (IsBattlerTurnDamaged(battler, EXCLUDING_SUBSTITUTES) && !IsBattlerAlive(battler)) numMonsFainted++; } @@ -3835,7 +3835,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum switch (gLastUsedAbility) { case ABILITY_COLOR_CHANGE: - if (IsBattlerTurnDamaged(battler) + if (IsBattlerTurnDamaged(battler, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(battler) && !IS_BATTLER_OF_TYPE(battler, moveType) && move != MOVE_STRUGGLE @@ -3850,7 +3850,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum } break; case ABILITY_BERSERK: - if (IsBattlerTurnDamaged(battler) + if (IsBattlerTurnDamaged(battler, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(battler) && HadMoreThanHalfHpNowDoesnt(battler) && CompareStat(battler, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) @@ -3862,7 +3862,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum } break; case ABILITY_ANGER_SHELL: - if (IsBattlerTurnDamaged(battler) + if (IsBattlerTurnDamaged(battler, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(battler) && HadMoreThanHalfHpNowDoesnt(battler)) { @@ -3879,7 +3879,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum switch (gLastUsedAbility) { case ABILITY_JUSTIFIED: - if (IsBattlerTurnDamaged(battler) + if (IsBattlerTurnDamaged(battler, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(battler) && moveType == TYPE_DARK && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) @@ -3891,7 +3891,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum } break; case ABILITY_RATTLED: - if (IsBattlerTurnDamaged(battler) + if (IsBattlerTurnDamaged(battler, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(battler) && (moveType == TYPE_DARK || moveType == TYPE_BUG || moveType == TYPE_GHOST) && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) @@ -3903,7 +3903,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum } break; case ABILITY_WATER_COMPACTION: - if (IsBattlerTurnDamaged(battler) + if (IsBattlerTurnDamaged(battler, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(battler) && moveType == TYPE_WATER && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) @@ -3916,7 +3916,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum break; case ABILITY_STAMINA: if (gBattlerAttacker != gBattlerTarget - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(battler) && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) { @@ -3927,7 +3927,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum } break; case ABILITY_WEAK_ARMOR: - if (IsBattlerTurnDamaged(battler) + if (IsBattlerTurnDamaged(battler, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(battler) && IsBattleMovePhysical(gCurrentMove) && (CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) // Don't activate if both Speed and Defense cannot be raised. @@ -3941,7 +3941,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum } break; case ABILITY_CURSED_BODY: - if (IsBattlerTurnDamaged(gBattlerTarget) + if (IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && gBattleMons[gBattlerAttacker].volatiles.disabledMove == MOVE_NONE && IsBattlerAlive(gBattlerAttacker) && !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL) @@ -3958,7 +3958,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum case ABILITY_LINGERING_AROMA: case ABILITY_MUMMY: if (IsBattlerAlive(gBattlerAttacker) - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) && gBattleMons[gBattlerAttacker].volatiles.overwrittenAbility != GetBattlerAbility(gBattlerTarget) && gBattleMons[gBattlerAttacker].ability != ABILITY_MUMMY @@ -3981,7 +3981,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum break; case ABILITY_WANDERING_SPIRIT: if (IsBattlerAlive(gBattlerAttacker) - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) && !(GetActiveGimmick(gBattlerTarget) == GIMMICK_DYNAMAX) && !gAbilitiesInfo[gBattleMons[gBattlerAttacker].ability].cantBeSwapped) @@ -4008,7 +4008,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum break; case ABILITY_ANGER_POINT: if (gSpecialStatuses[battler].criticalHit - && IsBattlerTurnDamaged(battler) + && IsBattlerTurnDamaged(battler, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(battler) && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility)) { @@ -4022,7 +4022,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum if (IsBattlerAlive(gBattlerAttacker) && (CompareStat(gBattlerAttacker, STAT_SPEED, MIN_STAT_STAGE, CMP_GREATER_THAN, gLastUsedAbility) || GetBattlerAbility(gBattlerAttacker) == ABILITY_MIRROR_ARMOR) && !gBattleStruct->unableToUseMove - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move)) { SET_STATCHANGER(STAT_SPEED, 1, TRUE); @@ -4035,7 +4035,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum case ABILITY_IRON_BARBS: if (IsBattlerAlive(gBattlerAttacker) && !gBattleStruct->unableToUseMove - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move)) { SetPassiveDamageAmount(gBattlerAttacker, GetNonDynamaxMaxHP(gBattlerAttacker) / (B_ROUGH_SKIN_DMG >= GEN_4 ? 8 : 16)); @@ -4108,7 +4108,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum if (i < sleep && IsBattlerAlive(gBattlerAttacker) && !gBattleStruct->unableToUseMove - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && CanBeSlept(gBattlerTarget, gBattlerAttacker, abilityAtk, NOT_BLOCKED_BY_SLEEP_CLAUSE) && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, abilityAtk, holdEffectAtk, move)) { @@ -4132,7 +4132,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker); if (IsBattlerAlive(gBattlerAttacker) && !gBattleStruct->unableToUseMove - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && CanBePoisoned(gBattlerTarget, gBattlerAttacker, gLastUsedAbility, abilityAtk) && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, abilityAtk, GetBattlerHoldEffect(gBattlerAttacker), move)) { @@ -4154,7 +4154,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum enum Ability abilityAtk = GetBattlerAbility(gBattlerAttacker); if (IsBattlerAlive(gBattlerAttacker) && !gBattleStruct->unableToUseMove - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && CanBeParalyzed(gBattlerTarget, gBattlerAttacker, abilityAtk) && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, abilityAtk, GetBattlerHoldEffect(gBattlerAttacker), move)) { @@ -4172,7 +4172,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum if (IsBattlerAlive(gBattlerAttacker) && !gBattleStruct->unableToUseMove && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && CanBeBurned(gBattlerTarget, gBattlerAttacker, GetBattlerAbility(gBattlerAttacker)) && (GetConfig(B_ABILITY_TRIGGER_CHANCE) >= GEN_4 ? RandomPercentage(RNG_FLAME_BODY, 30) : RandomChance(RNG_FLAME_BODY, 1, 3))) { @@ -4187,7 +4187,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum case ABILITY_CUTE_CHARM: if (IsBattlerAlive(gBattlerAttacker) && !gBattleStruct->unableToUseMove - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerTarget) && (GetConfig(B_ABILITY_TRIGGER_CHANCE) >= GEN_4 ? RandomPercentage(RNG_CUTE_CHARM, 30) : RandomChance(RNG_CUTE_CHARM, 1, 3)) && !(gBattleMons[gBattlerAttacker].volatiles.infatuation) @@ -4202,7 +4202,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum } break; case ABILITY_ILLUSION: - if (gBattleStruct->illusion[gBattlerTarget].state == ILLUSION_ON && IsBattlerTurnDamaged(gBattlerTarget)) + if (gBattleStruct->illusion[gBattlerTarget].state == ILLUSION_ON && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES)) { gBattleScripting.battler = gBattlerTarget; BattleScriptCall(BattleScript_IllusionOff); @@ -4210,7 +4210,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum } break; case ABILITY_COTTON_DOWN: - if (IsBattlerTurnDamaged(gBattlerTarget)) + if (IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES)) { gEffectBattler = gBattlerTarget; // Will ability popup activate if there is only one target? @@ -4219,7 +4219,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum } break; case ABILITY_STEAM_ENGINE: - if (IsBattlerTurnDamaged(gBattlerTarget) + if (IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(battler) && CompareStat(battler, STAT_SPEED, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) && (moveType == TYPE_FIRE || moveType == TYPE_WATER)) @@ -4232,7 +4232,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum break; case ABILITY_SAND_SPIT: if (!gBattleStruct->unableToUseMove - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && !(gBattleWeather & B_WEATHER_SANDSTORM && HasWeatherEffect())) { if (gBattleWeather & B_WEATHER_PRIMAL_ANY && HasWeatherEffect()) @@ -4250,7 +4250,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum break; case ABILITY_PERISH_BODY: if (!gBattleStruct->unableToUseMove - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(battler) && !CanBattlerAvoidContactEffects(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) && !gBattleMons[gBattlerAttacker].volatiles.perishSong) @@ -4268,7 +4268,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum break; case ABILITY_SEED_SOWER: if (!gBattleStruct->unableToUseMove - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerTarget) && TryChangeBattleTerrain(gBattlerTarget, STATUS_FIELD_GRASSY_TERRAIN)) { @@ -4277,7 +4277,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum } break; case ABILITY_THERMAL_EXCHANGE: - if (IsBattlerTurnDamaged(gBattlerTarget) + if (IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerTarget) && CompareStat(gBattlerTarget, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility) && moveType == TYPE_FIRE) @@ -4294,7 +4294,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum // fall through case ABILITY_ELECTROMORPHOSIS: if (!gBattleStruct->unableToUseMove - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && IsBattlerAlive(gBattlerTarget)) { BattleScriptCall(BattleScript_WindPowerActivates); @@ -4305,7 +4305,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum if (!gBattleStruct->isSkyBattle && !gBattleStruct->unableToUseMove && IsBattleMovePhysical(gCurrentMove) - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && (gSideTimers[GetBattlerSide(gBattlerAttacker)].toxicSpikesAmount != 2)) { SaveBattlerTarget(gBattlerTarget); @@ -4328,7 +4328,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum && !gBattleStruct->unableToUseMove && CanBePoisoned(gBattlerAttacker, gBattlerTarget, gLastUsedAbility, GetBattlerAbility(gBattlerTarget)) && IsMoveMakingContact(gBattlerAttacker, gBattlerTarget, GetBattlerAbility(gBattlerAttacker), GetBattlerHoldEffect(gBattlerAttacker), move) - && IsBattlerTurnDamaged(gBattlerTarget) // Need to actually hit the target + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) // Need to actually hit the target && RandomPercentage(RNG_POISON_TOUCH, 30)) { gEffectBattler = gBattlerTarget; @@ -4355,7 +4355,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum if (IsBattlerAlive(gBattlerTarget) && !gBattleStruct->unableToUseMove && RandomChance(RNG_STENCH, 1, 10) - && IsBattlerTurnDamaged(gBattlerTarget) + && IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) && !MoveHasAdditionalEffect(gCurrentMove, MOVE_EFFECT_FLINCH)) { SetMoveEffect(gBattlerAttacker, gBattlerTarget, MOVE_EFFECT_FLINCH, gBattlescriptCurrInstr, EFFECT_PRIMARY); @@ -4382,7 +4382,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum speciesForm = gBattleMons[gBattlerTarget].species; if (gBattleStruct->unableToUseMove - || !IsBattlerTurnDamaged(gBattlerTarget) + || !IsBattlerTurnDamaged(gBattlerTarget, EXCLUDING_SUBSTITUTES) || !TryBattleFormChange(gBattlerTarget, FORM_CHANGE_BATTLE_HIT_BY_MOVE_CATEGORY, ability)) break; @@ -4472,7 +4472,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum { if (gBattleMons[battlerDef].item != ITEM_NONE && battlerDef != battler - && IsBattlerTurnDamaged(battlerDef) + && IsBattlerTurnDamaged(battlerDef, EXCLUDING_SUBSTITUTES) && CanStealItem(battler, battlerDef, gBattleMons[battlerDef].item) && !GetBattlerPartyState(battlerDef)->isKnockedOff && !DoesSubstituteBlockMove(battler, battlerDef, move) @@ -10008,7 +10008,7 @@ bool32 HasWeatherEffect(void) void UpdateStallMons(void) { - if (IsBattlerTurnDamaged(gBattlerTarget) || GetMoveCategory(gCurrentMove) == DAMAGE_CATEGORY_STATUS) + if (IsBattlerTurnDamaged(gBattlerTarget, INCLUDING_SUBSTITUTES) || GetMoveCategory(gCurrentMove) == DAMAGE_CATEGORY_STATUS) return; struct BattleContext ctx = {0}; @@ -10702,7 +10702,7 @@ bool32 IsAnyTargetTurnDamaged(enum BattlerId battlerAtk) { if (battlerDef == battlerAtk) continue; - if (IsBattlerTurnDamaged(battlerDef)) + if (IsBattlerTurnDamaged(battlerDef, INCLUDING_SUBSTITUTES)) return TRUE; } return FALSE; diff --git a/test/battle/gimmick/zmove.c b/test/battle/gimmick/zmove.c index cabfb60481..b0464e8ca3 100644 --- a/test/battle/gimmick/zmove.c +++ b/test/battle/gimmick/zmove.c @@ -598,6 +598,24 @@ SINGLE_BATTLE_TEST("(Z-MOVE) Genesis Supernova sets up psychic terrain") } } +SINGLE_BATTLE_TEST("(Z-MOVE) Genesis Supernova sets up psychic terrain when the target is behind a Substitute") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_GENESIS_SUPERNOVA, MOVE_EFFECT_PSYCHIC_TERRAIN)); + PLAYER(SPECIES_MEW) { Item(ITEM_MEWNIUM_Z); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_PSYCHIC, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GENESIS_SUPERNOVA, player); + SUB_HIT(opponent); + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); } + MESSAGE("The opposing Wobbuffet is protected by the Psychic Terrain!"); + } +} + SINGLE_BATTLE_TEST("(Z-MOVE) Splintered Stormshards removes terrain") { GIVEN { @@ -616,6 +634,25 @@ SINGLE_BATTLE_TEST("(Z-MOVE) Splintered Stormshards removes terrain") } } +SINGLE_BATTLE_TEST("(Z-MOVE) Splintered Stormshards removes terrain when the target is behind a Substitute") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SPLINTERED_STORMSHARDS) == EFFECT_ICE_SPINNER); + PLAYER(SPECIES_LYCANROC_DUSK) { Item(ITEM_LYCANIUM_Z); } + OPPONENT(SPECIES_TAPU_LELE) { Ability(ABILITY_PSYCHIC_SURGE); HP(1000); MaxHP(1000); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_STONE_EDGE, gimmick: GIMMICK_Z_MOVE); } + TURN { MOVE(player, MOVE_QUICK_ATTACK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ZMOVE_ACTIVATE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLINTERED_STORMSHARDS, player); + SUB_HIT(opponent); + MESSAGE("The weirdness disappeared from the battlefield!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, player); + } +} + SINGLE_BATTLE_TEST("(Z-MOVE) Clangorous Soulblaze boosts all the user's stats by one stage") { GIVEN { diff --git a/test/battle/move_effect/absorb.c b/test/battle/move_effect/absorb.c index b6a39d05ed..7eb55c253d 100644 --- a/test/battle/move_effect/absorb.c +++ b/test/battle/move_effect/absorb.c @@ -104,7 +104,24 @@ SINGLE_BATTLE_TEST("Absorb does not drain any HP if user flinched") } } -TO_DO_BATTLE_TEST("Absorb recovers 50% of the damage dealt to a Substitute"); +SINGLE_BATTLE_TEST("Absorb recovers 50% of the damage dealt to a Substitute") +{ + u16 damage; + s16 healing; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_ABSORB); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ABSORB, opponent); + SUB_HIT(player, captureDamage: &damage); + HP_BAR(opponent, captureDamage: &healing); + } THEN { + EXPECT_MUL_EQ(damage, Q_4_12(-0.5), healing); + } +} SINGLE_BATTLE_TEST("Absorb does not drain any HP if user does 0 damage") { diff --git a/test/battle/move_effect/ceaseless_edge.c b/test/battle/move_effect/ceaseless_edge.c index 288045945f..97e9fcd73d 100644 --- a/test/battle/move_effect/ceaseless_edge.c +++ b/test/battle/move_effect/ceaseless_edge.c @@ -94,3 +94,20 @@ SINGLE_BATTLE_TEST("Ceaseless Edge does not set up hazards if target was not hit } } } + +SINGLE_BATTLE_TEST("Ceaseless Edge will set up rocks if the target is behind a Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_CEASELESS_EDGE); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CEASELESS_EDGE, opponent); + SUB_HIT(player); + HP_BAR(player); + } +} diff --git a/test/battle/move_effect/chloroblast.c b/test/battle/move_effect/chloroblast.c index cd81496934..30bbd76d42 100644 --- a/test/battle/move_effect/chloroblast.c +++ b/test/battle/move_effect/chloroblast.c @@ -156,3 +156,18 @@ SINGLE_BATTLE_TEST("Chloroblast is not affected by Reckless", s16 damage) EXPECT_EQ(results[0].damage, results[1].damage); } } + +SINGLE_BATTLE_TEST("Chloroblast has recoil if the target is behind a Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { HP(400); MaxHP(400); } + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_CHLOROBLAST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CHLOROBLAST, opponent); + SUB_HIT(player); + HP_BAR(opponent, damage: 200); + } +} diff --git a/test/battle/move_effect/core_enforcer.c b/test/battle/move_effect/core_enforcer.c new file mode 100644 index 0000000000..a8b45b108b --- /dev/null +++ b/test/battle/move_effect/core_enforcer.c @@ -0,0 +1,54 @@ +#include "global.h" +#include "test/battle.h" + +SINGLE_BATTLE_TEST("Core Enforcer suppresses the ability of targets that have already acted") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_LEVITATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CORE_ENFORCER); } + TURN { MOVE(player, MOVE_EARTHQUAKE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CORE_ENFORCER, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Core Enforcer doesn't suppresses the ability of targets that haven't acted") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_LEVITATE); } + } WHEN { + TURN { MOVE(player, MOVE_CORE_ENFORCER); MOVE(opponent, MOVE_CELEBRATE); } + TURN { MOVE(player, MOVE_EARTHQUAKE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CORE_ENFORCER, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, player); + HP_BAR(opponent); + } + } +} + +SINGLE_BATTLE_TEST("Core Enforcer suppresses the ability of targets that have already acted that are behind Substitutes") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_LEVITATE); } + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_CORE_ENFORCER); } + TURN { MOVE(player, MOVE_EARTHQUAKE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CORE_ENFORCER, player); + SUB_HIT(opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, player); + SUB_HIT(opponent); + } +} diff --git a/test/battle/move_effect/hit_escape.c b/test/battle/move_effect/hit_escape.c index b4e3e2f4e5..b244c4b161 100644 --- a/test/battle/move_effect/hit_escape.c +++ b/test/battle/move_effect/hit_escape.c @@ -207,3 +207,17 @@ SINGLE_BATTLE_TEST("Hit Escape: U-turn will fail to switch if the user faints") HP_BAR(opponent); } } + +SINGLE_BATTLE_TEST("Hit Escape: U-turn will switch if the target is behind a Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_U_TURN); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_U_TURN, opponent); + } +} diff --git a/test/battle/move_effect/ice_spinner.c b/test/battle/move_effect/ice_spinner.c index 82bf1c7d41..032da8a51d 100644 --- a/test/battle/move_effect/ice_spinner.c +++ b/test/battle/move_effect/ice_spinner.c @@ -125,3 +125,20 @@ AI_SINGLE_BATTLE_TEST("Ice Spinner can be chosen by AI regardless if there is a } } } + +SINGLE_BATTLE_TEST("Ice Spinner will remove terrain if target is behind a Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_GRASSY_TERRAIN); } + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_ICE_SPINNER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ICE_SPINNER, opponent); + SUB_HIT(player); + NOT HP_BAR(player); + } +} diff --git a/test/battle/move_effect/max_hp_50_recoil.c b/test/battle/move_effect/max_hp_50_recoil.c index 566b6ced98..edffb7e952 100644 --- a/test/battle/move_effect/max_hp_50_recoil.c +++ b/test/battle/move_effect/max_hp_50_recoil.c @@ -147,3 +147,18 @@ SINGLE_BATTLE_TEST("Steel Beam is not blocked by Damp") } } } + +SINGLE_BATTLE_TEST("Steel Beam inflicts recoil if it hits a Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(400); MaxHP(400); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); MOVE(player, MOVE_STEEL_BEAM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEEL_BEAM, player); + SUB_HIT(opponent); + HP_BAR(player, damage: 200); + } +} diff --git a/test/battle/move_effect/rapid_spin.c b/test/battle/move_effect/rapid_spin.c index 0ef6cce558..a41ffc5d11 100644 --- a/test/battle/move_effect/rapid_spin.c +++ b/test/battle/move_effect/rapid_spin.c @@ -120,3 +120,27 @@ SINGLE_BATTLE_TEST("Rapid Spin doesn't blow away Wrap, hazards or raise Speed wh } } } + +SINGLE_BATTLE_TEST("Rapid Spin and Mortal Spin will remove hazards if the target is behind a Substitute") +{ + enum Move move; + + PARAMETRIZE { move = MOVE_RAPID_SPIN; } + PARAMETRIZE { move = MOVE_MORTAL_SPIN; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_STEALTH_ROCK); } + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, move); } + TURN { SWITCH(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEALTH_ROCK, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + SUB_HIT(player); + NOT HP_BAR(opponent); + } +} diff --git a/test/battle/move_effect/steel_roller.c b/test/battle/move_effect/steel_roller.c index 3ca334480c..762cff8169 100644 --- a/test/battle/move_effect/steel_roller.c +++ b/test/battle/move_effect/steel_roller.c @@ -73,3 +73,19 @@ AI_SINGLE_BATTLE_TEST("Steel Roller wont be chosen by AI if there is no terrain } } } + +SINGLE_BATTLE_TEST("Steel Roller will remove terrain if target is behind a Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_GRASSY_TERRAIN); } + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_STEEL_ROLLER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_GRASSY_TERRAIN, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STEEL_ROLLER, opponent); + NOT HP_BAR(player); + } +} diff --git a/test/battle/move_effect/stone_axe.c b/test/battle/move_effect/stone_axe.c index d65798d18b..34a00c000b 100644 --- a/test/battle/move_effect/stone_axe.c +++ b/test/battle/move_effect/stone_axe.c @@ -92,3 +92,20 @@ SINGLE_BATTLE_TEST("Stone Axe fails to set up hazards if user faints") NOT MESSAGE("Pointed stones float in the air around the opposing team!"); } } + +SINGLE_BATTLE_TEST("Stone Axe will set up rocks if the target is behind a Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_STONE_AXE); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_STONE_AXE, opponent); + SUB_HIT(player); + HP_BAR(player); + } +} diff --git a/test/battle/move_effect_secondary/break_screens.c b/test/battle/move_effect_secondary/break_screens.c index 3035002a0d..f400795fed 100644 --- a/test/battle/move_effect_secondary/break_screens.c +++ b/test/battle/move_effect_secondary/break_screens.c @@ -159,3 +159,25 @@ DOUBLE_BATTLE_TEST("Brick Break and Psychic Fangs can remove Light Screen, Refle } } +SINGLE_BATTLE_TEST("Brick Break and Psychic Fangs can remove screens when the target is behind a Substitute") +{ + enum Move move; + + PARAMETRIZE { move = MOVE_BRICK_BREAK; } + PARAMETRIZE { move = MOVE_PSYCHIC_FANGS; } + + GIVEN { + + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REFLECT); } + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, move); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + ANIMATION(ANIM_TYPE_MOVE, move, opponent); + MESSAGE("The wall shattered!"); + SUB_HIT(player); + } +} diff --git a/test/battle/move_effect_secondary/ion_deluge.c b/test/battle/move_effect_secondary/ion_deluge.c index 003bfc825f..51605eb59b 100644 --- a/test/battle/move_effect_secondary/ion_deluge.c +++ b/test/battle/move_effect_secondary/ion_deluge.c @@ -116,3 +116,22 @@ SINGLE_BATTLE_TEST("Plasma Fists turns normal type dynamax-moves into electric t MESSAGE("It's super effective!"); } } + +SINGLE_BATTLE_TEST("Plasma Fists turns normal moves into electric moves even if it hits a substitute") +{ + GIVEN { + PLAYER(SPECIES_JOLTEON) { Ability(ABILITY_VOLT_ABSORB); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); } + TURN { MOVE(player, MOVE_PLASMA_FISTS); MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PLASMA_FISTS, player); + SUB_HIT(opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent); + HP_BAR(player); + } + } +} diff --git a/test/battle/move_flags/recoil.c b/test/battle/move_flags/recoil.c index 2593a0e1f6..1c59c7525f 100644 --- a/test/battle/move_flags/recoil.c +++ b/test/battle/move_flags/recoil.c @@ -137,3 +137,23 @@ SINGLE_BATTLE_TEST("Recoil: No recoil is taken if the move is blocked by Disguis EXPECT_EQ(player->hp, player->maxHP); } } + +SINGLE_BATTLE_TEST("Recoil: Hitting substitutes inflicts recoil") +{ + u16 damage; + s16 recoil; + GIVEN { + ASSUME(GetMoveRecoil(MOVE_TAKE_DOWN) == 25); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_TAKE_DOWN); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAKE_DOWN, opponent); + SUB_HIT(player, captureDamage: &damage); + HP_BAR(opponent, captureDamage: &recoil); + } THEN { + EXPECT_MUL_EQ(damage, Q_4_12(0.25), recoil); + } +}