From 618b7694b17b61f54de4e8a15f5d3f9bd741ca41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= <159546+serprex@users.noreply.github.com> Date: Sat, 4 Jul 2026 13:25:44 +0000 Subject: [PATCH] SpeedModifiers.cpp (#6861) Prevent speed modifiers affecting dive speed when bunny jump not enabled --- soh/soh/Enhancements/SpeedModifiers.cpp | 105 +++++++++++++ .../vanilla-behavior/GIVanillaBehavior.h | 43 ++++++ .../actors/ovl_player_actor/z_player.c | 141 ++++-------------- 3 files changed, 181 insertions(+), 108 deletions(-) create mode 100644 soh/soh/Enhancements/SpeedModifiers.cpp diff --git a/soh/soh/Enhancements/SpeedModifiers.cpp b/soh/soh/Enhancements/SpeedModifiers.cpp new file mode 100644 index 0000000000..92f8974533 --- /dev/null +++ b/soh/soh/Enhancements/SpeedModifiers.cpp @@ -0,0 +1,105 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/enhancementTypes.h" + +extern "C" { +#include "z64.h" +#include "functions.h" +#include "macros.h" +#include "variables.h" +extern PlayState* gPlayState; +} + +#define CVAR_SPEED_MODIFIER_VALUE_NAME CVAR_CHEAT("SpeedModifier.Value") +#define CVAR_BUNNY_HOOD_NAME CVAR_ENHANCEMENT("MMBunnyHood") + +static f32 GetSpeedModifierFactor(bool inputAvailable) { + f32 value = CVarGetFloat(CVAR_SPEED_MODIFIER_VALUE_NAME, 1.0f); + if (value == 1.0f) { + return 1.0f; + } + + if (CVarGetInteger(CVAR_CHEAT("SpeedModifier.SpeedToggle"), 0)) { + return gWalkSpeedToggle ? value : 1.0f; + } + + if (inputAvailable) { + s32 mod1Mask = CVarGetInteger(CVAR_CHEAT("SpeedModifier.Btn"), BTN_CUSTOM_MODIFIER1); + Input* input = &gPlayState->state.input[0]; + if (mod1Mask != 0 && CHECK_BTN_ALL(input->cur.button, mod1Mask)) { + return value; + } + } + + return 1.0f; +} + +static f32 GetSpeedModifierJumpFactor() { + if (CVarGetInteger(CVAR_CHEAT("SpeedModifier.DoesntChangeJump"), 0)) { + return 1.0f; + } + return GetSpeedModifierFactor(true); +} + +static f32 GetBunnyHoodRunFactor(Player* player) { + if (CVarGetInteger(CVAR_BUNNY_HOOD_NAME, BUNNY_HOOD_VANILLA) != BUNNY_HOOD_VANILLA && + player->currentMask == PLAYER_MASK_BUNNY) { + return 1.5f; + } + return 1.0f; +} + +static f32 GetBunnyHoodJumpFactor(Player* player) { + if (CVarGetInteger(CVAR_BUNNY_HOOD_NAME, BUNNY_HOOD_VANILLA) == BUNNY_HOOD_FAST_AND_JUMP && + player->currentMask == PLAYER_MASK_BUNNY) { + return 1.5f; + } + return 1.0f; +} + +static bool ShouldAmplifyJump(Player* player) { + return GetBunnyHoodJumpFactor(player) != 1.0f || GetSpeedModifierJumpFactor() != 1.0f; +} + +static void RegisterSpeedModifiers() { + bool speedModifierActive = CVarGetFloat(CVAR_SPEED_MODIFIER_VALUE_NAME, 1.0f) != 1.0f; + bool bunnyHoodActive = CVarGetInteger(CVAR_BUNNY_HOOD_NAME, BUNNY_HOOD_VANILLA) != BUNNY_HOOD_VANILLA; + + // Airborne (jump) velocity. z_player clamps linearVelocity to the vanilla run speed limit when this returns true; + // skip that clamp so the amplified running velocity carries into the jump. + COND_VB_SHOULD(VB_PLAYER_LIMIT_JUMP_SPEED, speedModifierActive || bunnyHoodActive, { + Player* player = va_arg(args, Player*); + if (ShouldAmplifyJump(player)) { + *should = false; + } + }); + + // dive-into-water animation never clamped by vanilla, so re-clamp to vanilla run speed limit here unless jump be + // amplified. This keeps dive vanilla-distance (e.g. Gerudo Valley canyon) for bunny hood "fast run" & "Don't affect + // jump distance" option. + COND_VB_SHOULD(VB_PLAYER_LIMIT_DIVE_XZ_SPEED, speedModifierActive || bunnyHoodActive, { + Player* player = va_arg(args, Player*); + if (!ShouldAmplifyJump(player)) { + f32 maxSpeed = R_RUN_SPEED_LIMIT / 100.0f; + player->linearVelocity = CLAMP(player->linearVelocity, -maxSpeed, maxSpeed); + } + }); + + // Ground run speed target, multiplied in place. + COND_VB_SHOULD(VB_PLAYER_MODIFY_RUN_SPEED, speedModifierActive || bunnyHoodActive, { + Player* player = va_arg(args, Player*); + f32* speedTarget = va_arg(args, f32*); + *speedTarget *= GetBunnyHoodRunFactor(player) * GetSpeedModifierFactor(true); + }); + + // Swim speed multiplied in place. Called per speed z_player scales; bunny hood does not apply underwater. + COND_VB_SHOULD(VB_PLAYER_MODIFY_SWIM_SPEED, speedModifierActive, { + [[maybe_unused]] Player* player = va_arg(args, Player*); + f32* value = va_arg(args, f32*); + bool inputAvailable = va_arg(args, int) != 0; + *value *= GetSpeedModifierFactor(inputAvailable); + }); +} + +static RegisterShipInitFunc initFunc(RegisterSpeedModifiers, { CVAR_SPEED_MODIFIER_VALUE_NAME, CVAR_BUNNY_HOOD_NAME }); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index 6998709b66..2e1c717ba9 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -2035,11 +2035,54 @@ typedef enum { // - `*int32_t` (arrowType) VB_PLAYER_ARROW_MAGIC_CONSUMPTION, + // #### `result` + // ```c + // true + // ``` // #### `args` // - `void*` player (Player*) // - `PlayState*` play VB_PLAYER_DRAW_BOTTLE, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Player` + VB_PLAYER_LIMIT_DIVE_XZ_SPEED, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Player` + VB_PLAYER_LIMIT_JUMP_SPEED, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Player` + // - `f32*` speedTarget + VB_PLAYER_MODIFY_RUN_SPEED, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Player` + // - `f32*` swimSpeed + // - `s32` sControlInput != NULL + VB_PLAYER_MODIFY_SWIM_SPEED, + + // #### `result` + // ```c + // true + // ``` // #### `args` // - `s32` limbIndex // - `Gfx**` dList (write to *dList to replace the resolved display list) 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 b1d2f4c47d..4ae297f319 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -5811,6 +5811,7 @@ void func_8083AA10(Player* this, PlayState* play) { if (WaterBox_GetSurface1(play, &play->colCtx, sp44.x, sp44.z, &sp3C, &sp50) && ((sp3C - sp40) > 50.0f)) { func_808389E8(this, &gPlayerAnim_link_normal_run_jump_water_fall, 6.0f, play); + GameInteractor_Should(VB_PLAYER_LIMIT_DIVE_XZ_SPEED, true, this); Player_SetupAction(play, this, Player_Action_80844A44, 0); return; } @@ -7118,29 +7119,10 @@ void func_8083DFE0(Player* this, f32* arg1, s16* arg2) { s16 yawDiff = this->yaw - *arg2; if (this->meleeWeaponState == 0) { - float maxSpeed = R_RUN_SPEED_LIMIT / 100.0f; - - if (CVarGetInteger(CVAR_ENHANCEMENT("MMBunnyHood"), BUNNY_HOOD_VANILLA) == BUNNY_HOOD_FAST_AND_JUMP && - this->currentMask == PLAYER_MASK_BUNNY) { - maxSpeed *= 1.5f; + if (GameInteractor_Should(VB_PLAYER_LIMIT_JUMP_SPEED, true, this)) { + this->linearVelocity = + CLAMP(this->linearVelocity, -(R_RUN_SPEED_LIMIT / 100.0f), (R_RUN_SPEED_LIMIT / 100.0f)); } - - if (CVarGetFloat(CVAR_CHEAT("SpeedModifier.Value"), 1.0f) != 1.0f && - !CVarGetInteger(CVAR_CHEAT("SpeedModifier.DoesntChangeJump"), 0)) { - if (CVarGetInteger(CVAR_CHEAT("SpeedModifier.SpeedToggle"), 0)) { - if (gWalkSpeedToggle) { - maxSpeed *= CVarGetFloat(CVAR_CHEAT("SpeedModifier.Value"), 1.0f); - } - } else { - const s32 mod1Mask = CVarGetInteger(CVAR_CHEAT("SpeedModifier.Btn"), BTN_CUSTOM_MODIFIER1); - - if (mod1Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod1Mask)) { - maxSpeed *= CVarGetFloat(CVAR_CHEAT("SpeedModifier.Value"), 1.0f); - } - } - } - - this->linearVelocity = CLAMP(this->linearVelocity, -maxSpeed, maxSpeed); } if (ABS(yawDiff) > 0x6000) { @@ -8847,8 +8829,8 @@ void func_80841EE4(Player* this, PlayState* play) { } void Player_Action_80842180(Player* this, PlayState* play) { - f32 sp2C; - s16 sp2A; + f32 speedTarget; + s16 yawTarget; this->stateFlags2 |= PLAYER_STATE2_DISABLE_ROTATION_Z_TARGET; func_80841EE4(this, play); @@ -8859,33 +8841,15 @@ void Player_Action_80842180(Player* this, PlayState* play) { return; } - Player_GetMovementSpeedAndYaw(this, &sp2C, &sp2A, SPEED_MODE_CURVED, play); + Player_GetMovementSpeedAndYaw(this, &speedTarget, &yawTarget, SPEED_MODE_CURVED, play); - if (!func_8083C484(this, &sp2C, &sp2A)) { + if (!func_8083C484(this, &speedTarget, &yawTarget)) { + GameInteractor_Should(VB_PLAYER_MODIFY_RUN_SPEED, true, this, &speedTarget); - if (CVarGetInteger(CVAR_ENHANCEMENT("MMBunnyHood"), BUNNY_HOOD_VANILLA) != BUNNY_HOOD_VANILLA && - this->currentMask == PLAYER_MASK_BUNNY) { - sp2C *= 1.5f; - } - - if (CVarGetFloat(CVAR_CHEAT("SpeedModifier.Value"), 1.0f) != 1.0f) { - if (CVarGetInteger(CVAR_CHEAT("SpeedModifier.SpeedToggle"), 0)) { - if (gWalkSpeedToggle) { - sp2C *= CVarGetFloat(CVAR_CHEAT("SpeedModifier.Value"), 1.0f); - } - } else { - const s32 mod1Mask = CVarGetInteger(CVAR_CHEAT("SpeedModifier.Btn"), BTN_CUSTOM_MODIFIER1); - - if (mod1Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod1Mask)) { - sp2C *= CVarGetFloat(CVAR_CHEAT("SpeedModifier.Value"), 1.0f); - } - } - } - - func_8083DF68(this, sp2C, sp2A); + func_8083DF68(this, speedTarget, yawTarget); func_8083DDC8(this, play); - if ((this->linearVelocity == 0.0f) && (sp2C == 0.0f)) { + if ((this->linearVelocity == 0.0f) && (speedTarget == 0.0f)) { func_8083C0B8(this, play); } } @@ -9848,8 +9812,7 @@ void Player_Action_Roll(Player* this, PlayState* play) { Player_GetMovementSpeedAndYaw(this, &speedTarget, &yawTarget, SPEED_MODE_CURVED, play); // `speedTarget` at this point is the speed that would be used for regular walking. - // Rolling speed is 1.5 times faster than what the walking speed would be for the current control stick - // input. + // Rolling speed is 1.5 times faster than walking speed would be for the current control stick input. speedTarget *= 1.5f; if ((speedTarget < 3.0f) || (this->controlStickDirections[this->controlStickDataIndex] != 0)) { @@ -12703,59 +12666,24 @@ void func_8084AEEC(Player* this, f32* arg1, f32 arg2, s16 arg3) { f32 temp1; f32 temp2; - // #region SOH [Enhancement] - f32 swimMod = 1.0f; + temp1 = this->skelAnime.curFrame - 10.0f; - if (CVarGetFloat(CVAR_CHEAT("SpeedModifier.Value"), 1.0f) != 1.0f) { - if (CVarGetInteger(CVAR_CHEAT("SpeedModifier.SpeedToggle"), 0) == 1) { - if (gWalkSpeedToggle) { - swimMod *= CVarGetFloat(CVAR_CHEAT("SpeedModifier.Value"), 1.0f); - } - // sControlInput is NULL to prevent inputs while surfacing after obtaining an underwater item so we want to - // ignore it for that case - } else if (sControlInput != NULL) { - const s32 mod1Mask = CVarGetInteger(CVAR_CHEAT("SpeedModifier.Btn"), BTN_CUSTOM_MODIFIER1); - - if (mod1Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod1Mask)) { - swimMod *= CVarGetFloat(CVAR_CHEAT("SpeedModifier.Value"), 1.0f); - } - } - temp1 = this->skelAnime.curFrame - 10.0f; - - temp2 = (R_RUN_SPEED_LIMIT / 100.0f) * 0.8f * swimMod; - if (*arg1 > temp2) { - *arg1 = temp2; - } - - if ((0.0f < temp1) && (temp1 < 10.0f)) { - temp1 *= 6.0f; - } else { - temp1 = 0.0f; - arg2 = 0.0f; - } - - Math_AsymStepToF(arg1, arg2 * 0.8f * swimMod, temp1, (fabsf(*arg1) * 0.02f) + 0.05f); - Math_ScaledStepToS(&this->yaw, arg3, 1600); - // #endregion - } else { - - temp1 = this->skelAnime.curFrame - 10.0f; - - temp2 = (R_RUN_SPEED_LIMIT / 100.0f) * 0.8f; - if (*arg1 > temp2) { - *arg1 = temp2; - } - - if ((0.0f < temp1) && (temp1 < 10.0f)) { - temp1 *= 6.0f; - } else { - temp1 = 0.0f; - arg2 = 0.0f; - } - - Math_AsymStepToF(arg1, arg2 * 0.8f, temp1, (fabsf(*arg1) * 0.02f) + 0.05f); - Math_ScaledStepToS(&this->yaw, arg3, 1600); + temp2 = (R_RUN_SPEED_LIMIT / 100.0f) * 0.8f; + GameInteractor_Should(VB_PLAYER_MODIFY_SWIM_SPEED, true, this, &temp2, sControlInput != NULL); + if (*arg1 > temp2) { + *arg1 = temp2; } + + if ((0.0f < temp1) && (temp1 < 10.0f)) { + temp1 *= 6.0f; + } else { + temp1 = 0.0f; + arg2 = 0.0f; + } + + GameInteractor_Should(VB_PLAYER_MODIFY_SWIM_SPEED, true, this, &arg2, sControlInput != NULL); + Math_AsymStepToF(arg1, arg2 * 0.8f, temp1, (fabsf(*arg1) * 0.02f) + 0.05f); + Math_ScaledStepToS(&this->yaw, arg3, 1600); } // #region SOH [Enhancement] @@ -13963,14 +13891,11 @@ void func_8084DBC4(PlayState* play, Player* this, f32 arg2) { Player_GetMovementSpeedAndYaw(this, &sp2C, &sp2A, SPEED_MODE_LINEAR, play); func_8084AEEC(this, &this->linearVelocity, sp2C * 0.5f, sp2A); - // Original implementation of func_8084AEEC (SurfaceWithoutSwimMod) to prevent velocity increases via swim mod which - // push Link into the air #region SOH [Enhancement] - if (CVarGetFloat(CVAR_CHEAT("SpeedModifier.Value"), 1.0f) != 1.0f) { - SurfaceWithoutSwimMod(this, &this->actor.velocity.y, arg2, this->yaw); - // #endregion - } else { - func_8084AEEC(this, &this->actor.velocity.y, arg2, this->yaw); - } + // #region SOH [Enhancement] + // Use swim-mod-free variant for y (surfacing) velocity so an active swim speed modifier can't push Link into air. + // This is identical to func_8084AEEC when no modifier active (swimMod == 1.0f), thus safe to always use. + SurfaceWithoutSwimMod(this, &this->actor.velocity.y, arg2, this->yaw); + // #endregion } void Player_Action_8084DC48(Player* this, PlayState* play) {