Fixes octolock not ending after user switched out (#7556)

Co-authored-by: Bassoonian <iasperbassoonian@gmail.com>
This commit is contained in:
Alex 2025-08-25 15:46:36 +02:00 committed by GitHub
parent 0a3c281cdd
commit 6df3a48ca2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 67 additions and 29 deletions

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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;
}
}

View File

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

View File

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