diff --git a/include/battle.h b/include/battle.h index 9cf4f100a..0219ebd72 100644 --- a/include/battle.h +++ b/include/battle.h @@ -594,6 +594,7 @@ struct BattleStruct u8 wishPerishSongBattlerId; bool8 overworldWeatherDone; bool8 terrainDone; + u8 isAtkCancelerForCalledMove; // Certain cases in atk canceler should only be checked once, when the original move is called, however others need to be checked the twice. u8 atkCancellerTracker; struct BattleTvMovePoints tvMovePoints; struct BattleTv tv; diff --git a/include/battle_util.h b/include/battle_util.h index b5ba5249e..684aa2b51 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -136,6 +136,7 @@ u8 DoBattlerEndTurnEffects(void); bool8 HandleWishPerishSongOnTurnEnd(void); bool8 HandleFaintedMonActions(void); void TryClearRageAndFuryCutter(void); +void SetAtkCancellerForCalledMove(void); u8 AtkCanceller_UnableToUseMove(void); u8 AtkCanceller_UnableToUseMove2(void); bool8 HasNoMonsToSwitch(u8 battlerId, u8 r1, u8 r2); diff --git a/src/battle_message.c b/src/battle_message.c index 3edb02f60..d2ca9dfd7 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -350,7 +350,7 @@ static const u8 sText_DontLeaveBirch[] = _("PROF. BIRCH: Don't leave me like thi static const u8 sText_ButNothingHappened[] = _("But nothing happened!"); static const u8 sText_ButItFailed[] = _("But it failed!"); static const u8 sText_ItHurtConfusion[] = _("It hurt itself in its\nconfusion!"); -static const u8 sText_MirrorMoveFailed[] = _("The MIRROR MOVE failed!"); +static const u8 sText_MirrorMoveFailed[] = _("The Mirror Move failed!"); static const u8 sText_StartedToRain[] = _("It started to rain!"); static const u8 sText_DownpourStarted[] = _("A downpour started!"); // corresponds to DownpourText in pokegold and pokecrystal and is used by Rain Dance in GSC static const u8 sText_RainContinues[] = _("Rain continues to fall."); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index d4975fd7b..b77f92964 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -1614,7 +1614,8 @@ static void Cmd_attackcanceler(void) PressurePPLose(gBattlerAttacker, gBattlerTarget, MOVE_MAGIC_COAT); gProtectStructs[gBattlerTarget].usesBouncedMove = TRUE; gBattleCommunication[MULTISTRING_CHOOSER] = 0; - gBattleStruct->atkCancellerTracker = CANCELLER_POWDER_MOVE; // Edge case for bouncing a powder move against a grass type pokemon. + // Edge case for bouncing a powder move against a grass type pokemon. + SetAtkCancellerForCalledMove(); if (BlocksPrankster(gCurrentMove, gBattlerTarget, gBattlerAttacker, TRUE)) { // Opponent used a prankster'd magic coat -> reflected status move should fail against a dark-type attacker @@ -1634,7 +1635,8 @@ static void Cmd_attackcanceler(void) { gProtectStructs[gBattlerTarget].usesBouncedMove = TRUE; gBattleCommunication[MULTISTRING_CHOOSER] = 1; - gBattleStruct->atkCancellerTracker = CANCELLER_POWDER_MOVE; // Edge case for bouncing a powder move against a grass type pokemon. + // Edge case for bouncing a powder move against a grass type pokemon. + SetAtkCancellerForCalledMove(); BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_MagicCoatBounce; gBattlerAbility = gBattlerTarget; @@ -6342,6 +6344,7 @@ static void Cmd_moveend(void) gBattleStruct->zmove.toBeUsed[gBattlerAttacker] = MOVE_NONE; gBattleStruct->zmove.effect = EFFECT_HIT; gBattleStruct->hitSwitchTargetFailed = FALSE; + gBattleStruct->isAtkCancelerForCalledMove = FALSE; gBattleScripting.moveendState++; break; case MOVEEND_COUNT: @@ -11408,12 +11411,20 @@ static void Cmd_tryhealhalfhealth(void) gBattlescriptCurrInstr = cmd->nextInstr; } +static void SetMoveForMirrorMove(u32 move) +{ + gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; + gCurrentMove = move; + SetAtkCancellerForCalledMove(); + gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); + gBattlescriptCurrInstr = gBattleScriptsForMoveEffects[gBattleMoves[gCurrentMove].effect]; +} + static void Cmd_trymirrormove(void) { CMD_ARGS(); - s32 validMovesCount; - s32 i; + s32 i, validMovesCount; u16 move; u16 validMoves[MAX_BATTLERS_COUNT] = {0}; @@ -11422,7 +11433,6 @@ static void Cmd_trymirrormove(void) if (i != gBattlerAttacker) { move = gBattleStruct->lastTakenMoveFrom[gBattlerAttacker][i]; - if (move != MOVE_NONE && move != MOVE_UNAVAILABLE) { validMoves[validMovesCount] = move; @@ -11432,21 +11442,13 @@ static void Cmd_trymirrormove(void) } move = gBattleStruct->lastTakenMove[gBattlerAttacker]; - if (move != MOVE_NONE && move != MOVE_UNAVAILABLE) { - gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; - gCurrentMove = move; - gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); - gBattlescriptCurrInstr = gBattleScriptsForMoveEffects[gBattleMoves[gCurrentMove].effect]; + SetMoveForMirrorMove(move); } else if (validMovesCount != 0) { - gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; - i = Random() % validMovesCount; - gCurrentMove = validMoves[i]; - gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); - gBattlescriptCurrInstr = gBattleScriptsForMoveEffects[gBattleMoves[gCurrentMove].effect]; + SetMoveForMirrorMove(validMoves[Random() % validMovesCount]); } else // no valid moves found { @@ -13003,6 +13005,7 @@ static void Cmd_metronome(void) if (!(sForbiddenMoves[gCurrentMove] & FORBIDDEN_METRONOME)) { gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; + SetAtkCancellerForCalledMove(); gBattlescriptCurrInstr = gBattleScriptsForMoveEffects[gBattleMoves[gCurrentMove].effect]; gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); return; diff --git a/src/battle_util.c b/src/battle_util.c index e608b0e51..f13e89bff 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3383,6 +3383,12 @@ void TryClearRageAndFuryCutter(void) } } +void SetAtkCancellerForCalledMove(void) +{ + gBattleStruct->atkCancellerTracker = CANCELLER_HEAL_BLOCKED; + gBattleStruct->isAtkCancelerForCalledMove = TRUE; +} + u8 AtkCanceller_UnableToUseMove(void) { u8 effect = 0; @@ -3565,7 +3571,7 @@ u8 AtkCanceller_UnableToUseMove(void) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_CONFUSED: // confusion - if (gBattleMons[gBattlerAttacker].status2 & STATUS2_CONFUSION) + if (!gBattleStruct->isAtkCancelerForCalledMove && gBattleMons[gBattlerAttacker].status2 & STATUS2_CONFUSION) { if (!(gStatuses4[gBattlerAttacker] & STATUS4_INFINITE_CONFUSION)) gBattleMons[gBattlerAttacker].status2 -= STATUS2_CONFUSION_TURN(1); @@ -3601,7 +3607,7 @@ u8 AtkCanceller_UnableToUseMove(void) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_PARALYSED: // paralysis - if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_PARALYSIS) && !RandomPercentage(RNG_PARALYSIS, 75)) + if (!gBattleStruct->isAtkCancelerForCalledMove && (gBattleMons[gBattlerAttacker].status1 & STATUS1_PARALYSIS) && !RandomPercentage(RNG_PARALYSIS, 75)) { gProtectStructs[gBattlerAttacker].prlzImmobility = TRUE; // This is removed in FRLG and Emerald for some reason @@ -3613,7 +3619,7 @@ u8 AtkCanceller_UnableToUseMove(void) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_IN_LOVE: // infatuation - if (gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION) + if (!gBattleStruct->isAtkCancelerForCalledMove && gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION) { gBattleScripting.battler = CountTrailingZeroBits((gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION) >> 0x10); if (!RandomPercentage(RNG_INFATUATION, 50)) diff --git a/test/ability_magic_bounce.c b/test/ability_magic_bounce.c index eaf925975..ab51369ef 100644 --- a/test/ability_magic_bounce.c +++ b/test/ability_magic_bounce.c @@ -56,7 +56,6 @@ SINGLE_BATTLE_TEST("Magic Bounce cannot bounce back powder moves against Grass T } } - DOUBLE_BATTLE_TEST("Magic Bounce bounces back moves hitting both foes at two foes") { GIVEN { diff --git a/test/move_effect_metronome.c b/test/move_effect_metronome.c new file mode 100644 index 000000000..11a906f94 --- /dev/null +++ b/test/move_effect_metronome.c @@ -0,0 +1,69 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_METRONOME].effect == EFFECT_METRONOME); +} + +// To do: Turn the seeds to work with WITH_RNG for Metronome. +#define RNG_METRONOME_SCRATCH 0x118 +#define RNG_METRONOME_PSN_POWDER 0x119 +#define RNG_METRONOME_ROCK_BLAST 0x1F5 + +SINGLE_BATTLE_TEST("Metronome picks a random move") +{ + GIVEN { + RNGSeed(RNG_METRONOME_SCRATCH); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_METRONOME); } + } SCENE { + MESSAGE("Wobbuffet used Metronome!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_METRONOME, player); + MESSAGE("Wobbuffet used Scratch!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Metronome's called powder move fails against Grass Types") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_POISON_POWDER].flags & FLAG_POWDER); + ASSUME(gSpeciesInfo[SPECIES_TANGELA].types[0] == TYPE_GRASS); + ASSUME(gBattleMoves[MOVE_POISON_POWDER].effect == EFFECT_POISON); + RNGSeed(RNG_METRONOME_PSN_POWDER); + PLAYER(SPECIES_WOBBUFFET) {Speed(5);} + OPPONENT(SPECIES_TANGELA) {Speed(2);} + } WHEN { + TURN { MOVE(player, MOVE_METRONOME); } + } SCENE { + MESSAGE("Wobbuffet used Metronome!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_METRONOME, player); + MESSAGE("Wobbuffet used PoisonPowder!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_POISON_POWDER, player); + MESSAGE("It doesn't affect Foe Tangela…"); + NOT STATUS_ICON(opponent, poison: TRUE); + } +} + +SINGLE_BATTLE_TEST("Metronome's called multi-hit move hits multiple times") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_ROCK_BLAST].effect == EFFECT_MULTI_HIT); + RNGSeed(RNG_METRONOME_ROCK_BLAST); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_METRONOME); } + } SCENE { + MESSAGE("Wobbuffet used Metronome!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_METRONOME, player); + MESSAGE("Wobbuffet used Rock Blast!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_BLAST, player); + HP_BAR(opponent); + MESSAGE("Hit 4 time(s)!"); + } +} diff --git a/test/move_effect_mirror_move.c b/test/move_effect_mirror_move.c new file mode 100644 index 000000000..2b8b9dbe9 --- /dev/null +++ b/test/move_effect_mirror_move.c @@ -0,0 +1,81 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_MIRROR_MOVE].effect == EFFECT_MIRROR_MOVE); +} + +SINGLE_BATTLE_TEST("Mirror Move copies the last used move by the target") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) {Speed(2);} + OPPONENT(SPECIES_WOBBUFFET) {Speed(5);} + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_MIRROR_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + HP_BAR(player); + MESSAGE("Wobbuffet used Mirror Move!"); + MESSAGE("Wobbuffet used Tackle!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Mirror Move fails if no move was used before") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) {Speed(5);} + OPPONENT(SPECIES_WOBBUFFET) {Speed(2);} + } WHEN { + TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_MIRROR_MOVE); } + } SCENE { + MESSAGE("Wobbuffet used Mirror Move!"); + MESSAGE("The Mirror Move failed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponent); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Mirror Move's called powder move fails against Grass Types") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_STUN_SPORE].flags & FLAG_POWDER); + ASSUME(gSpeciesInfo[SPECIES_ODDISH].types[0] == TYPE_GRASS); + ASSUME(gBattleMoves[MOVE_STUN_SPORE].effect == EFFECT_PARALYZE); + PLAYER(SPECIES_ODDISH) {Speed(5);} + OPPONENT(SPECIES_WOBBUFFET) {Speed(2);} + } WHEN { + TURN { MOVE(player, MOVE_STUN_SPORE); MOVE(opponent, MOVE_MIRROR_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, player); + STATUS_ICON(opponent, paralysis: TRUE); + MESSAGE("Foe Wobbuffet used Mirror Move!"); + MESSAGE("Foe Wobbuffet used Stun Spore!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_STUN_SPORE, opponent); + MESSAGE("It doesn't affect Oddish…"); + NOT STATUS_ICON(player, paralysis: TRUE); + } +} + +// It hits first 2 times, then 5 times with the default rng seed. +SINGLE_BATTLE_TEST("Mirror Move's called multi-hit move hits multiple times") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_BULLET_SEED].effect == EFFECT_MULTI_HIT); + PLAYER(SPECIES_WOBBUFFET) {Speed(5);} + OPPONENT(SPECIES_WOBBUFFET) {Speed(2);} + } WHEN { + TURN { MOVE(player, MOVE_BULLET_SEED); MOVE(opponent, MOVE_MIRROR_MOVE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); + HP_BAR(opponent); + MESSAGE("Hit 2 time(s)!"); + MESSAGE("Foe Wobbuffet used Mirror Move!"); + MESSAGE("Foe Wobbuffet used Bullet Seed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, opponent); + HP_BAR(player); + MESSAGE("Hit 5 time(s)!"); + } +}