Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Instruct bypassing AtkCanceler, Entry Hazards targeting wrong side of the field if opponent fainted #5262

Merged
merged 6 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions data/battle_scripts_1.s
Original file line number Diff line number Diff line change
Expand Up @@ -1757,6 +1757,8 @@ BattleScript_EffectInstruct::
tryinstruct BattleScript_ButItFailed
attackanimation
waitanimation
copybyte gBattlerAttacker, gBattlerTarget
copybyte gBattlerTarget, gEffectBattler
printstring STRINGID_USEDINSTRUCTEDMOVE
waitmessage B_WAIT_TIME_LONG
setbyte sB_ANIM_TURN, 0
Expand Down
1 change: 0 additions & 1 deletion include/battle_scripts.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,6 @@ extern const u8 BattleScript_EmbargoEndTurn[];
extern const u8 BattleScript_TelekinesisEndTurn[];
extern const u8 BattleScript_BufferEndTurn[];
extern const u8 BattleScript_AquaRingHeal[];
extern const u8 BattleScript_AuroraVeilEnds[];
extern const u8 BattleScript_LuckyChantEnds[];
extern const u8 BattleScript_TailwindEnds[];
extern const u8 BattleScript_TrickRoomEnds[];
Expand Down
539 changes: 269 additions & 270 deletions include/constants/battle_string_ids.h

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/battle_message.c
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,6 @@ static const u8 sText_TargetAbilityRaisedStat[] = _("{B_DEF_NAME_WITH_PREFIX}'s
static const u8 sText_TargetAbilityLoweredStat[] = _("{B_DEF_NAME_WITH_PREFIX}'s {B_DEF_ABILITY}\nlowered its {B_BUFF1}!");
static const u8 sText_AttackerAbilityRaisedStat[] = _("{B_ATK_NAME_WITH_PREFIX}'s {B_ATK_ABILITY}\nraised its {B_BUFF1}!");
static const u8 sText_ScriptingAbilityRaisedStat[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX}'s {B_SCR_ACTIVE_ABILITY}\nraised its {B_BUFF1}!");
static const u8 sText_AuroraVeilEnds[] = _("{B_DEF_NAME_WITH_PREFIX}'s {B_DEF_ABILITY}\nwore off!");
static const u8 sText_ElectricTerrainEnds[] = _("The electricity disappeared\nfrom the battlefield.");
static const u8 sText_MistyTerrainEnds[] = _("The mist disappeared\nfrom the battlefield.");
static const u8 sText_PsychicTerrainEnds[] = _("The weirdness disappeared\nfrom the battlefield.");
Expand Down Expand Up @@ -1442,7 +1441,6 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
[STRINGID_WATERSPORTENDS - BATTLESTRINGS_TABLE_START] = sText_WaterSportEnds,
[STRINGID_GRAVITYENDS - BATTLESTRINGS_TABLE_START] = sText_GravityEnds,
[STRINGID_AQUARINGHEAL - BATTLESTRINGS_TABLE_START] = sText_AquaRingHeal,
[STRINGID_AURORAVEILENDS - BATTLESTRINGS_TABLE_START] = sText_AuroraVeilEnds,
[STRINGID_ELECTRICTERRAINENDS - BATTLESTRINGS_TABLE_START] = sText_ElectricTerrainEnds,
[STRINGID_MISTYTERRAINENDS - BATTLESTRINGS_TABLE_START] = sText_MistyTerrainEnds,
[STRINGID_PSYCHICTERRAINENDS - BATTLESTRINGS_TABLE_START] = sText_PsychicTerrainEnds,
Expand Down
10 changes: 5 additions & 5 deletions src/battle_script_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -9957,23 +9957,23 @@ static void Cmd_various(void)
else
{
gSpecialStatuses[gBattlerTarget].instructedChosenTarget = *(gBattleStruct->moveTarget + gBattlerTarget) | 0x4;
gBattlerAttacker = gBattlerTarget;
gCalledMove = move;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBattleMons[gBattlerAttacker].moves[i] == gCalledMove)
if (gBattleMons[gBattlerTarget].moves[i] == gCalledMove)
{
gCurrMovePos = i;
i = 4;
break;
}
}
if (i != 4 || gBattleMons[gBattlerAttacker].pp[gCurrMovePos] == 0)
if (i != 4 || gBattleMons[gBattlerTarget].pp[gCurrMovePos] == 0)
gBattlescriptCurrInstr = cmd->failInstr;
else
{
gBattlerTarget = gBattleStruct->lastMoveTarget[gBattlerAttacker];
gEffectBattler = gBattleStruct->lastMoveTarget[gBattlerTarget];
gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED;
gBattleStruct->atkCancellerTracker = 0;
PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, battler, gBattlerPartyIndexes[battler]);
gBattlescriptCurrInstr = cmd->nextInstr;
}
Expand Down Expand Up @@ -11408,7 +11408,7 @@ static void Cmd_jumpifnotfirstturn(void)

