diff --git a/assets/config_menu/general.rml b/assets/config_menu/general.rml index b32f80d..ee96595 100644 --- a/assets/config_menu/general.rml +++ b/assets/config_menu/general.rml @@ -106,7 +106,7 @@ type="range" min="0" max="100" - style="flex: 1; margin: 0dp;" + style="flex: 1; margin: 0dp; nav-down: #bg_input_enabled" data-value="joystick_deadzone" /> @@ -154,7 +154,7 @@ data-checked="autosave_mode" value="On" id="autosave_enabled" - style="nav-up: #bg_input_enabled; nav-down: #analog_cam_enabled" + style="nav-up: #bg_input_enabled; nav-down: #camera_inversion_none" /> @@ -166,11 +166,149 @@ data-checked="autosave_mode" value="Off" id="autosave_disabled" - style="nav-up: #bg_input_disabled; nav-down: #analog_cam_disabled" + style="nav-up: #bg_input_disabled; nav-down: #camera_inversion_x" /> + + +
+ +
+ + + + + + + + + + + +
+
+ + +
+ +
+ + + + + +
+
+ + +
+ +
+ + + + + + + + + + + +
+
@@ -209,6 +347,21 @@
If autosaving is disabled, existing autosaves will be deleted when loaded.

+

+ Inverts the camera controls for first-person aiming. Invert Y is the default and matches the original game. +

+

+ Enables an analog "free" camera similar to later entries in the series that's mapped to the right analog stick on the controller. +
+
+ When you move the right stick, the camera will enter free mode and stop centering behind Link. Press the Target button at any time to go back into the normal camera mode. The camera will also return to normal mode after a cutscene plays or when you move between areas. +
+
+ This option also enables right stick control while looking and aiming. +

+

+ Inverts the camera controls for the analog camera if it's enabled. None is the default. +

