From 6df3a48ca232068be1e9237f77d77f2275fbbf64 Mon Sep 17 00:00:00 2001 From: Alex <93446519+AlexOn1ine@users.noreply.github.com> Date: Mon, 25 Aug 2025 15:46:36 +0200 Subject: [PATCH] Fixes octolock not ending after user switched out (#7556) Co-authored-by: Bassoonian --- asm/macros/battle_script.inc | 5 ++- data/battle_scripts_1.s | 2 +- .../how_to_battle_script_command_macro.md | 33 +++++++++---------- include/battle.h | 2 +- src/battle_main.c | 4 +++ src/battle_script_commands.c | 12 +++---- test/battle/move_effect/octolock.c | 25 ++++++++++++++ test/battle/move_effect/sky_drop.c | 13 ++++++++ 8 files changed, 67 insertions(+), 29 deletions(-) diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 721c844554..46581dfab2 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1440,9 +1440,8 @@ callnative BS_TryRevertWeatherForm .endm - .macro trysetoctolock battler:req, failInstr:req + .macro trysetoctolock failInstr:req callnative BS_TrySetOctolock - .byte \battler .4byte \failInstr .endm @@ -1737,7 +1736,7 @@ callnative BS_ActivateTerrainChangeAbilities .byte \battler .endm - + .macro resetterrainabilityflags callnative BS_ResetTerrainAbilityFlags .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index bebce183e9..45f5a5babf 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -868,7 +868,7 @@ BattleScript_EffectOctolock:: accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE attackstring ppreduce - trysetoctolock BS_TARGET, BattleScript_ButItFailed + trysetoctolock BattleScript_ButItFailed attackanimation waitanimation printstring STRINGID_CANTESCAPEBECAUSEOFCURRENTMOVE diff --git a/docs/tutorials/how_to_battle_script_command_macro.md b/docs/tutorials/how_to_battle_script_command_macro.md index 2bbf503fbe..911da7d09c 100644 --- a/docs/tutorials/how_to_battle_script_command_macro.md +++ b/docs/tutorials/how_to_battle_script_command_macro.md @@ -12,36 +12,33 @@ In general, `gBattlescriptCurrInstr` tracks the current battle script position a ``` `callnative` uses the last battle script command ID in order to pass a native function as an argument. Additional optional arguments are added recursively via a macro, so no need to worry about how they need to align to the amount of instructions to skip. -Now, how might we add a custom `callnative` command? Here are the steps. We will use `BS_TrySetOctolock` as an example. +Now, how might we add a custom `callnative` command? Here are the steps. We will use `BS_JumpIfTerrainAffected` as an example. ### 1. Create a macro in `asm/macros/battle_script.inc`. For example: ```c - .macro trysetoctolock battler:req, failInstr:req - callnative BS_TrySetOctolock + .macro jumpifterrainaffected battler:req, terrainFlags:req, jumpInstr:req + callnative BS_JumpIfTerrainAffected .byte \battler - .4byte \failInstr + .4byte \terrainFlags + .4byte \jumpInstr .endm ``` ### 2. Add your new callnative command ID to `src/battle_script_commands.c`. For example: ```c -void BS_TrySetOctolock(void) +void BS_JumpIfTerrainAffected(void) { - NATIVE_ARGS(u8 battler, const u8 *failInstr); + NATIVE_ARGS(u8 battler, u32 flags, const u8 *jumpInstr); u32 battler = GetBattlerForBattleScript(cmd->battler); - if (gDisableStructs[battler].octolock) - { - gBattlescriptCurrInstr = cmd->failInstr; - } + if (IsBattlerTerrainAffected(battler, cmd->flags)) + gBattlescriptCurrInstr = cmd->jumpInstr; else - { - gDisableStructs[battler].octolock = TRUE; - gBattleMons[battler].status2 |= STATUS2_ESCAPE_PREVENTION; - gDisableStructs[battler].battlerPreventingEscape = gBattlerAttacker; gBattlescriptCurrInstr = cmd->nextInstr; - } } ``` -Each of the arguments defined in the macro (`battler`, `failInstr`) need to be called at the start of the command using `NATIVE_ARGS`. +Each of the arguments defined in the macro (`battler`, `flags`, `failInstr`) need to be called at the start of the command using `NATIVE_ARGS`. The byte count in the macro should correspond to the type that will be used for the command (eg, `u8` is `byte`, while the pointer are `4byte`). -These arguments can then be accessed as `cmd->battler` and `cmd->battler`. -`gBattlescriptCurrInstr = cmd->nextInstr;` advances to the next instruction. +These arguments can then be accessed as `cmd->battler`, `cmd->flags` and `cmd->failInstr`. +Note that for `cmd->battler` we need to use `GetBattlerForBattleScript` to fetch the correct battler because with the macro we are accessing a scripting command that doesn't corresponds to `gBattlerTarget`, `gBattlerAttacker`, etc. +For the battler argument specifically, most of the time the battler is accessed through `gBattlerAttacker`, `gBattlerTarget` and the battler argument left out. +In the majority of cases, this is fine since the script commands are mostly used for moves and the interaction is usually between an attacker and target. +A script command usually ends with either a jump or next instruction `gBattlescriptCurrInstr = cmd->nextInstr / cmd->nextInstr;` advancing to the next instruction. diff --git a/include/battle.h b/include/battle.h index 179ea47769..d8b9c70102 100644 --- a/include/battle.h +++ b/include/battle.h @@ -133,7 +133,7 @@ struct DisableStruct u8 neutralizingGas:1; u8 iceFaceActivationPrevention:1; // fixes hit escape move edge case u8 unnerveActivated:1; // Unnerve and As One (Unnerve part) activate only once per switch in - u8 padding:3; + u8 octolockedBy:3; }; // Fully Cleared each turn after end turn effects are done. A few things are cleared before end turn effects diff --git a/src/battle_main.c b/src/battle_main.c index 50e4df8a50..efbe2dd0ff 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3173,6 +3173,8 @@ void SwitchInClearSetData(u32 battler) gBattleMons[i].status2 &= ~STATUS2_WRAPPED; if ((gStatuses4[i] & STATUS4_SYRUP_BOMB) && gBattleStruct->stickySyrupdBy[i] == battler) gStatuses4[i] &= ~STATUS4_SYRUP_BOMB; + if (gDisableStructs[i].octolock && gDisableStructs[i].octolockedBy == battler) + gDisableStructs[i].octolock = FALSE; } gActionSelectionCursor[battler] = 0; @@ -3291,6 +3293,8 @@ const u8* FaintClearSetData(u32 battler) gBattleMons[i].status2 &= ~STATUS2_WRAPPED; if ((gStatuses4[i] & STATUS4_SYRUP_BOMB) && gBattleStruct->stickySyrupdBy[i] == battler) gStatuses4[i] &= ~STATUS4_SYRUP_BOMB; + if (gDisableStructs[i].octolock && gDisableStructs[i].octolockedBy == battler) + gDisableStructs[i].octolock = FALSE; } gActionSelectionCursor[battler] = 0; diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 70540ef275..a9fc0c6f46 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -17447,18 +17447,18 @@ void BS_TryReflectType(void) void BS_TrySetOctolock(void) { - NATIVE_ARGS(u8 battler, const u8 *failInstr); - u32 battler = GetBattlerForBattleScript(cmd->battler); + NATIVE_ARGS(const u8 *failInstr); - if (gDisableStructs[battler].octolock) + if (gDisableStructs[gBattlerTarget].octolock) { gBattlescriptCurrInstr = cmd->failInstr; } else { - gDisableStructs[battler].octolock = TRUE; - gBattleMons[battler].status2 |= STATUS2_ESCAPE_PREVENTION; - gDisableStructs[battler].battlerPreventingEscape = gBattlerAttacker; + gDisableStructs[gBattlerTarget].octolock = TRUE; + gDisableStructs[gBattlerTarget].octolockedBy = gBattlerAttacker; + gBattleMons[gBattlerTarget].status2 |= STATUS2_ESCAPE_PREVENTION; + gDisableStructs[gBattlerTarget].battlerPreventingEscape = gBattlerAttacker; gBattlescriptCurrInstr = cmd->nextInstr; } } diff --git a/test/battle/move_effect/octolock.c b/test/battle/move_effect/octolock.c index e93f6f29cb..e50e7de514 100644 --- a/test/battle/move_effect/octolock.c +++ b/test/battle/move_effect/octolock.c @@ -152,3 +152,28 @@ SINGLE_BATTLE_TEST("Octolock triggers Defiant for both stat reductions") MESSAGE("The opposing Bisharp's Attack sharply rose!"); } } + +SINGLE_BATTLE_TEST("Octolock ends after user that set the lock switches out") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_OCTOLOCK); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_OCTOLOCK, player); + MESSAGE("The opposing Wobbuffet can no longer escape because of Octolock!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Defense fell!"); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Sp. Def fell!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent); + MESSAGE("The opposing Wobbuffet's Defense fell!"); + MESSAGE("The opposing Wobbuffet's Sp. Def fell!"); + } + + } +} diff --git a/test/battle/move_effect/sky_drop.c b/test/battle/move_effect/sky_drop.c index c2a1e104e1..3790fd4ef3 100644 --- a/test/battle/move_effect/sky_drop.c +++ b/test/battle/move_effect/sky_drop.c @@ -122,3 +122,16 @@ SINGLE_BATTLE_TEST("Sky Drop stops the confusion count until the target is dropp ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_CONFUSION, player); } } + +SINGLE_BATTLE_TEST("Sky Drop fails if the targe is in a semi-invulnerable state") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_FLY); MOVE(player, MOVE_SKY_DROP); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_FLY, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SKY_DROP, player); + } +}