mirror of
https://github.com/Raicuparta/nomai-vr.git
synced 2025-12-11 20:15:08 +01:00
* Added Snap Turning - Disabled by default, can be enabled in settings - Amount per input can be defined in settings (current range is 15 - 90 degrees) - Default input action is the right thumb stick (same as smooth turn) * Moved snap turn from slider to selector for settings * Fixed snap turning with smooth HUD/Helmet - Previously, the smooth HUD/Helmet would uncomfortably move a long distance after you snap turn - Now, if smooth HUD and snap turning are both enabled, the helmet snap turns for a frame, then resumes it's previous behavior * Disable snap turning while in zero-g --------- Co-authored-by: Raicuparta <Raicuparta@users.noreply.github.com>
256 lines
11 KiB
C#
256 lines
11 KiB
C#
using System;
|
|
using NomaiVR.Hands;
|
|
using NomaiVR.Helpers;
|
|
using NomaiVR.ModConfig;
|
|
using UnityEngine;
|
|
using Valve.VR;
|
|
|
|
namespace NomaiVR.Player
|
|
{
|
|
internal class PlayerBodyPosition : NomaiVRModule<PlayerBodyPosition.Behaviour, PlayerBodyPosition.Behaviour.Patch>
|
|
{
|
|
protected override bool IsPersistent => false;
|
|
protected override OWScene[] Scenes => PlayableScenes;
|
|
|
|
public class Behaviour : MonoBehaviour
|
|
{
|
|
public static Action OnSnapTurn;
|
|
|
|
private Transform cameraParent;
|
|
private static Transform playArea;
|
|
private static OWCamera playerCamera;
|
|
private static Animator playerAnimator;
|
|
private static OWRigidbody playerBody;
|
|
private static PlayerCharacterController playerController;
|
|
private static Autopilot autopilot;
|
|
private readonly SteamVR_Action_Boolean recenterAction = SteamVR_Actions._default.Recenter;
|
|
private static readonly SteamVR_Action_Vector2 turnAction = SteamVR_Actions._default.Look;
|
|
private static readonly float snapTurnInputThreshold = 0.15f;
|
|
private static bool isSnapTurnInCooldown = false;
|
|
private float lastTurnTime;
|
|
|
|
internal void Start()
|
|
{
|
|
// This component is messing with our ability to read the VR camera's rotation.
|
|
// Seems to be responsible for controlling the camera rotation with the mouse / joystick.
|
|
var playerCameraController = Camera.main.GetComponent<PlayerCameraController>();
|
|
if (playerCameraController)
|
|
{
|
|
playerCameraController.enabled = false;
|
|
}
|
|
|
|
AdjustPlayerHeadPosition();
|
|
SetupCamera();
|
|
playerBody = Locator.GetPlayerBody();
|
|
playerAnimator = playerBody.GetComponentInChildren<PlayerAnimController>()._animator;
|
|
playerController = playerBody.GetComponent<PlayerCharacterController>();
|
|
autopilot = playerBody.GetComponent<Autopilot>();
|
|
|
|
CreateRecenterMenuEntry();
|
|
}
|
|
|
|
private static void AdjustPlayerHeadPosition()
|
|
{
|
|
var playerhead = PlayerHelper.PlayerHead;
|
|
playerhead.localPosition = new Vector3(playerhead.localPosition.x, playerhead.localPosition.y, 0);
|
|
}
|
|
|
|
private void SetupCamera()
|
|
{
|
|
// Make an empty parent object for moving the camera around.
|
|
playerCamera = Locator.GetPlayerCamera();
|
|
cameraParent = new GameObject("VrCameraParent").transform;
|
|
playArea = new GameObject("VrPlayArea").transform;
|
|
playArea.parent = Locator.GetPlayerTransform();
|
|
playArea.position = PlayerHelper.PlayerHead.position;
|
|
playArea.rotation = PlayerHelper.PlayerHead.rotation;
|
|
cameraParent.parent = playArea;
|
|
cameraParent.localRotation = Quaternion.identity;
|
|
playerCamera.transform.parent = cameraParent;
|
|
playerCamera.gameObject.AddComponent<VRCameraManipulator>();
|
|
|
|
MoveCameraToPlayerHead();
|
|
}
|
|
|
|
private void MoveCameraToPlayerHead()
|
|
{
|
|
var movement = PlayerHelper.PlayerHead.position - playerCamera.transform.position;
|
|
cameraParent.position += movement;
|
|
}
|
|
|
|
private void CreateRecenterMenuEntry()
|
|
{
|
|
FindObjectOfType<PauseMenuManager>().AddPauseMenuAction("RECENTER VR", 2, MoveCameraToPlayerHead);
|
|
}
|
|
|
|
private void UpdateRecenter()
|
|
{
|
|
if (recenterAction.stateDown)
|
|
{
|
|
MoveCameraToPlayerHead();
|
|
}
|
|
}
|
|
|
|
internal void Update()
|
|
{
|
|
var cameraToHead = Vector3.ProjectOnPlane(PlayerHelper.PlayerHead.position - playerCamera.transform.position, PlayerHelper.PlayerHead.up);
|
|
|
|
if ((cameraToHead.sqrMagnitude > 0.5f && ModSettings.PreventClipping) || cameraToHead.sqrMagnitude > 10f)
|
|
{
|
|
MoveCameraToPlayerHead();
|
|
}
|
|
|
|
UpdateRecenter();
|
|
|
|
if (ModSettings.SnapTurning)
|
|
{
|
|
// Check if time has passed and input stick was returned to neutral
|
|
if (isSnapTurnInCooldown && Time.time - lastTurnTime > 0.05f && Mathf.Abs(turnAction.axis.x) < snapTurnInputThreshold)
|
|
{
|
|
isSnapTurnInCooldown = false;
|
|
lastTurnTime = Time.time;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class Patch : NomaiVRPatch
|
|
{
|
|
public override void ApplyPatches()
|
|
{
|
|
Postfix<OWInput>(nameof(OWInput.GetAxisValue), nameof(PostGetAxisValue));
|
|
Postfix<PlayerCharacterController>(nameof(PlayerCharacterController.UpdateTurning), nameof(PostCharacterTurning));
|
|
Postfix<JetpackThrusterController>(nameof(JetpackThrusterController.FixedUpdate), nameof(PostThrusterUpdate));
|
|
Prefix<OWCamera>("set_" + nameof(OWCamera.fieldOfView), nameof(PatchOwCameraFOV));
|
|
Prefix<OWCamera>("get_" + nameof(OWCamera.fieldOfView), nameof(GetOwCameraFOVScaled));
|
|
}
|
|
|
|
private static void PostThrusterUpdate(Vector3 ____rotationalInput)
|
|
{
|
|
if (!PlayerState.InZeroG() || ____rotationalInput.sqrMagnitude != 0 || autopilot.IsMatchingVelocity())
|
|
{
|
|
return;
|
|
}
|
|
|
|
playerBody.SetAngularVelocity(Vector3.zero);
|
|
|
|
PatchTurning(rotation => playerBody.MoveToRotation(rotation));
|
|
}
|
|
|
|
private static void PostCharacterTurning()
|
|
{
|
|
PatchTurning(rotation => playerBody.transform.rotation = rotation);
|
|
}
|
|
|
|
private static void PatchTurning(Action<Quaternion> rotationSetter)
|
|
{
|
|
var runSpeedX = playerAnimator.GetFloat("RunSpeedX");
|
|
var runSpeedY = playerAnimator.GetFloat("RunSpeedY");
|
|
var isStoppedOnGround = playerController.IsGrounded() && (runSpeedX + runSpeedY == 0);
|
|
var isControllerOriented = !(isStoppedOnGround) && ModSettings.ControllerOrientedMovement;
|
|
|
|
if ((OWInput.GetInputMode() != InputMode.Character))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ModSettings.SnapTurning && !PlayerState.InZeroG())
|
|
{
|
|
float turnInput = turnAction.axis.x;
|
|
|
|
// If snap turning, only do the snap turn, skip reorienting the play area
|
|
if (!isSnapTurnInCooldown && Mathf.Abs(turnInput) > snapTurnInputThreshold)
|
|
{
|
|
isSnapTurnInCooldown = true;
|
|
float sign = Mathf.Sign(turnInput);
|
|
Quaternion snapRotation = Quaternion.AngleAxis(GetSnapTurnIncrement() * sign, playerBody.transform.up);
|
|
var fromToSnap = Quaternion.FromToRotation(playerBody.transform.forward, snapRotation * playerBody.transform.forward);
|
|
|
|
rotationSetter(fromToSnap * playerBody.transform.rotation);
|
|
OnSnapTurn?.Invoke();
|
|
return;
|
|
}
|
|
}
|
|
|
|
var rotationSource = isControllerOriented ? LaserPointer.Behaviour.MovementLaser : playerCamera.transform;
|
|
var magnitude = 0f;
|
|
if (!isControllerOriented)
|
|
{
|
|
var magnitudeUp = 1 - Vector3.ProjectOnPlane(rotationSource.transform.up, playerBody.transform.up).magnitude;
|
|
var magnitudeForward = 1 - Vector3.ProjectOnPlane(rotationSource.transform.up, playerBody.transform.right).magnitude;
|
|
magnitude = magnitudeUp + magnitudeForward;
|
|
|
|
if (magnitude < 0.3f)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
var fromTo = Quaternion.FromToRotation(playerBody.transform.forward, Vector3.ProjectOnPlane(rotationSource.transform.forward, playerBody.transform.up));
|
|
var targetRotation = fromTo * playerBody.transform.rotation;
|
|
var inverseRotation = Quaternion.Inverse(fromTo) * playArea.rotation;
|
|
|
|
if (isControllerOriented)
|
|
{
|
|
playArea.rotation = inverseRotation;
|
|
rotationSetter(targetRotation);
|
|
}
|
|
else
|
|
{
|
|
var maxDegreesDelta = magnitude * 3f;
|
|
playArea.rotation = Quaternion.RotateTowards(playArea.rotation, inverseRotation, maxDegreesDelta);
|
|
rotationSetter(Quaternion.RotateTowards(playerBody.transform.rotation, targetRotation, maxDegreesDelta));
|
|
}
|
|
}
|
|
|
|
// Override vanilla input handling for disabling turning while snap turning is enabled
|
|
private static void PostGetAxisValue(ref Vector2 __result, IInputCommands command, InputMode mask)
|
|
{
|
|
if (!ModSettings.SnapTurning || (OWInput.GetInputMode() != InputMode.Character))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (command.CommandType == InputConsts.InputCommandType.LOOK)
|
|
{
|
|
__result = Vector2.zero;
|
|
}
|
|
}
|
|
|
|
private static bool PatchOwCameraFOV(OWCamera __instance)
|
|
{
|
|
//Prevents changing the fov of VR cameras
|
|
//This prevents log spams in projection pools
|
|
return !__instance.mainCamera.stereoEnabled;
|
|
}
|
|
|
|
private static bool GetOwCameraFOVScaled(OWCamera __instance, ref float __result)
|
|
{
|
|
//Returns FOV scaled by scale factor
|
|
if (__instance.mainCamera.stereoEnabled) __result = CameraHelper.GetScaledFieldOfView(__instance.mainCamera);
|
|
return !__instance.mainCamera.stereoEnabled;
|
|
}
|
|
|
|
private static float GetSnapTurnIncrement()
|
|
{
|
|
switch(ModSettings.SnapTurnIncrement)
|
|
{
|
|
case "15":
|
|
return 15f;
|
|
case "30":
|
|
return 30f;
|
|
case "45":
|
|
return 45f;
|
|
case "60":
|
|
return 60f;
|
|
case "90":
|
|
return 90f;
|
|
default:
|
|
return 45f;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|