From b1c597495b17181bc33d6bcca3fd414a57768c0a Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Fri, 14 Feb 2025 12:47:02 +0000 Subject: [PATCH] Fixes Called moves ignoring redirection (#6267) --- data/battle_scripts_1.s | 14 -- include/battle_util.h | 1 + src/battle_script_commands.c | 29 ++-- src/battle_util.c | 226 ++++++++++++++------------- test/battle/move_effect/follow_me.c | 71 +++++++++ test/battle/move_effect/instruct.c | 69 +++++++- test/battle/move_effect/sleep_talk.c | 57 ++++++- test/battle/move_effect/snore.c | 59 +++++++ 8 files changed, 391 insertions(+), 135 deletions(-) create mode 100644 test/battle/move_effect/follow_me.c create mode 100644 test/battle/move_effect/snore.c diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 2b0ca93a9d..c69129125a 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -1744,8 +1744,6 @@ BattleScript_EffectCopycat:: trycopycat BattleScript_CopycatFail attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 jumptocalledmove TRUE BattleScript_CopycatFail: ppreduce @@ -1763,8 +1761,6 @@ BattleScript_EffectInstruct:: copybyte gBattlerTarget, gEffectBattler printstring STRINGID_USEDINSTRUCTEDMOVE waitmessage B_WAIT_TIME_LONG - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 jumptocalledmove TRUE BattleScript_EffectAutotomize:: @@ -2188,8 +2184,6 @@ BattleScript_EffectMeFirst:: trymefirst BattleScript_FailedFromPpReduce attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 jumptocalledmove TRUE BattleScript_EffectAttackSpAttackUp:: @@ -3842,8 +3836,6 @@ BattleScript_EffectMetronome:: pause B_WAIT_TIME_SHORT attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 metronome BattleScript_EffectLeechSeed:: @@ -4038,8 +4030,6 @@ BattleScript_SleepTalkIsAsleep:: BattleScript_SleepTalkUsingMove:: attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 jumptocalledmove TRUE BattleScript_EffectDestinyBond:: @@ -5055,8 +5045,6 @@ BattleScript_EffectAssist:: assistattackselect BattleScript_FailedFromPpReduce attackanimation waitanimation - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 jumptocalledmove TRUE BattleScript_EffectIngrain:: @@ -8709,8 +8697,6 @@ BattleScript_BattleBondActivatesOnMoveEndAttacker:: BattleScript_DancerActivates:: call BattleScript_AbilityPopUp waitmessage B_WAIT_TIME_SHORT - setbyte sB_ANIM_TURN, 0 - setbyte sB_ANIM_TARGETS_HIT, 0 orword gHitMarker, HITMARKER_ALLOW_NO_PP jumptocalledmove TRUE diff --git a/include/battle_util.h b/include/battle_util.h index 463d6609db..636428854b 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -166,6 +166,7 @@ STATIC_ASSERT(sizeof(struct DamageCalculationData) <= 4, StructExceedsFourBytes) void HandleAction_ThrowBall(void); bool32 IsAffectedByFollowMe(u32 battlerAtk, u32 defSide, u32 move); +bool32 HandleMoveTargetRedirection(void); void HandleAction_UseMove(void); void HandleAction_Switch(void); void HandleAction_UseItem(void); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index a34a6dc4b0..c3dc016314 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -336,6 +336,7 @@ static bool8 CanBurnHitThaw(u16 move); static u32 GetNextTarget(u32 moveTarget, bool32 excludeCurrent); static void TryUpdateEvolutionTracker(u32 evolutionMethod, u32 upAmount, u16 usedMove); static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u8 *failInstr, u16 move); +static void ResetValuesForCalledMove(void); static void Cmd_attackcanceler(void); static void Cmd_accuracycheck(void); @@ -8133,6 +8134,18 @@ static void Cmd_hidepartystatussummary(void) gBattlescriptCurrInstr = cmd->nextInstr; } +static void ResetValuesForCalledMove(void) +{ + if (gBattlerByTurnOrder[gCurrentTurnActionNumber] != gBattlerAttacker) + gBattleStruct->atkCancellerTracker = 0; + else + SetAtkCancellerForCalledMove(); + gBattleScripting.animTurn = 0; + gBattleScripting.animTargetsHit = 0; + SetTypeBeforeUsingMove(gCurrentMove, gBattlerAttacker); + HandleMoveTargetRedirection(); +} + static void Cmd_jumptocalledmove(void) { CMD_ARGS(bool8 notChosenMove); @@ -8142,6 +8155,8 @@ static void Cmd_jumptocalledmove(void) else gChosenMove = gCurrentMove = gCalledMove; + ResetValuesForCalledMove(); + gBattlescriptCurrInstr = GET_MOVE_BATTLESCRIPT(gCurrentMove); } @@ -10193,10 +10208,8 @@ static void Cmd_various(void) gBattlescriptCurrInstr = cmd->failInstr; else { - SetTypeBeforeUsingMove(gCalledMove, gBattlerTarget); gEffectBattler = gBattleStruct->lastMoveTarget[gBattlerTarget]; gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; - gBattleStruct->atkCancellerTracker = 0; PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, battler, gBattlerPartyIndexes[battler]); gBattlescriptCurrInstr = cmd->nextInstr; } @@ -11212,8 +11225,8 @@ static void SetMoveForMirrorMove(u32 move) gCurrentMove = move; } - SetAtkCancellerForCalledMove(); gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); + ResetValuesForCalledMove(); gBattlescriptCurrInstr = GET_MOVE_BATTLESCRIPT(gCurrentMove); } @@ -12765,10 +12778,10 @@ static void Cmd_metronome(void) #endif gCurrentMove = RandomUniformExcept(RNG_METRONOME, 1, moveCount - 1, InvalidMetronomeMove); - SetAtkCancellerForCalledMove(); PrepareStringBattle(STRINGID_WAGGLINGAFINGER, gBattlerAttacker); gBattlescriptCurrInstr = GET_MOVE_BATTLESCRIPT(gCurrentMove); gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); + ResetValuesForCalledMove(); } static void Cmd_dmgtolevel(void) @@ -17342,14 +17355,10 @@ void BS_JumpIfBlockedBySoundproof(void) void BS_SetMagicCoatTarget(void) { NATIVE_ARGS(); - u32 side; gBattleStruct->attackerBeforeBounce = gBattleScripting.battler = gBattlerAttacker; gBattlerAttacker = gBattlerTarget; - side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)); - if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove)) - gBattlerTarget = gSideTimers[side].followmeTarget; - else - gBattlerTarget = gBattleStruct->attackerBeforeBounce; + gBattlerTarget = gBattleStruct->attackerBeforeBounce; + HandleMoveTargetRedirection(); gBattlescriptCurrInstr = cmd->nextInstr; } diff --git a/src/battle_util.c b/src/battle_util.c index 20e4ba635e..14469b072a 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -124,11 +124,94 @@ bool32 IsAffectedByFollowMe(u32 battlerAtk, u32 defSide, u32 move) return TRUE; } +bool32 HandleMoveTargetRedirection(void) +{ + u32 redirectorOrderNum = MAX_BATTLERS_COUNT; + u16 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); + u32 moveType = GetMoveType(gCurrentMove); + u32 side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)); + u32 ability = GetBattlerAbility(gBattleStruct->moveTarget[gBattlerAttacker]); + + if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove) + && moveTarget == MOVE_TARGET_SELECTED + && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gSideTimers[side].followmeTarget)) + { + gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = gSideTimers[side].followmeTarget; // follow me moxie fix + return FALSE; + } + else if (IsDoubleBattle() + && gSideTimers[side].followmeTimer == 0 + && (!IS_MOVE_STATUS(gCurrentMove) || (moveTarget != MOVE_TARGET_USER && moveTarget != MOVE_TARGET_ALL_BATTLERS)) + && ((ability != ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC) + || (ability != ABILITY_STORM_DRAIN && moveType == TYPE_WATER))) + { + // Find first battler that redirects the move (in turn order) + u32 battler; + for (battler = 0; battler < gBattlersCount; battler++) + { + ability = GetBattlerAbility(battler); + if ((B_REDIRECT_ABILITY_ALLIES >= GEN_4 || !IsAlly(gBattlerAttacker, battler)) + && battler != gBattlerAttacker + && gBattleStruct->moveTarget[gBattlerAttacker] != battler + && ((ability == ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC) + || (ability == ABILITY_STORM_DRAIN && moveType == TYPE_WATER)) + && GetBattlerTurnOrderNum(battler) < redirectorOrderNum + && gMovesInfo[gCurrentMove].effect != EFFECT_SNIPE_SHOT + && gMovesInfo[gCurrentMove].effect != EFFECT_PLEDGE + && GetBattlerAbility(gBattlerAttacker) != ABILITY_PROPELLER_TAIL + && GetBattlerAbility(gBattlerAttacker) != ABILITY_STALWART) + { + redirectorOrderNum = GetBattlerTurnOrderNum(battler); + } + } + if (redirectorOrderNum == MAX_BATTLERS_COUNT) + { + if (moveTarget & MOVE_TARGET_RANDOM) + { + gBattlerTarget = SetRandomTarget(gBattlerAttacker); + } + else if (moveTarget & MOVE_TARGET_FOES_AND_ALLY) + { + for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++) + { + if (gBattlerTarget == gBattlerAttacker) + continue; + if (IsBattlerAlive(gBattlerTarget)) + break; + } + } + else + { + gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker); + } + + if (!IsBattlerAlive(gBattlerTarget) && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)) + { + gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); + } + } + else + { + u16 battlerAbility; + battler = gBattlerByTurnOrder[redirectorOrderNum]; + battlerAbility = GetBattlerAbility(battler); + + RecordAbilityBattle(battler, gBattleMons[battler].ability); + if (battlerAbility == ABILITY_LIGHTNING_ROD && gCurrentMove != MOVE_TEATIME) + gSpecialStatuses[battler].lightningRodRedirected = TRUE; + else if (battlerAbility == ABILITY_STORM_DRAIN) + gSpecialStatuses[battler].stormDrainRedirected = TRUE; + gBattlerTarget = battler; + } + return TRUE; + } + return FALSE; +} + // Functions void HandleAction_UseMove(void) { - u32 battler, i, side, moveType, ability, var = MAX_BATTLERS_COUNT; - u16 moveTarget; + u32 i; gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber]; if (gBattleStruct->absentBattlerFlags & (1u << gBattlerAttacker) @@ -199,7 +282,6 @@ void HandleAction_UseMove(void) // Set dynamic move type. SetTypeBeforeUsingMove(gChosenMove, gBattlerAttacker); - moveType = GetMoveType(gCurrentMove); // check Z-Move used if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !IS_MOVE_STATUS(gCurrentMove) && !IsZMove(gCurrentMove)) @@ -214,115 +296,44 @@ void HandleAction_UseMove(void) gCurrentMove = gChosenMove = GetMaxMove(gBattlerAttacker, gCurrentMove); } - moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); - - // choose target - side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)); - ability = GetBattlerAbility(gBattleStruct->moveTarget[gBattlerAttacker]); - if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove) - && moveTarget == MOVE_TARGET_SELECTED - && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gSideTimers[side].followmeTarget)) + if (!HandleMoveTargetRedirection()) { - gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = gSideTimers[side].followmeTarget; // follow me moxie fix - } - else if (IsDoubleBattle() - && gSideTimers[side].followmeTimer == 0 - && (!IS_MOVE_STATUS(gCurrentMove) || (moveTarget != MOVE_TARGET_USER && moveTarget != MOVE_TARGET_ALL_BATTLERS)) - && ((ability != ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC) - || (ability != ABILITY_STORM_DRAIN && moveType == TYPE_WATER))) - { - // Find first battler that redirects the move (in turn order) - for (battler = 0; battler < gBattlersCount; battler++) + u32 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove); + if (IsDoubleBattle() && moveTarget & MOVE_TARGET_RANDOM) { - ability = GetBattlerAbility(battler); - if ((B_REDIRECT_ABILITY_ALLIES >= GEN_4 || !IsAlly(gBattlerAttacker, battler)) - && battler != gBattlerAttacker - && gBattleStruct->moveTarget[gBattlerAttacker] != battler - && ((ability == ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC) - || (ability == ABILITY_STORM_DRAIN && moveType == TYPE_WATER)) - && GetBattlerTurnOrderNum(battler) < var - && gMovesInfo[gCurrentMove].effect != EFFECT_SNIPE_SHOT - && gMovesInfo[gCurrentMove].effect != EFFECT_PLEDGE - && GetBattlerAbility(gBattlerAttacker) != ABILITY_PROPELLER_TAIL - && GetBattlerAbility(gBattlerAttacker) != ABILITY_STALWART) - { - var = GetBattlerTurnOrderNum(battler); - } - } - if (var == MAX_BATTLERS_COUNT) - { - if (moveTarget & MOVE_TARGET_RANDOM) - { - gBattlerTarget = SetRandomTarget(gBattlerAttacker); - } - else if (moveTarget & MOVE_TARGET_FOES_AND_ALLY) - { - for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++) - { - if (gBattlerTarget == gBattlerAttacker) - continue; - if (IsBattlerAlive(gBattlerTarget)) - break; - } - } - else - { - gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker); - } - - if (!IsBattlerAlive(gBattlerTarget) && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)) + gBattlerTarget = SetRandomTarget(gBattlerAttacker); + if (gAbsentBattlerFlags & (1u << gBattlerTarget) + && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)) { gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); } } + else if (moveTarget == MOVE_TARGET_ALLY) + { + if (IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker))) + gBattlerTarget = BATTLE_PARTNER(gBattlerAttacker); + else + gBattlerTarget = gBattlerAttacker; + } + else if (IsDoubleBattle() && moveTarget == MOVE_TARGET_FOES_AND_ALLY) + { + for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++) + { + if (gBattlerTarget == gBattlerAttacker) + continue; + if (IsBattlerAlive(gBattlerTarget)) + break; + } + } else { - u16 battlerAbility; - battler = gBattlerByTurnOrder[var]; - battlerAbility = GetBattlerAbility(battler); - - RecordAbilityBattle(battler, gBattleMons[battler].ability); - if (battlerAbility == ABILITY_LIGHTNING_ROD && gCurrentMove != MOVE_TEATIME) - gSpecialStatuses[battler].lightningRodRedirected = TRUE; - else if (battlerAbility == ABILITY_STORM_DRAIN) - gSpecialStatuses[battler].stormDrainRedirected = TRUE; - gBattlerTarget = battler; - } - } - else if (IsDoubleBattle() && moveTarget & MOVE_TARGET_RANDOM) - { - gBattlerTarget = SetRandomTarget(gBattlerAttacker); - if (gAbsentBattlerFlags & (1u << gBattlerTarget) - && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)) - { - gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); - } - } - else if (moveTarget == MOVE_TARGET_ALLY) - { - if (IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker))) - gBattlerTarget = BATTLE_PARTNER(gBattlerAttacker); - else - gBattlerTarget = gBattlerAttacker; - } - else if (IsDoubleBattle() && moveTarget == MOVE_TARGET_FOES_AND_ALLY) - { - for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++) - { - if (gBattlerTarget == gBattlerAttacker) - continue; - if (IsBattlerAlive(gBattlerTarget)) - break; - } - } - else - { - gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker); - if (!IsBattlerAlive(gBattlerTarget) - && moveTarget != MOVE_TARGET_OPPONENTS_FIELD - && (GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget))) - { - gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); + gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker); + if (!IsBattlerAlive(gBattlerTarget) + && moveTarget != MOVE_TARGET_OPPONENTS_FIELD + && (GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget))) + { + gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget))); + } } } @@ -3275,7 +3286,8 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gBattleMons[gBattlerAttacker].status1 -= toSub; if (gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP) { - if (gChosenMove != MOVE_SNORE && gChosenMove != MOVE_SLEEP_TALK) + u32 moveEffect = gMovesInfo[gChosenMove].effect; + if (moveEffect != EFFECT_SNORE && moveEffect != EFFECT_SLEEP_TALK) { gBattlescriptCurrInstr = BattleScript_MoveUsedIsAsleep; gHitMarker |= HITMARKER_UNABLE_TO_USE_MOVE; @@ -3591,7 +3603,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_STANCE_CHANGE_2: - if (B_STANCE_CHANGE_FAIL >= GEN_7 && TryFormChangeBeforeMove()) + if (B_STANCE_CHANGE_FAIL >= GEN_7 && !gBattleStruct->isAtkCancelerForCalledMove && TryFormChangeBeforeMove()) effect = 1; gBattleStruct->atkCancellerTracker++; break; @@ -6222,7 +6234,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 // Set bit and save Dancer mon's original target gSpecialStatuses[battler].dancerUsedMove = TRUE; gSpecialStatuses[battler].dancerOriginalTarget = *(gBattleStruct->moveTarget + battler) | 0x4; - gBattleStruct->atkCancellerTracker = 0; gBattlerAttacker = gBattlerAbility = battler; gCalledMove = gCurrentMove; @@ -6231,7 +6242,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 // Edge case for dance moves that hit multiply targets gHitMarker &= ~HITMARKER_NO_ATTACKSTRING; - SetTypeBeforeUsingMove(gCalledMove, battler); // Make sure that the target isn't an ally - if it is, target the original user if (GetBattlerSide(gBattlerTarget) == GetBattlerSide(gBattlerAttacker)) diff --git a/test/battle/move_effect/follow_me.c b/test/battle/move_effect/follow_me.c new file mode 100644 index 0000000000..fe7b96207d --- /dev/null +++ b/test/battle/move_effect/follow_me.c @@ -0,0 +1,71 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_FOLLOW_ME].effect == EFFECT_FOLLOW_ME); + ASSUME(gMovesInfo[MOVE_SPOTLIGHT].effect == EFFECT_FOLLOW_ME); +} + +DOUBLE_BATTLE_TEST("Follow Me redirects single target moves used by opponents to user") +{ + struct BattlePokemon *moveUser = NULL; + struct BattlePokemon *partner = NULL; + PARAMETRIZE { moveUser = opponentLeft; partner = opponentRight; } + PARAMETRIZE { moveUser = opponentRight; partner = opponentLeft; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_TACKLE, target: moveUser); + MOVE(playerRight, MOVE_TACKLE, target: partner); + MOVE(moveUser, MOVE_FOLLOW_ME); + MOVE(partner, MOVE_TACKLE, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FOLLOW_ME, moveUser); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + HP_BAR(moveUser); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + HP_BAR(moveUser); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, partner); + HP_BAR(playerLeft); + } +} + +DOUBLE_BATTLE_TEST("Spotlight redirects single target moves used by the opposing side to Spotlight's target") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = playerRight; } + PARAMETRIZE { moveTarget = opponentLeft; } + PARAMETRIZE { moveTarget = opponentRight; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_SPOTLIGHT, target: moveTarget); + MOVE(playerRight, MOVE_TACKLE, target: opponentRight); + MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); + MOVE(opponentRight, MOVE_TACKLE, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPOTLIGHT, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight); + if (moveTarget != playerRight) + HP_BAR(moveTarget); + else + HP_BAR(opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + if (moveTarget == playerRight) + HP_BAR(moveTarget); + else + HP_BAR(playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight); + if (moveTarget == playerRight) + HP_BAR(moveTarget); + else + HP_BAR(playerLeft); + } +} diff --git a/test/battle/move_effect/instruct.c b/test/battle/move_effect/instruct.c index b31076f3fe..313fb05cef 100644 --- a/test/battle/move_effect/instruct.c +++ b/test/battle/move_effect/instruct.c @@ -217,8 +217,11 @@ DOUBLE_BATTLE_TEST("Instruct-called moves keep their priority") } } -DOUBLE_BATTLE_TEST("Instructed move will be absorbed by Lightning Rod if it turns into an Electric Type move") +DOUBLE_BATTLE_TEST("Instructed move will be redirected and absorbed by Lightning Rod if it turns into an Electric Type move") { + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = opponentLeft; } + PARAMETRIZE { moveTarget = opponentRight; } GIVEN { PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WYNAUT); @@ -226,7 +229,7 @@ DOUBLE_BATTLE_TEST("Instructed move will be absorbed by Lightning Rod if it turn OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { - MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); + MOVE(playerLeft, MOVE_TACKLE, target: moveTarget); MOVE(opponentLeft, MOVE_PLASMA_FISTS, target: playerLeft); MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); MOVE(opponentRight, MOVE_CELEBRATE); @@ -239,3 +242,65 @@ DOUBLE_BATTLE_TEST("Instructed move will be absorbed by Lightning Rod if it turn NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); } } + +DOUBLE_BATTLE_TEST("Instructed move will be redirected by Follow Me after instructed target loses Stalwart") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = opponentLeft; } + PARAMETRIZE { moveTarget = opponentRight; } + GIVEN { + ASSUME(gMovesInfo[MOVE_FOLLOW_ME].effect == EFFECT_FOLLOW_ME); + ASSUME(gMovesInfo[MOVE_SKILL_SWAP].effect == EFFECT_SKILL_SWAP); + PLAYER(SPECIES_DURALUDON) { Ability(ABILITY_STALWART); } + PLAYER(SPECIES_DURALUDON) { Ability(ABILITY_STALWART); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_TACKLE, target: moveTarget); + MOVE(opponentLeft, MOVE_FOLLOW_ME); + MOVE(opponentRight, MOVE_SKILL_SWAP, target: playerLeft); + MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FOLLOW_ME, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + HP_BAR(moveTarget); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponentRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + HP_BAR(opponentLeft); + } +} + +DOUBLE_BATTLE_TEST("Instructed move will be redirected by Rage Powder after instructed target loses Grass typing") +{ + struct BattlePokemon *moveTarget = NULL; + PARAMETRIZE { moveTarget = opponentLeft; } + PARAMETRIZE { moveTarget = opponentRight; } + GIVEN { + ASSUME(gMovesInfo[MOVE_RAGE_POWDER].effect == EFFECT_FOLLOW_ME); + ASSUME(gMovesInfo[MOVE_RAGE_POWDER].powderMove == TRUE); + ASSUME(gMovesInfo[MOVE_SOAK].effect == EFFECT_SOAK); + PLAYER(SPECIES_TREECKO); + PLAYER(SPECIES_SCEPTILE); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { + MOVE(playerLeft, MOVE_TACKLE, target: moveTarget); + MOVE(opponentLeft, MOVE_RAGE_POWDER); + MOVE(opponentRight, MOVE_SOAK, target: playerLeft); + MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAGE_POWDER, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + HP_BAR(moveTarget); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SOAK, opponentRight); + MESSAGE("Treecko transformed into the Water type!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + HP_BAR(opponentLeft); + } +} diff --git a/test/battle/move_effect/sleep_talk.c b/test/battle/move_effect/sleep_talk.c index 8ecd600f36..aaff5dc123 100644 --- a/test/battle/move_effect/sleep_talk.c +++ b/test/battle/move_effect/sleep_talk.c @@ -32,7 +32,6 @@ SINGLE_BATTLE_TEST("Sleep Talk fails if not asleep") } } - SINGLE_BATTLE_TEST("Sleep Talk works if user has Comatose") { @@ -91,3 +90,59 @@ SINGLE_BATTLE_TEST("Sleep Talk can use moves while choiced into Sleep Talk") ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE); } } + +SINGLE_BATTLE_TEST("Sleep Talk fails if user is taunted") +{ + GIVEN { + ASSUME(gMovesInfo[MOVE_TAUNT].effect == EFFECT_TAUNT); + ASSUME(gMovesInfo[MOVE_SLEEP_TALK].category == DAMAGE_CATEGORY_STATUS); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_TACKLE, MOVE_FLY, MOVE_DIG); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_TAUNT); MOVE(player, MOVE_SLEEP_TALK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, opponent); + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + } + } +} + +DOUBLE_BATTLE_TEST("Sleep Talk calls move and that move may be redirected by Lightning Rod") +{ + PASSES_RANDOMLY(1, 2, RNG_RANDOM_TARGET); + GIVEN { + ASSUME(gMovesInfo[MOVE_SPARK].type == TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_SPARK, MOVE_FLY, MOVE_DIG); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SLEEP_TALK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, playerLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SPARK, playerLeft); + MESSAGE("The opposing Raichu's Lightning Rod took the attack!"); + ABILITY_POPUP(opponentRight, ABILITY_LIGHTNING_ROD); + } +} + +DOUBLE_BATTLE_TEST("Sleep Talk calls move and that move may be redirected by Storm Drain") +{ + PASSES_RANDOMLY(1, 2, RNG_RANDOM_TARGET); + GIVEN { + ASSUME(gMovesInfo[MOVE_WATER_GUN].type == TYPE_WATER); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_WATER_GUN, MOVE_FLY, MOVE_DIG); } + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_GASTRODON) { Ability(ABILITY_STORM_DRAIN); } + } WHEN { + TURN { MOVE(playerLeft, MOVE_SLEEP_TALK); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, playerLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerLeft); + MESSAGE("The opposing Gastrodon's Storm Drain took the attack!"); + ABILITY_POPUP(opponentRight, ABILITY_STORM_DRAIN); + } +} diff --git a/test/battle/move_effect/snore.c b/test/battle/move_effect/snore.c new file mode 100644 index 0000000000..d83e35b16c --- /dev/null +++ b/test/battle/move_effect/snore.c @@ -0,0 +1,59 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_SNORE].effect == EFFECT_SNORE); +} + +SINGLE_BATTLE_TEST("Snore fails if not asleep") +{ + u32 status; + PARAMETRIZE { status = STATUS1_SLEEP; } + PARAMETRIZE { status = STATUS1_NONE; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Status1(status); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SNORE); } + } SCENE { + if (status == STATUS1_SLEEP) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player); + NOT MESSAGE("But it failed!"); + } + else { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player); + MESSAGE("But it failed!"); + } + } +} + +SINGLE_BATTLE_TEST("Snore works if user has Comatose") +{ + + GIVEN { + PLAYER(SPECIES_KOMALA); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SNORE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player); + NOT MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Snore fails if user is throat chopped") +{ + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_THROAT_CHOP, MOVE_EFFECT_THROAT_CHOP)); + ASSUME(gMovesInfo[MOVE_SNORE].soundMove == TRUE); + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_THROAT_CHOP); MOVE(player, MOVE_SNORE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_THROAT_CHOP, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player); + } +}