From e67d5a23ed54a0fc8a584199d99fe3019804bbbd Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:40:44 +0100 Subject: [PATCH] Adds some Snatch interactions, fixes for Dragon Darts, Trace, Primal Reversion, Protosynthesis/Quark Drive (#5430) * Fixes Electrified Dragon Darts sometimes targeting battlers with absorbing abilities (Volt Absorb, Motor Drive) * Add Snatch interactions with Dancer, Swallow * Trace fix + cleanup * Simplify Quash * Fixes multiple mons with Primal Reversion causing only one Primal Reversion, add tests * Fix Booster Energy Ability Popup * Accidentally removed healing from Swallow * More Trace cleanup --- data/battle_scripts_1.s | 12 +-- include/battle.h | 3 +- include/battle_scripts.h | 1 - include/battle_util.h | 2 +- src/battle_script_commands.c | 52 ++++++------ src/battle_util.c | 29 +++---- test/battle/ability/dancer.c | 79 +++++++++++++++++ test/battle/ability/trace.c | 15 ++++ test/battle/form_change/primal_reversion.c | 98 ++++++++++++++++++++++ test/battle/move_effect/dragon_darts.c | 77 +++++++++++++++++ 10 files changed, 313 insertions(+), 55 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 06b82f43b7..94f5736317 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -6961,12 +6961,12 @@ BattleScript_WishMegaEvolution:: BattleScript_PrimalReversion:: call BattleScript_PrimalReversionRet - end2 + end3 BattleScript_PrimalReversionRestoreAttacker:: call BattleScript_PrimalReversionRet copybyte gBattlerAttacker, sSAVED_BATTLER - end2 + end3 BattleScript_PrimalReversionRet:: flushtextbox @@ -7675,15 +7675,11 @@ BattleScript_EmergencyExitWildNoPopUp:: BattleScript_TraceActivates:: pause B_WAIT_TIME_SHORT - call BattleScript_AbilityPopUp + call BattleScript_AbilityPopUpScripting printstring STRINGID_PKMNTRACED waitmessage B_WAIT_TIME_LONG settracedability BS_SCRIPTING switchinabilities BS_SCRIPTING - return - -BattleScript_TraceActivatesEnd3:: - call BattleScript_TraceActivates end3 BattleScript_ReceiverActivates:: @@ -10011,7 +10007,7 @@ BattleScript_BerserkGeneRet_End: BattleScript_BoosterEnergyEnd2:: playanimation BS_SCRIPTING, B_ANIM_HELD_ITEM_EFFECT, sB_ANIM_ARG1 - call BattleScript_AbilityPopUpTarget + call BattleScript_AbilityPopUpScripting printstring STRINGID_BOOSTERENERGYACTIVATES waitmessage B_WAIT_TIME_MED printstring STRINGID_STATWASHEIGHTENED diff --git a/include/battle.h b/include/battle.h index 91233c8fe8..582dcedfc2 100644 --- a/include/battle.h +++ b/include/battle.h @@ -93,7 +93,7 @@ struct ResourceFlags #define RESOURCE_FLAG_ROOST 0x2 #define RESOURCE_FLAG_UNBURDEN 0x4 #define RESOURCE_FLAG_UNUSED 0x8 -#define RESOURCE_FLAG_TRACED 0x10 +#define RESOURCE_FLAG_UNUSED_2 0x10 #define RESOURCE_FLAG_EMERGENCY_EXIT 0x20 #define RESOURCE_FLAG_NEUTRALIZING_GAS 0x40 #define RESOURCE_FLAG_ICE_FACE 0x80 @@ -751,6 +751,7 @@ struct BattleStruct u8 blunderPolicy:1; // should blunder policy activate u8 swapDamageCategory:1; // Photon Geyser, Shell Side Arm, Light That Burns the Sky u8 bouncedMoveIsUsed:1; + u8 snatchedMoveIsUsed:1; u8 descriptionSubmenu:1; // For Move Description window in move selection screen u8 ackBallUseBtn:1; // Used for the last used ball feature u8 ballSwapped:1; // Used for the last used ball feature diff --git a/include/battle_scripts.h b/include/battle_scripts.h index e98790764d..84c1b70cf4 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -166,7 +166,6 @@ extern const u8 BattleScript_ItemSteal[]; extern const u8 BattleScript_DrizzleActivates[]; extern const u8 BattleScript_SpeedBoostActivates[]; extern const u8 BattleScript_TraceActivates[]; -extern const u8 BattleScript_TraceActivatesEnd3[]; extern const u8 BattleScript_RainDishActivates[]; extern const u8 BattleScript_SandstreamActivates[]; extern const u8 BattleScript_ShedSkinActivates[]; diff --git a/include/battle_util.h b/include/battle_util.h index 3a64b84462..ec8c03a6bd 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -256,7 +256,7 @@ bool32 MoveHasAdditionalEffectSelf(u32 move, u32 moveEffect); bool32 MoveHasAdditionalEffectSelfArg(u32 move, u32 moveEffect, u32 argument); bool32 MoveHasChargeTurnAdditionalEffect(u32 move); bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef); -bool32 TargetFullyImmuneToCurrMove(u32 BattlerAtk, u32 battlerDef); +bool32 TargetFullyImmuneToCurrMove(u32 battlerAtk, u32 battlerDef); bool32 CanBeSlept(u32 battler, u32 ability); bool32 CanBePoisoned(u32 battlerAtk, u32 battlerDef, u32 defAbility); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index c65e1455d8..b018535cbe 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1401,6 +1401,7 @@ static void Cmd_attackcanceler(void) if ((gProtectStructs[gBattlerByTurnOrder[i]].stealMove) && gMovesInfo[gCurrentMove].snatchAffected) { gProtectStructs[gBattlerByTurnOrder[i]].stealMove = FALSE; + gBattleStruct->snatchedMoveIsUsed = TRUE; gBattleScripting.battler = gBattlerByTurnOrder[i]; BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_SnatchedMove; @@ -6287,7 +6288,7 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_DANCER: // Special case because it's so annoying - if (gMovesInfo[gCurrentMove].danceMove) + if (gMovesInfo[gCurrentMove].danceMove && !gBattleStruct->snatchedMoveIsUsed) { u32 battler, nextDancer = 0; bool32 hasDancerTriggered = FALSE; @@ -6431,6 +6432,7 @@ static void Cmd_moveend(void) gBattleStruct->swapDamageCategory = FALSE; gBattleStruct->categoryOverride = FALSE; gBattleStruct->bouncedMoveIsUsed = FALSE; + gBattleStruct->snatchedMoveIsUsed = FALSE; gBattleStruct->enduredDamage = 0; gBattleStruct->additionalEffectsCounter = 0; gBattleStruct->poisonPuppeteerConfusion = FALSE; @@ -11537,7 +11539,7 @@ static void Cmd_stockpiletohpheal(void) const u8 *failInstr = cmd->failInstr; - if (gDisableStructs[gBattlerAttacker].stockpileCounter == 0) + if (gDisableStructs[gBattlerAttacker].stockpileCounter == 0 && !gBattleStruct->snatchedMoveIsUsed) { gBattlescriptCurrInstr = failInstr; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWALLOW_FAILED; @@ -11553,14 +11555,22 @@ static void Cmd_stockpiletohpheal(void) } else { - gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / (1 << (3 - gDisableStructs[gBattlerAttacker].stockpileCounter)); + if (gDisableStructs[gBattlerAttacker].stockpileCounter > 0) + { + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / (1 << (3 - gDisableStructs[gBattlerAttacker].stockpileCounter)); + gBattleScripting.animTurn = gDisableStructs[gBattlerAttacker].stockpileCounter; + gBattleStruct->moveEffect2 = MOVE_EFFECT_STOCKPILE_WORE_OFF; + } + else // Snatched move + { + gBattleMoveDamage = GetNonDynamaxMaxHP(gBattlerAttacker) / 4; + gBattleScripting.animTurn = 1; + } if (gBattleMoveDamage == 0) gBattleMoveDamage = 1; gBattleMoveDamage *= -1; - - gBattleScripting.animTurn = gDisableStructs[gBattlerAttacker].stockpileCounter; - gBattleStruct->moveEffect2 = MOVE_EFFECT_STOCKPILE_WORE_OFF; + gBattlescriptCurrInstr = cmd->nextInstr; gBattlerTarget = gBattlerAttacker; } @@ -17122,30 +17132,18 @@ void BS_TryQuash(void) // If the above condition is not true, it means we are faster than the foe, so we can set the quash bit gProtectStructs[gBattlerTarget].quash = TRUE; - - if (B_QUASH_TURN_ORDER < GEN_8) + + // this implementation assumes turn order is correct when using Quash + i = GetBattlerTurnOrderNum(gBattlerTarget); + for (j = i + 1; j < gBattlersCount; j++) { // Gen 7- config makes target go last so that the order of quash targets is kept for the correct turn order - j = GetBattlerTurnOrderNum(gBattlerTarget); - for (i = j + 1; i < gBattlersCount; i++) - { + // Gen 8+ config alters Turn Order of the target according to speed, dynamic speed should handle the rest + if (B_QUASH_TURN_ORDER < GEN_8 || GetWhichBattlerFaster(gBattlerByTurnOrder[i], gBattlerByTurnOrder[j], FALSE) == -1) SwapTurnOrder(i, j); - j++; - } - } - else - { - // Gen 8+ config only alters Turn Order of battlers affected by Quash, dynamic speed should handle the rest - for (i = gCurrentTurnActionNumber + 1; i < gBattlersCount - 1; i++) - { - for (j = i + 1; j < gBattlersCount; j++) - { - u32 battler1 = gBattlerByTurnOrder[i], battler2 = gBattlerByTurnOrder[j]; - if ((gProtectStructs[battler1].quash || gProtectStructs[battler2].quash) - && GetWhichBattlerFaster(battler1, battler2, FALSE) == -1) - SwapTurnOrder(i, j); - } - } + else + break; + i++; } gBattlescriptCurrInstr = cmd->nextInstr; } diff --git a/src/battle_util.c b/src/battle_util.c index 612965fff2..86e846d09c 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4368,13 +4368,10 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (gSpecialStatuses[battler].switchInAbilityDone) break; - if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_TRACED) - break; side = (BATTLE_OPPOSITE(GetBattlerPosition(battler))) & BIT_SIDE; target1 = GetBattlerAtPosition(side); target2 = GetBattlerAtPosition(side + BIT_FLANK); - gSpecialStatuses[battler].switchInAbilityDone = TRUE; if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) { if (!gAbilitiesInfo[gBattleMons[target1].ability].cantBeTraced && gBattleMons[target1].hp != 0 @@ -4393,11 +4390,10 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 if (effect != 0) { - BattleScriptPushCursorAndCallback(BattleScript_TraceActivatesEnd3); - gBattleResources->flags->flags[battler] &= ~RESOURCE_FLAG_TRACED; + BattleScriptPushCursorAndCallback(BattleScript_TraceActivates); gBattleStruct->tracedAbility[battler] = gLastUsedAbility = gBattleMons[chosenTarget].ability; RecordAbilityBattle(chosenTarget, gLastUsedAbility); // Record the opposing battler has this ability - battler = gBattlerAbility = gBattleScripting.battler = battler; + gBattlerAbility = battler; PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, chosenTarget, gBattlerPartyIndexes[chosenTarget]) PREPARE_ABILITY_BUFFER(gBattleTextBuff2, gLastUsedAbility) @@ -5214,7 +5210,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 break; case ABILITY_GOOD_AS_GOLD: if (IS_MOVE_STATUS(gCurrentMove) - && !(moveTarget & MOVE_TARGET_USER) && !(moveTarget & MOVE_TARGET_OPPONENTS_FIELD) && !(moveTarget & MOVE_TARGET_ALL_BATTLERS)) effect = 3; @@ -6388,14 +6383,14 @@ bool32 TryPrimalReversion(u32 battler) { if (gBattlerAttacker == battler) { - BattleScriptExecute(BattleScript_PrimalReversion); + BattleScriptPushCursorAndCallback(BattleScript_PrimalReversion); } else { // edge case for scenarios like a switch-in after activated eject button gBattleScripting.savedBattler = gBattlerAttacker; gBattlerAttacker = battler; - BattleScriptExecute(BattleScript_PrimalReversionRestoreAttacker); + BattleScriptPushCursorAndCallback(BattleScript_PrimalReversionRestoreAttacker); } return TRUE; } @@ -8706,7 +8701,7 @@ u32 CountBattlerStatIncreases(u32 battler, bool32 countEvasionAcc) u32 GetMoveTargetCount(u32 move, u32 battlerAtk, u32 battlerDef) { - switch (GetBattlerMoveTargetType(gBattlerAttacker, move)) + switch (GetBattlerMoveTargetType(battlerAtk, move)) { case MOVE_TARGET_BOTH: return !(gAbsentBattlerFlags & gBitTable[battlerDef]) @@ -11816,19 +11811,19 @@ bool32 CanTargetPartner(u32 battlerAtk, u32 battlerDef) && battlerDef != BATTLE_PARTNER(battlerAtk)); } -static inline bool32 DoesCurrentTargetHaveAbilityImmunity(void) +static inline bool32 DoesBattlerHaveAbilityImmunity(u32 battlerDef) { - return (AbilityBattleEffects(ABILITYEFFECT_WOULD_BLOCK, gBattlerTarget, 0, 0, 0) - || AbilityBattleEffects(ABILITYEFFECT_WOULD_ABSORB, gBattlerTarget, 0, 0, 0)); + return (AbilityBattleEffects(ABILITYEFFECT_WOULD_BLOCK, battlerDef, 0, 0, 0) + || AbilityBattleEffects(ABILITYEFFECT_WOULD_ABSORB, battlerDef, 0, 0, 0)); } -bool32 TargetFullyImmuneToCurrMove(u32 BattlerAtk, u32 battlerDef) +bool32 TargetFullyImmuneToCurrMove(u32 battlerAtk, u32 battlerDef) { u32 moveType = 0; GET_MOVE_TYPE(gCurrentMove, moveType); - return ((CalcTypeEffectivenessMultiplier(gCurrentMove, moveType, BattlerAtk, battlerDef, GetBattlerAbility(battlerDef), FALSE) == UQ_4_12(0.0)) - || IsBattlerProtected(BattlerAtk, battlerDef, gCurrentMove) + return ((CalcTypeEffectivenessMultiplier(gCurrentMove, moveType, battlerAtk, battlerDef, GetBattlerAbility(battlerDef), FALSE) == UQ_4_12(0.0)) + || IsBattlerProtected(battlerAtk, battlerDef, gCurrentMove) || IsSemiInvulnerable(battlerDef, gCurrentMove) - || DoesCurrentTargetHaveAbilityImmunity()); + || DoesBattlerHaveAbilityImmunity(battlerDef)); } diff --git a/test/battle/ability/dancer.c b/test/battle/ability/dancer.c index b39fa291c8..2132530958 100644 --- a/test/battle/ability/dancer.c +++ b/test/battle/ability/dancer.c @@ -146,6 +146,85 @@ SINGLE_BATTLE_TEST("Dancer-called attacks have their type updated") } } +DOUBLE_BATTLE_TEST("Dancer doesn't trigger on a snatched move") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_DRAGON_DANCE].danceMove == TRUE); + ASSUME(gMovesInfo[MOVE_SNATCH].effect == EFFECT_SNATCH); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_ORICORIO); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, MOVE_SNATCH); MOVE(playerRight, MOVE_DRAGON_DANCE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SNATCH, opponentRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + NONE_OF { + ABILITY_POPUP(opponentLeft, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } + } +} + +DOUBLE_BATTLE_TEST("Dancer triggers on Instructed dance moves") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_DRAGON_DANCE].danceMove == TRUE); + ASSUME(gMovesInfo[MOVE_DRAGON_DANCE].instructBanned == FALSE); + ASSUME(gMovesInfo[MOVE_INSTRUCT].effect == EFFECT_INSTRUCT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_ORICORIO); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(playerRight, MOVE_DRAGON_DANCE); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + ABILITY_POPUP(opponentLeft, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + ABILITY_POPUP(opponentLeft, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } +} + +DOUBLE_BATTLE_TEST("Dancer-called move doesn't update move to be Instructed") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_DRAGON_DANCE].danceMove == TRUE); + ASSUME(gMovesInfo[MOVE_TACKLE].instructBanned == FALSE); + ASSUME(gMovesInfo[MOVE_INSTRUCT].effect == EFFECT_INSTRUCT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_ORICORIO); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); MOVE(playerRight, MOVE_DRAGON_DANCE); MOVE(opponentRight, MOVE_INSTRUCT, target: opponentLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight); + ABILITY_POPUP(opponentLeft, ABILITY_DANCER); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft); + } + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + } +} + DOUBLE_BATTLE_TEST("Dancer doesn't call a move that didn't execute due to Powder") { GIVEN { diff --git a/test/battle/ability/trace.c b/test/battle/ability/trace.c index acc49bcf13..3042f8e22b 100644 --- a/test/battle/ability/trace.c +++ b/test/battle/ability/trace.c @@ -80,6 +80,21 @@ SINGLE_BATTLE_TEST("Trace will copy an opponent's ability whenever it has the ch } } + +SINGLE_BATTLE_TEST("Trace copies opponent's Intimidate and triggers it immediately") +{ + GIVEN { + PLAYER(SPECIES_RALTS) { Ability(ABILITY_TRACE); } + OPPONENT(SPECIES_MASQUERAIN) { Ability(ABILITY_INTIMIDATE); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ABILITY_TRACE); + ABILITY_POPUP(player, ABILITY_INTIMIDATE); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + } +} + DOUBLE_BATTLE_TEST("Trace respects the turn order") { GIVEN { diff --git a/test/battle/form_change/primal_reversion.c b/test/battle/form_change/primal_reversion.c index 2f2f406827..df19a1d0d6 100644 --- a/test/battle/form_change/primal_reversion.c +++ b/test/battle/form_change/primal_reversion.c @@ -234,3 +234,101 @@ SINGLE_BATTLE_TEST("Primal reversion happens immediately if it was brought in by EXPECT_EQ(player->species, SPECIES_GROUDON_PRIMAL); } } + + +DOUBLE_BATTLE_TEST("Primal reversion triggers for multiple battlers if multiple fainted the previous turn") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_EARTHQUAKE].target == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CATERPIE) { HP(1); } + PLAYER(SPECIES_RESHIRAM); + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_KYOGRE) { Item(ITEM_BLUE_ORB); } + OPPONENT(SPECIES_GROUDON) { Item(ITEM_RED_ORB); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EARTHQUAKE); + SEND_OUT(opponentRight, 3); + SEND_OUT(opponentLeft, 2); + SEND_OUT(playerRight, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EARTHQUAKE, playerLeft); + ABILITY_POPUP(opponentLeft, ABILITY_PRIMORDIAL_SEA); + ABILITY_POPUP(opponentRight, ABILITY_DESOLATE_LAND); + } +} + +DOUBLE_BATTLE_TEST("Primal reversion triggers for all battlers if multiple fainted the previous turn") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); + ASSUME(gMovesInfo[MOVE_EXPLOSION].target == MOVE_TARGET_FOES_AND_ALLY); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CATERPIE) { HP(1); } + PLAYER(SPECIES_KYOGRE) { Item(ITEM_BLUE_ORB); } + PLAYER(SPECIES_GROUDON) { Item(ITEM_RED_ORB); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_KYOGRE) { Item(ITEM_BLUE_ORB); } + OPPONENT(SPECIES_GROUDON) { Item(ITEM_RED_ORB); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_EXPLOSION); + SEND_OUT(opponentRight, 3); + SEND_OUT(opponentLeft, 2); + SEND_OUT(playerRight, 3); + SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_PRIMORDIAL_SEA); + ABILITY_POPUP(playerRight, ABILITY_DESOLATE_LAND); + ABILITY_POPUP(opponentLeft, ABILITY_PRIMORDIAL_SEA); + ABILITY_POPUP(opponentRight, ABILITY_DESOLATE_LAND); + } +} + +DOUBLE_BATTLE_TEST("Primal reversion and other switch-in effects trigger for all battlers if multiple fainted the previous turn") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_EXPLOSION].effect == EFFECT_EXPLOSION); + ASSUME(gMovesInfo[MOVE_EXPLOSION].target == MOVE_TARGET_FOES_AND_ALLY); + ASSUME(gMovesInfo[MOVE_STICKY_WEB].effect == EFFECT_STICKY_WEB); + ASSUME(gMovesInfo[MOVE_SPIKES].effect == EFFECT_SPIKES); + ASSUME(gMovesInfo[MOVE_TOXIC_SPIKES].effect == EFFECT_TOXIC_SPIKES); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CATERPIE) { HP(1); } + PLAYER(SPECIES_SCRAFTY) { Ability(ABILITY_INTIMIDATE); } + PLAYER(SPECIES_RESHIRAM); + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_CATERPIE) { HP(1); } + OPPONENT(SPECIES_KYOGRE) { Item(ITEM_BLUE_ORB); } + OPPONENT(SPECIES_GROUDON) { Item(ITEM_RED_ORB); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_STICKY_WEB); + MOVE(opponentLeft, MOVE_SPIKES); + MOVE(playerRight, MOVE_TOXIC_SPIKES); } + TURN { MOVE(playerLeft, MOVE_EXPLOSION); + SEND_OUT(opponentRight, 3); + SEND_OUT(opponentLeft, 2); + SEND_OUT(playerRight, 3); + SEND_OUT(playerLeft, 2); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPIKES, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC_SPIKES, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, playerLeft); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + ABILITY_POPUP(playerRight, ABILITY_TURBOBLAZE); + ABILITY_POPUP(opponentLeft, ABILITY_PRIMORDIAL_SEA); + ABILITY_POPUP(opponentRight, ABILITY_DESOLATE_LAND); + } THEN { + EXPECT_NE(playerLeft->hp, playerLeft->maxHP); + EXPECT_NE(playerRight->hp, playerRight->maxHP); + EXPECT_EQ(opponentLeft->status1, STATUS1_POISON); + EXPECT_EQ(opponentRight->status1, STATUS1_POISON); + EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponentLeft->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE - 1); + } +} diff --git a/test/battle/move_effect/dragon_darts.c b/test/battle/move_effect/dragon_darts.c index 8190f0f4bd..08913d6011 100644 --- a/test/battle/move_effect/dragon_darts.c +++ b/test/battle/move_effect/dragon_darts.c @@ -94,6 +94,83 @@ DOUBLE_BATTLE_TEST("Dragon Darts strikes the left ally twice if the target is a } } +DOUBLE_BATTLE_TEST("Dragon Darts strikes left ally twice if electrified and right ally has Volt Absorb") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_ELECTRIFY].effect == EFFECT_ELECTRIFY); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_LANTURN) { Ability(ABILITY_VOLT_ABSORB); }; + } WHEN { + TURN { MOVE(opponentRight, MOVE_ELECTRIFY, target: playerLeft); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentLeft); + MESSAGE("Hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes right ally twice if electrified and left ally has Volt Absorb") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_ELECTRIFY].effect == EFFECT_ELECTRIFY); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_LANTURN) { Ability(ABILITY_VOLT_ABSORB); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, MOVE_ELECTRIFY, target: playerLeft); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + MESSAGE("Hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes left ally twice if electrified and right ally has Motor Drive") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_ELECTRIFY].effect == EFFECT_ELECTRIFY); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ELECTIVIRE) { Ability(ABILITY_MOTOR_DRIVE); }; + } WHEN { + TURN { MOVE(opponentRight, MOVE_ELECTRIFY, target: playerLeft); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentLeft); + MESSAGE("Hit 2 time(s)!"); + } +} + +DOUBLE_BATTLE_TEST("Dragon Darts strikes right ally twice if electrified and left ally has Motor Drive") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_ELECTRIFY].effect == EFFECT_ELECTRIFY); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_ELECTIVIRE) { Ability(ABILITY_MOTOR_DRIVE); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentRight, MOVE_ELECTRIFY, target: playerLeft); MOVE(playerLeft, MOVE_DRAGON_DARTS, target: opponentRight); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DARTS, playerLeft); + HP_BAR(opponentRight); + MESSAGE("Hit 2 time(s)!"); + } +} + + DOUBLE_BATTLE_TEST("Dragon Darts strikes the ally twice if the target is in a semi-invulnerable turn") { GIVEN {