const u8 *jumpInstr = cmd->jumpInstr;

if (gDisableStructs[gBattlerAttacker].isFirstTurn)
if (gDisableStructs[gBattlerAttacker].isFirstTurn && !(gSpecialStatuses[gBattlerAttacker].instructedChosenTarget))
gBattlescriptCurrInstr = cmd->nextInstr;
else
gBattlescriptCurrInstr = jumpInstr;
Expand Down
8 changes: 5 additions & 3 deletions src/battle_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ void HandleAction_UseMove(void)
else
{
gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker);
if (!IsBattlerAlive(gBattlerTarget))
if (!IsBattlerAlive(gBattlerTarget) && moveTarget != MOVE_TARGET_OPPONENTS_FIELD)
{
if (GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget))
{
Expand Down Expand Up @@ -3233,7 +3233,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType)
case CANCELLER_FLAGS: // flags clear
gBattleMons[gBattlerAttacker].status2 &= ~STATUS2_DESTINY_BOND;
gStatuses3[gBattlerAttacker] &= ~STATUS3_GRUDGE;
gStatuses4[gBattlerAttacker] &= ~ STATUS4_GLAIVE_RUSH;
gStatuses4[gBattlerAttacker] &= ~STATUS4_GLAIVE_RUSH;
gBattleStruct->atkCancellerTracker++;
break;
case CANCELLER_SKY_DROP:
Expand Down Expand Up @@ -8302,11 +8302,13 @@ u32 GetMoveTarget(u16 move, u8 setTarget)
case MOVE_TARGET_DEPENDS:
case MOVE_TARGET_BOTH:
case MOVE_TARGET_FOES_AND_ALLY:
case MOVE_TARGET_OPPONENTS_FIELD:
targetBattler = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)));
if (!IsBattlerAlive(targetBattler))
targetBattler ^= BIT_FLANK;
break;
case MOVE_TARGET_OPPONENTS_FIELD:
targetBattler = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)));
break;
case MOVE_TARGET_RANDOM:
side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
if (IsAffectedByFollowMe(gBattlerAttacker, side, move))
Expand Down
218 changes: 218 additions & 0 deletions test/battle/move_effect/instruct.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#include "global.h"
#include "test/battle.h"

ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_INSTRUCT].effect == EFFECT_INSTRUCT);
}

DOUBLE_BATTLE_TEST("Instruct fails if target hasn't made a move")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
}
}

DOUBLE_BATTLE_TEST("Instruct fails if move is banned by Instruct")
{
ASSUME(gMovesInfo[MOVE_BIDE].instructBanned == TRUE);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_BIDE); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_BIDE); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_BIDE, playerRight);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
}
}

DOUBLE_BATTLE_TEST("Instruct-called move targets the target of the move picked on its last use")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_FAKE_OUT); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
HP_BAR(opponentLeft);
NOT HP_BAR(opponentRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
HP_BAR(opponentLeft);
NOT HP_BAR(opponentRight);
}
}

DOUBLE_BATTLE_TEST("Instruct doesn't bypass sleep")
{
ASSUME(gMovesInfo[MOVE_SPORE].effect == EFFECT_SLEEP);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_GROWL); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); MOVE(opponentLeft, MOVE_SPORE, target: playerRight); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
MESSAGE("Wobbuffet is fast asleep.");
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
}
}

