From cfb4f864e953f2108bc7c88fc3f33044c7dbb33a Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Fri, 1 May 2026 15:18:43 +0200 Subject: [PATCH] Fixes Ruin abilities / gastro acid interaction (#9880) --- data/battle_scripts_1.s | 16 ++--- include/battle_util.h | 1 + src/battle_script_commands.c | 1 + src/battle_util.c | 37 ++++++++--- test/battle/ability/beads_of_ruin.c | 97 ++++++++++++++++++++++++++++ test/battle/ability/vessel_of_ruin.c | 25 +++++++ 6 files changed, 158 insertions(+), 19 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 9bb9adf948..a33a5809fb 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -7775,23 +7775,19 @@ BattleScript_PastelVeilEnd: return BattleScript_NeutralizingGasExits:: - saveattacker savetarget pause B_WAIT_TIME_SHORT printstring STRINGID_NEUTRALIZINGGASOVER waitmessage B_WAIT_TIME_LONG - setbyte gBattlerAttacker, 0 + setbyte gBattlerTarget, 0 sortbattlers BattleScript_NeutralizingGasExitsLoop: - copyarraywithindex gBattlerTarget, gBattlersBySpeed, gBattlerAttacker, 1 - jumpifabilitycantbereactivated BS_TARGET, BattleScript_NeutralizingGasExitsLoopIncrement - saveattacker - switchinabilities BS_TARGET - restoreattacker + copyarraywithindex gEffectBattler, gBattlersBySpeed, gBattlerTarget, 1 + jumpifabilitycantbereactivated BS_EFFECT_BATTLER, BattleScript_NeutralizingGasExitsLoopIncrement + switchinabilities BS_EFFECT_BATTLER BattleScript_NeutralizingGasExitsLoopIncrement: - addbyte gBattlerAttacker, 1 - jumpifbytenotequal gBattlerAttacker, gBattlersCount, BattleScript_NeutralizingGasExitsLoop - restoreattacker + addbyte gBattlerTarget, 1 + jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_NeutralizingGasExitsLoop restoretarget return diff --git a/include/battle_util.h b/include/battle_util.h index ac042f5e00..709f0c47ab 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -408,6 +408,7 @@ bool32 HasPartnerTrainer(enum BattlerId battler); bool32 IsAffectedByPowderMove(enum BattlerId battler, enum Ability ability, enum HoldEffect holdEffect); enum Move GetNaturePowerMove(void); void RemoveAbilityFlags(enum BattlerId battler); +void RemoveRuinAbilityFlags(enum BattlerId battler); void CheckSetUnburden(enum BattlerId battler); bool32 IsDazzlingAbility(enum Ability ability); bool32 IsAllowedToUseBag(void); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 65c149f0c6..38502d0497 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -9943,6 +9943,7 @@ static void Cmd_setgastroacid(void) if (gBattleMons[gBattlerTarget].volatiles.neutralizingGas) gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = TRUE; + RemoveRuinAbilityFlags(gBattlerTarget); gBattleMons[gBattlerTarget].volatiles.gastroAcid = TRUE; gBattlescriptCurrInstr = cmd->nextInstr; } diff --git a/src/battle_util.c b/src/battle_util.c index fd81211b8d..20c4431771 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4732,6 +4732,13 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum case ABILITYEFFECT_NEUTRALIZINGGAS: if (ability == ABILITY_NEUTRALIZING_GAS && !gBattleMons[battler].volatiles.neutralizingGas) { + for (enum BattlerId battlerDef = B_BATTLER_0; battlerDef < gBattlersCount; battlerDef++) + { + if (battler == battlerDef || GetBattlerHoldEffectIgnoreAbility(battlerDef) == HOLD_EFFECT_ABILITY_SHIELD) + continue; + RemoveRuinAbilityFlags(battlerDef); + } + gBattleMons[battler].volatiles.neutralizingGas = TRUE; gBattlerAbility = battler; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_NEUTRALIZING_GAS; @@ -6862,17 +6869,8 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct BattleContext *ctx) static bool32 IsRuinStatusActive(u32 fieldEffect) { - bool32 isNeutralizingGasOnField = IsNeutralizingGasOnField(); for (enum BattlerId battler = 0; battler < gBattlersCount; battler++) { - // Mold Breaker doesn't ignore Ruin field status but Gastro Acid and Neutralizing Gas do - if (gBattleMons[battler].volatiles.gastroAcid) - continue; - if (GetBattlerHoldEffectIgnoreAbility(battler) != HOLD_EFFECT_ABILITY_SHIELD - && isNeutralizingGasOnField - && gBattleMons[battler].ability != ABILITY_NEUTRALIZING_GAS) - continue; - if (GetBattlerVolatile(battler, fieldEffect)) return TRUE; } @@ -10739,6 +10737,27 @@ void RemoveAbilityFlags(enum BattlerId battler) } } +void RemoveRuinAbilityFlags(enum BattlerId battler) +{ + switch (GetBattlerAbility(battler)) + { + case ABILITY_VESSEL_OF_RUIN: + gBattleMons[battler].volatiles.vesselOfRuin = FALSE; + break; + case ABILITY_TABLETS_OF_RUIN: + gBattleMons[battler].volatiles.tabletsOfRuin = FALSE; + break; + case ABILITY_SWORD_OF_RUIN: + gBattleMons[battler].volatiles.swordOfRuin = FALSE; + break; + case ABILITY_BEADS_OF_RUIN: + gBattleMons[battler].volatiles.beadsOfRuin = FALSE; + break; + default: + break; + } +} + void CheckSetUnburden(enum BattlerId battler) { if (!(gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) diff --git a/test/battle/ability/beads_of_ruin.c b/test/battle/ability/beads_of_ruin.c index f6a0378e49..eba22c0b9c 100644 --- a/test/battle/ability/beads_of_ruin.c +++ b/test/battle/ability/beads_of_ruin.c @@ -189,3 +189,100 @@ DOUBLE_BATTLE_TEST("Beads of Ruin's Sp. Def reduction is ignored by Gastro Acid" EXPECT_LT(results[0].damage, results[1].damage); } } + +SINGLE_BATTLE_TEST("Beads of Ruin reduces Sp. Def if opposing mon's ability doesn't match (Neutralizing switches in and out)") +{ + s16 damage[2]; + + GIVEN { + ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); + PLAYER(SPECIES_CHI_YU) { Ability(ABILITY_BEADS_OF_RUIN); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + } WHEN { + TURN { MOVE(player, MOVE_WATER_GUN); } + TURN { SWITCH(opponent, 1); MOVE(player, MOVE_CELEBRATE); } + TURN { SWITCH(opponent, 0); MOVE(player, MOVE_WATER_GUN); } + } SCENE { + ABILITY_POPUP(player, ABILITY_BEADS_OF_RUIN); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, player); + HP_BAR(opponent, captureDamage: &damage[0]); + + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + ABILITY_POPUP(player, ABILITY_BEADS_OF_RUIN); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[1], damage[0]); + } +} + +DOUBLE_BATTLE_TEST("Beads of Ruin will not reactivate after Sunsteel Strike faints Neutralizing Gas target") +{ + s16 damage[2]; + + GIVEN { + ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(MoveIgnoresTargetAbility(MOVE_SUNSTEEL_STRIKE)); + PLAYER(SPECIES_CHI_YU) { Ability(ABILITY_BEADS_OF_RUIN); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_WATER_GUN, target: opponentRight); + MOVE(playerRight, MOVE_SUNSTEEL_STRIKE, target: opponentLeft); + SEND_OUT(opponentLeft, 2); + } + TURN { MOVE(playerLeft, MOVE_WATER_GUN, target: opponentRight); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_NEUTRALIZING_GAS); + NOT ABILITY_POPUP(playerLeft, ABILITY_BEADS_OF_RUIN); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerLeft); + HP_BAR(opponentRight, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNSTEEL_STRIKE, playerRight); + HP_BAR(opponentLeft); + MESSAGE("The opposing Weezing fainted!"); + + NOT ABILITY_POPUP(playerLeft, ABILITY_BEADS_OF_RUIN); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerLeft); + HP_BAR(opponentRight, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[1], damage[0]); + } +} + +DOUBLE_BATTLE_TEST("Beads of Ruin will not be deactivated with Ability Shield") +{ + s16 damage[2]; + + GIVEN { + ASSUME(GetMoveCategory(MOVE_WATER_GUN) == DAMAGE_CATEGORY_SPECIAL); + ASSUME(gItemsInfo[ITEM_ABILITY_SHIELD].holdEffect == HOLD_EFFECT_ABILITY_SHIELD); + PLAYER(SPECIES_CHI_YU) { Ability(ABILITY_BEADS_OF_RUIN); Item(ITEM_ABILITY_SHIELD); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_WATER_GUN, target: opponentRight); } + TURN { SWITCH(opponentLeft, 2); MOVE(playerLeft, MOVE_WATER_GUN, target: opponentRight); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_BEADS_OF_RUIN); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerLeft); + HP_BAR(opponentRight, captureDamage: &damage[0]); + + ABILITY_POPUP(opponentLeft, ABILITY_NEUTRALIZING_GAS); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerLeft); + HP_BAR(opponentRight, captureDamage: &damage[1]); + } THEN { + EXPECT_EQ(damage[1], damage[0]); + } +} diff --git a/test/battle/ability/vessel_of_ruin.c b/test/battle/ability/vessel_of_ruin.c index 970715f543..b4b8398a31 100644 --- a/test/battle/ability/vessel_of_ruin.c +++ b/test/battle/ability/vessel_of_ruin.c @@ -146,3 +146,28 @@ DOUBLE_BATTLE_TEST("Vessel of Ruin is active if removed by Mold Breaker Entrainm EXPECT_EQ(isSwordOfRuinActive, TRUE); } } + +DOUBLE_BATTLE_TEST("Vessel of Ruin affects a Gastro Acid-suppressed Vessel of Ruin user") +{ + s16 damage[2]; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_GASTRO_ACID) == EFFECT_GASTRO_ACID); + PLAYER(SPECIES_TING_LU) { Ability(ABILITY_VESSEL_OF_RUIN); } + PLAYER(SPECIES_TING_LU) { Ability(ABILITY_VESSEL_OF_RUIN); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); } + TURN { MOVE(opponentRight, MOVE_GASTRO_ACID, target: playerRight); } + TURN { MOVE(playerRight, MOVE_WATER_GUN, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerRight); + HP_BAR(opponentLeft, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GASTRO_ACID, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerRight); + HP_BAR(opponentLeft, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[1], Q_4_12(1.33), damage[0]); + } +}