From f2e848248859746d4344176893f74ee39ed1aa3e Mon Sep 17 00:00:00 2001 From: DizzyEggg Date: Mon, 24 Jun 2024 20:37:47 +0200 Subject: [PATCH] Fix how switch-in effects are played out after multiple faints in the same turn (#4864) * Multiple switch-ins after fainting * empty new lines * Fix failing tests --- data/battle_scripts_1.s | 1 + include/battle.h | 6 +- src/battle_script_commands.c | 101 +++++++++++++++------- test/battle/ability/beads_of_ruin.c | 2 +- test/battle/ability/download.c | 2 +- test/battle/ability/intimidate.c | 1 - test/battle/ability/supreme_overlord.c | 1 - test/battle/ability/switch_in_abilities.c | 4 - test/battle/ability/sword_of_ruin.c | 1 - test/battle/ability/tablets_of_ruin.c | 1 - test/battle/ability/vessel_of_ruin.c | 1 - test/battle/ability/zero_to_hero.c | 1 - test/battle/move_effect/sticky_web.c | 10 +-- 13 files changed, 84 insertions(+), 48 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 32015580b8..1bbb759460 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -5509,6 +5509,7 @@ BattleScript_HandleFaintedMonLoop:: switchineffects BS_FAINTED_MULTIPLE_1 jumpifbytenotequal gBattlerFainted, gBattlersCount, BattleScript_HandleFaintedMonLoop BattleScript_HandleFaintedMonMultipleEnd:: + switchineffects BS_FAINTED_MULTIPLE_2 end2 BattleScript_LocalTrainerBattleWon:: diff --git a/include/battle.h b/include/battle.h index 13359d0f4f..addf654d91 100644 --- a/include/battle.h +++ b/include/battle.h @@ -662,7 +662,11 @@ struct BattleStruct u16 abilityPreventingSwitchout; u8 hpScale; u16 synchronizeMoveEffect; - bool8 anyMonHasTransformed; + u8 anyMonHasTransformed:1; // Only used in battle_tv.c + u8 multipleSwitchInBattlers:4; // One bit per battler + u8 multipleSwitchInState:2; + u8 multipleSwitchInCursor:3; + u8 multipleSwitchInSortedBattlers[MAX_BATTLERS_COUNT]; void (*savedCallback)(void); u16 usedHeldItems[PARTY_SIZE][NUM_BATTLE_SIDES]; // For each party member and side. For harvest, recycle u16 chosenItem[MAX_BATTLERS_COUNT]; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 2aa68b896d..83c910f6b0 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -4633,7 +4633,7 @@ bool32 NoAliveMonsForEitherParty(void) return (NoAliveMonsForPlayer() || NoAliveMonsForOpponent()); } -// For battles that aren't BATTLE_TYPE_LINK or BATTLE_TYPE_RECORDED_LINK or double trainer battles, the only thing this +// For battles that aren't BATTLE_TYPE_LINK or BATTLE_TYPE_RECORDED_LINK or trainer battles, the only thing this // command does is check whether the player has won/lost by totaling each team's HP. It then // sets gBattleOutcome accordingly, if necessary. static void Cmd_checkteamslost(void) @@ -4650,15 +4650,11 @@ static void Cmd_checkteamslost(void) gBattleOutcome |= B_OUTCOME_WON; // Fair switching - everyone has to switch in most at the same time, without knowing which pokemon the other trainer selected. - // In vanilla Emerald this was only used for link battles, in expansion it's also used for regular trainers in double battles. + // In vanilla Emerald this was only used for link battles, in expansion it's also used for regular trainer battles. // For battles that haven't ended, count number of empty battler spots // In multi battles, jump to pointer if more than 1 spot empty // In non-multi battles, jump to pointer if 1 spot is missing on both sides - if (gBattleOutcome == 0 - && (((gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK))) - || ((gBattleTypeFlags & BATTLE_TYPE_TRAINER) && (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) - ) - ) + if (gBattleOutcome == 0 && (gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_TRAINER))) { s32 i, emptyPlayerSpots, emptyOpponentSpots; @@ -7148,13 +7144,8 @@ bool32 DoSwitchInAbilities(u32 battler) || AbilityBattleEffects(ABILITYEFFECT_TRACE2, 0, 0, 0, 0)); } -static void Cmd_switchineffects(void) +static void UpdateSentMonFlags(u32 battler) { - CMD_ARGS(u8 battler); - - s32 i; - u32 battler = GetBattlerForBattleScript(cmd->battler); - UpdateSentPokesToOpponentValue(battler); gHitMarker &= ~HITMARKER_FAINTED(battler); @@ -7162,7 +7153,11 @@ static void Cmd_switchineffects(void) if (!BattlerHasAi(battler)) gBattleStruct->appearedInBattle |= gBitTable[gBattlerPartyIndexes[battler]]; +} +static bool32 DoSwitchInEffectsForBattler(u32 battler) +{ + u32 i; // Neutralizing Gas announces itself before hazards if (gBattleMons[battler].ability == ABILITY_NEUTRALIZING_GAS && gSpecialStatuses[battler].announceNeutralizingGas == 0) { @@ -7280,7 +7275,7 @@ static void Cmd_switchineffects(void) BattleScriptPushCursor(); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_Z_HP_TRAP; gBattlescriptCurrInstr = BattleScript_HealReplacementZMove; - return; + return TRUE; } else { @@ -7295,9 +7290,9 @@ static void Cmd_switchineffects(void) gDisableStructs[battler].truantSwitchInHack = 0; if (DoSwitchInAbilities(battler) || ItemBattleEffects(ITEMEFFECT_ON_SWITCH_IN, battler, FALSE)) - return; + return TRUE; else if (AbilityBattleEffects(ABILITYEFFECT_OPPORTUNIST, battler, 0, 0, 0)) - return; + return TRUE; gDisableStructs[battler].stickyWebDone = FALSE; gDisableStructs[battler].spikesDone = FALSE; @@ -7313,22 +7308,68 @@ static void Cmd_switchineffects(void) gBattleStruct->hpOnSwitchout[GetBattlerSide(i)] = gBattleMons[i].hp; } - if (cmd->battler == BS_FAINTED_MULTIPLE_1) - { - u32 hitmarkerFaintBits = gHitMarker >> 28; - - gBattlerFainted++; - while (1) - { - if (hitmarkerFaintBits & gBitTable[gBattlerFainted] && !(gAbsentBattlerFlags & gBitTable[gBattlerFainted])) - break; - if (gBattlerFainted >= gBattlersCount) - break; - gBattlerFainted++; - } - } gBattleStruct->forcedSwitch &= ~(gBitTable[battler]); + return FALSE; + } + + return TRUE; // Effect's script plays. +} + +static void Cmd_switchineffects(void) +{ + CMD_ARGS(u8 battler); + u32 i, battler = GetBattlerForBattleScript(cmd->battler); + + switch (cmd->battler) + { + // Multiple mons fainted and are being switched-in. Their abilities/hazards will play according to speed ties. + case BS_FAINTED_MULTIPLE_1: // Saves the battlers. + gBattleStruct->multipleSwitchInBattlers |= gBitTable[battler]; + UpdateSentMonFlags(battler); + + // Increment fainted battler. + do + { + gBattlerFainted++; + if (gBattlerFainted >= gBattlersCount) + break; + if (gHitMarker & HITMARKER_FAINTED(gBattlerFainted) && !(gAbsentBattlerFlags & gBitTable[gBattlerFainted])) + break; + } while (1); + gBattlescriptCurrInstr = cmd->nextInstr; + return; + case BS_FAINTED_MULTIPLE_2: // Plays hazards/abilities. + switch (gBattleStruct->multipleSwitchInState) + { + case 0: // Sort battlers by speed + for (i = 0; i < gBattlersCount; i++) + gBattleStruct->multipleSwitchInSortedBattlers[i] = i; + SortBattlersBySpeed(gBattleStruct->multipleSwitchInSortedBattlers, FALSE); + gBattleStruct->multipleSwitchInState++; + gBattleStruct->multipleSwitchInCursor = 0; + // Loop through all available battlers + case 1: + for (; gBattleStruct->multipleSwitchInCursor < gBattlersCount; gBattleStruct->multipleSwitchInCursor++) + { + gBattlerFainted = gBattleStruct->multipleSwitchInSortedBattlers[gBattleStruct->multipleSwitchInCursor]; + if (gBattleStruct->multipleSwitchInBattlers & gBitTable[gBattlerFainted]) + { + if (DoSwitchInEffectsForBattler(gBattlerFainted)) + return; + } + } + // All battlers done, end + gBattleStruct->multipleSwitchInBattlers = 0; + gBattleStruct->multipleSwitchInState = 0; + gBattlescriptCurrInstr = cmd->nextInstr; + } + break; + default: + UpdateSentMonFlags(battler); + if (!DoSwitchInEffectsForBattler(battler)) + gBattlescriptCurrInstr = cmd->nextInstr; + break; } } diff --git a/test/battle/ability/beads_of_ruin.c b/test/battle/ability/beads_of_ruin.c index 2fc4f9cdc1..f7c9c267dc 100644 --- a/test/battle/ability/beads_of_ruin.c +++ b/test/battle/ability/beads_of_ruin.c @@ -46,9 +46,9 @@ SINGLE_BATTLE_TEST("Beads of Ruin's message displays correctly after all battler ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, opponent); // Everyone faints. MESSAGE("Go! Chi-Yu!"); + MESSAGE("2 sent out Wobbuffet!"); ABILITY_POPUP(player, ABILITY_BEADS_OF_RUIN); MESSAGE("Chi-Yu's Beads of Ruin weakened the Sp. Def of all surrounding Pokémon!"); - MESSAGE("2 sent out Wobbuffet!"); } } diff --git a/test/battle/ability/download.c b/test/battle/ability/download.c index 9a8d535a81..4a6daf8d18 100644 --- a/test/battle/ability/download.c +++ b/test/battle/ability/download.c @@ -56,7 +56,7 @@ SINGLE_BATTLE_TEST("Download raises Sp.Attack if enemy has lower Sp. Def than De SINGLE_BATTLE_TEST("Download doesn't activate if target hasn't been sent out yet", s16 damagePhysical, s16 damageSpecial) { u32 ability; - KNOWN_FAILING; + PARAMETRIZE { ability = ABILITY_TRACE; } PARAMETRIZE { ability = ABILITY_DOWNLOAD; } GIVEN { diff --git a/test/battle/ability/intimidate.c b/test/battle/ability/intimidate.c index 46742e6935..09677523c1 100644 --- a/test/battle/ability/intimidate.c +++ b/test/battle/ability/intimidate.c @@ -59,7 +59,6 @@ SINGLE_BATTLE_TEST("Intimidate (opponent) lowers player's attack after KO", s16 DOUBLE_BATTLE_TEST("Intimidate doesn't activate on an empty field in a double battle") { - KNOWN_FAILING; GIVEN { ASSUME(gMovesInfo[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/ability/supreme_overlord.c b/test/battle/ability/supreme_overlord.c index 11ab83d7f5..baf2076429 100644 --- a/test/battle/ability/supreme_overlord.c +++ b/test/battle/ability/supreme_overlord.c @@ -94,7 +94,6 @@ SINGLE_BATTLE_TEST("Supreme Overlord does not boost attack if party members are SINGLE_BATTLE_TEST("Supreme Overlord's message displays correctly after all battlers fainted - Player") { - KNOWN_FAILING; // Explosion causes the ability to wait GIVEN { ASSUME(gMovesInfo[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); PLAYER(SPECIES_WOBBUFFET) { HP(1);} diff --git a/test/battle/ability/switch_in_abilities.c b/test/battle/ability/switch_in_abilities.c index b466d0c646..79cf2b2dc9 100644 --- a/test/battle/ability/switch_in_abilities.c +++ b/test/battle/ability/switch_in_abilities.c @@ -63,8 +63,6 @@ SINGLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order after post-KO swi { u32 spdPlayer, spdOpponent; - KNOWN_FAILING; - PARAMETRIZE { spdPlayer = 5; spdOpponent = 1; } PARAMETRIZE { spdOpponent = 5; spdPlayer = 1; } @@ -92,8 +90,6 @@ DOUBLE_BATTLE_TEST("Switch-in abilities trigger in Speed Order after post-KO swi { u32 spdPlayer1, spdPlayer2, spdOpponent1, spdOpponent2; - KNOWN_FAILING; - PARAMETRIZE { spdPlayer1 = 5; spdPlayer2 = 4; spdOpponent1 = 3; spdOpponent2 = 2; } PARAMETRIZE { spdPlayer1 = 2; spdPlayer2 = 3; spdOpponent1 = 4; spdOpponent2 = 5; } PARAMETRIZE { spdPlayer1 = 4; spdPlayer2 = 3; spdOpponent1 = 5; spdOpponent2 = 2; } diff --git a/test/battle/ability/sword_of_ruin.c b/test/battle/ability/sword_of_ruin.c index 493c50f0d2..7fc2c30d2a 100644 --- a/test/battle/ability/sword_of_ruin.c +++ b/test/battle/ability/sword_of_ruin.c @@ -32,7 +32,6 @@ SINGLE_BATTLE_TEST("Sword of Ruin reduces Defense if opposing mon's ability does SINGLE_BATTLE_TEST("Sword of Ruin's message displays correctly after all battlers fainted - Player") { - KNOWN_FAILING; // Explosion causes the ability to wait GIVEN { ASSUME(gMovesInfo[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); PLAYER(SPECIES_WOBBUFFET) { HP(1);} diff --git a/test/battle/ability/tablets_of_ruin.c b/test/battle/ability/tablets_of_ruin.c index 0550cdc814..d8858aca2e 100644 --- a/test/battle/ability/tablets_of_ruin.c +++ b/test/battle/ability/tablets_of_ruin.c @@ -32,7 +32,6 @@ SINGLE_BATTLE_TEST("Tablets of Ruin reduces Attack if opposing mon's ability doe SINGLE_BATTLE_TEST("Tablets of Ruin's message displays correctly after all battlers fainted - Player") { - KNOWN_FAILING; // Explosion causes the ability to wait GIVEN { ASSUME(gMovesInfo[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); PLAYER(SPECIES_WOBBUFFET) { HP(1);} diff --git a/test/battle/ability/vessel_of_ruin.c b/test/battle/ability/vessel_of_ruin.c index 6a65616ed4..5f83a6c07f 100644 --- a/test/battle/ability/vessel_of_ruin.c +++ b/test/battle/ability/vessel_of_ruin.c @@ -32,7 +32,6 @@ SINGLE_BATTLE_TEST("Vessel of Ruin reduces Sp. Atk if opposing mon's ability doe SINGLE_BATTLE_TEST("Vessel of Ruin's message displays correctly after all battlers fainted - Player") { - KNOWN_FAILING; // Explosion causes the ability to wait GIVEN { ASSUME(gMovesInfo[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); PLAYER(SPECIES_WOBBUFFET) { HP(1);} diff --git a/test/battle/ability/zero_to_hero.c b/test/battle/ability/zero_to_hero.c index 91725f50db..3939c6cb78 100644 --- a/test/battle/ability/zero_to_hero.c +++ b/test/battle/ability/zero_to_hero.c @@ -138,7 +138,6 @@ SINGLE_BATTLE_TEST("Imposter doesn't apply the heroic transformation message whe SINGLE_BATTLE_TEST("Zero to Hero's message displays correctly after all battlers fainted - Player") { - KNOWN_FAILING; // Explosion causes the ability to wait GIVEN { ASSUME(gMovesInfo[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); PLAYER(SPECIES_PALAFIN_ZERO); diff --git a/test/battle/move_effect/sticky_web.c b/test/battle/move_effect/sticky_web.c index 6c6f8f156c..37f5fce077 100644 --- a/test/battle/move_effect/sticky_web.c +++ b/test/battle/move_effect/sticky_web.c @@ -56,7 +56,7 @@ DOUBLE_BATTLE_TEST("Sticky Web lowers Speed by 1 in a double battle after Explos OPPONENT(SPECIES_WOBBUFFET) {HP(1); Speed(1);} OPPONENT(SPECIES_WOBBUFFET) {HP(1); Speed(1);} OPPONENT(SPECIES_WYNAUT) {Speed(10);} - OPPONENT(SPECIES_WYNAUT) {Speed(10);} + OPPONENT(SPECIES_ALAKAZAM) {Speed(100);} } WHEN { TURN { MOVE(playerRight, MOVE_STICKY_WEB); MOVE(playerLeft, MOVE_EXPLOSION); SEND_OUT(playerLeft, 2); SEND_OUT(opponentLeft, 2); SEND_OUT(opponentRight, 3); } TURN {} @@ -65,13 +65,13 @@ DOUBLE_BATTLE_TEST("Sticky Web lowers Speed by 1 in a double battle after Explos MESSAGE("A sticky web spreads out on the ground around the opposing team!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); MESSAGE("2 sent out Wynaut!"); + MESSAGE("2 sent out Alakazam!"); + MESSAGE("Foe Alakazam was caught in a Sticky Web!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Foe Alakazam's Speed fell!"); MESSAGE("Foe Wynaut was caught in a Sticky Web!"); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); MESSAGE("Foe Wynaut's Speed fell!"); - MESSAGE("2 sent out Wynaut!"); - MESSAGE("Foe Wynaut was caught in a Sticky Web!"); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); - MESSAGE("Foe Wynaut's Speed fell!"); } }