From 58b9642cb11dddd5356aa471e577aac017167d66 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Wed, 24 Sep 2025 18:11:45 +0100 Subject: [PATCH] Fixes Instructed moves looking at the wrong turn order number (#7788) --- include/battle_script_commands.h | 3 +- src/battle_script_commands.c | 51 ++++++++++++++++---------- src/battle_util.c | 6 +-- test/battle/move_effect/sucker_punch.c | 19 ++++++++++ test/battle/move_effect/upper_hand.c | 22 +++++++++++ 5 files changed, 78 insertions(+), 23 deletions(-) diff --git a/include/battle_script_commands.h b/include/battle_script_commands.h index df7163840f..4ba801b98c 100644 --- a/include/battle_script_commands.h +++ b/include/battle_script_commands.h @@ -44,7 +44,8 @@ s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordA s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, enum ItemHoldEffect holdEffectAtk); s32 GetCritHitOdds(s32 critChanceIndex); u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect); -u8 GetBattlerTurnOrderNum(u8 battler); +bool32 HasBattlerActedThisTurn(u32 battler); +u32 GetBattlerTurnOrderNum(u32 battler); bool32 NoAliveMonsForBattlerSide(u32 battler); bool32 NoAliveMonsForPlayer(void); bool32 NoAliveMonsForEitherParty(void); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 37f25637d4..df620dd3f7 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2804,9 +2804,20 @@ static void Cmd_printselectionstringfromtable(void) } } -u8 GetBattlerTurnOrderNum(u8 battler) +bool32 HasBattlerActedThisTurn(u32 battler) { - s32 i; + u32 i; + for (i = 0; i < gCurrentTurnActionNumber; i++) + { + if (gBattlerByTurnOrder[i] == battler) + return TRUE; + } + return FALSE; +} + +u32 GetBattlerTurnOrderNum(u32 battler) +{ + u32 i; for (i = 0; i < gBattlersCount; i++) { if (gBattlerByTurnOrder[i] == battler) @@ -3091,8 +3102,8 @@ void SetMoveEffect(u32 battler, u32 effectBattler, bool32 primary, bool32 certai { gBattlescriptCurrInstr++; } - else if (GetBattlerTurnOrderNum(gEffectBattler) > gCurrentTurnActionNumber - && !(GetActiveGimmick(gEffectBattler) == GIMMICK_DYNAMAX)) + else if (!HasBattlerActedThisTurn(gEffectBattler) + && GetActiveGimmick(gEffectBattler) != GIMMICK_DYNAMAX) { gBattleMons[gEffectBattler].volatiles.flinched = TRUE; gBattlescriptCurrInstr++; @@ -3425,7 +3436,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, bool32 primary, bool32 certai } break; case MOVE_EFFECT_CORE_ENFORCER: - if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget) + if (HasBattlerActedThisTurn(gBattlerTarget) && !NoAliveMonsForEitherParty()) { BattleScriptPush(gBattlescriptCurrInstr + 1); @@ -9455,10 +9466,12 @@ static bool32 ChangeOrderTargetAfterAttacker(void) u32 i; u8 data[MAX_BATTLERS_COUNT]; u8 actionsData[MAX_BATTLERS_COUNT]; + u32 attackerTurnOrderNum = GetBattlerTurnOrderNum(gBattlerAttacker); + u32 targetTurnOrderNum = GetBattlerTurnOrderNum(gBattlerTarget); - if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) + if (attackerTurnOrderNum > targetTurnOrderNum) return FALSE; - if (GetBattlerTurnOrderNum(gBattlerAttacker) + 1 == GetBattlerTurnOrderNum(gBattlerTarget)) + if (attackerTurnOrderNum + 1 == targetTurnOrderNum) return GetGenConfig(GEN_CONFIG_AFTER_YOU_TURN_ORDER) >= GEN_8; for (i = 0; i < MAX_BATTLERS_COUNT; i++) @@ -9466,14 +9479,14 @@ static bool32 ChangeOrderTargetAfterAttacker(void) data[i] = gBattlerByTurnOrder[i]; actionsData[i] = gActionsByTurnOrder[i]; } - if (GetBattlerTurnOrderNum(gBattlerAttacker) == 0 && GetBattlerTurnOrderNum(gBattlerTarget) == 2) + if (attackerTurnOrderNum == 0 && targetTurnOrderNum == 2) { gBattlerByTurnOrder[1] = gBattlerTarget; gActionsByTurnOrder[1] = actionsData[2]; gBattlerByTurnOrder[2] = data[1]; gActionsByTurnOrder[2] = actionsData[1]; } - else if (GetBattlerTurnOrderNum(gBattlerAttacker) == 0 && GetBattlerTurnOrderNum(gBattlerTarget) == 3) + else if (attackerTurnOrderNum == 0 && targetTurnOrderNum == 3) { gBattlerByTurnOrder[1] = gBattlerTarget; gActionsByTurnOrder[1] = actionsData[3]; @@ -9482,7 +9495,7 @@ static bool32 ChangeOrderTargetAfterAttacker(void) gBattlerByTurnOrder[3] = data[2]; gActionsByTurnOrder[3] = actionsData[2]; } - else // Attacker == 1, Target == 3 + else // attackerTurnOrderNum == 1, targetTurnOrderNum == 3 { gBattlerByTurnOrder[2] = gBattlerTarget; gActionsByTurnOrder[2] = actionsData[3]; @@ -11436,7 +11449,7 @@ static void Cmd_trysetencore(void) gDisableStructs[gBattlerTarget].encoredMove = gBattleMons[gBattlerTarget].moves[i]; gDisableStructs[gBattlerTarget].encoredMovePos = i; // Encore always lasts 3 turns, but we need to account for a scenario where Encore changes the move during the same turn. - if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) + if (HasBattlerActedThisTurn(gBattlerTarget)) gDisableStructs[gBattlerTarget].encoreTimer = 4; else gDisableStructs[gBattlerTarget].encoreTimer = 3; @@ -12742,7 +12755,7 @@ static void Cmd_settaunt(void) if (B_TAUNT_TURNS >= GEN_5) { turns = 4; - if (GetBattlerTurnOrderNum(gBattlerTarget) > GetBattlerTurnOrderNum(gBattlerAttacker)) + if (!HasBattlerActedThisTurn(gBattlerTarget)) turns--; // If the target hasn't yet moved this turn, Taunt lasts for only three turns (source: Bulbapedia) } else if (B_TAUNT_TURNS >= GEN_4) @@ -12771,7 +12784,7 @@ static void Cmd_trysethelpinghand(void) if (IsDoubleBattle() && !(gAbsentBattlerFlags & (1u << gBattlerTarget)) - && gCurrentTurnActionNumber < GetBattlerTurnOrderNum(gBattlerTarget)) + && !HasBattlerActedThisTurn(gBattlerTarget)) { gProtectStructs[gBattlerTarget].helpingHand++; gBattlescriptCurrInstr = cmd->nextInstr; @@ -15401,7 +15414,7 @@ void BS_SetPledge(void) else if ((gChosenActionByBattler[partner] == B_ACTION_USE_MOVE) && IsDoubleBattle() && IsBattlerAlive(partner) - && GetBattlerTurnOrderNum(gBattlerAttacker) < GetBattlerTurnOrderNum(partner) + && !HasBattlerActedThisTurn(partner) && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) && gCurrentMove != partnerMove && GetMoveEffect(partnerMove) == EFFECT_PLEDGE) @@ -15600,7 +15613,7 @@ void BS_TryUpperHand(void) u32 abilityDef = GetBattlerAbility(gBattlerTarget); u32 prio = GetChosenMovePriority(gBattlerTarget, abilityDef); - if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget) + if (HasBattlerActedThisTurn(gBattlerTarget) || gChosenMoveByBattler[gBattlerTarget] == MOVE_NONE || IsBattleMoveStatus(gChosenMoveByBattler[gBattlerTarget]) || prio < 1 @@ -15817,7 +15830,7 @@ void BS_TryQuash(void) u32 i, j; // It's true if foe is faster, has a bigger priority, or switches - if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) + if (HasBattlerActedThisTurn(gBattlerTarget)) { gBattlescriptCurrInstr = cmd->failInstr; return; @@ -17346,7 +17359,7 @@ void BS_SuckerPunchCheck(void) NATIVE_ARGS(const u8 *failInstr); if (gProtectStructs[gBattlerTarget].protected == PROTECT_OBSTRUCT) gBattlescriptCurrInstr = cmd->failInstr; - else if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) + else if (HasBattlerActedThisTurn(gBattlerTarget)) gBattlescriptCurrInstr = cmd->failInstr; else if (IsBattleMoveStatus(gBattleMons[gBattlerTarget].moves[gBattleStruct->chosenMovePositions[gBattlerTarget]]) && !gProtectStructs[gBattlerTarget].noValidMoves) gBattlescriptCurrInstr = cmd->failInstr; @@ -17433,7 +17446,7 @@ void BS_TryMeFirst(void) NATIVE_ARGS(const u8 *failInstr); u16 move = gBattleMons[gBattlerTarget].moves[gBattleStruct->chosenMovePositions[gBattlerTarget]]; if (IsBattleMoveStatus(move) || IsMoveMeFirstBanned(move) - || GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) + || HasBattlerActedThisTurn(gBattlerTarget)) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -17457,7 +17470,7 @@ void BS_TryMeFirst(void) void BS_TryElectrify(void) { NATIVE_ARGS(const u8 *failInstr); - if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) + if (HasBattlerActedThisTurn(gBattlerTarget)) { gBattlescriptCurrInstr = cmd->failInstr; } diff --git a/src/battle_util.c b/src/battle_util.c index 6d1f4efa4f..e633a20bad 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -8169,12 +8169,12 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx) } break; case EFFECT_PAYBACK: - if (GetBattlerTurnOrderNum(battlerAtk) > GetBattlerTurnOrderNum(battlerDef) + if (HasBattlerActedThisTurn(battlerDef) && (B_PAYBACK_SWITCH_BOOST < GEN_5 || gDisableStructs[battlerDef].isFirstTurn != 2)) basePower *= 2; break; case EFFECT_BOLT_BEAK: - if (GetBattlerTurnOrderNum(battlerAtk) < GetBattlerTurnOrderNum(battlerDef) + if (!HasBattlerActedThisTurn(battlerDef) || gDisableStructs[battlerDef].isFirstTurn == 2) basePower *= 2; break; @@ -11818,7 +11818,7 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u calc = (calc * (100 + atkParam)) / 100; break; case HOLD_EFFECT_ZOOM_LENS: - if (GetBattlerTurnOrderNum(battlerAtk) > GetBattlerTurnOrderNum(battlerDef)) + if (HasBattlerActedThisTurn(battlerDef)) calc = (calc * (100 + atkParam)) / 100; break; } diff --git a/test/battle/move_effect/sucker_punch.c b/test/battle/move_effect/sucker_punch.c index 40ba135b6a..0fd2273607 100644 --- a/test/battle/move_effect/sucker_punch.c +++ b/test/battle/move_effect/sucker_punch.c @@ -51,3 +51,22 @@ SINGLE_BATTLE_TEST("Sucker Punch doesn't hit targets that has already moved") } } } + +DOUBLE_BATTLE_TEST("Sucker Punch fails if the target has attempted to act even if previously successful") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_INSTRUCT) == EFFECT_INSTRUCT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); MOVE(playerLeft, MOVE_SUCKER_PUNCH, target: opponentLeft); MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUCKER_PUNCH, playerLeft); + HP_BAR(opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SUCKER_PUNCH, playerLeft); + } +} diff --git a/test/battle/move_effect/upper_hand.c b/test/battle/move_effect/upper_hand.c index ae63fc9569..36b35781e3 100644 --- a/test/battle/move_effect/upper_hand.c +++ b/test/battle/move_effect/upper_hand.c @@ -132,3 +132,25 @@ AI_SINGLE_BATTLE_TEST("AI won't use Upper Hand unless it has seen a priority mov TURN { MOVE(player, move); EXPECT_MOVE(opponent, move == MOVE_QUICK_ATTACK ? MOVE_UPPER_HAND : MOVE_KARATE_CHOP); } } } + +DOUBLE_BATTLE_TEST("Upper Hand fails if the target has attempted to act even if previously successful") +{ + GIVEN { + ASSUME(GetMoveCategory(MOVE_EXTREME_SPEED) == DAMAGE_CATEGORY_PHYSICAL); + ASSUME(GetMovePriority(MOVE_EXTREME_SPEED) == 2); + ASSUME(GetMoveEffect(MOVE_INSTRUCT) == EFFECT_INSTRUCT); + PLAYER(SPECIES_MIENSHAO); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_EXTREME_SPEED, target: playerLeft); MOVE(playerLeft, MOVE_UPPER_HAND, target: opponentLeft); MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, playerLeft); + HP_BAR(opponentLeft); + MESSAGE("The opposing Wobbuffet flinched and couldn't move!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EXTREME_SPEED, opponentLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, playerLeft); + } +}