fix mirror move/metronome powder/multi hit moves & tests

This commit is contained in:
DizzyEggg 2023-07-15 14:00:55 +02:00
parent 98ae1beaec
commit 2a249654e7
8 changed files with 180 additions and 20 deletions

View File

@ -594,6 +594,7 @@ struct BattleStruct
u8 wishPerishSongBattlerId; u8 wishPerishSongBattlerId;
bool8 overworldWeatherDone; bool8 overworldWeatherDone;
bool8 terrainDone; 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; u8 atkCancellerTracker;
struct BattleTvMovePoints tvMovePoints; struct BattleTvMovePoints tvMovePoints;
struct BattleTv tv; struct BattleTv tv;

View File

@ -136,6 +136,7 @@ u8 DoBattlerEndTurnEffects(void);
bool8 HandleWishPerishSongOnTurnEnd(void); bool8 HandleWishPerishSongOnTurnEnd(void);
bool8 HandleFaintedMonActions(void); bool8 HandleFaintedMonActions(void);
void TryClearRageAndFuryCutter(void); void TryClearRageAndFuryCutter(void);
void SetAtkCancellerForCalledMove(void);
u8 AtkCanceller_UnableToUseMove(void); u8 AtkCanceller_UnableToUseMove(void);
u8 AtkCanceller_UnableToUseMove2(void); u8 AtkCanceller_UnableToUseMove2(void);
bool8 HasNoMonsToSwitch(u8 battlerId, u8 r1, u8 r2); bool8 HasNoMonsToSwitch(u8 battlerId, u8 r1, u8 r2);

View File

@ -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_ButNothingHappened[] = _("But nothing happened!");
static const u8 sText_ButItFailed[] = _("But it failed!"); static const u8 sText_ButItFailed[] = _("But it failed!");
static const u8 sText_ItHurtConfusion[] = _("It hurt itself in its\nconfusion!"); 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_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_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."); static const u8 sText_RainContinues[] = _("Rain continues to fall.");

View File

