From 7fd0745766425a16c1b8d70791a3c9ac3080a475 Mon Sep 17 00:00:00 2001 From: FosterProgramming Date: Sun, 23 Nov 2025 17:04:01 +0100 Subject: [PATCH] Fix switch-in abilities not triggering on revive (#8293) --- data/battle_scripts_2.s | 4 + src/battle_script_commands.c | 12 +-- src/battle_util.c | 4 + test/battle/ability/unnerve.c | 25 +++++++ test/battle/item_effect/revive.c | 124 +++++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 6 deletions(-) diff --git a/data/battle_scripts_2.s b/data/battle_scripts_2.s index 586a79c5ef..b086582250 100755 --- a/data/battle_scripts_2.s +++ b/data/battle_scripts_2.s @@ -75,6 +75,10 @@ BattleScript_ItemRestoreHP_Party:: return BattleScript_ItemRestoreHP_SendOutRevivedBattler: + switchhandleorder BS_SCRIPTING, 0 + getswitchedmondata BS_SCRIPTING + switchindataupdate BS_SCRIPTING + trytoclearprimalweather switchinanim BS_SCRIPTING, FALSE, FALSE waitstate switchineffects BS_SCRIPTING diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 69c23da4d7..dcc03aa74e 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -4862,8 +4862,6 @@ static void Cmd_getexp(void) // not sure why gf clears the item and ability here gBattleStruct->expOrderId = 0; gBattleStruct->teamGotExpMsgPrinted = FALSE; - gBattleMons[gBattlerFainted].item = ITEM_NONE; - gBattleMons[gBattlerFainted].ability = ABILITY_NONE; gBattlescriptCurrInstr = cmd->nextInstr; } break; @@ -15192,12 +15190,14 @@ void BS_ItemRestoreHP(void) hp += healAmount; SetMonData(&party[gBattleStruct->itemPartyIndex[gBattlerAttacker]], MON_DATA_HP, &hp); - // Revived battlers on the field need to be brought back. - if (IsDoubleBattle() && battler != MAX_BATTLERS_COUNT) + u32 partner = BATTLE_PARTNER(gBattlerAttacker); + // Absent battlers on the field need to be replaced + if (IsDoubleBattle() && (gAbsentBattlerFlags & (1u << partner))) { - gAbsentBattlerFlags &= ~(1u << battler); - gBattleMons[battler].hp = hp; + gAbsentBattlerFlags &= ~(1u << partner); gBattleCommunication[MULTIUSE_STATE] = TRUE; + gBattleScripting.battler = partner; + BtlController_EmitChosenMonReturnValue(partner, B_COMM_TO_ENGINE, gBattleStruct->itemPartyIndex[gBattlerAttacker], NULL); } gBattlescriptCurrInstr = cmd->nextInstr; } diff --git a/src/battle_util.c b/src/battle_util.c index baa77a27b9..ecf836d882 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -5195,6 +5195,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITYEFFECT_ON_WEATHER: // For ability effects that activate when the battle weather changes. + if (!IsBattlerAlive(battler)) + return effect; gLastUsedAbility = GetBattlerAbility(battler); switch (gLastUsedAbility) { @@ -5249,6 +5251,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 } break; case ABILITYEFFECT_ON_TERRAIN: // For ability effects that activate when the field terrain changes. + if (!IsBattlerAlive(battler)) + return effect; gLastUsedAbility = GetBattlerAbility(battler); switch (gLastUsedAbility) { diff --git a/test/battle/ability/unnerve.c b/test/battle/ability/unnerve.c index 9a8972eb5c..e53b510fe6 100644 --- a/test/battle/ability/unnerve.c +++ b/test/battle/ability/unnerve.c @@ -99,3 +99,28 @@ SINGLE_BATTLE_TEST("Unnerve activates only once per switch-in") } } + +DOUBLE_BATTLE_TEST("Unnerve stops applying on death but applies on revive") +{ + u16 mon; + u16 ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE;} + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + ASSUME(gItemsInfo[ITEM_RAWST_BERRY].holdEffect == HOLD_EFFECT_CURE_BRN); + ASSUME(gItemsInfo[ITEM_REVIVE].battleUsage == EFFECT_ITEM_REVIVE); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(mon) { Ability(ability); HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RAWST_BERRY); Status1(STATUS1_BURN); } + OPPONENT(SPECIES_WYNAUT) { Item(ITEM_RAWST_BERRY); } + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); MOVE(opponentLeft, MOVE_WILL_O_WISP, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentLeft); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_BRN, opponentRight); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponentRight); + } + +} diff --git a/test/battle/item_effect/revive.c b/test/battle/item_effect/revive.c index 9ff693fc30..df866edf0e 100644 --- a/test/battle/item_effect/revive.c +++ b/test/battle/item_effect/revive.c @@ -99,4 +99,128 @@ DOUBLE_BATTLE_TEST("Revive works for a partner in a double battle") } } +DOUBLE_BATTLE_TEST("Revive can trigger switch-in abilities") +{ + GIVEN { + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); } + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 0); SKIP_TURN(playerLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + } +} + +DOUBLE_BATTLE_TEST("Revive does reset abilities") +{ + GIVEN { + PLAYER(SPECIES_ARBOK) { Ability(ABILITY_INTIMIDATE); HP(1); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentRight, MOVE_WORRY_SEED, target: playerLeft); MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); } + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 0); SKIP_TURN(playerLeft); MOVE(opponentRight, MOVE_SPORE, target: playerLeft);} + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } THEN { + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 2); + } +} + +DOUBLE_BATTLE_TEST("Revive does not grant a mon its pre-death status condition") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { Status1(STATUS1_SLEEP); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); } + } THEN { + EXPECT_EQ(opponentRight->status1, 0); + } +} + +DOUBLE_BATTLE_TEST("Revive does not grant a mon its pre-death stat change") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerRight, MOVE_SWORDS_DANCE); MOVE(opponentLeft, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); } + } THEN { + EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE); + } +} + +DOUBLE_BATTLE_TEST("Revive does not grant a mon its pre-death types") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_POOCHYENA); + } WHEN { + TURN { MOVE(playerRight, MOVE_REFLECT_TYPE, target: opponentRight); MOVE(opponentLeft, MOVE_PSYSHOCK, target: playerRight); MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); } + TURN { USE_ITEM(playerLeft, ITEM_MAX_REVIVE, partyIndex: 1); SKIP_TURN(playerRight); MOVE(opponentLeft, MOVE_PSYCHIC, target: playerRight);} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REFLECT_TYPE, playerRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYSHOCK, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC, opponentLeft); + } THEN { + EXPECT_LT(playerRight->hp, playerRight->maxHP); + } +} + +DOUBLE_BATTLE_TEST("Revive force revived pokemon to replace absent battler immediately", s16 damage) +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_INTIMIDATE; } + PARAMETRIZE { ability = ABILITY_SHED_SKIN; } + + GIVEN { + PLAYER(SPECIES_WYNAUT) { HP(1); } + PLAYER(SPECIES_WOBBUFFET) { }; + PLAYER(SPECIES_ARBOK) { Ability(ability); HP(0) ;} ; + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft);} + TURN { USE_ITEM(playerRight, ITEM_REVIVE, partyIndex: 2); SKIP_TURN(playerLeft); MOVE(opponentRight, MOVE_SCRATCH, target: playerRight); } + } SCENE { + if (ability == ABILITY_INTIMIDATE) + { + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentRight); + HP_BAR(playerRight, captureDamage: &results[i].damage); + } FINALLY { + EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage); + } +} + TO_DO_BATTLE_TEST("Revive won't restore a battler's HP if it hasn't fainted")