From 65a5d1e7d85668afa207253564d900634bec6105 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:58:02 +0000 Subject: [PATCH] Fixes Neutralizing Gas displaying message when exiting with multiple users (#8318) --- asm/macros/battle_script.inc | 4 +- data/battle_scripts_1.s | 3 +- src/battle_main.c | 9 +- src/battle_script_commands.c | 116 +++++++++++++++---------- src/battle_util.c | 22 ++--- test/battle/ability/neutralizing_gas.c | 43 +++++++++ 6 files changed, 129 insertions(+), 68 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 754a5221f6..517ab09e45 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1924,8 +1924,8 @@ 1: .endm - .macro jumpifabilitycantbesuppressed battler:req, jumpInstr:req - callnative BS_JumpIfAbilityCantBeSuppressed + .macro jumpifabilitycantbereactivated battler:req, jumpInstr:req + callnative BS_JumpIfAbilityCantBeReactivated .byte \battler .4byte \jumpInstr .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 640d57bc6e..8a93eb9436 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -9296,8 +9296,7 @@ BattleScript_NeutralizingGasExits:: setbyte gBattlerAttacker, 0 BattleScript_NeutralizingGasExitsLoop: copyarraywithindex gBattlerTarget, gBattlerByTurnOrder, gBattlerAttacker, 1 - jumpifabilitycantbesuppressed BS_TARGET, BattleScript_NeutralizingGasExitsLoopIncrement - jumpifability BS_TARGET, ABILITY_IMPOSTER, BattleScript_NeutralizingGasExitsLoopIncrement @ Imposter only activates when first entering the field + jumpifabilitycantbereactivated BS_TARGET, BattleScript_NeutralizingGasExitsLoopIncrement saveattacker switchinabilities BS_TARGET restoreattacker diff --git a/src/battle_main.c b/src/battle_main.c index 3c992bc87f..c8c8480007 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3861,8 +3861,13 @@ static void TryDoEventsBeforeFirstTurn(void) gBattleStruct->eventsBeforeFirstTurnState++; break; case FIRST_TURN_EVENTS_NEUTRALIZING_GAS: - if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, 0, 0, 0, 0) != 0) - return; + while (gBattleStruct->switchInBattlerCounter < gBattlersCount) // From fastest to slowest + { + i = gBattlerByTurnOrder[gBattleStruct->switchInBattlerCounter++]; + if (AbilityBattleEffects(ABILITYEFFECT_NEUTRALIZINGGAS, i, 0, 0, 0) != 0) + return; + } + gBattleStruct->switchInBattlerCounter = 0; gBattleStruct->eventsBeforeFirstTurnState++; break; case FIRST_TURN_EVENTS_SWITCH_IN_ABILITIES: diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 820023793d..c45c3cc9a5 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -4244,14 +4244,17 @@ static void Cmd_tryfaintmon(void) } else { - if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS + if (gDisableStructs[battler].neutralizingGas && !(gAbsentBattlerFlags & (1u << battler)) && !IsBattlerAlive(battler)) { - gBattleMons[battler].ability = ABILITY_NONE; - BattleScriptPush(gBattlescriptCurrInstr); - gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; - return; + gDisableStructs[battler].neutralizingGas = FALSE; + if (!IsNeutralizingGasOnField()) + { + BattleScriptPush(gBattlescriptCurrInstr); + gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; + return; + } } if (cmd->battler == BS_ATTACKER) { @@ -13424,44 +13427,46 @@ static void Cmd_switchoutabilities(void) CMD_ARGS(u8 battler); u32 battler = GetBattlerForBattleScript(cmd->battler); - if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS) + if (gDisableStructs[battler].neutralizingGas) { - gBattleMons[battler].ability = ABILITY_NONE; - BattleScriptPush(gBattlescriptCurrInstr); - gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; + gDisableStructs[battler].neutralizingGas = FALSE; + if (!IsNeutralizingGasOnField()) + { + BattleScriptPush(gBattlescriptCurrInstr); + gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; + return; + } } - else + + switch (GetBattlerAbility(battler)) { - switch (GetBattlerAbility(battler)) - { - case ABILITY_NATURAL_CURE: - if (gBattleMons[battler].status1 & STATUS1_SLEEP) - TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]); + case ABILITY_NATURAL_CURE: + if (gBattleMons[battler].status1 & STATUS1_SLEEP) + TryDeactivateSleepClause(GetBattlerSide(battler), gBattlerPartyIndexes[battler]); - gBattleMons[battler].status1 = 0; - BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, - 1u << gBattleStruct->battlerPartyIndexes[battler], - sizeof(gBattleMons[battler].status1), - &gBattleMons[battler].status1); - MarkBattlerForControllerExec(battler); - break; - case ABILITY_REGENERATOR: - { - u32 regenerate = GetNonDynamaxMaxHP(battler) / 3; - regenerate += gBattleMons[battler].hp; - if (regenerate > gBattleMons[battler].maxHP) - regenerate = gBattleMons[battler].maxHP; - BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HP_BATTLE, - 1u << gBattleStruct->battlerPartyIndexes[battler], - sizeof(regenerate), - ®enerate); - MarkBattlerForControllerExec(battler); - break; - } - } - - gBattlescriptCurrInstr = cmd->nextInstr; + gBattleMons[battler].status1 = 0; + BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, + 1u << gBattleStruct->battlerPartyIndexes[battler], + sizeof(gBattleMons[battler].status1), + &gBattleMons[battler].status1); + MarkBattlerForControllerExec(battler); + break; + case ABILITY_REGENERATOR: + { + u32 regenerate = GetNonDynamaxMaxHP(battler) / 3; + regenerate += gBattleMons[battler].hp; + if (regenerate > gBattleMons[battler].maxHP) + regenerate = gBattleMons[battler].maxHP; + BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HP_BATTLE, + 1u << gBattleStruct->battlerPartyIndexes[battler], + sizeof(regenerate), + ®enerate); + MarkBattlerForControllerExec(battler); + break; } + } + + gBattlescriptCurrInstr = cmd->nextInstr; } static void Cmd_jumpifhasnohp(void) @@ -16797,15 +16802,27 @@ void BS_TryBoosterEnergy(void) gBattlescriptCurrInstr = cmd->nextInstr; } -void BS_JumpIfAbilityCantBeSuppressed(void) +void BS_JumpIfAbilityCantBeReactivated(void) { NATIVE_ARGS(u8 battler, const u8 *jumpInstr); u32 battler = GetBattlerForBattleScript(cmd->battler); + u32 ability = gBattleMons[battler].ability; - if (gAbilitiesInfo[gBattleMons[battler].ability].cantBeSuppressed) + switch (ability) + { + case ABILITY_IMPOSTER: + case ABILITY_NEUTRALIZING_GAS: + case ABILITY_AIR_LOCK: + case ABILITY_CLOUD_NINE: gBattlescriptCurrInstr = cmd->jumpInstr; - else - gBattlescriptCurrInstr = cmd->nextInstr; + break; + default: + if (gAbilitiesInfo[ability].cantBeSuppressed) + gBattlescriptCurrInstr = cmd->jumpInstr; + else + gBattlescriptCurrInstr = cmd->nextInstr; + break; + } } void BS_TryActivateAbilityShield(void) @@ -18245,13 +18262,16 @@ void BS_TryEndNeutralizingGas(void) if (gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved) { gSpecialStatuses[gBattlerTarget].neutralizingGasRemoved = FALSE; - BattleScriptPush(cmd->nextInstr); - gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; - } - else - { - gBattlescriptCurrInstr = cmd->nextInstr; + gDisableStructs[gBattlerTarget].neutralizingGas = FALSE; + if (!IsNeutralizingGasOnField()) + { + BattleScriptPush(cmd->nextInstr); + gBattlescriptCurrInstr = BattleScript_NeutralizingGasExits; + return; + } } + + gBattlescriptCurrInstr = cmd->nextInstr; } void BS_GetRototillerTargets(void) diff --git a/src/battle_util.c b/src/battle_util.c index 696efdea4d..8266c22f76 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5170,19 +5170,13 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITYEFFECT_NEUTRALIZINGGAS: // Prints message only. separate from ABILITYEFFECT_ON_SWITCHIN bc activates before entry hazards - for (i = 0; i < gBattlersCount; i++) + if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS && !gDisableStructs[battler].neutralizingGas) { - if (gBattleMons[i].ability == ABILITY_NEUTRALIZING_GAS && !gDisableStructs[i].neutralizingGas) - { - gDisableStructs[i].neutralizingGas = TRUE; - gBattlerAbility = i; - gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_NEUTRALIZING_GAS; - BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); - effect++; - } - - if (effect != 0) - break; + gDisableStructs[battler].neutralizingGas = TRUE; + gBattlerAbility = battler; + gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_NEUTRALIZING_GAS; + BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); + effect++; } break; case ABILITYEFFECT_ON_WEATHER: // For ability effects that activate when the battle weather changes. @@ -5295,7 +5289,7 @@ bool32 IsNeutralizingGasOnField(void) for (i = 0; i < gBattlersCount; i++) { - if (IsBattlerAlive(i) && gBattleMons[i].ability == ABILITY_NEUTRALIZING_GAS && !gBattleMons[i].volatiles.gastroAcid) + if (gDisableStructs[i].neutralizingGas && !gBattleMons[i].volatiles.gastroAcid) return TRUE; } @@ -5371,7 +5365,7 @@ u32 GetBattlerAbilityInternal(u32 battler, u32 ignoreMoldBreaker, u32 noAbilityS if (!hasAbilityShield && IsNeutralizingGasOnField() - && gBattleMons[battler].ability != ABILITY_NEUTRALIZING_GAS) + && !gDisableStructs[battler].neutralizingGas) return ABILITY_NONE; if (CanBreakThroughAbility(gBattlerAttacker, battler, gBattleMons[gBattlerAttacker].ability, hasAbilityShield, ignoreMoldBreaker)) diff --git a/test/battle/ability/neutralizing_gas.c b/test/battle/ability/neutralizing_gas.c index f83e928c4a..426f4c969f 100644 --- a/test/battle/ability/neutralizing_gas.c +++ b/test/battle/ability/neutralizing_gas.c @@ -310,3 +310,46 @@ SINGLE_BATTLE_TEST("Neutralizing Gas exiting the field does not activate Imposte NOT ABILITY_POPUP(player, ABILITY_IMPOSTER); } } + +SINGLE_BATTLE_TEST("Neutralizing Gas exiting the field does not activate Air Lock/Cloud Nine but their effects are kept") +{ + u32 species, ability; + + PARAMETRIZE { species = SPECIES_GOLDUCK; ability = ABILITY_CLOUD_NINE; } + PARAMETRIZE { species = SPECIES_RAYQUAZA; ability = ABILITY_AIR_LOCK; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_RAIN_DANCE) == EFFECT_RAIN_DANCE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(species) { Ability(ability); } + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + OPPONENT(SPECIES_LUDICOLO) { Ability(ABILITY_RAIN_DISH); } + } WHEN { + TURN { SWITCH(player, 1); SWITCH(opponent, 1); } + TURN { MOVE(player, MOVE_RAIN_DANCE); } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_AIR_LOCK); + MESSAGE("The effects of the neutralizing gas wore off!"); + NOT ABILITY_POPUP(player, ABILITY_AIR_LOCK); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAIN_DANCE, player); + NOT ABILITY_POPUP(opponent, ABILITY_RAIN_DISH); + } +} + +SINGLE_BATTLE_TEST("Neutralizing Gas only displays exiting message for the last user leaving the field") +{ + GIVEN { + PLAYER(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WEEZING) { Ability(ABILITY_NEUTRALIZING_GAS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { SWITCH(player, 1); SWITCH(opponent, 1); } + } SCENE { + ABILITY_POPUP(player, ABILITY_NEUTRALIZING_GAS); + ABILITY_POPUP(opponent, ABILITY_NEUTRALIZING_GAS); + SEND_IN_MESSAGE("Wobbuffet"); + MESSAGE("The effects of the neutralizing gas wore off!"); + NOT MESSAGE("The effects of the neutralizing gas wore off!"); + } +}