diff --git a/include/recomp_config.h b/include/recomp_config.h index e616a80..51e0424 100644 --- a/include/recomp_config.h +++ b/include/recomp_config.h @@ -28,13 +28,27 @@ namespace recomp { OptionCount }; + enum class AnalogCamMode { + On, + Off, + OptionCount + }; + NLOHMANN_JSON_SERIALIZE_ENUM(recomp::AutosaveMode, { {recomp::AutosaveMode::On, "On"}, {recomp::AutosaveMode::Off, "Off"} }); + NLOHMANN_JSON_SERIALIZE_ENUM(recomp::AnalogCamMode, { + {recomp::AnalogCamMode::On, "On"}, + {recomp::AnalogCamMode::Off, "Off"} + }); + AutosaveMode get_autosave_mode(); void set_autosave_mode(AutosaveMode mode); + + AnalogCamMode get_analog_cam_mode(); + void set_analog_cam_mode(AnalogCamMode mode); }; #endif diff --git a/include/recomp_input.h b/include/recomp_input.h index b95d53e..d2d718e 100644 --- a/include/recomp_input.h +++ b/include/recomp_input.h @@ -67,6 +67,7 @@ namespace recomp { bool get_input_digital(const std::span fields); void get_gyro_deltas(float* x, float* y); void get_mouse_deltas(float* x, float* y); + void get_right_analog(float* x, float* y); enum class InputDevice { Controller, @@ -133,6 +134,8 @@ namespace recomp { void set_gyro_sensitivity(int strength); void set_mouse_sensitivity(int strength); void set_joystick_deadzone(int strength); + void apply_joystick_deadzone(float x_in, float y_in, float* x_out, float* y_out); + void set_right_analog_suppressed(bool suppressed); enum class TargetingMode { Switch, @@ -162,6 +165,27 @@ namespace recomp { BackgroundInputMode get_background_input_mode(); void set_background_input_mode(BackgroundInputMode mode); + enum class CameraInvertMode { + InvertNone, + InvertX, + InvertY, + InvertBoth, + OptionCount + }; + + NLOHMANN_JSON_SERIALIZE_ENUM(recomp::CameraInvertMode, { + {recomp::CameraInvertMode::InvertNone, "InvertNone"}, + {recomp::CameraInvertMode::InvertX, "InvertX"}, + {recomp::CameraInvertMode::InvertY, "InvertY"}, + {recomp::CameraInvertMode::InvertBoth, "InvertBoth"} + }); + + CameraInvertMode get_camera_invert_mode(); + void set_camera_invert_mode(CameraInvertMode mode); + + CameraInvertMode get_analog_camera_invert_mode(); + void set_analog_camera_invert_mode(CameraInvertMode mode); + bool game_input_disabled(); bool all_input_disabled(); diff --git a/patches/camera_patches.c b/patches/camera_patches.c index 32365a1..a1d0075 100644 --- a/patches/camera_patches.c +++ b/patches/camera_patches.c @@ -1,65 +1,102 @@ #include "patches.h" #include "input.h" #include "z64quake.h" -#if 0 -RecompCameraMode recomp_camera_mode = RECOMP_CAMERA_NORMAL; +#include "play_patches.h" +#include "camera_patches.h" +#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h" -VecGeo recomp_camera_pos = { .r = 66.0f, .pitch = 0, .yaw = 0 }; +static bool prev_analog_cam_active = false; +static bool can_use_analog_cam = false; +static bool analog_cam_active = false; -float recomp_camera_yaw_vel = 0.0f; -float recomp_camera_pitch_vel = 0.0f; +VecGeo analog_camera_pos = { .r = 66.0f, .pitch = 0, .yaw = 0 }; +float analog_camera_yaw_vel = 0.0f; +float analog_camera_pitch_vel = 0.0f; -float recomp_deadzone = 0.2f; -float recomp_camera_x_sensitivity = 1500.0f; -float recomp_camera_y_sensitivity = 500.0f; -// float recomp_camera_acceleration = 500.0f; +float analog_deadzone = 0.2f; +float analog_camera_x_sensitivity = 1500.0f; +float analog_camera_y_sensitivity = 500.0f; +// float analog_camera_acceleration = 500.0f; -void update_recomp_camera_params(Camera* camera) { - recomp_camera_pos.yaw = Math_Atan2S(-camera->at.x + camera->eye.x, -camera->at.z + camera->eye.z); +static const float analog_cam_threshold = 0.1f; + +void update_analog_camera_params(Camera* camera) { // recomp_printf("Camera at: %.2f %.2f %.2f\n" // " eye: %.2f %.2f %.2f\n" // " yaw: %d", // camera->at.x, camera->at.y, camera->at.z, // camera->eye.x, camera->eye.y, camera->eye.z, - // recomp_camera_pos.yaw); + // analog_camera_pos.yaw); + // Check if the analog camera was usable in this frame. + if (!can_use_analog_cam) { + // It wasn't, so mark the analog cam as being off for this frame. + analog_cam_active = false; + } + prev_analog_cam_active = analog_cam_active; + if (!analog_cam_active) { + // recomp_printf("Analog cam not active\n"); + analog_camera_pos.yaw = Math_Atan2S(camera->eye.x - camera->at.x, camera->eye.z - camera->at.z); + analog_camera_pos.pitch = Math_Vec3f_Pitch(&camera->eye, &camera->at); + } +} +void update_analog_cam(Camera* c) { + can_use_analog_cam = true; + + Player* player = GET_PLAYER(c->play); + // recomp_printf(" val: %d\n", func_80123434(player)); + + // Check if the player just started Z targeting and reset to auto cam if so. + static bool prev_targeting_held = false; + bool targeting_held = func_80123434(player) || player->lockOnActor != NULL; + if (targeting_held && !prev_targeting_held) { + analog_cam_active = false; + } + + // Enable analog cam if the right stick is held. float input_x, input_y; recomp_get_camera_inputs(&input_x, &input_y); - // Math_StepToF(&recomp_camera_yaw_vel, input_x * recomp_camera_x_sensitivity, recomp_camera_acceleration); - // Math_StepToF(&recomp_camera_pitch_vel, input_y * recomp_camera_y_sensitivity, recomp_camera_acceleration); - if (fabsf(input_x) > recomp_deadzone) { - recomp_camera_yaw_vel = input_x * recomp_camera_x_sensitivity; - } - else { - recomp_camera_yaw_vel = 0; + if (fabsf(input_x) >= analog_cam_threshold || fabsf(input_y) >= analog_cam_threshold) { + analog_cam_active = true; } - if (fabsf(input_y) > recomp_deadzone) { - recomp_camera_pitch_vel = input_y * recomp_camera_y_sensitivity; - } - else { - recomp_camera_pitch_vel = 0; - } + // Record the Z targeting state. + prev_targeting_held = targeting_held; - recomp_camera_pos.pitch += recomp_camera_pitch_vel; - recomp_camera_pos.yaw += recomp_camera_yaw_vel; + if (analog_cam_active) { + s32 inverted_x, inverted_y; + recomp_get_analog_inverted_axes(&inverted_x, &inverted_y); + + if (inverted_x) { + input_x = -input_x; + } + + if (inverted_y) { + input_y = -input_y; + } + + analog_camera_yaw_vel = -input_x * analog_camera_x_sensitivity; + analog_camera_pitch_vel = input_y * analog_camera_y_sensitivity; + + analog_camera_pos.pitch += analog_camera_pitch_vel; + analog_camera_pos.yaw += analog_camera_yaw_vel; + + if (analog_camera_pos.pitch > 0x36B0) { + analog_camera_pos.pitch = 0x36B0; + } + + if (analog_camera_pos.pitch < -0x16D0) { + analog_camera_pos.pitch = -0x16D0; + } + + // recomp_printf("analog cam pitch: %05X\n", analog_camera_pos.pitch); + } } extern s32 sUpdateCameraDirection; -static s32 sIsFalse = false; -extern s32 sCameraInitSceneTimer; - -extern s16 sCameraNextUID; extern s32 sCameraInterfaceFlags; -extern s32 sCameraHudVisibility; -extern s32 sCameraLetterboxSize; -extern s32 sCameraNegOne1; - -#define CAM_DATA_IS_BG (1 << 12) // if not set, then cam data is for actor cutscenes - -typedef s32 (*CameraUpdateFunc)(Camera*); typedef struct { /* 0x0 */ s16 val; @@ -78,20 +115,54 @@ typedef struct { /* 0x8 */ CameraMode* cameraModes; } CameraSetting; -extern CameraUpdateFunc sCameraUpdateHandlers[]; extern CameraSetting sCameraSettings[]; +s32 Camera_GetFocalActorPos(Vec3f* dst, Camera* camera); +f32 Camera_GetFocalActorHeight(Camera* camera); +f32 Camera_Vec3fMagnitude(Vec3f* vec); +f32 Camera_GetFloorY(Camera* camera, Vec3f* pos); +f32 Camera_GetFloorYNorm(Camera* camera, Vec3f* floorNorm, Vec3f* chkPos, s32* bgId); +s16 Camera_ScaledStepToFloorS(s16 target, s16 cur, f32 stepScale, s16 minDiff); +s32 func_800CBC84(Camera* camera, Vec3f* from, CameraCollision* to, s32 arg3); +void func_800CBFA4(Camera* camera, Vec3f* arg1, Vec3f* arg2, s32 arg3); +s32 Camera_CalcAtForParallel(Camera* camera, VecGeo* arg1, f32 yOffset, f32 xzOffsetMax, f32* focalActorPosY, + s16 flags); +Vec3s* Camera_GetBgCamOrActorCsCamFuncData(Camera* camera, u32 camDataId); +s32 Camera_IsClimbingLedge(Camera* camera); +void Camera_ScaledStepToCeilVec3f(Vec3f* target, Vec3f* cur, f32 xzStepScale, f32 yStepScale, f32 minDiff); +void Camera_SetFocalActorAtOffset(Camera* camera, Vec3f* focalActorPos); +s32 Camera_CalcAtForHorse(Camera* camera, VecGeo* eyeAtDir, f32 yOffset, f32* yPosOffset, s16 calcSlope); +f32 Camera_fabsf(f32 f); +f32 Camera_GetRunSpeedLimit(Camera* camera); + +#define CAM_CHANGE_SETTING_0 (1 << 0) +#define CAM_CHANGE_SETTING_1 (1 << 1) +#define CAM_CHANGE_SETTING_2 (1 << 2) +#define CAM_CHANGE_SETTING_3 (1 << 3) + +#if 0 +static s32 sIsFalse = false; +extern s32 sCameraInitSceneTimer; + +extern s16 sCameraNextUID; +extern s32 sCameraHudVisibility; +extern s32 sCameraLetterboxSize; +extern s32 sCameraNegOne1; + +#define CAM_DATA_IS_BG (1 << 12) // if not set, then cam data is for actor cutscenes + +typedef s32 (*CameraUpdateFunc)(Camera*); + +extern CameraUpdateFunc sCameraUpdateHandlers[]; Vec3f Camera_CalcUpVec(s16 pitch, s16 yaw, s16 roll); f32 Camera_fabsf(f32 f); s32 Camera_GetBgCamIndex(Camera* camera, s32* bgId, CollisionPoly* poly); -f32 Camera_GetFocalActorHeight(Camera* camera); f32 Camera_GetRunSpeedLimit(Camera* camera); s32 Camera_IsDekuHovering(Camera* camera); s32 Camera_IsMountedOnHorse(Camera* camera); s32 Camera_IsUnderwaterAsZora(Camera* camera); s32 Camera_IsUsingZoraFins(Camera* camera); void Camera_UpdateInterface(s32 interfaceFlags); -f32 Camera_Vec3fMagnitude(Vec3f* vec); s32 func_800CB7CC(Camera* camera); s32 func_800CB854(Camera* camera); @@ -274,9 +345,6 @@ Vec3s Camera_Update(Camera* camera) { // Call the camera update function sCameraUpdateHandlers[sCameraSettings[camera->setting].cameraModes[camera->mode].funcId](camera); - // @recomp - update_recomp_camera_params(camera); - // Update the interface if (sCameraInitSceneTimer != 0) { sCameraInitSceneTimer--; @@ -357,6 +425,8 @@ Vec3s Camera_Update(Camera* camera) { return camera->inputDir; } +#endif + extern SwingAnimation D_801EDC30[4]; s32 Camera_CalcAtDefault(Camera* camera, VecGeo* eyeAtDir, f32 yOffset, s16 calcSlope); s32 Camera_CalcAtForNormal1(Camera* camera, VecGeo* arg1, f32 yOffset, f32 forwardDist); @@ -380,6 +450,7 @@ s32 func_800CBA7C(Camera* camera); #define GET_NEXT_RO_DATA(values) ((values++)->val) #define RELOAD_PARAMS(camera) ((camera->animState == 0) || (camera->animState == 10) || (camera->animState == 20)) +// @recomp Patched for analog cam. s32 Camera_Normal1(Camera* camera) { Vec3f* eye = &camera->eye; Vec3f* at = &camera->at; @@ -690,13 +761,6 @@ s32 Camera_Normal1(Camera* camera) { } } - // @recomp - if (recomp_camera_mode == RECOMP_CAMERA_DUALANALOG) { - spB4.pitch = recomp_camera_pos.pitch; - // spB4.r = recomp_camera_pos.r; - spB4.yaw = recomp_camera_pos.yaw; - } - // 76.9 degrees if (spB4.pitch > 0x36B0) { spB4.pitch = 0x36B0; @@ -707,8 +771,16 @@ s32 Camera_Normal1(Camera* camera) { spB4.pitch = -0x36B0; } - // @recomp - recomp_camera_pos.pitch = spB4.pitch; + // @recomp Update the analog camera. + if (recomp_analog_cam_enabled()) { + update_analog_cam(camera); + + if (analog_cam_active) { + spB4.pitch = analog_camera_pos.pitch; + // spB4.r = analog_camera_pos.r; + spB4.yaw = analog_camera_pos.yaw; + } + } *eyeNext = OLib_AddVecGeoToVec3f(at, &spB4); @@ -755,11 +827,10 @@ s32 Camera_Normal1(Camera* camera) { phi_f2 = (gSaveContext.save.saveInfo.playerData.health <= 0x10) ? 0.8f : 1.0f; - // @recomp - // // Don't zoom in on low health when dual analog is used - // if (recomp_camera_mode == RECOMP_CAMERA_DUALANALOG) { - // phi_f2; - // } + // @recomp Don't zoom in on low health when dual analog is used + if (recomp_analog_cam_enabled()) { + phi_f2 = 1.0f; + } camera->fov = Camera_ScaledStepToCeilF(roData->unk_18 * phi_f2, camera->fov, camera->fovUpdateRate, 0.1f); @@ -783,4 +854,1022 @@ s32 Camera_Normal1(Camera* camera) { return true; } -#endif // #if 0 + + +/** + * Camera for climbing structures + */ +// @recomp Patched for analog cam. +s32 Camera_Jump2(Camera* camera) { + Vec3f* eye = &camera->eye; + Vec3f* at = &camera->at; + Vec3f* eyeNext = &camera->eyeNext; + Vec3f spC8; + Vec3f spBC; + VecGeo spB4; + VecGeo spAC; + VecGeo spA4; + VecGeo sp9C; + s16 temp_t2; + s16 yawDiff; + s32 pad; + f32 sp90; + f32 sp8C; + s32 sp88; + CameraCollision sp60; + PosRot* focalActorPosRot = &camera->focalActorPosRot; + Jump2ReadOnlyData* roData = &camera->paramData.jump2.roData; + Jump2ReadWriteData* rwData = &camera->paramData.jump2.rwData; + f32 phi_f2; + f32 yNormal; // used twice + f32 focalActorHeight = Camera_GetFocalActorHeight(camera); + f32 temp_f16; + + if (RELOAD_PARAMS(camera)) { + CameraModeValue* values = sCameraSettings[camera->setting].cameraModes[camera->mode].values; + + yNormal = 0.8f - (-0.2f * (68.0f / focalActorHeight)); + + if (camera->unk_0F0.y > 0.0f) { + phi_f2 = -10.0f; + } else { + phi_f2 = 10.0f; + } + + roData->unk_00 = CAM_RODATA_UNSCALE(phi_f2 + GET_NEXT_RO_DATA(values)) * focalActorHeight * yNormal; + roData->unk_04 = GET_NEXT_SCALED_RO_DATA(values) * focalActorHeight * yNormal; + roData->unk_08 = GET_NEXT_SCALED_RO_DATA(values) * focalActorHeight * yNormal; + roData->unk_0C = GET_NEXT_SCALED_RO_DATA(values); + roData->unk_10 = GET_NEXT_RO_DATA(values); + roData->unk_14 = GET_NEXT_SCALED_RO_DATA(values); + roData->unk_18 = GET_NEXT_RO_DATA(values); + roData->unk_1C = GET_NEXT_SCALED_RO_DATA(values); + roData->interfaceFlags = GET_NEXT_RO_DATA(values); + } + + sp9C = OLib_Vec3fDiffToVecGeo(at, eye); + spA4 = OLib_Vec3fDiffToVecGeo(at, eyeNext); + + sCameraInterfaceFlags = roData->interfaceFlags; + + if (RELOAD_PARAMS(camera)) { + spC8 = focalActorPosRot->pos; + rwData->unk_00 = Camera_GetFloorY(camera, &spC8); + rwData->unk_04 = spA4.yaw; + rwData->unk_06 = 0; + + if (rwData->unk_00 == BGCHECK_Y_MIN) { + rwData->unk_0A = -1; + rwData->unk_00 = focalActorPosRot->pos.y - 1000.0f; + } else if ((focalActorPosRot->pos.y - rwData->unk_00) < focalActorHeight) { + rwData->unk_0A = 1; + } else { + rwData->unk_0A = -1; + } + + yawDiff = BINANG_SUB(BINANG_ROT180(focalActorPosRot->rot.y), spA4.yaw); + rwData->unk_06 = ((yawDiff / 6) / 4) * 3; + + if (roData->interfaceFlags & JUMP2_FLAG_1) { + rwData->unk_08 = 10; + } else { + rwData->unk_08 = 10000; + } + + focalActorPosRot->pos.x -= camera->unk_0F0.x; + focalActorPosRot->pos.y -= camera->unk_0F0.y; + focalActorPosRot->pos.z -= camera->unk_0F0.z; + + rwData->timer = 6; + camera->animState++; + camera->atLerpStepScale = roData->unk_1C; + } + + sp90 = camera->speedRatio * 0.5f; + sp8C = camera->speedRatio * 0.2f; + + camera->yawUpdateRateInv = Camera_ScaledStepToCeilF(roData->unk_10, camera->yawUpdateRateInv, sp90, 0.1f); + camera->yOffsetUpdateRate = Camera_ScaledStepToCeilF(roData->unk_14, camera->yOffsetUpdateRate, sp90, 0.0001f); + camera->xzOffsetUpdateRate = Camera_ScaledStepToCeilF(0.05f, camera->xzOffsetUpdateRate, sp8C, 0.0001f); + camera->fovUpdateRate = Camera_ScaledStepToCeilF(0.05f, camera->fovUpdateRate, camera->speedRatio * .05f, 0.0001f); + camera->rUpdateRateInv = 1800.0f; + + Camera_CalcAtDefault(camera, &spA4, roData->unk_00, 0); + spB4 = OLib_Vec3fDiffToVecGeo(at, eye); + + //! FAKE: Unused + yNormal = roData->unk_04; + + phi_f2 = roData->unk_08 + (roData->unk_08 * roData->unk_0C); + temp_f16 = roData->unk_04 - (roData->unk_04 * roData->unk_0C); + + if (spB4.r > phi_f2) { + spB4.r = phi_f2; + } else if (spB4.r < temp_f16) { + spB4.r = temp_f16; + } + + yawDiff = BINANG_SUB(BINANG_ROT180(focalActorPosRot->rot.y), spB4.yaw); + if (rwData->timer != 0) { + rwData->unk_04 = BINANG_ROT180(focalActorPosRot->rot.y); + rwData->timer--; + spB4.yaw = Camera_ScaledStepToCeilS(rwData->unk_04, spA4.yaw, 0.5f, 5); + } else if (rwData->unk_08 < ABS(yawDiff)) { + temp_t2 = BINANG_ROT180(focalActorPosRot->rot.y); + spB4.yaw = Camera_ScaledStepToFloorS((yawDiff < 0) ? (temp_t2 + rwData->unk_08) : (temp_t2 - rwData->unk_08), + spA4.yaw, 0.1f, 1); + } else { + spB4.yaw = Camera_ScaledStepToCeilS(spB4.yaw, spA4.yaw, 0.25f, 5); + } + + spC8.x = focalActorPosRot->pos.x + (Math_SinS(focalActorPosRot->rot.y) * 25.0f); + spC8.y = focalActorPosRot->pos.y + (focalActorHeight * 2.2f); + spC8.z = focalActorPosRot->pos.z + (Math_CosS(focalActorPosRot->rot.y) * 25.0f); + + yNormal = Camera_GetFloorYNorm(camera, &spBC, &spC8, &sp88); + if (camera->focalActor->bgCheckFlags & 0x10) { + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF(20.0f, camera->pitchUpdateRateInv, 0.2f, 0.1f); + camera->rUpdateRateInv = Camera_ScaledStepToCeilF(20.0f, camera->rUpdateRateInv, 0.2f, 0.1f); + spB4.pitch = Camera_ScaledStepToCeilS(-DEG_TO_BINANG(27.47f), spA4.pitch, 0.2f, 5); + } else if ((yNormal != BGCHECK_Y_MIN) && (focalActorPosRot->pos.y < yNormal)) { + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF(20.0f, camera->pitchUpdateRateInv, 0.2f, 0.1f); + camera->rUpdateRateInv = Camera_ScaledStepToCeilF(20.0f, camera->rUpdateRateInv, 0.2f, 0.1f); + if (camera->unk_0F0.y > 1.0f) { + spB4.pitch = Camera_ScaledStepToCeilS(0x1F4, spA4.pitch, 1.0f / camera->pitchUpdateRateInv, 5); + } + } else if ((focalActorPosRot->pos.y - rwData->unk_00) < focalActorHeight) { + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF(20.0f, camera->pitchUpdateRateInv, 0.2f, 0.1f); + camera->rUpdateRateInv = Camera_ScaledStepToCeilF(20.0f, camera->rUpdateRateInv, 0.2f, 0.1f); + if (camera->unk_0F0.y > 1.0f) { + spB4.pitch = Camera_ScaledStepToCeilS(0x1F4, spA4.pitch, 1.0f / camera->pitchUpdateRateInv, 5); + } + } else { + camera->pitchUpdateRateInv = 100.0f; + camera->rUpdateRateInv = 100.0f; + } + + spB4.pitch = CLAMP_MAX(spB4.pitch, DEG_TO_BINANG(60.43f)); + spB4.pitch = CLAMP_MIN(spB4.pitch, -DEG_TO_BINANG(60.43f)); + + // @recomp Update the analog camera. + if (recomp_analog_cam_enabled()) { + update_analog_cam(camera); + + if (analog_cam_active) { + spB4.pitch = analog_camera_pos.pitch; + // spB4.r = analog_camera_pos.r; + spB4.yaw = analog_camera_pos.yaw; + } + } + + *eyeNext = OLib_AddVecGeoToVec3f(at, &spB4); + sp60.pos = *eyeNext; + + if (func_800CBC84(camera, at, &sp60, 0) != 0) { + spC8 = sp60.pos; + spAC.pitch = 0; + spAC.r = spB4.r; + spAC.yaw = spB4.yaw; + sp60.pos = OLib_AddVecGeoToVec3f(at, &spAC); + if (func_800CBC84(camera, at, &sp60, 0) != 0) { + *eye = spC8; + } else { + spB4.pitch = Camera_ScaledStepToCeilS(0, spB4.pitch, 0.2f, 5); + *eye = OLib_AddVecGeoToVec3f(at, &spB4); + func_800CBFA4(camera, at, eye, 0); + } + } else { + *eye = *eyeNext; + } + + camera->dist = spB4.r; + camera->fov = Camera_ScaledStepToCeilF(roData->unk_18, camera->fov, camera->fovUpdateRate, 0.1f); + camera->roll = Camera_ScaledStepToCeilS(0, camera->roll, 0.5f, 5); + + return true; +} + +/** + * Used for targeting + */ +// @recomp Patched for analog cam. +s32 Camera_Parallel1(Camera* camera) { + Vec3f* eye = &camera->eye; + Vec3f* at = &camera->at; + Vec3f* eyeNext = &camera->eyeNext; + Vec3f spB0; + Vec3f spA4; + f32 spA0; + f32 sp9C; + PosRot* focalActorPosRot = &camera->focalActorPosRot; + VecGeo sp90; + VecGeo sp88; + VecGeo sp80; + VecGeo sp78; + BgCamFuncData* bgCamFuncData; + s16 sp72; + s16 tangle; + Parallel1ReadOnlyData* roData = &camera->paramData.para1.roData; + Parallel1ReadWriteData* rwData = &camera->paramData.para1.rwData; + s32 parallelFlagCond; + f32 focalActorHeight = Camera_GetFocalActorHeight(camera); + s16 new_var2; + s16 phi_a0; + s32 phi_a0_2; + CameraModeValue* values; + f32 yNormal; + + if (!RELOAD_PARAMS(camera)) { + } else { + values = sCameraSettings[camera->setting].cameraModes[camera->mode].values; + roData->unk_00 = + GET_NEXT_SCALED_RO_DATA(values) * focalActorHeight * (0.8f - ((68.0f / focalActorHeight) * -0.2f)); + roData->unk_04 = + GET_NEXT_SCALED_RO_DATA(values) * focalActorHeight * (0.8f - ((68.0f / focalActorHeight) * -0.2f)); + roData->unk_08 = + GET_NEXT_SCALED_RO_DATA(values) * focalActorHeight * (0.8f - ((68.0f / focalActorHeight) * -0.2f)); + roData->unk_20 = CAM_DEG_TO_BINANG(GET_NEXT_RO_DATA(values)); + roData->unk_22 = CAM_DEG_TO_BINANG(GET_NEXT_RO_DATA(values)); + roData->unk_0C = GET_NEXT_RO_DATA(values); + roData->unk_10 = GET_NEXT_RO_DATA(values); + roData->unk_14 = GET_NEXT_RO_DATA(values); + roData->unk_18 = GET_NEXT_SCALED_RO_DATA(values); + roData->interfaceFlags = GET_NEXT_RO_DATA(values); + roData->unk_1C = GET_NEXT_SCALED_RO_DATA(values); + roData->unk_24 = GET_NEXT_RO_DATA(values); + rwData->unk_00 = roData->unk_04; + } + + sp80 = OLib_Vec3fDiffToVecGeo(at, eye); + sp78 = OLib_Vec3fDiffToVecGeo(at, eyeNext); + Camera_GetFocalActorPos(&spA4, camera); + + switch (camera->animState) { + case 20: + if ((roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) == 0) { + Camera_SetUpdateRatesFastYaw(camera); + } + // fallthrough + case 0: + case 10: + if ((roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) == + (PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) { + rwData->unk_10 = focalActorPosRot->pos; + } else { + camera->xzOffsetUpdateRate = 0.5f; + camera->yOffsetUpdateRate = 0.5f; + } + + if ((roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) == + PARALLEL1_FLAG_3) { + rwData->unk_10 = camera->focalActorPosRot.pos; + } + + rwData->timer1 = 200.0f; + + if ((2.0f * roData->unk_04) < camera->dist) { + camera->dist = 2.0f * roData->unk_04; + sp78.r = camera->dist; + sp80.r = sp78.r; + *eye = OLib_AddVecGeoToVec3f(at, &sp80); + *eyeNext = *eye; + } + + rwData->unk_1C = 0; + + if (roData->interfaceFlags & PARALLEL1_FLAG_2) { + rwData->timer2 = 20; + } else { + rwData->timer2 = 6; + } + + if ((camera->focalActor == &GET_PLAYER(camera->play)->actor) && (camera->mode == CAM_MODE_CHARGE)) { + rwData->timer2 = 30; + if (((Player*)camera->focalActor)->transformation == PLAYER_FORM_DEKU) { + roData->unk_24 = -1; + } + } + + if ((roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) == + (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_1)) { + rwData->timer2 = 1; + yNormal = 0.8f - ((68.0f / focalActorHeight) * -0.2f); + + bgCamFuncData = (BgCamFuncData*)Camera_GetBgCamOrActorCsCamFuncData(camera, camera->bgCamIndex); + + rwData->unk_20 = bgCamFuncData->rot.x; + rwData->unk_1E = bgCamFuncData->rot.y; + rwData->unk_08 = (bgCamFuncData->fov == -1) ? roData->unk_14 + : (bgCamFuncData->fov > 360) ? CAM_RODATA_UNSCALE(bgCamFuncData->fov) + : bgCamFuncData->fov; + rwData->unk_00 = (bgCamFuncData->unk_0E == -1) + ? roData->unk_04 + : CAM_RODATA_UNSCALE(bgCamFuncData->unk_0E) * focalActorHeight * yNormal; + //! FAKE + dummy:; + } else { + rwData->unk_08 = roData->unk_14; + rwData->unk_00 = roData->unk_04; + } + + rwData->timer3 = roData->unk_24; + rwData->unk_04 = focalActorPosRot->pos.y - camera->unk_0F0.y; + rwData->unk_26 = 1; + camera->animState = 1; + sCameraInterfaceFlags = roData->interfaceFlags; + break; + } + + if (rwData->timer2 != 0) { + switch (roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) { + case PARALLEL1_FLAG_1: + case (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1): + rwData->unk_1E = BINANG_ROT180(camera->focalActorPosRot.rot.y) + roData->unk_22; + rwData->unk_20 = roData->unk_20; + break; + + case PARALLEL1_FLAG_2: + rwData->unk_1E = roData->unk_22; + rwData->unk_20 = roData->unk_20; + break; + + case (PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1): + if (rwData->timer3 == 1) { + sp88 = OLib_Vec3fDiffToVecGeo(&rwData->unk_10, &spA4); + rwData->unk_1E = ((ABS(BINANG_SUB(sp88.yaw, sp80.yaw)) < 0x3A98) || Camera_IsClimbingLedge(camera)) + ? sp80.yaw + : sp80.yaw + (s16)((BINANG_SUB(sp88.yaw, sp80.yaw) >> 2) * 3); + } + rwData->unk_20 = roData->unk_20; + break; + + case PARALLEL1_FLAG_3: + rwData->unk_1E = sp80.yaw; + rwData->unk_20 = roData->unk_20; + break; + + case (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_1): + break; + + default: + rwData->unk_1E = sp78.yaw + roData->unk_22; + rwData->unk_20 = roData->unk_20; + break; + } + } else if (roData->interfaceFlags & PARALLEL1_FLAG_5) { + rwData->unk_1E = BINANG_ROT180(camera->focalActorPosRot.rot.y) + roData->unk_22; + } + + if (camera->animState == 21) { + camera->animState = 1; + } else if (camera->animState == 11) { + camera->animState = 1; + } + + spA0 = camera->speedRatio * 0.5f; + sp9C = camera->speedRatio * 0.2f; + + if (((roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) == + (PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) || + ((roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) == PARALLEL1_FLAG_3) || + (roData->interfaceFlags & PARALLEL1_FLAG_5)) { + camera->rUpdateRateInv = Camera_ScaledStepToCeilF(20.0f, camera->rUpdateRateInv, 0.5f, 0.1f); + camera->yawUpdateRateInv = Camera_ScaledStepToCeilF(roData->unk_0C, camera->yawUpdateRateInv, 0.5f, 0.1f); + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF(20.0f, camera->pitchUpdateRateInv, 0.5f, 0.1f); + } else { + camera->rUpdateRateInv = Camera_ScaledStepToCeilF(20.0f, camera->rUpdateRateInv, spA0, 0.1f); + camera->yawUpdateRateInv = Camera_ScaledStepToCeilF(roData->unk_0C, camera->yawUpdateRateInv, spA0, 0.1f); + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF(2.0f, camera->pitchUpdateRateInv, sp9C, 0.1f); + } + + if ((roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) == + (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) { + camera->yOffsetUpdateRate = Camera_ScaledStepToCeilF(0.1f, camera->yOffsetUpdateRate, spA0, 0.0001f); + camera->xzOffsetUpdateRate = Camera_ScaledStepToCeilF(0.1f, camera->xzOffsetUpdateRate, sp9C, 0.0001f); + } else if (roData->interfaceFlags & PARALLEL1_FLAG_7) { + camera->yOffsetUpdateRate = Camera_ScaledStepToCeilF(0.5f, camera->yOffsetUpdateRate, spA0, 0.0001f); + camera->xzOffsetUpdateRate = Camera_ScaledStepToCeilF(0.5f, camera->xzOffsetUpdateRate, sp9C, 0.0001f); + } else { + camera->yOffsetUpdateRate = Camera_ScaledStepToCeilF(0.05f, camera->yOffsetUpdateRate, spA0, 0.0001f); + camera->xzOffsetUpdateRate = Camera_ScaledStepToCeilF(0.05f, camera->xzOffsetUpdateRate, sp9C, 0.0001f); + } + + // TODO: Extra trailing 0 in 0.050f needed? + camera->fovUpdateRate = + Camera_ScaledStepToCeilF(0.050f, camera->fovUpdateRate, camera->speedRatio * 0.05f, 0.0001f); + + if (roData->interfaceFlags & PARALLEL1_FLAG_0) { + tangle = Camera_GetPitchAdjFromFloorHeightDiffs(camera, BINANG_ROT180(sp80.yaw), rwData->unk_26 = 1); + spA0 = ((1.0f / roData->unk_10)); + spA0 *= 0.6f; + sp9C = ((1.0f / roData->unk_10) * 0.4f) * (1.0f - camera->speedRatio); + rwData->unk_1C = Camera_ScaledStepToCeilS(tangle, rwData->unk_1C, spA0 + sp9C, 5); + } else { + rwData->unk_1C = 0; + } + + if (func_800CB950(camera) || (((Player*)camera->focalActor)->stateFlags1 & PLAYER_STATE1_1000) || + (((Player*)camera->focalActor)->stateFlags3 & PLAYER_STATE3_100)) { + rwData->unk_04 = camera->focalActorPosRot.pos.y; + sp72 = false; + } else { + sp72 = true; + } + + if ((((Player*)camera->focalActor)->stateFlags1 & PLAYER_STATE1_4000) || + (((Player*)camera->focalActor)->stateFlags1 & PLAYER_STATE1_4) || + ((roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) == + (PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1))) { + spB0 = spA4; + spB0.y += ((focalActorHeight * 0.6f) + roData->unk_00); + Camera_ScaledStepToCeilVec3f(&spB0, at, camera->xzOffsetUpdateRate, camera->yOffsetUpdateRate, 0.0001f); + Camera_SetFocalActorAtOffset(camera, &focalActorPosRot->pos); + } else if ((roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) == + (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) { + spB0 = focalActorPosRot->pos; + spB0.y += focalActorHeight + roData->unk_00; + Camera_ScaledStepToCeilVec3f(&spB0, at, camera->xzOffsetUpdateRate, camera->yOffsetUpdateRate, 0.0001f); + Camera_SetFocalActorAtOffset(camera, &focalActorPosRot->pos); + } else if (rwData->timer2 != 0) { + Camera_CalcAtDefault(camera, &sp78, roData->unk_00, 0); + rwData->timer1 = 200.0f; + } else if (!(roData->interfaceFlags & PARALLEL1_FLAG_7) && !sp72) { + Camera_CalcAtForParallel(camera, &sp78, roData->unk_00, roData->unk_08, &rwData->unk_04, + roData->interfaceFlags & (PARALLEL1_FLAG_6 | PARALLEL1_FLAG_0)); + rwData->timer1 = 200.0f; + } else { + Camera_CalcAtForScreen(camera, &sp78, roData->unk_00, &rwData->unk_04, rwData->timer1); + if (rwData->timer1 > 10.0f) { + rwData->timer1--; + } + } + + camera->dist = Camera_ScaledStepToCeilF(rwData->unk_00, camera->dist, 1.0f / camera->rUpdateRateInv, 0.1f); + + if (rwData->timer2 != 0) { + if (rwData->timer3 <= 0) { + if (rwData->timer3 == 0) { + Camera_SetStateFlag(camera, CAM_STATE_DISABLE_MODE_CHANGE); + } + + tangle = ((rwData->timer2 + 1) * rwData->timer2) >> 1; + sp90.yaw = sp80.yaw + ((BINANG_SUB(rwData->unk_1E, sp80.yaw) / tangle) * rwData->timer2); + phi_a0 = ((roData->interfaceFlags & PARALLEL1_FLAG_0) ? BINANG_SUB(rwData->unk_20, rwData->unk_1C) + : rwData->unk_20); + new_var2 = BINANG_SUB(phi_a0, sp80.pitch); + sp90.pitch = sp80.pitch + ((new_var2 / tangle) * rwData->timer2); + sp90.r = camera->dist; + rwData->timer2--; + } else { + sp90 = sp80; + sp90.r = camera->dist; + } + } else { + sp90 = OLib_Vec3fDiffToVecGeo(at, eyeNext); + sp90.r = camera->dist; + + if (roData->interfaceFlags & PARALLEL1_FLAG_1) { + sp90.yaw = Camera_ScaledStepToCeilS(rwData->unk_1E, sp78.yaw, 1.0f / camera->yawUpdateRateInv, 0xC8); + } + + parallelFlagCond = (roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)); + + if (roData->interfaceFlags & PARALLEL1_FLAG_0) { + phi_a0 = (rwData->unk_20 - rwData->unk_1C); + } else { + phi_a0 = rwData->unk_20; + } + + if (parallelFlagCond == (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) { + spA0 = CLAMP_MAX(camera->speedRatio, 1.0f); + phi_a0 = (sp90.pitch * spA0) + (phi_a0 * (1.0f - spA0)); + sp90.pitch = Camera_ScaledStepToCeilS(phi_a0, sp78.pitch, 1.0f / camera->pitchUpdateRateInv, 5); + } else if (parallelFlagCond != PARALLEL1_FLAG_3) { + sp90.pitch = Camera_ScaledStepToCeilS(phi_a0, sp78.pitch, 1.0f / camera->pitchUpdateRateInv, 5); + } + + if (sp90.pitch > DEG_TO_BINANG(79.655f)) { + sp90.pitch = DEG_TO_BINANG(79.655f); + } + + if (sp90.pitch < -DEG_TO_BINANG(29.995f)) { + sp90.pitch = -DEG_TO_BINANG(29.995f); + } + } + + if (rwData->timer3 > 0) { + rwData->timer3--; + } + + // @recomp Update the analog camera. + if (recomp_analog_cam_enabled()) { + update_analog_cam(camera); + + if (analog_cam_active) { + sp90.pitch = analog_camera_pos.pitch; + // sp90.r = analog_camera_pos.r; + sp90.yaw = analog_camera_pos.yaw; + } + } + + *eyeNext = OLib_AddVecGeoToVec3f(at, &sp90); + + if (camera->status == CAM_STATUS_ACTIVE) { + if ((camera->play->envCtx.skyboxDisabled == 0) || (roData->interfaceFlags & PARALLEL1_FLAG_4)) { + spB0 = *at; + if ((((Player*)camera->focalActor)->stateFlags1 & PLAYER_STATE1_4000) || + (((Player*)camera->focalActor)->stateFlags1 & PLAYER_STATE1_4) || + ((roData->interfaceFlags & (PARALLEL1_FLAG_3 | PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1)) == + (PARALLEL1_FLAG_2 | PARALLEL1_FLAG_1))) { + spB0.y += focalActorHeight; + } + *eye = *eyeNext; + func_800CBFA4(camera, &spB0, eye, 0); + } else { + *eye = *eyeNext; + func_800CBFA4(camera, at, eye, 3); + } + + if (rwData->timer2 != 0) { + sUpdateCameraDirection = true; + } else { + sUpdateCameraDirection = false; + } + } + + camera->fov = Camera_ScaledStepToCeilF(rwData->unk_08, camera->fov, camera->fovUpdateRate, 0.1f); + camera->roll = Camera_ScaledStepToCeilS(0, camera->roll, 0.5f, 5); + camera->atLerpStepScale = Camera_ClampLerpScale(camera, sp72 ? roData->unk_1C : roData->unk_18); + rwData->unk_26 &= ~1; + + return 1; +} + +#define NORMAL3_RW_FLAG (1 << 0) + +/** + * Riding Epona and Zora + */ +// @recomp Patched for analog cam. +s32 Camera_Normal3(Camera* camera) { + Normal3ReadOnlyData* roData = &camera->paramData.norm3.roData; + Normal3ReadWriteData* rwData = &camera->paramData.norm3.rwData; + f32 sp8C; + f32 sp90; + f32 temp_f2; // multi-use temp + f32 sp88; + VecGeo sp80; + VecGeo sp78; + VecGeo sp70; + VecGeo sp68; + f32 phi_f2; + s16 sp62; + s16 phi_v1_2; + Player* player = (Player*)camera->focalActor; + Vec3f* eye = &camera->eye; + Vec3f* at = &camera->at; + Vec3f* eyeNext = &camera->eyeNext; + PosRot* focalActorPosRot = &camera->focalActorPosRot; + + temp_f2 = Camera_GetFocalActorHeight(camera); + + if ((camera->setting == CAM_SET_HORSE) && (player->rideActor == NULL)) { + Camera_ChangeSettingFlags(camera, camera->prevSetting, CAM_CHANGE_SETTING_1); + return 1; + } + + if (RELOAD_PARAMS(camera)) { + CameraModeValue* values = sCameraSettings[camera->setting].cameraModes[camera->mode].values; + + temp_f2 = CAM_RODATA_UNSCALE(temp_f2); + + roData->yOffset = GET_NEXT_RO_DATA(values) * temp_f2; + roData->distMin = GET_NEXT_RO_DATA(values) * temp_f2; + roData->distMax = GET_NEXT_RO_DATA(values) * temp_f2; + roData->pitchTarget = CAM_DEG_TO_BINANG(GET_NEXT_RO_DATA(values)); + roData->yawUpdateRateInv = GET_NEXT_RO_DATA(values); + roData->pitchUpdateRateInv = GET_NEXT_RO_DATA(values); + roData->fovTarget = GET_NEXT_RO_DATA(values); + roData->maxAtLERPScale = GET_NEXT_SCALED_RO_DATA(values); + roData->interfaceFlags = GET_NEXT_RO_DATA(values); + } + + sp70 = OLib_Vec3fDiffToVecGeo(at, eye); + sp68 = OLib_Vec3fDiffToVecGeo(at, eyeNext); + sUpdateCameraDirection = true; + sCameraInterfaceFlags = roData->interfaceFlags; + + //! FAKE: fake temp + phi_v1_2 = camera->animState; + if (!(((phi_v1_2 == 0) || (phi_v1_2 == 10)) || (phi_v1_2 == 20))) { + } else { + rwData->isZero = 0; + rwData->curPitch = 0; + rwData->yPosOffset = camera->focalActorFloorHeight; + + D_801EDC30[camera->camId].yaw = D_801EDC30[camera->camId].pitch = D_801EDC30[camera->camId].unk_64 = 0; + D_801EDC30[camera->camId].swingUpdateRate = roData->yawUpdateRateInv; + rwData->yawUpdateRate = BINANG_SUB(BINANG_ROT180(camera->focalActorPosRot.rot.y), sp70.yaw) * (1.0f / 6.0f); + rwData->distTimer = 0; + rwData->is1200 = 1200; + + if (roData->interfaceFlags & NORMAL3_FLAG_1) { + rwData->yawTimer = 6; + Camera_SetStateFlag(camera, CAM_STATE_DISABLE_MODE_CHANGE); + } else { + rwData->yawTimer = 0; + } + + camera->animState = 1; + D_801EDC30[camera->camId].timer = 0; + rwData->flag = NORMAL3_RW_FLAG; + } + + if (rwData->distTimer != 0) { + rwData->distTimer--; + } + + sp90 = ((camera->speedRatio * 3.0f) + 1.0f) * 0.25f * 0.5f; + sp8C = temp_f2 = camera->speedRatio * 0.2f; + + if (D_801EDC30[camera->camId].timer != 0) { + camera->yawUpdateRateInv = Camera_ScaledStepToCeilF( + (D_801EDC30[camera->camId].timer * 2) + roData->yawUpdateRateInv, camera->yawUpdateRateInv, sp90, 0.1f); + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF((D_801EDC30[camera->camId].timer * 2) + 16.0f, + camera->pitchUpdateRateInv, sp8C, 0.1f); + D_801EDC30[camera->camId].timer--; + } else { + camera->yawUpdateRateInv = + Camera_ScaledStepToCeilF(roData->yawUpdateRateInv, camera->yawUpdateRateInv, sp90, 0.1f); + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF(16.0f, camera->pitchUpdateRateInv, sp8C, 0.1f); + } + + camera->yOffsetUpdateRate = Camera_ScaledStepToCeilF(0.05f, camera->yOffsetUpdateRate, sp90, 0.001f); + camera->xzOffsetUpdateRate = Camera_ScaledStepToCeilF(0.05f, camera->xzOffsetUpdateRate, sp8C, 0.0001f); + camera->fovUpdateRate = Camera_ScaledStepToCeilF(0.05f, camera->fovUpdateRate, sp8C, 0.0001f); + + phi_v1_2 = Camera_GetPitchAdjFromFloorHeightDiffs(camera, BINANG_ROT180(sp70.yaw), rwData->flag & NORMAL3_RW_FLAG); + temp_f2 = ((1.0f / roData->pitchUpdateRateInv) * 0.5f) * (1.0f - camera->speedRatio); + rwData->curPitch = + Camera_ScaledStepToCeilS(phi_v1_2, rwData->curPitch, ((1.0f / roData->pitchUpdateRateInv) * 0.5f) + temp_f2, 5); + + if ((roData->interfaceFlags & NORMAL3_FLAG_6) || (player->rideActor == NULL)) { + Camera_CalcAtDefault(camera, &sp68, roData->yOffset, 1); + } else { + Camera_CalcAtForHorse(camera, &sp68, roData->yOffset, &rwData->yPosOffset, 1); + } + + sp88 = (roData->distMax + roData->distMin) * 0.5f; + sp80 = OLib_Vec3fDiffToVecGeo(at, eyeNext); + temp_f2 = Camera_ClampDist1(camera, sp80.r, roData->distMin, roData->distMax, rwData->distTimer); + + phi_f2 = sp88 - temp_f2; + phi_f2 *= 0.002f; + camera->dist = sp80.r = temp_f2 + phi_f2; + + if (roData->interfaceFlags & NORMAL3_FLAG_7) { + sp80.pitch = Camera_ScaledStepToCeilS(camera->focalActor->focus.rot.x - rwData->curPitch, sp68.pitch, 0.25f, 5); + } else { + sp62 = roData->pitchTarget - rwData->curPitch; + sp80.pitch = Camera_ScaledStepToCeilS(sp62, sp68.pitch, 1.0f / camera->pitchUpdateRateInv, 5); + } + + sp80.pitch = CLAMP_MAX(sp80.pitch, DEG_TO_BINANG(79.655f)); + + if (sp80.pitch < -DEG_TO_BINANG(29.995f)) { + sp80.pitch = -DEG_TO_BINANG(29.995f); + } + + if (roData->interfaceFlags & NORMAL3_FLAG_7) { + sp62 = BINANG_SUB(camera->focalActor->focus.rot.y, BINANG_ROT180(sp68.yaw)); + temp_f2 = 1.0f; + } else { + sp62 = BINANG_SUB(focalActorPosRot->rot.y, BINANG_ROT180(sp68.yaw)); + sp78 = OLib_Vec3fToVecGeo(&camera->unk_0F0); + phi_v1_2 = focalActorPosRot->rot.y - sp78.yaw; + if (phi_v1_2 < 0) { + phi_v1_2 *= -1; + } + + if (phi_v1_2 < 0x555A) { + temp_f2 = 1.0f; + } else { + temp_f2 = ((f32)0x8000 - phi_v1_2) / (f32)0x2AA6; + } + } + + sp90 = (sp62 * ((SQ(camera->speedRatio) * 0.8f) + 0.2f) * temp_f2) / camera->yawUpdateRateInv; + if ((Camera_fabsf(sp90) > 150.0f) && (camera->speedRatio > 0.05f)) { + sp80.yaw = sp68.yaw + sp90; + } + + if (rwData->yawTimer > 0) { + sp80.yaw += rwData->yawUpdateRate; + rwData->yawTimer--; + if (rwData->yawTimer == 0) { + Camera_UnsetStateFlag(camera, CAM_STATE_DISABLE_MODE_CHANGE); + } + } + + // @recomp Update the analog camera. + if (recomp_analog_cam_enabled()) { + update_analog_cam(camera); + + if (analog_cam_active) { + sp80.pitch = analog_camera_pos.pitch; + // sp80.r = analog_camera_pos.r; + sp80.yaw = analog_camera_pos.yaw; + } + } + + *eyeNext = OLib_AddVecGeoToVec3f(at, &sp80); + + if (camera->status == CAM_STATUS_ACTIVE) { + *eye = *eyeNext; + func_800CBFA4(camera, at, eye, 0); + } else { + *eye = *eyeNext; + } + + camera->fov = Camera_ScaledStepToCeilF(roData->fovTarget, camera->fov, camera->fovUpdateRate, 0.1f); + + if (roData->interfaceFlags & NORMAL3_FLAG_5) { + camera->roll = Camera_ScaledStepToCeilS(0, camera->roll, 0.05f, 5); + } else { + camera->roll = Camera_ScaledStepToCeilS(0, camera->roll, 0.1f, 5); + } + + camera->atLerpStepScale = Camera_ClampLerpScale(camera, roData->maxAtLERPScale); + rwData->flag &= ~NORMAL3_RW_FLAG; + + return 1; +} + +/** + * Used for water-based camera settings + * e.g. Gyorg, Pinnacle Rock, whirlpool, water + */ +// @recomp Patched for analog cam. +s32 Camera_Jump3(Camera* camera) { + Vec3f* sp48 = &camera->eye; + Vec3f* sp44 = &camera->at; + Vec3f* sp40 = &camera->eyeNext; + f32 spD0; + f32 spCC; + PosRot* focalActorPosRot = &camera->focalActorPosRot; + f32 phi_f0; + f32 spC0; + Vec3f spB4; + VecGeo spAC; + CameraModeValue* values; + f32 phi_f14; + VecGeo sp9C; + VecGeo sp94; + f32 phi_f2_2; + f32 temp_f0; + f32 temp1; + f32 pad; + Jump3ReadOnlyData* roData = &camera->paramData.jump3.roData; + Jump3ReadWriteData* rwData = &camera->paramData.jump3.rwData; + f32 focalActorHeight; + PosRot focalActorFocus; + f32 sp60; + f32 sp5C; + s32 sp58; + + focalActorHeight = Camera_GetFocalActorHeight(camera); + focalActorFocus = Actor_GetFocus(camera->focalActor); + sp60 = camera->waterYPos - sp48->y; + + sp58 = false; + + if (RELOAD_PARAMS(camera)) { + rwData->unk_0A = camera->mode; + rwData->timer2 = 0; + } + + if (camera->mode == CAM_MODE_NORMAL) { + if ((camera->focalActor->bgCheckFlags & 0x10) || (rwData->timer2 != 0)) { + if (rwData->unk_0A != 0xF) { + rwData->unk_0A = 0xF; + sp58 = true; + rwData->timer2 = 10; + } + } else if (sp60 < 50.0f) { + if (rwData->unk_0A != 0) { + rwData->unk_0A = 0; + sp58 = true; + } + } else if (Camera_fabsf(camera->focalActorPosRot.pos.y - camera->focalActorFloorHeight) < 11.0f) { + if (rwData->unk_0A != 5) { + rwData->unk_0A = 5; + sp58 = true; + } + } else if ((sp60 > 250.0f) && (rwData->unk_0A != 0x1A)) { + rwData->unk_0A = 0x1A; + sp58 = true; + } + } + + if (rwData->timer2 != 0) { + rwData->timer2--; + } + + sp9C = OLib_Vec3fDiffToVecGeo(sp44, sp48); + sp94 = OLib_Vec3fDiffToVecGeo(sp44, sp40); + + if (!RELOAD_PARAMS(camera) && !sp58) { + } else { + values = sCameraSettings[camera->setting].cameraModes[rwData->unk_0A].values; + + sp5C = 0.8f - (-0.2f * (68.0f / focalActorHeight)); + spD0 = focalActorHeight * 0.01f * sp5C; + + roData->unk_00 = GET_NEXT_RO_DATA(values) * spD0; + roData->unk_04 = GET_NEXT_RO_DATA(values) * spD0; + roData->unk_08 = GET_NEXT_RO_DATA(values) * spD0; + roData->unk_20 = CAM_DEG_TO_BINANG(GET_NEXT_RO_DATA(values)); + roData->unk_0C = GET_NEXT_RO_DATA(values); + roData->unk_10 = GET_NEXT_RO_DATA(values); + roData->unk_14 = GET_NEXT_SCALED_RO_DATA(values); + roData->unk_18 = GET_NEXT_RO_DATA(values); + roData->unk_1C = GET_NEXT_SCALED_RO_DATA(values); + roData->interfaceFlags = GET_NEXT_RO_DATA(values); + } + + sCameraInterfaceFlags = roData->interfaceFlags; + + switch (camera->animState) { + case 0: + rwData->unk_10 = 0x1000; + // fallthrough + case 10: + case 20: + rwData->unk_00 = camera->focalActorFloorHeight; + D_801EDC30[camera->camId].yaw = D_801EDC30[camera->camId].pitch = D_801EDC30[camera->camId].unk_64 = 0; + rwData->timer1 = 10; + D_801EDC30[camera->camId].swingUpdateRate = roData->unk_0C; + camera->animState++; + D_801EDC30[camera->camId].timer = 0; + break; + + default: + if (rwData->timer1 != 0) { + rwData->timer1--; + } + break; + } + + spC0 = focalActorFocus.pos.y - focalActorPosRot->pos.y; + spB4 = *sp48; + + spD0 = camera->speedRatio * 0.5f; + spCC = camera->speedRatio * 0.2f; + + temp_f0 = (D_801EDC30[camera->camId].unk_64 == 1) ? 0.5f : spD0; + + if (D_801EDC30[camera->camId].timer != 0) { + camera->yawUpdateRateInv = + Camera_ScaledStepToCeilF((D_801EDC30[camera->camId].swingUpdateRate + D_801EDC30[camera->camId].timer * 2), + camera->yawUpdateRateInv, spD0, 0.1f); + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF((40.0f + D_801EDC30[camera->camId].timer * 2), + camera->pitchUpdateRateInv, spCC, 0.1f); + D_801EDC30[camera->camId].timer--; + } else { + camera->yawUpdateRateInv = Camera_ScaledStepToCeilF(D_801EDC30[camera->camId].swingUpdateRate, + camera->yawUpdateRateInv, temp_f0, 0.1f); + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF(40.0f, camera->pitchUpdateRateInv, spCC, 0.1f); + } + + if (roData->interfaceFlags & JUMP3_FLAG_7) { + camera->yOffsetUpdateRate = Camera_ScaledStepToCeilF(0.01f, camera->yOffsetUpdateRate, spD0, 0.0001f); + sp5C = sqrtf((camera->unk_0F0.x * camera->unk_0F0.x) + (camera->unk_0F0.z * camera->unk_0F0.z)) / + Camera_GetRunSpeedLimit(camera); + camera->speedRatio = OLib_ClampMaxDist(sp5C / Camera_GetRunSpeedLimit(camera), 1.8f); + spCC = camera->speedRatio * 0.2f; + } else { + camera->yOffsetUpdateRate = Camera_ScaledStepToCeilF(0.05f, camera->yOffsetUpdateRate, spD0, 0.0001f); + } + + camera->xzOffsetUpdateRate = Camera_ScaledStepToCeilF(0.05f, camera->xzOffsetUpdateRate, spCC, 0.0001f); + camera->fovUpdateRate = + Camera_ScaledStepToCeilF(0.050f, camera->fovUpdateRate, camera->speedRatio * 0.05f, 0.0001f); + + if (sp60 < 50.0f) { + sp5C = camera->waterYPos - spC0; + + Camera_CalcAtForScreen(camera, &sp94, roData->unk_00, &sp5C, + ((sp60 < 0.0f) ? 1.0f : 1.0f - (sp60 / 50.0f)) * 50.0f); + } else { + Camera_CalcAtDefault(camera, &sp94, roData->unk_00, roData->interfaceFlags); + } + + spAC = OLib_Vec3fDiffToVecGeo(sp44, sp40); + spAC.r = Camera_ClampDist1(camera, spAC.r, roData->unk_04, roData->unk_08, rwData->timer1); + camera->dist = spAC.r; + + if (!(Camera_fabsf(focalActorPosRot->pos.y - camera->focalActorFloorHeight) < 10.0f) && + !(Camera_fabsf(focalActorFocus.pos.y - camera->waterYPos) < 50.f)) { + camera->pitchUpdateRateInv = 100.0f; + } + + if (roData->interfaceFlags & JUMP3_FLAG_5) { + spD0 = CLAMP_MAX(camera->speedRatio * 1.3f, 0.6f); + + //! FAKE: spCC = + spAC.pitch = Camera_ScaledStepToCeilS( + (spAC.pitch * spD0) + (roData->unk_20 * (1.0f - spD0)), sp94.pitch, + 1.0f / (spCC = ((camera->pitchUpdateRateInv + 1.0f) - (camera->pitchUpdateRateInv * spD0))), 5); + } else if (D_801EDC30[camera->camId].unk_64 == 1) { + spAC.yaw = + Camera_ScaledStepToCeilS(D_801EDC30[camera->camId].yaw, sp94.yaw, 1.0f / camera->yawUpdateRateInv, 5); + + // Bug? Should be pitchUpdateRateInv + spAC.pitch = + Camera_ScaledStepToCeilS(D_801EDC30[camera->camId].pitch, sp94.pitch, 1.0f / camera->yawUpdateRateInv, 5); + } else if (roData->interfaceFlags & (JUMP3_FLAG_7 | JUMP3_FLAG_3)) { + spAC.yaw = Camera_CalcDefaultYaw(camera, sp94.yaw, focalActorPosRot->rot.y, roData->unk_14, 0.0f); + + spD0 = CLAMP_MAX(camera->speedRatio * 1.3f, 1.0f); + + //! FAKE: spCC = + spAC.pitch = Camera_ScaledStepToCeilS( + (spAC.pitch * spD0) + (roData->unk_20 * (1.0f - spD0)), sp94.pitch, + 1.0f / (spCC = (camera->pitchUpdateRateInv + 1.0f) - (camera->pitchUpdateRateInv * spD0)), 5); + } else { + spAC.yaw = Camera_CalcDefaultYaw(camera, sp94.yaw, focalActorPosRot->rot.y, roData->unk_14, 0.0f); + spAC.pitch = Camera_CalcDefaultPitch(camera, sp94.pitch, roData->unk_20, 0); + } + + if (spAC.pitch > DEG_TO_BINANG(79.655f)) { + spAC.pitch = DEG_TO_BINANG(79.655f); + } + + if (spAC.pitch < -DEG_TO_BINANG(29.995f)) { + spAC.pitch = -DEG_TO_BINANG(29.995f); + } + + // @recomp Update the analog camera. + if (recomp_analog_cam_enabled()) { + update_analog_cam(camera); + + if (analog_cam_active) { + spAC.pitch = analog_camera_pos.pitch; + // spAC.r = analog_camera_pos.r; + spAC.yaw = analog_camera_pos.yaw; + } + } + + *sp40 = OLib_AddVecGeoToVec3f(sp44, &spAC); + + if ((camera->status == CAM_STATUS_ACTIVE) && !(roData->interfaceFlags & JUMP3_FLAG_6)) { + if (func_800CBA7C(camera) == 0) { + Camera_CalcDefaultSwing(camera, &spAC, &sp9C, roData->unk_04, roData->unk_0C, &D_801EDC30[camera->camId], + &rwData->unk_10); + } + + if (roData->interfaceFlags & JUMP3_FLAG_2) { + camera->inputDir.x = -sp9C.pitch; + camera->inputDir.y = sp9C.yaw + 0x8000; + camera->inputDir.z = 0; + } else { + spAC = OLib_Vec3fDiffToVecGeo(sp48, sp44); + camera->inputDir.x = spAC.pitch; + camera->inputDir.y = spAC.yaw; + camera->inputDir.z = 0; + } + } else { + D_801EDC30[camera->camId].swingUpdateRate = roData->unk_0C; + D_801EDC30[camera->camId].unk_64 = 0; + sUpdateCameraDirection = false; + *sp48 = *sp40; + } + + camera->fov = Camera_ScaledStepToCeilF(roData->unk_18, camera->fov, camera->fovUpdateRate, 0.1f); + camera->roll = Camera_ScaledStepToCeilS(0, camera->roll, .5f, 5); + camera->atLerpStepScale = Camera_ClampLerpScale(camera, roData->unk_1C); + + return 1; +} + + +void analog_cam_pre_play_update(PlayState* play) { +} + +void analog_cam_post_play_update(PlayState* play) { + Camera *active_cam = play->cameraPtrs[play->activeCamId]; + // recomp_printf("prev_analog_cam_active: %d can_use_analog_cam: %d\n", prev_analog_cam_active, can_use_analog_cam); + // recomp_printf("setting: %d mode: %d func: %d\n", active_cam->setting, active_cam->mode, sCameraSettings[active_cam->setting].cameraModes[active_cam->mode].funcId); + // recomp_printf("active cam yaw %d\n", Camera_GetInputDirYaw(GET_ACTIVE_CAM(play))); + + // Update parameters for the analog cam if the game is unpaused. + if (play->pauseCtx.state == PAUSE_STATE_OFF && R_PAUSE_BG_PRERENDER_STATE == PAUSE_BG_PRERENDER_OFF) { + update_analog_camera_params(active_cam); + can_use_analog_cam = false; + } + + if (analog_cam_active) { + active_cam->inputDir.x = analog_camera_pos.pitch; + active_cam->inputDir.y = analog_camera_pos.yaw + DEG_TO_BINANG(180); + } +} diff --git a/patches/camera_patches.h b/patches/camera_patches.h new file mode 100644 index 0000000..80b7d76 --- /dev/null +++ b/patches/camera_patches.h @@ -0,0 +1,15 @@ +#ifndef __CAMERA_PATCHES_H__ +#define __CAMERA_PATCHES_H__ + +#include "z64camera.h" + +#define RELOAD_PARAMS(camera) ((camera->animState == 0) || (camera->animState == 10) || (camera->animState == 20)) +#define CAM_RODATA_SCALE(x) ((x)*100.0f) +#define CAM_RODATA_UNSCALE(x) ((x)*0.01f) + +// Load the next value from camera read-only data stored in CameraModeValue +#define GET_NEXT_RO_DATA(values) ((values++)->val) +// Load the next value and scale down from camera read-only data stored in CameraModeValue +#define GET_NEXT_SCALED_RO_DATA(values) CAM_RODATA_UNSCALE(GET_NEXT_RO_DATA(values)) + +#endif diff --git a/patches/camera_transform_tagging.c b/patches/camera_transform_tagging.c index 40c6f3b..b775158 100644 --- a/patches/camera_transform_tagging.c +++ b/patches/camera_transform_tagging.c @@ -1,4 +1,5 @@ #include "patches.h" +#include "camera_patches.h" #include "transform_ids.h" #include "z64cutscene.h" #include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h" @@ -286,11 +287,6 @@ void Camera_ScaledStepToCeilVec3f(Vec3f* target, Vec3f* cur, f32 xzStepScale, f3 void Camera_SetFocalActorAtOffset(Camera* camera, Vec3f* focalActorPos); void Camera_SetUpdateRatesSlow(Camera* camera); Vec3f Camera_Vec3sToVec3f(Vec3s* src); -#define RELOAD_PARAMS(camera) ((camera->animState == 0) || (camera->animState == 10) || (camera->animState == 20)) -#define CAM_RODATA_SCALE(x) ((x)*100.0f) -#define CAM_RODATA_UNSCALE(x) ((x)*0.01f) -#define GET_NEXT_RO_DATA(values) ((values++)->val) -#define GET_NEXT_SCALED_RO_DATA(values) CAM_RODATA_UNSCALE(GET_NEXT_RO_DATA(values)) /** * Used for many fixed-based camera settings i.e. camera is fixed in rotation, and often position (but not always) diff --git a/patches/input.c b/patches/input.c index 5000b46..ec28383 100644 --- a/patches/input.c +++ b/patches/input.c @@ -13,12 +13,44 @@ s32 func_8082EF20(Player* this); s32 func_80847190(PlayState* play, Player* this, s32 arg2) { s32 pad; s16 var_s0; + // @recomp Get the aiming camera inversion state. + s32 inverted_x, inverted_y; + recomp_get_inverted_axes(&inverted_x, &inverted_y); + // @recomp Get the analog camera input values if analog cam is enabled. + s32 analog_x = 0; + s32 analog_y = 0; + if (recomp_analog_cam_enabled()) { + float analog_x_float = 0.0f; + float analog_y_float = 0.0f; + recomp_get_camera_inputs(&analog_x_float, &analog_y_float); + // Scale by 127 to match what ultramodern does, then clamp to 60 to match the game's handling. + analog_x = (s32)(analog_x_float * 127.0f); + analog_x = CLAMP(analog_x, -60, 60); + analog_y = (s32)(analog_y_float * -127.0f); + analog_y = CLAMP(analog_y, -60, 60); + } + + // recomp_printf("stick_x: %d stick_y: %d analog_x: %d analog_y: %d\n", + // play->state.input[0].rel.stick_x, play->state.input[0].rel.stick_y, + // analog_x, analog_y); if (!func_800B7128(this) && !func_8082EF20(this) && !arg2) { - var_s0 = play->state.input[0].rel.stick_y * 0xF0; + // @recomp Add in the analog camera Y input. Clamp to prevent moving the camera twice as fast if both sticks are held. + var_s0 = CLAMP(play->state.input[0].rel.stick_y + analog_y, -61, 61) * 0xF0; + + // @recomp Invert the Y axis accordingly (default is inverted, so negate if not inverted). + if (!inverted_y) { + var_s0 = -var_s0; + } Math_SmoothStepToS(&this->actor.focus.rot.x, var_s0, 0xE, 0xFA0, 0x1E); - var_s0 = play->state.input[0].rel.stick_x * -0x10; + // @recomp Add in the analog camera X input. Clamp to prevent moving the camera twice as fast if both sticks are held. + var_s0 = CLAMP(play->state.input[0].rel.stick_x + analog_x, -61, 61) * -0x10; + + // @recomp Invert the X axis accordingly + if (inverted_x) { + var_s0 = -var_s0; + } var_s0 = CLAMP(var_s0, -0xBB8, 0xBB8); this->actor.focus.rot.y += var_s0; } @@ -62,8 +94,15 @@ s32 func_80847190(PlayState* play, Player* this, s32 arg2) { s16 temp3; - temp3 = ((play->state.input[0].rel.stick_y >= 0) ? 1 : -1) * - (s32)((1.0f - Math_CosS(play->state.input[0].rel.stick_y * 0xC8)) * 1500.0f); + // @recomp Invert the Y axis accordingly (default is inverted, so negate if not inverted). + // Also add in the analog camera Y input. Clamp to prevent moving the camera twice as fast if both sticks are held. + s32 stick_y = CLAMP(play->state.input[0].rel.stick_y + analog_y, -61, 61); + if (!inverted_y) { + stick_y = -stick_y; + } + + temp3 = ((stick_y >= 0) ? 1 : -1) * + (s32)((1.0f - Math_CosS(stick_y * 0xC8)) * 1500.0f); this->actor.focus.rot.x += temp3 + (s32)(target_aim_x - applied_aim_x); applied_aim_x = target_aim_x; @@ -75,8 +114,15 @@ s32 func_80847190(PlayState* play, Player* this, s32 arg2) { } var_s0 = this->actor.focus.rot.y - this->actor.shape.rot.y; - temp3 = ((play->state.input[0].rel.stick_x >= 0) ? 1 : -1) * - (s32)((1.0f - Math_CosS(play->state.input[0].rel.stick_x * 0xC8)) * -1500.0f); + + // @recomp Invert the X axis accordingly. Also add in the analog camera Y input. + // Clamp to prevent moving the camera twice as fast if both sticks are held. + s32 stick_x = CLAMP(play->state.input[0].rel.stick_x + analog_x, -61, 61); + if (inverted_x) { + stick_x = -stick_x; + } + temp3 = ((stick_x >= 0) ? 1 : -1) * + (s32)((1.0f - Math_CosS(stick_x * 0xC8)) * -1500.0f); var_s0 += temp3 + (s32)(target_aim_y - applied_aim_y); applied_aim_y = target_aim_y; @@ -88,6 +134,133 @@ s32 func_80847190(PlayState* play, Player* this, s32 arg2) { return func_80832754(this, (play->unk_1887C != 0) || func_800B7128(this) || func_8082EF20(this)); } +extern Input* sPlayerControlInput; + +/** + * Update for using telescopes. SCENE_AYASHIISHOP acts quite differently: it has a different camera mode and cannot use + * zooming. + * + * - Stick inputs move the view; shape.rot.y is used as a base position which cannot be looked too far away from. (This + * is not necessarily the same as the original angle of the spawn.) + * - A can be used to zoom (except in SCENE_AYASHIISHOP) + * - B exits, using the RESPAWN_MODE_DOWN entrance + */ +// @recomp Patched for aiming inversion and supporting the right stick in dual analog. +void func_8083A98C(Actor* thisx, PlayState* play2) { + PlayState* play = play2; + Player* this = (Player*)thisx; + s32 camMode; + + if (play->csCtx.state != CS_STATE_IDLE) { + return; + } + + // @recomp Get the aiming camera inversion state. + s32 inverted_x, inverted_y; + recomp_get_inverted_axes(&inverted_x, &inverted_y); + // @recomp Get the analog camera input values if analog cam is enabled. + s32 analog_x = 0; + s32 analog_y = 0; + if (recomp_analog_cam_enabled()) { + float analog_x_float = 0.0f; + float analog_y_float = 0.0f; + recomp_get_camera_inputs(&analog_x_float, &analog_y_float); + // Scale by 127 to match what ultramodern does, then clamp to 60 to match the game's handling. + analog_x = (s32)(analog_x_float * 127.0f); + analog_x = CLAMP(analog_x, -60, 60); + analog_y = (s32)(analog_y_float * -127.0f); + analog_y = CLAMP(analog_y, -60, 60); + } + + if (DECR(this->av2.actionVar2) != 0) { + camMode = (play->sceneId != SCENE_AYASHIISHOP) ? CAM_MODE_FIRSTPERSON : CAM_MODE_DEKUHIDE; + + // Show controls overlay. SCENE_AYASHIISHOP does not have Zoom, so has a different one. + if (this->av2.actionVar2 == 1) { + Message_StartTextbox(play, (play->sceneId == SCENE_AYASHIISHOP) ? 0x2A00 : 0x5E6, NULL); + } + } else { + // @recomp Manual relocation, TODO remove when automated. + Input* player_control_input = play->state.input; + *(Input**)KaleidoManager_GetRamAddr(&sPlayerControlInput) = player_control_input; + if (play->view.fovy >= 25.0f) { + s16 prevFocusX = thisx->focus.rot.x; + s16 prevFocusY = thisx->focus.rot.y; + s16 inputY; + s16 inputX; + s16 newYaw; // from base position shape.rot.y + + // @recomp Add in the analog camera Y input. Clamp to prevent moving the camera twice as fast if both sticks are held. + // Pitch: + inputY = CLAMP(player_control_input->rel.stick_y + analog_y, -60, 60) * 4; + // @recomp Invert the Y axis accordingly (default is inverted, so negate if not inverted). + if (!inverted_y) { + inputY = -inputY; + } + // Add input, clamped to prevent turning too fast + thisx->focus.rot.x += CLAMP(inputY, -0x12C, 0x12C); + // Prevent looking too far up or down + thisx->focus.rot.x = CLAMP(thisx->focus.rot.x, -0x2EE0, 0x2EE0); + + // @recomp Add in the analog camera X input. Clamp to prevent moving the camera twice as fast if both sticks are held. + // Yaw: shape.rot.y is used as a fixed starting position + inputX = CLAMP(player_control_input->rel.stick_x + analog_x, -60, 60) * -4; + // @recomp Invert the X axis accordingly. + if (inverted_x) { + inputX = -inputX; + } + // Start from current position: no input -> no change + newYaw = thisx->focus.rot.y - thisx->shape.rot.y; + // Add input, clamped to prevent turning too fast + newYaw += CLAMP(inputX, -0x12C, 0x12C); + // Prevent looking too far left or right of base position + newYaw = CLAMP(newYaw, -0x3E80, 0x3E80); + thisx->focus.rot.y = thisx->shape.rot.y + newYaw; + + if (play->sceneId == SCENE_00KEIKOKU) { + f32 focusDeltaX = (s16)(thisx->focus.rot.x - prevFocusX); + f32 focusDeltaY = (s16)(thisx->focus.rot.y - prevFocusY); + + Audio_PlaySfx_AtPosWithFreq(&gSfxDefaultPos, NA_SE_PL_TELESCOPE_MOVEMENT - SFX_FLAG, + sqrtf(SQ(focusDeltaX) + SQ(focusDeltaY)) / 300.0f); + } + } + + if (play->sceneId == SCENE_AYASHIISHOP) { + camMode = CAM_MODE_DEKUHIDE; + } else if (CHECK_BTN_ALL(player_control_input->cur.button, BTN_A)) { // Zoom + camMode = CAM_MODE_TARGET; + } else { + camMode = CAM_MODE_NORMAL; + } + + // Exit + if (CHECK_BTN_ALL(player_control_input->press.button, BTN_B)) { + Message_CloseTextbox(play); + + if (play->sceneId == SCENE_00KEIKOKU) { + gSaveContext.respawn[RESPAWN_MODE_DOWN].entrance = ENTRANCE(ASTRAL_OBSERVATORY, 2); + } else { + u16 entrance; + + if (play->sceneId == SCENE_AYASHIISHOP) { + entrance = ENTRANCE(CURIOSITY_SHOP, 3); + } else { + entrance = ENTRANCE(PIRATES_FORTRESS_INTERIOR, 8); + } + gSaveContext.respawn[RESPAWN_MODE_DOWN].entrance = entrance; + } + + func_80169EFC(&play->state); + gSaveContext.respawnFlag = -2; + play->transitionType = TRANS_TYPE_CIRCLE; + } + } + + Camera_ChangeSetting(Play_GetCamera(play, CAM_ID_MAIN), CAM_SET_TELESCOPE); + Camera_ChangeMode(Play_GetCamera(play, CAM_ID_MAIN), camMode); +} + u32 sPlayerItemButtons[] = { BTN_B, BTN_CLEFT, @@ -95,18 +268,6 @@ u32 sPlayerItemButtons[] = { BTN_CRIGHT, }; -// u32 sPlayerItemButtonsDualAnalog[] = { -// BTN_B, -// BTN_DLEFT, -// BTN_DDOWN, -// BTN_DRIGHT -// }; - -// u32 prev_item_buttons = 0; -// u32 cur_item_buttons = 0; -// u32 pressed_item_buttons = 0; -// u32 released_item_buttons = 0; - // D-Pad items #define EXTRA_ITEM_SLOT_COUNT 4 @@ -129,31 +290,6 @@ typedef enum { EQUIP_SLOT_EX_DDOWN, } EquipSlotEx; -// static inline void dup_to_cup(u16* button) { -// if (*button & BTN_DUP) { -// *button |= BTN_CUP; -// } -// } - -void GameState_GetInput(GameState* gameState) { - PadMgr_GetInput(gameState->input, true); - - // if (recomp_camera_mode == RECOMP_CAMERA_DUALANALOG) { - // gameState->input[0].cur.button &= ~BTN_CUP; - // gameState->input[0].press.button &= ~BTN_CUP; - // gameState->input[0].rel.button &= ~BTN_CUP; - // dup_to_cup(&gameState->input[0].cur.button); - // dup_to_cup(&gameState->input[0].press.button); - // dup_to_cup(&gameState->input[0].rel.button); - // } - - // prev_item_buttons = cur_item_buttons; - // recomp_get_item_inputs(&cur_item_buttons); - // u32 button_diff = prev_item_buttons ^ cur_item_buttons; - // pressed_item_buttons = cur_item_buttons & button_diff; - // released_item_buttons = prev_item_buttons & button_diff; -} - struct ExButtonMapping { u32 button; EquipSlotEx slot; @@ -215,9 +351,6 @@ u8* get_button_item_equip_ptr(u32 form, u32 button) { } } - -extern Input* sPlayerControlInput; - // Return currently-pressed button, in order of priority D-Pad, B, CLEFT, CDOWN, CRIGHT. EquipSlot func_8082FDC4(void) { EquipSlot i; @@ -320,7 +453,8 @@ void Player_Action_86(Player *this, PlayState *play) { s32 sp48 = false; func_808323C0(this, play->playerCsIds[PLAYER_CS_ID_MASK_TRANSFORMATION]); - sPlayerControlInput = play->state.input; + // @recomp Manual relocation, TODO remove when automated. + *(Input**)KaleidoManager_GetRamAddr(&sPlayerControlInput) = play->state.input; Camera_ChangeMode(GET_ACTIVE_CAM(play), (this->transformation == PLAYER_FORM_HUMAN) ? CAM_MODE_NORMAL : CAM_MODE_JUMP); @@ -349,7 +483,7 @@ void Player_Action_86(Player *this, PlayState *play) { (sp48 = ((this->transformation != PLAYER_FORM_HUMAN) || CHECK_WEEKEVENTREG(D_8085D908[GET_PLAYER_FORM])) && // @recomp Patched to also check for d-pad buttons for skipping the transformation cutscene. - CHECK_BTN_ANY(sPlayerControlInput->press.button, + CHECK_BTN_ANY(play->state.input[0].press.button, BTN_CRIGHT | BTN_CLEFT | BTN_CDOWN | BTN_CUP | BTN_B | BTN_A | BTN_DRIGHT | BTN_DLEFT | BTN_DDOWN | BTN_DUP)))) { R_PLAY_FILL_SCREEN_ON = 45; R_PLAY_FILL_SCREEN_R = 220; diff --git a/patches/input.h b/patches/input.h index d1399b7..0f00e36 100644 --- a/patches/input.h +++ b/patches/input.h @@ -12,6 +12,11 @@ extern RecompCameraMode recomp_camera_mode; DECLARE_FUNC(void, recomp_get_gyro_deltas, float* x, float* y); DECLARE_FUNC(void, recomp_get_mouse_deltas, float* x, float* y); -DECLARE_FUNC(int, recomp_get_targeting_mode); +DECLARE_FUNC(s32, recomp_get_targeting_mode); +DECLARE_FUNC(void, recomp_get_inverted_axes, s32* x, s32* y); +DECLARE_FUNC(s32, recomp_analog_cam_enabled); +DECLARE_FUNC(void, recomp_get_analog_inverted_axes, s32* x, s32* y); +DECLARE_FUNC(void, recomp_get_camera_inputs, float* x, float* y); +DECLARE_FUNC(void, recomp_set_right_analog_suppressed, s32 suppressed); #endif diff --git a/patches/input_latency.c b/patches/input_latency.c index 9660e7d..2e04c52 100644 --- a/patches/input_latency.c +++ b/patches/input_latency.c @@ -8,6 +8,7 @@ #include "z64viscvg.h" #include "z64vismono.h" #include "z64viszbuf.h" +#include "input.h" void recomp_set_current_frame_poll_id(); void PadMgr_HandleRetrace(void); @@ -62,11 +63,16 @@ void PadMgr_HandleRetrace(void) { } } +extern u8 sOcarinaInstrumentId; + void poll_inputs(void) { OSMesgQueue* serialEventQueue = PadMgr_AcquireSerialEventQueue(); // Begin reading controller data osContStartReadData(serialEventQueue); + // Suppress the right analog stick if analog camera is active unless the ocarina is in use. + recomp_set_right_analog_suppressed(recomp_analog_cam_enabled() && sOcarinaInstrumentId == OCARINA_INSTRUMENT_OFF); + // Wait for controller data osRecvMesg(serialEventQueue, NULL, OS_MESG_BLOCK); osContGetReadData(sPadMgrInstance->pads); diff --git a/patches/play_patches.c b/patches/play_patches.c index 3cc5a83..bff7066 100644 --- a/patches/play_patches.c +++ b/patches/play_patches.c @@ -16,6 +16,7 @@ void Play_Main(GameState* thisx) { // @recomp debug_play_update(this); controls_play_update(this); + analog_cam_pre_play_update(this); matrix_play_update(this); // @recomp avoid unused variable warning @@ -33,6 +34,7 @@ void Play_Main(GameState* thisx) { camera_pre_play_update(this); Play_Update(this); camera_post_play_update(this); + analog_cam_post_play_update(this); autosave_post_play_update(this); this->state.gfxCtx = gfxCtx; } diff --git a/patches/play_patches.h b/patches/play_patches.h index 62c39fb..19e7db2 100644 --- a/patches/play_patches.h +++ b/patches/play_patches.h @@ -6,6 +6,8 @@ void debug_play_update(PlayState* play); void camera_pre_play_update(PlayState* play); void camera_post_play_update(PlayState* play); +void analog_cam_pre_play_update(PlayState* play); +void analog_cam_post_play_update(PlayState* play); void matrix_play_update(PlayState* play); void autosave_post_play_update(PlayState* play); diff --git a/patches/syms.ld b/patches/syms.ld index 81418e8..dced678 100644 --- a/patches/syms.ld +++ b/patches/syms.ld @@ -52,5 +52,10 @@ osGetTime_recomp = 0x8F000088; recomp_autosave_enabled = 0x8F00008C; recomp_load_overlays = 0x8F000090; osInvalICache_recomp = 0x8F000094; +recomp_analog_cam_enabled = 0x8F000098; +recomp_get_camera_inputs = 0x8F00009C; +recomp_set_right_analog_suppressed = 0x8F0000A0; +recomp_get_inverted_axes = 0x8F0000A4; recomp_high_precision_fb_enabled = 0x8F0000A8; recomp_get_resolution_scale = 0x8F0000AC; +recomp_get_analog_inverted_axes = 0x8F0000B0; diff --git a/src/game/config.cpp b/src/game/config.cpp index 3216f1f..9e31488 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -165,6 +165,9 @@ void save_general_config(const std::filesystem::path& path) { config_json["mouse_sensitivity"] = recomp::get_mouse_sensitivity(); config_json["joystick_deadzone"] = recomp::get_joystick_deadzone(); config_json["autosave_mode"] = recomp::get_autosave_mode(); + config_json["camera_invert_mode"] = recomp::get_camera_invert_mode(); + config_json["analog_cam_mode"] = recomp::get_analog_cam_mode(); + config_json["analog_camera_invert_mode"] = recomp::get_analog_camera_invert_mode(); config_json["debug_mode"] = recomp::get_debug_mode_enabled(); config_file << std::setw(4) << config_json; } @@ -177,6 +180,9 @@ void set_general_settings_from_json(const nlohmann::json& config_json) { recomp::set_mouse_sensitivity(from_or_default(config_json, "mouse_sensitivity", is_steam_deck ? 50 : 0)); recomp::set_joystick_deadzone(from_or_default(config_json, "joystick_deadzone", 5)); recomp::set_autosave_mode(from_or_default(config_json, "autosave_mode", recomp::AutosaveMode::On)); + recomp::set_camera_invert_mode(from_or_default(config_json, "camera_invert_mode", recomp::CameraInvertMode::InvertY)); + recomp::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", recomp::AnalogCamMode::Off)); + recomp::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", recomp::CameraInvertMode::InvertNone)); recomp::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false)); } diff --git a/src/game/controls.cpp b/src/game/controls.cpp index 726e674..35db400 100644 --- a/src/game/controls.cpp +++ b/src/game/controls.cpp @@ -95,33 +95,7 @@ void recomp::get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out) { float joystick_y = recomp::get_input_analog(controller_input_mappings[(size_t)GameInput::Y_AXIS_POS]) - recomp::get_input_analog(controller_input_mappings[(size_t)GameInput::Y_AXIS_NEG]); - if(fabsf(joystick_x) < joystick_deadzone) { - joystick_x = 0.0f; - } - else { - if(joystick_x > 0.0f) { - joystick_x -= joystick_deadzone; - } - else { - joystick_x += joystick_deadzone; - } - - joystick_x /= (1.0f - joystick_deadzone); - } - - if(fabsf(joystick_y) < joystick_deadzone) { - joystick_y = 0.0f; - } - else { - if(joystick_y > 0.0f) { - joystick_y -= joystick_deadzone; - } - else { - joystick_y += joystick_deadzone; - } - - joystick_y /= (1.0f - joystick_deadzone); - } + recomp::apply_joystick_deadzone(joystick_x, joystick_y, &joystick_x, &joystick_y); cur_x = recomp::get_input_analog(keyboard_input_mappings[(size_t)GameInput::X_AXIS_POS]) - recomp::get_input_analog(keyboard_input_mappings[(size_t)GameInput::X_AXIS_NEG]) + joystick_x; diff --git a/src/game/input.cpp b/src/game/input.cpp index 786536b..94c4d29 100644 --- a/src/game/input.cpp +++ b/src/game/input.cpp @@ -483,7 +483,9 @@ bool controller_button_state(int32_t input_id) { return false; } -float controller_axis_state(int32_t input_id) { +static std::atomic_bool right_analog_suppressed = false; + +float controller_axis_state(int32_t input_id, bool allow_suppression) { if (abs(input_id) - 1 < SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_MAX) { SDL_GameControllerAxis axis = (SDL_GameControllerAxis)(abs(input_id) - 1); bool negative_range = input_id < 0; @@ -496,6 +498,12 @@ float controller_axis_state(int32_t input_id) { if (negative_range) { cur_val = -cur_val; } + + // Check if this input is a right analog axis and suppress it accordingly. + if (allow_suppression && right_analog_suppressed.load() && + (axis == SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX || axis == SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY)) { + cur_val = 0; + } ret += std::clamp(cur_val, 0.0f, 1.0f); } } @@ -518,7 +526,7 @@ float recomp::get_input_analog(const recomp::InputField& field) { case InputType::ControllerDigital: return controller_button_state(field.input_id) ? 1.0f : 0.0f; case InputType::ControllerAnalog: - return controller_axis_state(field.input_id); + return controller_axis_state(field.input_id, true); case InputType::Mouse: // TODO mouse support return 0.0f; @@ -549,7 +557,7 @@ bool recomp::get_input_digital(const recomp::InputField& field) { return controller_button_state(field.input_id); case InputType::ControllerAnalog: // TODO adjustable threshold - return controller_axis_state(field.input_id) >= axis_threshold; + return controller_axis_state(field.input_id, true) >= axis_threshold; case InputType::Mouse: // TODO mouse support return false; @@ -580,6 +588,55 @@ void recomp::get_mouse_deltas(float* x, float* y) { *y = cur_mouse_delta[1] * sensitivity; } +void recomp::apply_joystick_deadzone(float x_in, float y_in, float* x_out, float* y_out) { + float joystick_deadzone = (float)recomp::get_joystick_deadzone() / 100.0f; + + if(fabsf(x_in) < joystick_deadzone) { + x_in = 0.0f; + } + else { + if(x_in > 0.0f) { + x_in -= joystick_deadzone; + } + else { + x_in += joystick_deadzone; + } + + x_in /= (1.0f - joystick_deadzone); + } + + if(fabsf(y_in) < joystick_deadzone) { + y_in = 0.0f; + } + else { + if(y_in > 0.0f) { + y_in -= joystick_deadzone; + } + else { + y_in += joystick_deadzone; + } + + y_in /= (1.0f - joystick_deadzone); + } + + *x_out = x_in; + *y_out = y_in; +} + +void recomp::get_right_analog(float* x, float* y) { + float x_val = + controller_axis_state((SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX + 1), false) - + controller_axis_state(-(SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX + 1), false); + float y_val = + controller_axis_state((SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY + 1), false) - + controller_axis_state(-(SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY + 1), false); + recomp::apply_joystick_deadzone(x_val, y_val, x, y); +} + +void recomp::set_right_analog_suppressed(bool suppressed) { + right_analog_suppressed.store(suppressed); +} + bool recomp::game_input_disabled() { // Disable input if any menu is open. return recomp::get_current_menu() != recomp::Menu::None; diff --git a/src/game/recomp_api.cpp b/src/game/recomp_api.cpp index 8bdfae8..d310ffe 100644 --- a/src/game/recomp_api.cpp +++ b/src/game/recomp_api.cpp @@ -110,3 +110,59 @@ extern "C" void recomp_high_precision_fb_enabled(uint8_t * rdram, recomp_context extern "C" void recomp_get_resolution_scale(uint8_t* rdram, recomp_context* ctx) { _return(ctx, ultramodern::get_resolution_scale()); } + +extern "C" void recomp_get_inverted_axes(uint8_t* rdram, recomp_context* ctx) { + s32* x_out = _arg<0, s32*>(rdram, ctx); + s32* y_out = _arg<1, s32*>(rdram, ctx); + + recomp::CameraInvertMode mode = recomp::get_camera_invert_mode(); + + *x_out = (mode == recomp::CameraInvertMode::InvertX || mode == recomp::CameraInvertMode::InvertBoth); + *y_out = (mode == recomp::CameraInvertMode::InvertY || mode == recomp::CameraInvertMode::InvertBoth); +} + +extern "C" void recomp_get_analog_inverted_axes(uint8_t* rdram, recomp_context* ctx) { + s32* x_out = _arg<0, s32*>(rdram, ctx); + s32* y_out = _arg<1, s32*>(rdram, ctx); + + recomp::CameraInvertMode mode = recomp::get_analog_camera_invert_mode(); + + *x_out = (mode == recomp::CameraInvertMode::InvertX || mode == recomp::CameraInvertMode::InvertBoth); + *y_out = (mode == recomp::CameraInvertMode::InvertY || mode == recomp::CameraInvertMode::InvertBoth); +} + +extern "C" void recomp_analog_cam_enabled(uint8_t* rdram, recomp_context* ctx) { + _return(ctx, recomp::get_analog_cam_mode() == recomp::AnalogCamMode::On); +} + +extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) { + float* x_out = _arg<0, float*>(rdram, ctx); + float* y_out = _arg<1, float*>(rdram, ctx); + + // TODO expose this in the menu + constexpr float radial_deadzone = 0.05f; + + float x, y; + + recomp::get_right_analog(&x, &y); + + float magnitude = sqrtf(x * x + y * y); + + if (magnitude < radial_deadzone) { + *x_out = 0.0f; + *y_out = 0.0f; + } + else { + float x_normalized = x / magnitude; + float y_normalized = y / magnitude; + + *x_out = x_normalized * ((magnitude - radial_deadzone) / (1 - radial_deadzone)); + *y_out = y_normalized * ((magnitude - radial_deadzone) / (1 - radial_deadzone)); + } +} + +extern "C" void recomp_set_right_analog_suppressed(uint8_t* rdram, recomp_context* ctx) { + s32 suppressed = _arg<0, s32>(rdram, ctx); + + recomp::set_right_analog_suppressed(suppressed); +} diff --git a/src/ui/ui_config.cpp b/src/ui/ui_config.cpp index d8d20a4..ec25ab2 100644 --- a/src/ui/ui_config.cpp +++ b/src/ui/ui_config.cpp @@ -278,6 +278,9 @@ struct ControlOptionsContext { recomp::TargetingMode targeting_mode; recomp::BackgroundInputMode background_input_mode; recomp::AutosaveMode autosave_mode; + recomp::CameraInvertMode camera_invert_mode; + recomp::AnalogCamMode analog_cam_mode; + recomp::CameraInvertMode analog_camera_invert_mode; }; ControlOptionsContext control_options_context; @@ -365,6 +368,39 @@ void recomp::set_autosave_mode(recomp::AutosaveMode mode) { } } +recomp::CameraInvertMode recomp::get_camera_invert_mode() { + return control_options_context.camera_invert_mode; +} + +void recomp::set_camera_invert_mode(recomp::CameraInvertMode mode) { + control_options_context.camera_invert_mode = mode; + if (general_model_handle) { + general_model_handle.DirtyVariable("camera_invert_mode"); + } +} + +recomp::AnalogCamMode recomp::get_analog_cam_mode() { + return control_options_context.analog_cam_mode; +} + +void recomp::set_analog_cam_mode(recomp::AnalogCamMode mode) { + control_options_context.analog_cam_mode = mode; + if (general_model_handle) { + general_model_handle.DirtyVariable("analog_cam_mode"); + } +} + +recomp::CameraInvertMode recomp::get_analog_camera_invert_mode() { + return control_options_context.analog_camera_invert_mode; +} + +void recomp::set_analog_camera_invert_mode(recomp::CameraInvertMode mode) { + control_options_context.analog_camera_invert_mode = mode; + if (general_model_handle) { + general_model_handle.DirtyVariable("analog_camera_invert_mode"); + } +} + struct SoundOptionsContext { std::atomic main_volume; // Option to control the volume of all sound std::atomic bgm_volume; @@ -864,6 +900,9 @@ public: bind_option(constructor, "targeting_mode", &control_options_context.targeting_mode); bind_option(constructor, "background_input_mode", &control_options_context.background_input_mode); bind_option(constructor, "autosave_mode", &control_options_context.autosave_mode); + bind_option(constructor, "camera_invert_mode", &control_options_context.camera_invert_mode); + bind_option(constructor, "analog_cam_mode", &control_options_context.analog_cam_mode); + bind_option(constructor, "analog_camera_invert_mode", &control_options_context.analog_camera_invert_mode); general_model_handle = constructor.GetModelHandle(); }