From a629af2e9f1942e4914d0be27f207e572b40eae4 Mon Sep 17 00:00:00 2001 From: Reppan <72985260+Jepvid@users.noreply.github.com> Date: Sun, 28 Jun 2026 15:41:59 +0200 Subject: [PATCH] [Enhancement] Improved Roll (#6604) --- .../Enhancements/TimeSavers/ImprovedRoll.cpp | 36 +++++++++++++++++++ .../vanilla-behavior/GIVanillaBehavior.h | 21 +++++++++++ soh/soh/SohGui/SohMenuEnhancements.cpp | 10 ++++++ .../actors/ovl_player_actor/z_player.c | 5 +++ 4 files changed, 72 insertions(+) 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 0000000000..3c3edf54db --- /dev/null +++ b/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp @@ -0,0 +1,36 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include "global.h" + +extern "C" { +void Player_SetupRoll(Player* player, PlayState* play); +} + +#define CVAR_ROLL_CHAIN CVAR_ENHANCEMENT("ImprovedRoll") +#define CVAR_ROLL_STEER CVAR_ENHANCEMENT("ImprovedRollSteering") + +void ImprovedRoll_Register() { + 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, 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); + if (!CHECK_BTN_ALL(play->state.input[0].cur.button, BTN_Z)) { + Math_ScaledStepToS(&player->actor.shape.rot.y, yawTarget, 0x200); + } + *should = false; + }); +} + +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 d6624b9c75..6e3f9e4a19 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -2028,6 +2028,27 @@ typedef enum { // - `*PlayState` VB_PLAYER_UPDATE_BOTTLE_HELD, + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - `*Player` + // - `*PlayState` + // - `*Input` (sControlInput) + // - `s32` (sFloorType) + 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 diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index dc04eebac4..afdb28730d 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -462,6 +462,16 @@ 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. " + "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. " 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 9b6712e32e..171cb09ac6 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -9834,6 +9834,10 @@ void Player_Action_Roll(Player* this, PlayState* play) { } } + 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) { func_8083A060(this, play); @@ -9852,6 +9856,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)) {