DOUBLE_BATTLE_TEST("Instruct fails if target doesn't know the last move it used")
{
ASSUME(gMovesInfo[MOVE_DRAGON_DANCE].danceMove == TRUE);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_ORICORIO) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentLeft, MOVE_DRAGON_DANCE); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft);
ABILITY_POPUP(playerRight, ABILITY_DANCER);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight);
}
}
}

DOUBLE_BATTLE_TEST("Instruct-called move fails if it can only be used on the first turn but consumes PP")
{
ASSUME(gMovesInfo[MOVE_FAKE_OUT].effect == EFFECT_FIRST_TURN_ONLY);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_FAKE_OUT); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_FAKE_OUT, target: opponentLeft); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, playerRight);
} THEN {
EXPECT_EQ(playerRight->pp[3], gMovesInfo[MOVE_FAKE_OUT].pp - 2);
}
}

DOUBLE_BATTLE_TEST("Instruct-called move doesn't fail if tormented")
{
ASSUME(gMovesInfo[MOVE_TORMENT].effect == EFFECT_TORMENT);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_FAKE_OUT); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentLeft, MOVE_TORMENT, target: playerRight); MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TORMENT, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
}
}

DOUBLE_BATTLE_TEST("Instruct-called status moves don't fail if holding Assault Vest")
{
ASSUME(gItemsInfo[ITEM_ASSAULT_VEST].holdEffect == HOLD_EFFECT_ASSAULT_VEST);
ASSUME(gMovesInfo[MOVE_TRICK].effect == EFFECT_TRICK);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_TRICK); }
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_ASSAULT_VEST); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_TRICK, target: opponentLeft); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, playerRight);
}
}

DOUBLE_BATTLE_TEST("Instruct-called status move fails if taunted")
{
ASSUME(gMovesInfo[MOVE_TAUNT].effect == EFFECT_TAUNT);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_GROWL); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_GROWL); MOVE(opponentLeft, MOVE_TAUNT, target: playerRight); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, playerRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, playerRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
}
} THEN {
EXPECT_EQ(playerRight->pp[3], gMovesInfo[MOVE_GROWL].pp - 1);
}
}

DOUBLE_BATTLE_TEST("Instruct-called moves fail if disabled")
{
ASSUME(gMovesInfo[MOVE_DISABLE].effect == EFFECT_DISABLE);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_GROWL); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); MOVE(opponentLeft, MOVE_DISABLE, target: playerRight); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DISABLE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
} THEN {
EXPECT_EQ(playerRight->pp[0], gMovesInfo[MOVE_TACKLE].pp - 1);
}
}

DOUBLE_BATTLE_TEST("Instruct-called moves keep their priority")
{
ASSUME(gMovesInfo[MOVE_QUICK_ATTACK].priority == 1);
ASSUME(gMovesInfo[MOVE_PSYCHIC_TERRAIN].effect == EFFECT_PSYCHIC_TERRAIN);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_QUICK_ATTACK); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); MOVE(opponentLeft, MOVE_PSYCHIC_TERRAIN, target: playerRight); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC_TERRAIN, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, playerRight);
}
}
33 changes: 33 additions & 0 deletions test/battle/move_effect/sticky_web.c
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,36 @@ DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - no o
EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
}
}
SINGLE_BATTLE_TEST("Sticky Web is placed on the correct side after Explosion")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_EXPLOSION); MOVE(opponent, MOVE_STICKY_WEB); SEND_OUT(player, 1);}
} SCENE {
HP_BAR(player, hp: 0);
ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player);
MESSAGE("Wobbuffet fainted!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent);
MESSAGE("A sticky web spreads out on the ground around your team!");
}
}

SINGLE_BATTLE_TEST("Sticky Web is placed on the correct side after Memento")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_MEMENTO); MOVE(opponent, MOVE_STICKY_WEB); SEND_OUT(player, 1); }
} SCENE {
HP_BAR(player, hp: 0);
ANIMATION(ANIM_TYPE_MOVE, MOVE_MEMENTO, player);
MESSAGE("Wobbuffet fainted!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent);
MESSAGE("A sticky web spreads out on the ground around your team!");
}
}
Loading