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
This commit is contained in:
DizzyEggg 2024-06-24 20:37:47 +02:00 committed by GitHub
parent a8ae1a0342
commit f2e8482488
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 84 additions and 48 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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