From 1232e3b3bc975e9ce6ca1f78fe41d0e2c340516f Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Sat, 9 May 2026 15:29:03 +0200 Subject: [PATCH 1/6] Impement Improved Roll Enhancement --- .../Enhancements/TimeSavers/ImprovedRoll.cpp | 27 +++++++++++++++++++ .../vanilla-behavior/GIVanillaBehavior.h | 21 ++++++++++++++- soh/soh/SohGui/SohMenuEnhancements.cpp | 11 ++++++++ .../actors/ovl_player_actor/z_player.c | 7 +++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp diff --git a/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp b/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp new file mode 100644 index 00000000000..2ae44a38bfa --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp @@ -0,0 +1,27 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include "global.h" + +extern "C" { +extern PlayState* gPlayState; +} + +#define CVAR_ROLL_CHAIN_NAME CVAR_ENHANCEMENT("ImprovedRoll") +#define CVAR_ROLL_CHAIN_VALUE CVarGetInteger(CVAR_ROLL_CHAIN_NAME, 0) + +#define CVAR_ROLL_STEER_NAME CVAR_ENHANCEMENT("ImprovedRollSteering") +#define CVAR_ROLL_STEER_VALUE CVarGetInteger(CVAR_ROLL_STEER_NAME, 0) + +void ImprovedRoll_Register() { + COND_VB_SHOULD(VB_PLAYER_ROLL_CHAIN, CVAR_ROLL_CHAIN_VALUE, { *should = true; }); + + COND_VB_SHOULD(VB_PLAYER_ROLL_STEER, CVAR_ROLL_CHAIN_VALUE && CVAR_ROLL_STEER_VALUE, { + Player* player = va_arg(args, Player*); + va_arg(args, PlayState*); + s16 yawTarget = (s16)va_arg(args, int); + Math_ScaledStepToS(&player->actor.shape.rot.y, yawTarget, 0x200); + *should = false; + }); +} + +static RegisterShipInitFunc initFunc(ImprovedRoll_Register, { CVAR_ROLL_CHAIN_NAME, CVAR_ROLL_STEER_NAME }); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index d1ed4534100..1645be06602 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -2965,7 +2965,26 @@ typedef enum { // ``` // #### `args` // - `*int32_t (camId)` - VB_SHOULD_LOAD_BG_IMAGE + VB_SHOULD_LOAD_BG_IMAGE, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - `*Player` + // - `*PlayState` + VB_PLAYER_ROLL_CHAIN, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - `*Player` + // - `*PlayState` + // - `s16 yawTarget` (stick world-space yaw, promoted to int in va_list) + VB_PLAYER_ROLL_STEER } GIVanillaBehavior; #endif diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index fc4f9f95c80..8cf1e705309 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -454,6 +454,17 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("FastChests")) .Options(CheckboxOptions().Tooltip("Makes Link always kick the chest to open it, instead of doing the longer " "chest opening animation for major items.")); + AddWidget(path, "Improved Roll", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ImprovedRoll")) + .Options(CheckboxOptions().Tooltip( + "Allows Link to chain a new roll by pressing A during a roll, maintaining maximum roll speed.")); + AddWidget(path, "Improved Roll Steering", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ImprovedRollSteering")) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("ImprovedRoll"), 0); }) + .Options( + CheckboxOptions().Tooltip("Allows slight directional steering with the control stick while rolling. " + "WARNING: This will interfere with glitch setups that rely on Z-target rolls, " + "as it modifies the roll's facing direction each frame.")); AddWidget(path, "Skip Water Take Breath Animation", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("SkipSwimDeepEndAnim")) .Options(CheckboxOptions().Tooltip("Skips Link's taking breath animation after coming up from water. " diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 7ffe8af7f43..a28d26b5217 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -9853,6 +9853,12 @@ void Player_Action_Roll(Player* this, PlayState* play) { } } + if ((this->skelAnime.curFrame >= 15.0f) && CHECK_BTN_ALL(sControlInput->press.button, BTN_A) && + (sFloorType != 7) && GameInteractor_Should(VB_PLAYER_ROLL_CHAIN, false, this, play)) { + Player_SetupRoll(this, play); + return; + } + if ((this->skelAnime.curFrame < 15.0f) || !Player_ActionHandler_7(this, play)) { if (this->skelAnime.curFrame >= 20.0f) { func_8083A060(this, play); @@ -9871,6 +9877,7 @@ void Player_Action_Roll(Player* this, PlayState* play) { speedTarget = 3.0f; } + GameInteractor_Should(VB_PLAYER_ROLL_STEER, false, this, play, yawTarget); func_8083DF68(this, speedTarget, this->actor.shape.rot.y); if (func_8084269C(play, this)) { From 43e3897f1e63eff9236bf95e8d6556540796095a Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Sat, 9 May 2026 16:30:46 +0200 Subject: [PATCH 2/6] Rearrange GIVanillaBehavior --- .../vanilla-behavior/GIVanillaBehavior.h | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index 1645be06602..a3c613bedc1 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -1961,6 +1961,25 @@ typedef enum { // - `*int32_t` (arrowType) VB_PLAYER_ARROW_MAGIC_CONSUMPTION, + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - `*Player` + // - `*PlayState` + VB_PLAYER_ROLL_CHAIN, + + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - `*Player` + // - `*PlayState` + // - `s16 yawTarget` (stick world-space yaw, promoted to int in va_list) + VB_PLAYER_ROLL_STEER, + // #### `result` // ```c // item == ITEM_SAW @@ -2965,26 +2984,7 @@ typedef enum { // ``` // #### `args` // - `*int32_t (camId)` - VB_SHOULD_LOAD_BG_IMAGE, - - // #### `result` - // ```c - // false - // ``` - // #### `args` - // - `*Player` - // - `*PlayState` - VB_PLAYER_ROLL_CHAIN, - - // #### `result` - // ```c - // false - // ``` - // #### `args` - // - `*Player` - // - `*PlayState` - // - `s16 yawTarget` (stick world-space yaw, promoted to int in va_list) - VB_PLAYER_ROLL_STEER + VB_SHOULD_LOAD_BG_IMAGE } GIVanillaBehavior; #endif From 14bd55314d411997f69944ba0f7763637b3ccc37 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Sat, 9 May 2026 16:53:08 +0200 Subject: [PATCH 3/6] Remove Unused Extern --- soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp b/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp index 2ae44a38bfa..c28ac2c51e2 100644 --- a/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp +++ b/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp @@ -2,10 +2,6 @@ #include "soh/ShipInit.hpp" #include "global.h" -extern "C" { -extern PlayState* gPlayState; -} - #define CVAR_ROLL_CHAIN_NAME CVAR_ENHANCEMENT("ImprovedRoll") #define CVAR_ROLL_CHAIN_VALUE CVarGetInteger(CVAR_ROLL_CHAIN_NAME, 0) From 4e585f4804b2699dcb157e0f4352936067b35076 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Sun, 10 May 2026 11:14:07 +0200 Subject: [PATCH 4/6] Add Z Target protection to steering --- soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp | 6 ++++-- soh/soh/SohGui/SohMenuEnhancements.cpp | 7 +++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp b/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp index c28ac2c51e2..215b23b88af 100644 --- a/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp +++ b/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp @@ -13,9 +13,11 @@ void ImprovedRoll_Register() { COND_VB_SHOULD(VB_PLAYER_ROLL_STEER, CVAR_ROLL_CHAIN_VALUE && CVAR_ROLL_STEER_VALUE, { Player* player = va_arg(args, Player*); - va_arg(args, PlayState*); + PlayState* play = va_arg(args, PlayState*); s16 yawTarget = (s16)va_arg(args, int); - Math_ScaledStepToS(&player->actor.shape.rot.y, yawTarget, 0x200); + if (!CHECK_BTN_ALL(play->state.input[0].cur.button, BTN_Z)) { + Math_ScaledStepToS(&player->actor.shape.rot.y, yawTarget, 0x200); + } *should = false; }); } diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 8cf1e705309..2bc999f44b8 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -461,10 +461,9 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Improved Roll Steering", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("ImprovedRollSteering")) .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_ENHANCEMENT("ImprovedRoll"), 0); }) - .Options( - CheckboxOptions().Tooltip("Allows slight directional steering with the control stick while rolling. " - "WARNING: This will interfere with glitch setups that rely on Z-target rolls, " - "as it modifies the roll's facing direction each frame.")); + .Options(CheckboxOptions().Tooltip( + "Allows slight directional steering with the control stick while rolling. " + "Steering is automatically disabled while Z is held, preserving Z-target roll glitch setups.")); AddWidget(path, "Skip Water Take Breath Animation", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("SkipSwimDeepEndAnim")) .Options(CheckboxOptions().Tooltip("Skips Link's taking breath animation after coming up from water. " From 27c29e15a2c91b4ec4f01ec6312cb0f370a860c1 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Mon, 11 May 2026 10:20:56 +0200 Subject: [PATCH 5/6] Move roll_chain logic to cpp and cleanup --- .../Enhancements/TimeSavers/ImprovedRoll.cpp | 25 +++++++++++++------ .../vanilla-behavior/GIVanillaBehavior.h | 2 ++ .../actors/ovl_player_actor/z_player.c | 6 +---- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp b/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp index 215b23b88af..40af0d4868b 100644 --- a/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp +++ b/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp @@ -2,16 +2,27 @@ #include "soh/ShipInit.hpp" #include "global.h" -#define CVAR_ROLL_CHAIN_NAME CVAR_ENHANCEMENT("ImprovedRoll") -#define CVAR_ROLL_CHAIN_VALUE CVarGetInteger(CVAR_ROLL_CHAIN_NAME, 0) +extern "C" { +void Player_SetupRoll(Player* player, PlayState* play); +} -#define CVAR_ROLL_STEER_NAME CVAR_ENHANCEMENT("ImprovedRollSteering") -#define CVAR_ROLL_STEER_VALUE CVarGetInteger(CVAR_ROLL_STEER_NAME, 0) +#define CVAR_ROLL_CHAIN CVAR_ENHANCEMENT("ImprovedRoll") +#define CVAR_ROLL_STEER CVAR_ENHANCEMENT("ImprovedRollSteering") void ImprovedRoll_Register() { - COND_VB_SHOULD(VB_PLAYER_ROLL_CHAIN, CVAR_ROLL_CHAIN_VALUE, { *should = true; }); + COND_VB_SHOULD(VB_PLAYER_ROLL_CHAIN, CVarGetInteger(CVAR_ROLL_CHAIN, 0), { + Player* player = va_arg(args, Player*); + PlayState* play = va_arg(args, PlayState*); + Input* controlInput = va_arg(args, Input*); + s32 floorType = va_arg(args, s32); + if ((player->skelAnime.curFrame >= 15.0f) && + CHECK_BTN_ALL(controlInput->press.button, BTN_A) && (floorType != 7)) { + Player_SetupRoll(player, play); + *should = true; + } + }); - COND_VB_SHOULD(VB_PLAYER_ROLL_STEER, CVAR_ROLL_CHAIN_VALUE && CVAR_ROLL_STEER_VALUE, { + COND_VB_SHOULD(VB_PLAYER_ROLL_STEER, CVarGetInteger(CVAR_ROLL_CHAIN, 0) && CVarGetInteger(CVAR_ROLL_STEER, 0), { Player* player = va_arg(args, Player*); PlayState* play = va_arg(args, PlayState*); s16 yawTarget = (s16)va_arg(args, int); @@ -22,4 +33,4 @@ void ImprovedRoll_Register() { }); } -static RegisterShipInitFunc initFunc(ImprovedRoll_Register, { CVAR_ROLL_CHAIN_NAME, CVAR_ROLL_STEER_NAME }); +static RegisterShipInitFunc initFunc(ImprovedRoll_Register, { CVAR_ROLL_CHAIN, CVAR_ROLL_STEER }); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index a3c613bedc1..96ea5faca31 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -1968,6 +1968,8 @@ typedef enum { // #### `args` // - `*Player` // - `*PlayState` + // - `*Input` (sControlInput) + // - `s32` (sFloorType) VB_PLAYER_ROLL_CHAIN, // #### `result` diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index a28d26b5217..7c8ae1a03ec 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -9853,11 +9853,7 @@ void Player_Action_Roll(Player* this, PlayState* play) { } } - if ((this->skelAnime.curFrame >= 15.0f) && CHECK_BTN_ALL(sControlInput->press.button, BTN_A) && - (sFloorType != 7) && GameInteractor_Should(VB_PLAYER_ROLL_CHAIN, false, this, play)) { - Player_SetupRoll(this, play); - return; - } + if (GameInteractor_Should(VB_PLAYER_ROLL_CHAIN, false, this, play, sControlInput, sFloorType)) { return; } if ((this->skelAnime.curFrame < 15.0f) || !Player_ActionHandler_7(this, play)) { if (this->skelAnime.curFrame >= 20.0f) { From d5edaae44a64d92e1f1a3f09cf180422cc9a3b62 Mon Sep 17 00:00:00 2001 From: Jesper Arvidsson Date: Mon, 11 May 2026 10:23:21 +0200 Subject: [PATCH 6/6] clang --- soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp | 4 ++-- soh/src/overlays/actors/ovl_player_actor/z_player.c | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp b/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp index 40af0d4868b..3c3edf54db4 100644 --- a/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp +++ b/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp @@ -15,8 +15,8 @@ void ImprovedRoll_Register() { PlayState* play = va_arg(args, PlayState*); Input* controlInput = va_arg(args, Input*); s32 floorType = va_arg(args, s32); - if ((player->skelAnime.curFrame >= 15.0f) && - CHECK_BTN_ALL(controlInput->press.button, BTN_A) && (floorType != 7)) { + if ((player->skelAnime.curFrame >= 15.0f) && CHECK_BTN_ALL(controlInput->press.button, BTN_A) && + (floorType != 7)) { Player_SetupRoll(player, play); *should = true; } diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 7c8ae1a03ec..65b47479000 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -9853,7 +9853,9 @@ void Player_Action_Roll(Player* this, PlayState* play) { } } - if (GameInteractor_Should(VB_PLAYER_ROLL_CHAIN, false, this, play, sControlInput, sFloorType)) { return; } + if (GameInteractor_Should(VB_PLAYER_ROLL_CHAIN, false, this, play, sControlInput, sFloorType)) { + return; + } if ((this->skelAnime.curFrame < 15.0f) || !Player_ActionHandler_7(this, play)) { if (this->skelAnime.curFrame >= 20.0f) {