@ -1614,7 +1614,8 @@ static void Cmd_attackcanceler(void)
PressurePPLose(gBattlerAttacker, gBattlerTarget, MOVE_MAGIC_COAT); PressurePPLose(gBattlerAttacker, gBattlerTarget, MOVE_MAGIC_COAT);
gProtectStructs[gBattlerTarget].usesBouncedMove = TRUE; gProtectStructs[gBattlerTarget].usesBouncedMove = TRUE;
gBattleCommunication[MULTISTRING_CHOOSER] = 0; 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)) if (BlocksPrankster(gCurrentMove, gBattlerTarget, gBattlerAttacker, TRUE))
{ {
// Opponent used a prankster'd magic coat -> reflected status move should fail against a dark-type attacker // 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; gProtectStructs[gBattlerTarget].usesBouncedMove = TRUE;
gBattleCommunication[MULTISTRING_CHOOSER] = 1; 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(); BattleScriptPushCursor();
gBattlescriptCurrInstr = BattleScript_MagicCoatBounce; gBattlescriptCurrInstr = BattleScript_MagicCoatBounce;
gBattlerAbility = gBattlerTarget; gBattlerAbility = gBattlerTarget;
@ -6342,6 +6344,7 @@ static void Cmd_moveend(void)
gBattleStruct->zmove.toBeUsed[gBattlerAttacker] = MOVE_NONE; gBattleStruct->zmove.toBeUsed[gBattlerAttacker] = MOVE_NONE;
gBattleStruct->zmove.effect = EFFECT_HIT; gBattleStruct->zmove.effect = EFFECT_HIT;
gBattleStruct->hitSwitchTargetFailed = FALSE; gBattleStruct->hitSwitchTargetFailed = FALSE;
gBattleStruct->isAtkCancelerForCalledMove = FALSE;
gBattleScripting.moveendState++; gBattleScripting.moveendState++;
break; break;
case MOVEEND_COUNT: case MOVEEND_COUNT:
@ -11408,12 +11411,20 @@ static void Cmd_tryhealhalfhealth(void)
gBattlescriptCurrInstr = cmd->nextInstr; 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) static void Cmd_trymirrormove(void)
{ {
CMD_ARGS(); CMD_ARGS();
s32 validMovesCount; s32 i, validMovesCount;
s32 i;
u16 move; u16 move;
u16 validMoves[MAX_BATTLERS_COUNT] = {0}; u16 validMoves[MAX_BATTLERS_COUNT] = {0};
@ -11422,7 +11433,6 @@ static void Cmd_trymirrormove(void)
if (i != gBattlerAttacker) if (i != gBattlerAttacker)
{ {
move = gBattleStruct->lastTakenMoveFrom[gBattlerAttacker][i]; move = gBattleStruct->lastTakenMoveFrom[gBattlerAttacker][i];
if (move != MOVE_NONE && move != MOVE_UNAVAILABLE) if (move != MOVE_NONE && move != MOVE_UNAVAILABLE)
{ {
validMoves[validMovesCount] = move; validMoves[validMovesCount] = move;
@ -11432,21 +11442,13 @@ static void Cmd_trymirrormove(void)
} }
move = gBattleStruct->lastTakenMove[gBattlerAttacker]; move = gBattleStruct->lastTakenMove[gBattlerAttacker];
if (move != MOVE_NONE && move != MOVE_UNAVAILABLE) if (move != MOVE_NONE && move != MOVE_UNAVAILABLE)
{ {
gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; SetMoveForMirrorMove(move);
gCurrentMove = move;
gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE);
gBattlescriptCurrInstr = gBattleScriptsForMoveEffects[gBattleMoves[gCurrentMove].effect];
} }
else if (validMovesCount != 0) else if (validMovesCount != 0)
{ {
gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; SetMoveForMirrorMove(validMoves[Random() % validMovesCount]);
i = Random() % validMovesCount;
gCurrentMove = validMoves[i];
gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE);
gBattlescriptCurrInstr = gBattleScriptsForMoveEffects[gBattleMoves[gCurrentMove].effect];
} }
else // no valid moves found else // no valid moves found
{ {
@ -13003,6 +13005,7 @@ static void Cmd_metronome(void)
if (!(sForbiddenMoves[gCurrentMove] & FORBIDDEN_METRONOME)) if (!(sForbiddenMoves[gCurrentMove] & FORBIDDEN_METRONOME))
{ {
gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED;
SetAtkCancellerForCalledMove();
gBattlescriptCurrInstr = gBattleScriptsForMoveEffects[gBattleMoves[gCurrentMove].effect]; gBattlescriptCurrInstr = gBattleScriptsForMoveEffects[gBattleMoves[gCurrentMove].effect];
gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE);
return; return;

View File

@ -3383,6 +3383,12 @@ void TryClearRageAndFuryCutter(void)
} }
} }
void SetAtkCancellerForCalledMove(void)
{
gBattleStruct->atkCancellerTracker = CANCELLER_HEAL_BLOCKED;
gBattleStruct->isAtkCancelerForCalledMove = TRUE;
}
u8 AtkCanceller_UnableToUseMove(void) u8 AtkCanceller_UnableToUseMove(void)
{ {
u8 effect = 0; u8 effect = 0;
@ -3565,7 +3571,7 @@ u8 AtkCanceller_UnableToUseMove(void)
gBattleStruct->atkCancellerTracker++; gBattleStruct->atkCancellerTracker++;
break; break;
case CANCELLER_CONFUSED: // confusion case CANCELLER_CONFUSED: // confusion
if (gBattleMons[gBattlerAttacker].status2 & STATUS2_CONFUSION) if (!gBattleStruct->isAtkCancelerForCalledMove && gBattleMons[gBattlerAttacker].status2 & STATUS2_CONFUSION)
{ {
if (!(gStatuses4[gBattlerAttacker] & STATUS4_INFINITE_CONFUSION)) if (!(gStatuses4[gBattlerAttacker] & STATUS4_INFINITE_CONFUSION))
gBattleMons[gBattlerAttacker].status2 -= STATUS2_CONFUSION_TURN(1); gBattleMons[gBattlerAttacker].status2 -= STATUS2_CONFUSION_TURN(1);
@ -3601,7 +3607,7 @@ u8 AtkCanceller_UnableToUseMove(void)
gBattleStruct->atkCancellerTracker++; gBattleStruct->atkCancellerTracker++;
break; break;
case CANCELLER_PARALYSED: // paralysis 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; gProtectStructs[gBattlerAttacker].prlzImmobility = TRUE;
// This is removed in FRLG and Emerald for some reason // This is removed in FRLG and Emerald for some reason
@ -3613,7 +3619,7 @@ u8 AtkCanceller_UnableToUseMove(void)
gBattleStruct->atkCancellerTracker++; gBattleStruct->atkCancellerTracker++;
break; break;
case CANCELLER_IN_LOVE: // infatuation 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); gBattleScripting.battler = CountTrailingZeroBits((gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION) >> 0x10);
if (!RandomPercentage(RNG_INFATUATION, 50)) if (!RandomPercentage(RNG_INFATUATION, 50))

View File

@ -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") DOUBLE_BATTLE_TEST("Magic Bounce bounces back moves hitting both foes at two foes")
{ {
GIVEN { GIVEN {

View File

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

View File

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