diff --git a/README.md b/README.md index 17417baf..f18b1b55 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ For more documentation on using the mod, check out the [LethalCompanyVR Thunders # For developers -If you want to make your mod compatible with LCVR, make sure to check out the [API documentation](API.md). While at the time of writing it doesn't contain much, this might be expanded more in the future. +If you want to make your mod compatible with LCVR, make sure to check out the [API documentation](Docs/API). While at the time of writing it doesn't contain much, this might be expanded more in the future. Also make sure you know how to use BepInEx Dependencies and assembly referencing properly to make sure that your mod keeps working even when LCVR is not installed _(unless your mod **requires** LCVR to work)_. diff --git a/Resources/lethalcompanyvr b/Resources/lethalcompanyvr index 0655b54e..f118b911 100644 Binary files a/Resources/lethalcompanyvr and b/Resources/lethalcompanyvr differ diff --git a/Source/Assets/AssetManager.cs b/Source/Assets/AssetManager.cs index 8df6f3aa..c4e5d5d0 100644 --- a/Source/Assets/AssetManager.cs +++ b/Source/Assets/AssetManager.cs @@ -14,6 +14,7 @@ internal static class AssetManager public static GameObject animatedLogo; public static GameObject volumeManager; public static GameObject spectatorLight; + public static GameObject spectatorGhost; public static GameObject enemyPrefab; @@ -41,7 +42,7 @@ internal static class AssetManager public static bool LoadAssets() { assetBundle = AssetBundle.LoadFromMemory(Properties.Resources.lethalcompanyvr); - + if (assetBundle == null) { Logger.LogError("Failed to load asset bundle!"); @@ -56,6 +57,7 @@ public static bool LoadAssets() volumeManager = assetBundle.LoadAsset("Volume Manager"); enemyPrefab = assetBundle.LoadAsset("DressGirl"); spectatorLight = assetBundle.LoadAsset("Spectator Light"); + spectatorGhost = assetBundle.LoadAsset("SpectatorGhost"); defaultInputActions = assetBundle.LoadAsset("XR Input Actions"); nullInputActions = assetBundle.LoadAsset("NullPlayerActions"); diff --git a/Source/Networking/DNet.cs b/Source/Networking/DNet.cs index a678d94a..26e96013 100644 --- a/Source/Networking/DNet.cs +++ b/Source/Networking/DNet.cs @@ -39,6 +39,8 @@ public static class DNet private static readonly Dictionary clientByName = []; private static readonly List subscribers = []; + public static VRNetPlayer[] Players => players.Values.ToArray(); + public static IEnumerator Initialize() { dissonance = GameObject.Find("DissonanceSetup").GetComponent(); @@ -83,6 +85,11 @@ public static void BroadcastRig(Rig rig) BroadcastPacket(MessageType.RigData, rig.Serialize()); } + public static void BroadcastSpectatorRig(SpectatorRig rig) + { + BroadcastPacket(MessageType.SpectatorRigData, rig.Serialize()); + } + public static void InteractWithLever(bool started) { BroadcastPacket(MessageType.Lever, [started ? (byte)1 : (byte)0]); @@ -220,6 +227,10 @@ public static void OnPacketReceived(MessageType messageType, ushort sender, byte case MessageType.RigData: HandleRigUpdate(sender, data); break; + + case MessageType.SpectatorRigData: + HandleSpectatorRigUpdate(sender, data); + break; case MessageType.Lever: HandleInteractWithLever(sender, BitConverter.ToBoolean(data)); @@ -310,6 +321,15 @@ private static void HandleRigUpdate(ushort sender, byte[] packet) player.UpdateTargetTransforms(rig); } + private static void HandleSpectatorRigUpdate(ushort sender, byte[] packet) + { + if (!players.TryGetValue(sender, out var player)) + return; + + var rig = SpectatorRig.Deserialize(packet); + player.UpdateSpectatorTransforms(rig); + } + private static void HandleInteractWithLever(ushort sender, bool started) { if (!players.TryGetValue(sender, out var player)) @@ -449,6 +469,68 @@ public enum CrouchState : byte } } + public struct SpectatorRig + { + public Vector3 headPosition; + public Vector3 headRotation; + + public Vector3 leftHandPosition; + public Vector3 leftHandRotation; + + public Vector3 rightHandPosition; + public Vector3 rightHandRotation; + + public byte[] Serialize() + { + using var mem = new MemoryStream(); + using var bw = new BinaryWriter(mem); + + bw.Write(headPosition.x); + bw.Write(headPosition.y); + bw.Write(headPosition.z); + + bw.Write(headRotation.x); + bw.Write(headRotation.y); + bw.Write(headRotation.z); + + bw.Write(leftHandPosition.x); + bw.Write(leftHandPosition.y); + bw.Write(leftHandPosition.z); + + bw.Write(leftHandRotation.x); + bw.Write(leftHandRotation.y); + bw.Write(leftHandRotation.z); + + bw.Write(rightHandPosition.x); + bw.Write(rightHandPosition.y); + bw.Write(rightHandPosition.z); + + bw.Write(rightHandRotation.x); + bw.Write(rightHandRotation.y); + bw.Write(rightHandRotation.z); + + return mem.ToArray(); + } + + public static SpectatorRig Deserialize(byte[] raw) + { + using var mem = new MemoryStream(raw); + using var br = new BinaryReader(mem); + + var rig = new SpectatorRig() + { + headPosition = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), + headRotation = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), + leftHandPosition = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), + leftHandRotation = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), + rightHandPosition = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), + rightHandRotation = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), + }; + + return rig; + } + } + public struct Fingers { public const int BYTE_COUNT = 5; @@ -496,6 +578,7 @@ public enum MessageType : byte HandshakeRequest = 16, HandshakeResponse, RigData, + SpectatorRigData, Lever, CancelChargerAnim, Muffled diff --git a/Source/Networking/VRNetPlayer.cs b/Source/Networking/VRNetPlayer.cs index 79649a7f..38c0dcf6 100644 --- a/Source/Networking/VRNetPlayer.cs +++ b/Source/Networking/VRNetPlayer.cs @@ -1,6 +1,8 @@ using GameNetcodeStuff; +using LCVR.Assets; using LCVR.Input; using LCVR.Player; +using TMPro; using UnityEngine; using UnityEngine.Animations.Rigging; using CrouchState = LCVR.Networking.DNet.Rig.CrouchState; @@ -12,6 +14,11 @@ public class VRNetPlayer : MonoBehaviour private ChainIKConstraintData originalLeftArmConstraintData; private ChainIKConstraintData originalRightArmConstraintData; + private GameObject playerGhost; + private Transform usernameBillboard; + private CanvasGroup usernameAlpha; + private TextMeshProUGUI usernameText; + private Transform xrOrigin; private Transform leftController; private Transform rightController; @@ -21,8 +28,8 @@ public class VRNetPlayer : MonoBehaviour public Transform leftItemHolder; public Transform rightItemHolder; - public FingerCurler leftFingerCurler; - public FingerCurler rightFingerCurler; + private FingerCurler leftFingerCurler; + private FingerCurler rightFingerCurler; public Transform camera; @@ -89,6 +96,31 @@ private void Awake() rightFingerCurler = new FingerCurler(Bones.RightHand, false); BuildVRRig(); + + // Create spectating player + playerGhost = Instantiate(AssetManager.spectatorGhost, VRSession.Instance.transform); + playerGhost.name = $"Spectating Player: {PlayerController.playerUsername}"; + + usernameBillboard = playerGhost.GetComponentInChildren().transform; + usernameText = playerGhost.GetComponentInChildren(); + usernameAlpha = playerGhost.GetComponentInChildren(); + + playerGhost.GetComponentInChildren().player = this; + + // Disable rendering ghost until player dies + foreach (var renderer in playerGhost.GetComponentsInChildren()) + { + renderer.enabled = false; + } + + // Set username text + if (PlayerController.playerSteamId is 76561198438308784 or 76561199575858981) + { + usernameText.color = new Color(0, 1, 1, 1); + usernameText.fontStyle = FontStyles.Bold; + } + + usernameText.text = $"{PlayerController.playerUsername}"; } private void BuildVRRig() @@ -173,6 +205,8 @@ private void Update() // Arms need to be moved forward when crouched if (crouchState != CrouchState.None) xrOrigin.position += transform.forward * 0.55f; + + usernameAlpha.alpha -= Time.deltaTime; } private void LateUpdate() @@ -197,8 +231,50 @@ private void LateUpdate() { rightFingerCurler?.Update(); } + + // Rotate spectator username billboard + if (StartOfRound.Instance.localPlayerController.localVisorTargetPoint is not null) + { + usernameBillboard.LookAt(StartOfRound.Instance.localPlayerController.localVisorTargetPoint); + } + } + + /// + /// Show the spectator ghost + /// + public void ShowSpectatorGhost() + { + // Show player ghost when player dies + foreach (var renderer in playerGhost.GetComponentsInChildren()) + { + renderer.enabled = true; + } + } + + /// + /// Hide the spectator ghost and username billboard + /// + public void HideSpectatorGhost() + { + foreach (var renderer in playerGhost.GetComponentsInChildren()) + { + renderer.enabled = false; + } + + usernameAlpha.alpha = 0f; } + /// + /// Show the username of the player (if they are dead) + /// + public void ShowSpectatorNameBillboard() + { + if (!PlayerController.isPlayerDead) + return; + + usernameAlpha.alpha = 1f; + } + public void UpdateTargetTransforms(DNet.Rig rig) { leftController.localPosition = rig.leftHandPosition; @@ -219,10 +295,36 @@ public void UpdateTargetTransforms(DNet.Rig rig) } /// - /// Properly clean up the IK if a VR player leaves the game + /// Apply transforms for the spectator ghost + /// + public void UpdateSpectatorTransforms(DNet.SpectatorRig rig) + { + var head = playerGhost.transform.Find("Head"); + var leftHand = playerGhost.transform.Find("Hand.L"); + var rightHand = playerGhost.transform.Find("Hand.R"); + + head.position = rig.headPosition; + head.eulerAngles = rig.headRotation; + + leftHand.position = rig.leftHandPosition; + leftHand.eulerAngles = rig.leftHandRotation; + + rightHand.position = rig.rightHandPosition; + rightHand.eulerAngles = rig.rightHandRotation; + + if (StartOfRound.Instance.localPlayerController.localVisorTargetPoint is not null) + { + usernameBillboard.LookAt(StartOfRound.Instance.localPlayerController.localVisorTargetPoint); + } + } + + /// + /// Properly clean up the IK and spectator ghost if a VR player leaves the game /// void OnDestroy() { + Destroy(playerGhost); + Bones.ResetToPrefabPositions(); Destroy(Bones.LeftArmRig.GetComponent()); diff --git a/Source/Patches/PlayerControllerPatches.cs b/Source/Patches/PlayerControllerPatches.cs index 0fc2a432..598656db 100644 --- a/Source/Patches/PlayerControllerPatches.cs +++ b/Source/Patches/PlayerControllerPatches.cs @@ -230,13 +230,27 @@ private static void AfterPlayerLookInput(PlayerControllerB __instance) __instance.cameraUp = rot; - // Handle username billboard if (__instance.isGrabbingObjectAnimation) return; - + + // Handle username billboard var ray = new Ray(__instance.gameplayCamera.transform.position, __instance.gameplayCamera.transform.forward); if (!__instance.isFreeCamera && UnityEngine.Physics.SphereCast(ray, 0.5f, out var hit, 5, 8)) - hit.collider.gameObject.GetComponent()?.ShowNameBillboard(); + { + if (hit.collider.TryGetComponent(out var player)) + { + player.ShowNameBillboard(); + return; + } + + if (!__instance.isPlayerDead) + return; + + if (!hit.collider.TryGetComponent(out var spectator)) + return; + + spectator.player.ShowSpectatorNameBillboard(); + } } /// @@ -367,4 +381,53 @@ private static void SwitchedToItemSlot(PlayerControllerB __instance) component.enabled = true; } } + + /// + /// On death, show all other spectator ghosts + /// + [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.KillPlayer))] + [HarmonyPostfix] + private static void OnPlayerDeath(PlayerControllerB __instance) + { + if (__instance != StartOfRound.Instance.localPlayerController) + return; + + foreach (var player in DNet.Players.Where(player => player.PlayerController.isPlayerDead)) + { + player.ShowSpectatorGhost(); + } + } + + /// + /// Detect when another VR player has died + /// + [HarmonyPatch(typeof(PlayerControllerB), nameof(PlayerControllerB.KillPlayerClientRpc))] + [HarmonyPostfix] + private static void OnOtherPlayerDeath(PlayerControllerB __instance, int playerId) + { + if (!StartOfRound.Instance.localPlayerController.isPlayerDead) + return; + + var player = __instance.playersManager.allPlayerObjects[playerId].GetComponent(); + if (player == StartOfRound.Instance.localPlayerController) + return; + + if (!player.TryGetComponent(out var networkPlayer)) + return; + + networkPlayer.ShowSpectatorGhost(); + } + + /// + /// Notify VR players that they have been revived + /// + [HarmonyPatch(typeof(StartOfRound), nameof(StartOfRound.ReviveDeadPlayers))] + [HarmonyPostfix] + private static void OnPlayerRevived(StartOfRound __instance) + { + foreach (var player in DNet.Players) + { + player.HideSpectatorGhost(); + } + } } diff --git a/Source/Patches/TerminalPatches.cs b/Source/Patches/TerminalPatches.cs index f9f7c3ef..335de87d 100644 --- a/Source/Patches/TerminalPatches.cs +++ b/Source/Patches/TerminalPatches.cs @@ -55,8 +55,8 @@ private static void OnDisable(Terminal __instance) if (openMenuDelegate == null || (Terminal)openMenuDelegate.Target != __instance) return; - Actions.Instance["Movement/OpenMenu"].performed -= openMenuDelegate; Actions.Instance.OnReload -= OnReloadActions; + Actions.Instance["Movement/OpenMenu"].performed -= openMenuDelegate; } private static void OnReloadActions(InputActionAsset oldActions, InputActionAsset newActions) diff --git a/Source/Player/Spectating/Patches.cs b/Source/Player/Spectating/Patches.cs index f7847857..55e00f8b 100644 --- a/Source/Player/Spectating/Patches.cs +++ b/Source/Player/Spectating/Patches.cs @@ -24,6 +24,9 @@ internal static class SpectatorPlayerPatches private static bool allowDeathScreenToggle = false; + private static Material[] leftShipDoorMaterials; + private static Material[] rightShipDoorMaterials; + /// /// Store some fields that need to be restored after death /// @@ -114,6 +117,9 @@ private static void OnPlayerDeath(PlayerControllerB __instance) var shipDoorLeftRenderer = shipDoorLeft.GetComponent(); var shipDoorRightRenderer = shipDoorRight.GetComponent(); + leftShipDoorMaterials = shipDoorLeftRenderer.materials; + rightShipDoorMaterials = shipDoorRightRenderer.materials; + shipDoorLeftRenderer.materials = [AssetManager.transparentHangarShipDoor1, AssetManager.transparentHangarShipDoor2]; shipDoorRightRenderer.materials = @@ -219,11 +225,8 @@ private static void OnPlayerRevived() shipDoorWall.GetComponent().isTrigger = false; // Make the ship doors opaque again - var color1 = AssetManager.transparentHangarShipDoor1.color; - var color2 = AssetManager.transparentHangarShipDoor2.color; - - AssetManager.transparentHangarShipDoor1.color = new Color(color1.r, color1.g, color1.b, 1f); - AssetManager.transparentHangarShipDoor2.color = new Color(color2.r, color2.g, color2.b, 1f); + shipDoorLeft.GetComponent().materials = leftShipDoorMaterials; + shipDoorRight.GetComponent().materials = rightShipDoorMaterials; VRSession.Instance.HUD.ToggleSpectatorLight(false); } diff --git a/Source/Player/SpectatorGhost.cs b/Source/Player/SpectatorGhost.cs new file mode 100644 index 00000000..7504ce63 --- /dev/null +++ b/Source/Player/SpectatorGhost.cs @@ -0,0 +1,9 @@ +using LCVR.Networking; +using UnityEngine; + +namespace LCVR.Player; + +public class SpectatorGhost : MonoBehaviour +{ + public VRNetPlayer player; +} \ No newline at end of file diff --git a/Source/Player/VRController.cs b/Source/Player/VRController.cs index 434ed67e..af9f1059 100644 --- a/Source/Player/VRController.cs +++ b/Source/Player/VRController.cs @@ -1,11 +1,8 @@ using GameNetcodeStuff; using System; -using System.Collections; -using System.Reflection; using UnityEngine.InputSystem; using UnityEngine; using UnityEngine.XR; -using Unity.Netcode; using LCVR.Input; using LCVR.Assets; using LCVR.UI; @@ -16,17 +13,21 @@ namespace LCVR.Player; public class VRController : MonoBehaviour { private const int interactableObjectsMask = (1 << 6) | (1 << 8) | (1 << 9); - + + private static readonly int grabInvalidated = Animator.StringToHash("GrabInvalidated"); + private static readonly int grabValidated = Animator.StringToHash("GrabValidated"); + private static readonly int cancelHolding = Animator.StringToHash("cancelHolding"); + private static readonly int @throw = Animator.StringToHash("Throw"); + private static readonly HashSet disabledInteractTriggers = []; - private Transform interactOrigin; - private LineRenderer debugLineRenderer; - private bool hitInteractable = false; + private static InputAction GrabAction => Actions.Instance["Controls/Interact"]; + private static PlayerControllerB PlayerController => VRSession.Instance.LocalPlayer.PlayerController; - private InputAction GrabAction => Actions.Instance["Controls/Interact"]; - private PlayerControllerB PlayerController => VRSession.Instance.LocalPlayer.PlayerController; + private LineRenderer debugLineRenderer; + private bool hitInteractable; - private string CursorTip + private static string CursorTip { set { @@ -34,34 +35,22 @@ private string CursorTip } } - private GrabbableObject CurrentlyGrabbingObject - { - get - { - return GetFieldValue("currentlyGrabbingObject"); - } - set - { - SetFieldValue("currentlyGrabbingObject", value); - } - } - - public Transform InteractOrigin => interactOrigin; + public Transform InteractOrigin { get; private set; } private void Awake() { var interactOriginObject = new GameObject("Raycast Origin"); - interactOrigin = interactOriginObject.transform; - interactOrigin.SetParent(transform, false); - interactOrigin.localPosition = new Vector3(0.01f, 0, 0); - interactOrigin.rotation = Quaternion.Euler(80, 0, 0); + InteractOrigin = interactOriginObject.transform; + InteractOrigin.SetParent(transform, false); + InteractOrigin.localPosition = new Vector3(0.01f, 0, 0); + InteractOrigin.rotation = Quaternion.Euler(80, 0, 0); debugLineRenderer = gameObject.AddComponent(); debugLineRenderer.widthCurve.keys = [new Keyframe(0, 1)]; debugLineRenderer.widthMultiplier = 0.005f; debugLineRenderer.positionCount = 2; - debugLineRenderer.SetPositions(new Vector3[] { Vector3.zero, Vector3.zero }); + debugLineRenderer.SetPositions(new[] { Vector3.zero, Vector3.zero }); debugLineRenderer.numCornerVertices = 4; debugLineRenderer.numCapVertices = 4; debugLineRenderer.alignment = LineAlignment.View; @@ -71,19 +60,20 @@ private void Awake() debugLineRenderer.SetMaterials([AssetManager.defaultRayMat]); debugLineRenderer.enabled = Plugin.Config.EnableInteractRay.Value; - Actions.Instance.OnReload += OnActionsReload; - GrabAction.performed += OnInteractPerformed; + Actions.Instance.OnReload += OnReloadActions; + Actions.Instance["Controls/Interact"].performed += OnInteractPerformed; } - private void OnActionsReload(InputActionAsset oldActions, InputActionAsset newActions) + private void OnReloadActions(InputActionAsset oldActions, InputActionAsset newActions) { oldActions["Controls/Interact"].performed -= OnInteractPerformed; - oldActions["Controls/Interact"].performed += OnInteractPerformed; + newActions["Controls/Interact"].performed += OnInteractPerformed; } private void OnDestroy() { - GrabAction.performed -= OnInteractPerformed; + Actions.Instance.OnReload -= OnReloadActions; + Actions.Instance["Controls/Interact"].performed -= OnInteractPerformed; } public static void EnableInteractTrigger(string objectName) @@ -112,11 +102,12 @@ private void OnInteractPerformed(InputAction.CallbackContext context) if (!PlayerController.IsOwner || (PlayerController.IsServer && !PlayerController.isHostPlayerObject)) return; if (!context.performed) return; - if (GetFieldValue("timeSinceSwitchingSlots") < 0.2f) return; + if (PlayerController.timeSinceSwitchingSlots < 0.2f) return; - ShipBuildModeManager.Instance.CancelBuildMode(true); + ShipBuildModeManager.Instance.CancelBuildMode(); - if (PlayerController.isGrabbingObjectAnimation || PlayerController.isTypingChat || PlayerController.inTerminalMenu || GetFieldValue("throwingObject") || PlayerController.IsInspectingItem) + if (PlayerController.isGrabbingObjectAnimation || PlayerController.isTypingChat || + PlayerController.inTerminalMenu || PlayerController.throwingObject || PlayerController.IsInspectingItem) return; if (PlayerController.inAnimationWithEnemy != null) @@ -136,7 +127,7 @@ private void OnInteractPerformed(InputAction.CallbackContext context) if (PlayerController.hoveringOverTrigger == null || PlayerController.hoveringOverTrigger.holdInteraction || (PlayerController.isHoldingObject && !PlayerController.hoveringOverTrigger.oneHandedItemAllowed) || (PlayerController.twoHanded && (!PlayerController.hoveringOverTrigger.twoHandedItemAllowed || PlayerController.hoveringOverTrigger.specialCharacterAnimation))) return; - if (!InvokeFunction("InteractTriggerUseConditionsMet")) return; + if (!PlayerController.InteractTriggerUseConditionsMet()) return; PlayerController.hoveringOverTrigger.Interact(PlayerController.thisPlayerBody); } @@ -149,30 +140,30 @@ private void OnInteractPerformed(InputAction.CallbackContext context) private void ClickHoldInteraction() { - bool pressed = GrabAction.IsPressed() && !ShipBuildModeManager.Instance.InBuildMode; + var pressed = GrabAction.IsPressed() && !ShipBuildModeManager.Instance.InBuildMode; PlayerController.isHoldingInteract = pressed; if (!pressed) { - InvokeAction("StopHoldInteractionOnTrigger"); + PlayerController.StopHoldInteractionOnTrigger(); return; } if (PlayerController.hoveringOverTrigger == null || !PlayerController.hoveringOverTrigger.interactable) { - InvokeAction("StopHoldInteractionOnTrigger"); + PlayerController.StopHoldInteractionOnTrigger(); return; } if (PlayerController.hoveringOverTrigger == null || !PlayerController.hoveringOverTrigger.gameObject.activeInHierarchy || !PlayerController.hoveringOverTrigger.holdInteraction || PlayerController.hoveringOverTrigger.currentCooldownValue > 0f || (PlayerController.isHoldingObject && !PlayerController.hoveringOverTrigger.oneHandedItemAllowed) || (PlayerController.twoHanded && !PlayerController.hoveringOverTrigger.twoHandedItemAllowed)) { - InvokeAction("StopHoldInteractionOnTrigger"); + PlayerController.StopHoldInteractionOnTrigger(); return; } - if (PlayerController.isGrabbingObjectAnimation || PlayerController.isTypingChat || PlayerController.inSpecialInteractAnimation || GetFieldValue("throwingObject")) + if (PlayerController.isGrabbingObjectAnimation || PlayerController.isTypingChat || PlayerController.inSpecialInteractAnimation || PlayerController.throwingObject) { - InvokeAction("StopHoldInteractionOnTrigger"); + PlayerController.StopHoldInteractionOnTrigger(); return; } @@ -195,90 +186,50 @@ private void Update() private void LateUpdate() { - var origin = interactOrigin.position + interactOrigin.forward * 0.1f; - var end = interactOrigin.position + interactOrigin.forward * PlayerController.grabDistance; + var origin = InteractOrigin.position + InteractOrigin.forward * 0.1f; + var end = InteractOrigin.position + InteractOrigin.forward * PlayerController.grabDistance; + + debugLineRenderer.SetPositions(new[] { origin, end }); + + if (PlayerController.isGrabbingObjectAnimation) + return; - debugLineRenderer.SetPositions(new Vector3[] { origin, end }); + var ray = new Ray(InteractOrigin.position, InteractOrigin.forward); - if (!PlayerController.isGrabbingObjectAnimation) + if (ray.Raycast(out var hit, PlayerController.grabDistance, interactableObjectsMask) && hit.collider.gameObject.layer != 8) { - var ray = new Ray(interactOrigin.position, interactOrigin.forward); + // Place interaction hud on object + var position = hit.transform.position; + var offsetComponent = hit.transform.gameObject.GetComponent(); + if (offsetComponent != null) + { + position = hit.transform.TransformPoint(offsetComponent.offset); + } - if (ray.Raycast(out var hit, PlayerController.grabDistance, interactableObjectsMask) && hit.collider.gameObject.layer != 8) + if (hit.collider.gameObject.CompareTag("InteractTrigger")) { - // Place interaction hud on object - var position = hit.transform.position; - var offsetComponent = hit.transform.gameObject.GetComponent(); - if (offsetComponent != null) + var component = hit.transform.gameObject.GetComponent(); + if (component != PlayerController.previousHoveringOverTrigger && PlayerController.previousHoveringOverTrigger != null) { - position = hit.transform.TransformPoint(offsetComponent.offset); + PlayerController.previousHoveringOverTrigger.isBeingHeldByPlayer = false; } - if (hit.collider.gameObject.CompareTag("InteractTrigger")) - { - var component = hit.transform.gameObject.GetComponent(); - if (component != PlayerController.previousHoveringOverTrigger && PlayerController.previousHoveringOverTrigger != null) - { - PlayerController.previousHoveringOverTrigger.isBeingHeldByPlayer = false; - } - - // Ignore disabled triggers (like ship lever, charging station, etc) - if (disabledInteractTriggers.Contains(component.gameObject.name)) - return; - - if (VRSession.Instance.LocalPlayer.PlayerController.isPlayerDead) - { - if (component == null) - return; + // Ignore disabled triggers (like ship lever, charging station, etc) + if (disabledInteractTriggers.Contains(component.gameObject.name)) + return; - // Only ladders and entrance trigger are allowed - if (!component.isLadder && hit.transform.gameObject.GetComponent() == null) - return; - } - - if (component != null) - { - VRSession.Instance.HUD.UpdateInteractCanvasPosition(position); - - if (!hitInteractable) - VRSession.VibrateController(XRNode.RightHand, 0.1f, 0.2f); - - hitInteractable = true; - - PlayerController.hoveringOverTrigger = component; - if (!component.interactable) - { - PlayerController.cursorIcon.sprite = component.disabledHoverIcon; - PlayerController.cursorIcon.enabled = component.disabledHoverIcon != null; - CursorTip = component.disabledHoverTip; - } - else if (component.isPlayingSpecialAnimation) - { - PlayerController.cursorIcon.enabled = false; - CursorTip = ""; - } - else if (PlayerController.isHoldingInteract) - { - if (PlayerController.twoHanded) CursorTip = "[Hands full]"; - else if (!string.IsNullOrEmpty(component.holdTip)) CursorTip = component.holdTip; - } - else - { - PlayerController.cursorIcon.enabled = true; - PlayerController.cursorIcon.sprite = component.hoverIcon; - CursorTip = component.hoverTip; - } - } - } - else if (hit.collider.gameObject.CompareTag("PhysicsProp")) + if (VRSession.Instance.LocalPlayer.PlayerController.isPlayerDead) { - if (VRSession.Instance.LocalPlayer.PlayerController.isPlayerDead) + if (component == null) return; - // Ignore disabled triggers (like ship lever, charging station, etc) - if (disabledInteractTriggers.Contains(hit.collider.gameObject.name)) + // Only ladders and entrance trigger are allowed + if (!component.isLadder && hit.transform.gameObject.GetComponent() == null) return; + } + if (component != null) + { VRSession.Instance.HUD.UpdateInteractCanvasPosition(position); if (!hitInteractable) @@ -286,44 +237,84 @@ private void LateUpdate() hitInteractable = true; - if (FirstEmptyItemSlot() == -1) + PlayerController.hoveringOverTrigger = component; + if (!component.interactable) + { + PlayerController.cursorIcon.sprite = component.disabledHoverIcon; + PlayerController.cursorIcon.enabled = component.disabledHoverIcon != null; + CursorTip = component.disabledHoverTip; + } + else if (component.isPlayingSpecialAnimation) + { + PlayerController.cursorIcon.enabled = false; + CursorTip = ""; + } + else if (PlayerController.isHoldingInteract) { - CursorTip = "Inventory full!"; + if (PlayerController.twoHanded) CursorTip = "[Hands full]"; + else if (!string.IsNullOrEmpty(component.holdTip)) CursorTip = component.holdTip; } else { - var component = hit.collider.gameObject.GetComponent(); - - if (!GameNetworkManager.Instance.gameHasStarted && !component.itemProperties.canBeGrabbedBeforeGameStart) - { - CursorTip = "(Cannot hold until ship has landed)"; - return; - } - - if (component != null && !string.IsNullOrEmpty(component.customGrabTooltip)) - CursorTip = component.customGrabTooltip; - else - CursorTip = "Grab"; + PlayerController.cursorIcon.enabled = true; + PlayerController.cursorIcon.sprite = component.hoverIcon; + CursorTip = component.hoverTip; } } } - else + else if (hit.collider.gameObject.CompareTag("PhysicsProp")) { - hitInteractable = false; + if (VRSession.Instance.LocalPlayer.PlayerController.isPlayerDead) + return; - PlayerController.cursorIcon.enabled = false; - CursorTip = ""; - if (PlayerController.hoveringOverTrigger != null) - PlayerController.previousHoveringOverTrigger = PlayerController.hoveringOverTrigger; + // Ignore disabled triggers (like ship lever, charging station, etc) + if (disabledInteractTriggers.Contains(hit.collider.gameObject.name)) + return; - PlayerController.hoveringOverTrigger = null; + VRSession.Instance.HUD.UpdateInteractCanvasPosition(position); + + if (!hitInteractable) + VRSession.VibrateController(XRNode.RightHand, 0.1f, 0.2f); + + hitInteractable = true; + + if (PlayerController.FirstEmptyItemSlot() == -1) + { + CursorTip = "Inventory full!"; + } + else + { + var component = hit.collider.gameObject.GetComponent(); + + if (!GameNetworkManager.Instance.gameHasStarted && !component.itemProperties.canBeGrabbedBeforeGameStart) + { + CursorTip = "(Cannot hold until ship has landed)"; + return; + } + + if (component != null && !string.IsNullOrEmpty(component.customGrabTooltip)) + CursorTip = component.customGrabTooltip; + else + CursorTip = "Grab"; + } } } + else + { + hitInteractable = false; + + PlayerController.cursorIcon.enabled = false; + CursorTip = ""; + if (PlayerController.hoveringOverTrigger != null) + PlayerController.previousHoveringOverTrigger = PlayerController.hoveringOverTrigger; + + PlayerController.hoveringOverTrigger = null; + } } private void BeginGrabObject() { - var ray = new Ray(interactOrigin.position, interactOrigin.forward); + var ray = new Ray(InteractOrigin.position, InteractOrigin.forward); if (ray.Raycast(out var hit, PlayerController.grabDistance, interactableObjectsMask) && hit.collider.gameObject.layer != 8 && hit.collider.CompareTag("PhysicsProp")) { if (PlayerController.twoHanded || PlayerController.sinkingValue > 0.73f) return; @@ -338,73 +329,47 @@ private void BeginGrabObject() public void GrabItem(GrabbableObject item) { - CurrentlyGrabbingObject = item; + PlayerController.currentlyGrabbingObject = item; - if (!GameNetworkManager.Instance.gameHasStarted && !CurrentlyGrabbingObject.itemProperties.canBeGrabbedBeforeGameStart) + if (!GameNetworkManager.Instance.gameHasStarted && !PlayerController.currentlyGrabbingObject.itemProperties.canBeGrabbedBeforeGameStart) return; - SetFieldValue("grabInvalidated", false); + PlayerController.grabInvalidated = false; - if (CurrentlyGrabbingObject == null || PlayerController.inSpecialInteractAnimation || CurrentlyGrabbingObject.isHeld || CurrentlyGrabbingObject.isPocketed) + if (PlayerController.currentlyGrabbingObject == null || PlayerController.inSpecialInteractAnimation || PlayerController.currentlyGrabbingObject.isHeld || PlayerController.currentlyGrabbingObject.isPocketed) return; - var networkObject = CurrentlyGrabbingObject.NetworkObject; + var networkObject = PlayerController.currentlyGrabbingObject.NetworkObject; if (networkObject == null || !networkObject.IsSpawned) return; - CurrentlyGrabbingObject.InteractItem(); - if (CurrentlyGrabbingObject.grabbable && FirstEmptyItemSlot() != -1) + PlayerController.currentlyGrabbingObject.InteractItem(); + if (PlayerController.currentlyGrabbingObject.grabbable && PlayerController.FirstEmptyItemSlot() != -1) { - PlayerController.playerBodyAnimator.SetBool("GrabInvalidated", false); - PlayerController.playerBodyAnimator.SetBool("GrabValidated", false); - PlayerController.playerBodyAnimator.SetBool("cancelHolding", false); - PlayerController.playerBodyAnimator.ResetTrigger("Throw"); - InvokeAction("SetSpecialGrabAnimationBool", true, null); + PlayerController.playerBodyAnimator.SetBool(grabInvalidated, false); + PlayerController.playerBodyAnimator.SetBool(grabValidated, false); + PlayerController.playerBodyAnimator.SetBool(cancelHolding, false); + PlayerController.playerBodyAnimator.ResetTrigger(@throw); + PlayerController.SetSpecialGrabAnimationBool(true); PlayerController.isGrabbingObjectAnimation = true; PlayerController.cursorIcon.enabled = false; CursorTip = ""; - PlayerController.twoHanded = CurrentlyGrabbingObject.itemProperties.twoHanded; - PlayerController.carryWeight += Mathf.Clamp(CurrentlyGrabbingObject.itemProperties.weight - 1f, 0f, 10f); - if (CurrentlyGrabbingObject.itemProperties.grabAnimationTime > 0f) - PlayerController.grabObjectAnimationTime = CurrentlyGrabbingObject.itemProperties.grabAnimationTime; + PlayerController.twoHanded = PlayerController.currentlyGrabbingObject.itemProperties.twoHanded; + PlayerController.carryWeight += Mathf.Clamp(PlayerController.currentlyGrabbingObject.itemProperties.weight - 1f, 0f, 10f); + if (PlayerController.currentlyGrabbingObject.itemProperties.grabAnimationTime > 0f) + PlayerController.grabObjectAnimationTime = PlayerController.currentlyGrabbingObject.itemProperties.grabAnimationTime; else PlayerController.grabObjectAnimationTime = 0.4f; if (!PlayerController.isTestingPlayer) - InvokeAction("GrabObjectServerRpc", new NetworkObjectReference(networkObject)); + PlayerController.GrabObjectServerRpc(networkObject); - var grabObjectCoroutine = GetFieldValue("grabObjectCoroutine"); - if (grabObjectCoroutine != null) + if (PlayerController.grabObjectCoroutine != null) { - PlayerController.StopCoroutine(grabObjectCoroutine); + PlayerController.StopCoroutine(PlayerController.grabObjectCoroutine); } - SetFieldValue("grabObjectCoroutine", PlayerController.StartCoroutine(InvokeFunction("GrabObject"))); + PlayerController.grabObjectCoroutine = PlayerController.StartCoroutine(PlayerController.GrabObject()); } } - - private int FirstEmptyItemSlot() - { - return InvokeFunction("FirstEmptyItemSlot"); - } - - private void InvokeAction(string name, params object[] args) - { - typeof(PlayerControllerB).GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic).Invoke(PlayerController, args); - } - - private T InvokeFunction(string name, params object[] args) - { - return (T)typeof(PlayerControllerB).GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic).Invoke(PlayerController, args); - } - - private T GetFieldValue(string name) - { - return (T)typeof(PlayerControllerB).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(PlayerController); - } - - private void SetFieldValue(string name, T value) - { - typeof(PlayerControllerB).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic).SetValue(PlayerController, value); - } } diff --git a/Source/Player/VRPlayer.cs b/Source/Player/VRPlayer.cs index e77a1899..24c9e708 100644 --- a/Source/Player/VRPlayer.cs +++ b/Source/Player/VRPlayer.cs @@ -86,7 +86,7 @@ public class VRPlayer : MonoBehaviour private void Awake() { Logger.LogDebug("Going to intialize XR Rig"); - + playerController = GetComponent(); characterController = GetComponent(); bones = new Bones(transform); @@ -172,17 +172,10 @@ private void Awake() }; // Input actions - Actions.Instance.OnReload += (oldActions, newActions) => - { - oldActions["Controls/Reset Height"].performed -= ResetHeight_performed; - oldActions["Controls/Sprint"].performed -= Sprint_performed; - - newActions["Controls/Reset Height"].performed += ResetHeight_performed; - newActions["Controls/Sprint"].performed += Sprint_performed; - }; - + Actions.Instance.OnReload += OnReloadActions; Actions.Instance["Controls/Reset Height"].performed += ResetHeight_performed; Actions.Instance["Controls/Sprint"].performed += Sprint_performed; + ResetHeight(); // Set up item holders @@ -358,25 +351,28 @@ private void Sprint_performed(InputAction.CallbackContext obj) isSprinting = !isSprinting; } - private void OnDestroy() - { - Actions.Instance["Controls/Sprint"].performed -= Sprint_performed; - Actions.Instance["Controls/Reset Height"].performed -= ResetHeight_performed; - } - private void ResetHeight_performed(InputAction.CallbackContext obj) { if (obj.performed) ResetHeight(); } + private void OnReloadActions(InputActionAsset oldActions, InputActionAsset newActions) + { + oldActions["Controls/Reset Height"].performed -= ResetHeight_performed; + oldActions["Controls/Sprint"].performed -= Sprint_performed; + + newActions["Controls/Reset Height"].performed += ResetHeight_performed; + newActions["Controls/Sprint"].performed += Sprint_performed; + } + private void Update() { var movement = mainCamera.transform.localPosition - lastFrameHMDPosition; movement.y = 0; // Make sure player is facing towards the interacted object and that they're not sprinting - if (!wasInSpecialAnimation && playerController.inSpecialInteractAnimation && playerController.currentTriggerInAnimationWith?.playerPositionNode) + if (!wasInSpecialAnimation && playerController.inSpecialInteractAnimation && playerController.currentTriggerInAnimationWith is not null && playerController.currentTriggerInAnimationWith.playerPositionNode) { turningProvider.SetOffset(playerController.currentTriggerInAnimationWith.playerPositionNode.eulerAngles.y - mainCamera.transform.localEulerAngles.y); isSprinting = false; @@ -498,30 +494,43 @@ private void Update() } else PlayerControllerB_Sprint_Patch.sprint = !isRoomCrouching && Actions.Instance["Controls/Sprint"].IsPressed() ? 1 : 0; - - DNet.BroadcastRig(new DNet.Rig() - { - leftHandPosition = leftController.transform.localPosition, - leftHandEulers = leftController.transform.localEulerAngles, - leftHandFingers = LeftFingerCurler.GetCurls(), - - rightHandPosition = rightController.transform.localPosition, - rightHandEulers = rightController.transform.localEulerAngles, - rightHandFingers = RightFingerCurler.GetCurls(), - - cameraEulers = mainCamera.transform.eulerAngles, - cameraPosAccounted = cameraPosAccounted, - modelOffset = totalMovementSinceLastMove, - - crouchState = (playerController.isCrouching, isRoomCrouching) switch + + if (!playerController.isPlayerDead) + DNet.BroadcastRig(new DNet.Rig() + { + leftHandPosition = leftController.transform.localPosition, + leftHandEulers = leftController.transform.localEulerAngles, + leftHandFingers = LeftFingerCurler.GetCurls(), + + rightHandPosition = rightController.transform.localPosition, + rightHandEulers = rightController.transform.localEulerAngles, + rightHandFingers = RightFingerCurler.GetCurls(), + + cameraEulers = mainCamera.transform.eulerAngles, + cameraPosAccounted = cameraPosAccounted, + modelOffset = totalMovementSinceLastMove, + + crouchState = (playerController.isCrouching, isRoomCrouching) switch + { + (true, true) => CrouchState.Roomscale, + (true, false) => CrouchState.Button, + (false, _) => CrouchState.None + }, + rotationOffset = rotationOffset.eulerAngles.y, + cameraFloorOffset = cameraFloorOffset, + }); + else + DNet.BroadcastSpectatorRig(new DNet.SpectatorRig() { - (true, true) => CrouchState.Roomscale, - (true, false) => CrouchState.Button, - (false, _) => CrouchState.None - }, - rotationOffset = rotationOffset.eulerAngles.y, - cameraFloorOffset = cameraFloorOffset, - }); + headPosition = mainCamera.transform.position, + headRotation = mainCamera.transform.eulerAngles, + + leftHandPosition = leftController.transform.position, + leftHandRotation = leftController.transform.eulerAngles, + + rightHandPosition = rightController.transform.position, + rightHandRotation = rightController.transform.eulerAngles + }); } private void LateUpdate() @@ -545,7 +554,14 @@ private void LateUpdate() RightFingerCurler?.Update(); } } - + + private void OnDestroy() + { + Actions.Instance.OnReload -= OnReloadActions; + Actions.Instance["Controls/Sprint"].performed -= Sprint_performed; + Actions.Instance["Controls/Reset Height"].performed -= ResetHeight_performed; + } + public void EnableInteractorVisuals(bool enabled = true) { leftControllerRayInteractor.GetComponent().enabled = enabled; diff --git a/Source/Player/VRSession.cs b/Source/Player/VRSession.cs index 6848a76e..0cdd3330 100644 --- a/Source/Player/VRSession.cs +++ b/Source/Player/VRSession.cs @@ -200,7 +200,7 @@ private void InitializeVRSession() #region Add keyboard to Terminal var terminal = FindObjectOfType(); - var terminalKeyboardObject = Instantiate(AssetManager.keyboard, terminal.transform.parent); + var terminalKeyboardObject = Instantiate(AssetManager.keyboard, terminal.transform.parent.parent); terminalKeyboardObject.transform.localPosition = new Vector3(-0.584f, 0.333f, 0.791f); terminalKeyboardObject.transform.localEulerAngles = new Vector3(0, 90, 90); terminalKeyboardObject.transform.localScale = Vector3.one * 0.0009f; diff --git a/Source/UI/CanvasTransformFollow.cs b/Source/UI/CanvasTransformFollow.cs index 6064bdcd..7e182b24 100644 --- a/Source/UI/CanvasTransformFollow.cs +++ b/Source/UI/CanvasTransformFollow.cs @@ -19,7 +19,7 @@ internal class CanvasTransformFollow : MonoBehaviour private Transform enemyTransform; - void Awake() + private void Awake() { Actions.Instance.OnReload += OnReloadActions; Actions.Instance["Controls/Reset Height"].performed += OnResetHeight; @@ -29,18 +29,19 @@ void Awake() StartCoroutine(Init()); } - void OnReloadActions(InputActionAsset oldActions, InputActionAsset newActions) + private void OnReloadActions(InputActionAsset oldActions, InputActionAsset newActions) { oldActions["Controls/Reset Height"].performed -= OnResetHeight; newActions["Controls/Reset Height"].performed += OnResetHeight; } - void OnDestroy() + private void OnDestroy() { + Actions.Instance.OnReload -= OnReloadActions; Actions.Instance["Controls/Reset Height"].performed -= OnResetHeight; } - void Update() + private void Update() { transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, TURN_SMOOTHNESS); transform.position = Vector3.Lerp(transform.position, targetPosition, TURN_SMOOTHNESS);