Hookify FP movement (#7189)

Apply consistent movement effects to first person movement
This commit is contained in:
Philip Dubé
2026-09-14 16:49:50 +00:00
committed by GitHub
parent 09ca2ad383
commit 97fb13cbee
3 changed files with 111 additions and 39 deletions
+73
View File
@@ -0,0 +1,73 @@
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include "global.h"
extern PlayState* gPlayState;
void func_8083DF68(Player* player, f32 arg1, s16 arg2);
void func_8084029C(Player* player, f32 arg1);
s32 Player_GetMovementSpeedAndYaw(Player* player, f32* outSpeedTarget, s16* outYawTarget, f32 speedMode,
PlayState* play);
}
#define CVAR_MOVE_IN_FIRST_PERSON_NAME CVAR_SETTING("MoveInFirstPerson")
#define CVAR_RIGHT_STICK_AIM_NAME CVAR_SETTING("Controls.RightStickAim")
// from z_player.c
#define SPEED_MODE_CURVED 0.018f
static void MoveInFirstPerson(Player* player) {
f32 speedTarget;
s16 yawTarget;
// Normal movement ignores the stick while unk_6AD says Link is in first person
uint8_t firstPersonMode = player->unk_6AD;
player->unk_6AD = 0;
bool moving = Player_GetMovementSpeedAndYaw(player, &speedTarget, &yawTarget, SPEED_MODE_CURVED, gPlayState);
player->unk_6AD = firstPersonMode;
if ((player->stateFlags1 & PLAYER_STATE1_IN_WATER) && (player->currentBoots != PLAYER_BOOTS_IRON)) {
// Swim speed cap from func_8084AEEC. Its speed step only runs on swim stroke frames, so it isn't used here.
f32 swimSpeed = R_RUN_SPEED_LIMIT / 100.0f * 0.8f;
GameInteractor_Should(VB_PLAYER_MODIFY_SWIM_SPEED, true, player, &swimSpeed, true);
speedTarget = CLAMP_MAX(speedTarget, swimSpeed);
} else {
GameInteractor_Should(VB_PLAYER_MODIFY_FIRST_PERSON_SPEED, true, player, &speedTarget);
}
// Turn instantly so strafing feels direct
if (moving) {
player->yaw = yawTarget;
}
func_8083DF68(player, speedTarget, player->yaw);
if (!(player->stateFlags1 & PLAYER_STATE1_IN_WATER) && (player->linearVelocity > 0.0f)) {
func_8084029C(player, (REG(30) / 1000.0f) + ((REG(32) / 1000.0f) * player->linearVelocity));
}
}
static void RegisterFirstPerson() {
bool moveInFirstPerson =
CVarGetInteger(CVAR_MOVE_IN_FIRST_PERSON_NAME, 0) && CVarGetInteger(CVAR_RIGHT_STICK_AIM_NAME, 0);
// Left stick moves instead of aiming; right stick aims
COND_VB_SHOULD(VB_PLAYER_AIM_WITH_LEFT_STICK, moveInFirstPerson, { *should = false; });
COND_VB_SHOULD(VB_PLAYER_FIRST_PERSON_DECELERATE, moveInFirstPerson, {
Player* player = va_arg(args, Player*);
MoveInFirstPerson(player);
*should = false;
});
// Keep moving where stick points rather than where Link faces. Once stopped, face forward as normal.
COND_VB_SHOULD(VB_PLAYER_FIRST_PERSON_ALIGN_YAW, moveInFirstPerson, {
Player* player = va_arg(args, Player*);
if (player->linearVelocity != 0.0f) {
*should = false;
}
});
}
static RegisterShipInitFunc initFunc(RegisterFirstPerson,
{ CVAR_MOVE_IN_FIRST_PERSON_NAME, CVAR_RIGHT_STICK_AIM_NAME });
@@ -2323,6 +2323,15 @@ typedef enum {
// - `*DemoIm`
VB_PLAY_ZELDAS_LULLABY_CS,
// #### `result`
// ```c
// true
// ```
// Whether the left stick aims in first person. Off when it moves Link instead.
// #### `args`
// - `*Player`
VB_PLAYER_AIM_WITH_LEFT_STICK,
// #### `result`
// ```c
// true
@@ -2342,6 +2351,24 @@ typedef enum {
// - `PlayState*` play
VB_PLAYER_DRAW_BOTTLE,
// #### `result`
// ```c
// true
// ```
// Whether Link's movement direction is set to where he faces in first person.
// #### `args`
// - `*Player`
VB_PLAYER_FIRST_PERSON_ALIGN_YAW,
// #### `result`
// ```c
// true
// ```
// Whether Link slows to a stop in first person. Hooks may set his speed instead.
// #### `args`
// - `*Player`
VB_PLAYER_FIRST_PERSON_DECELERATE,
// #### `result`
// ```c
// true
@@ -12582,8 +12582,7 @@ s16 func_8084ABD8(PlayState* play, Player* this, s32 arg2, s16 arg3) {
if (!func_8002DD78(this) && !func_808334B4(this) && (arg2 == 0)) { // First person without weapon
// Y Axis
if (!(CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0) &&
CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0))) {
if (GameInteractor_Should(VB_PLAYER_AIM_WITH_LEFT_STICK, true, this)) {
temp2 += sControlInput->rel.stick_y * 240.0f * invertYAxisMulti * yAxisMulti;
}
if (CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0)) {
@@ -12601,8 +12600,7 @@ s16 func_8084ABD8(PlayState* play, Player* this, s32 arg2, s16 arg3) {
// X Axis
temp2 = 0;
if (!(CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0) &&
CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0))) {
if (GameInteractor_Should(VB_PLAYER_AIM_WITH_LEFT_STICK, true, this)) {
temp2 += sControlInput->rel.stick_x * -16.0f * invertXAxisMulti * xAxisMulti;
}
if (CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0)) {
@@ -12617,8 +12615,7 @@ s16 func_8084ABD8(PlayState* play, Player* this, s32 arg2, s16 arg3) {
// Y Axis
temp1 = (this->stateFlags1 & PLAYER_STATE1_ON_HORSE) ? 3500 : 14000;
if (!(CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0) &&
CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0))) {
if (GameInteractor_Should(VB_PLAYER_AIM_WITH_LEFT_STICK, true, this)) {
temp3 += ((sControlInput->rel.stick_y >= 0) ? 1 : -1) *
(s32)((1.0f - Math_CosS(sControlInput->rel.stick_y * 200)) * 1500.0f) * invertYAxisMulti *
yAxisMulti;
@@ -12638,8 +12635,7 @@ s16 func_8084ABD8(PlayState* play, Player* this, s32 arg2, s16 arg3) {
temp1 = 19114;
temp2 = this->actor.focus.rot.y - this->actor.shape.rot.y;
temp3 = 0;
if (!(CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0) &&
CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0))) {
if (GameInteractor_Should(VB_PLAYER_AIM_WITH_LEFT_STICK, true, this)) {
temp3 = ((sControlInput->rel.stick_x >= 0) ? 1 : -1) *
(s32)((1.0f - Math_CosS(sControlInput->rel.stick_x * 200)) * -1500.0f) * invertXAxisMulti *
xAxisMulti;
@@ -12656,34 +12652,6 @@ s16 func_8084ABD8(PlayState* play, Player* this, s32 arg2, s16 arg3) {
this->actor.focus.rot.y = CLAMP(temp2, -temp1, temp1) + this->actor.shape.rot.y;
}
if (CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0) &&
CVarGetInteger(CVAR_SETTING("Controls.RightStickAim"), 0)) {
f32 movementSpeed = LINK_IS_ADULT ? 9.0f : 8.25f;
GameInteractor_Should(VB_PLAYER_MODIFY_FIRST_PERSON_SPEED, true, this, &movementSpeed);
f32 relX =
(sControlInput->rel.stick_x * (CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 0) ? 1 : -1)) / 10.0f;
f32 relY = sControlInput->rel.stick_y / 10.0f;
// Normalize so that diagonal movement isn't faster
f32 relMag = sqrtf((relX * relX) + (relY * relY));
if (relMag > 1.0f) {
relX /= relMag;
relY /= relMag;
}
// Determine what left and right mean based on camera angle
f32 relX2 = relX * Math_CosS(this->actor.focus.rot.y) + relY * Math_SinS(this->actor.focus.rot.y);
f32 relY2 = relY * Math_CosS(this->actor.focus.rot.y) - relX * Math_SinS(this->actor.focus.rot.y);
// Calculate distance for footstep sound
f32 distance = sqrtf((relX2 * relX2) + (relY2 * relY2)) * movementSpeed;
func_8084029C(this, distance / 4.5f);
this->actor.world.pos.x += (relX2 * movementSpeed) + this->actor.colChkInfo.displacement.x;
this->actor.world.pos.z += (relY2 * movementSpeed) + this->actor.colChkInfo.displacement.z;
}
this->unk_6AE_rotFlags |= UNK6AE_ROT_FOCUS_Y;
return func_80836AB8(this, (play->shootingGalleryStatus != 0) || func_8002DD78(this) || func_808334B4(this)) - arg3;
}
@@ -12809,8 +12777,10 @@ void func_8084B158(PlayState* play, Player* this, Input* input, f32 arg3) {
void Player_Action_8084B1D8(Player* this, PlayState* play) {
if (this->stateFlags1 & PLAYER_STATE1_IN_WATER) {
func_8084B000(this);
func_8084AEEC(this, &this->linearVelocity, 0, this->actor.shape.rot.y);
} else {
if (GameInteractor_Should(VB_PLAYER_FIRST_PERSON_DECELERATE, true, this)) {
func_8084AEEC(this, &this->linearVelocity, 0, this->actor.shape.rot.y);
}
} else if (GameInteractor_Should(VB_PLAYER_FIRST_PERSON_DECELERATE, true, this)) {
Player_DecelerateToZero(this);
}
@@ -12838,7 +12808,9 @@ void Player_Action_8084B1D8(Player* this, PlayState* play) {
}
}
this->yaw = this->actor.shape.rot.y;
if (GameInteractor_Should(VB_PLAYER_FIRST_PERSON_ALIGN_YAW, true, this)) {
this->yaw = this->actor.shape.rot.y;
}
}
s32 func_8084B3CC(PlayState* play, Player* this) {