diff --git a/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp b/soh/soh/Enhancements/TimeSavers/ImprovedRoll.cpp new file mode 100644 index 00000000000..3c3edf54db4 --- /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 d1ed4534100..96ea5faca31 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -1961,6 +1961,27 @@ typedef enum { // - `*int32_t` (arrowType) VB_PLAYER_ARROW_MAGIC_CONSUMPTION, + // #### `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 fc4f9f95c80..2bc999f44b8 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -454,6 +454,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 7ffe8af7f43..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,6 +9853,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); @@ -9871,6 +9875,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)) {