/*using System; using System.Collections.Generic; using ABI.CCK.Components; using ABI_RC.Core.AudioEffects; using ABI_RC.Core.Base; using ABI_RC.Core.Networking.IO.Social; using ABI_RC.Core.Player; using ABI_RC.Core.Savior; using ABI_RC.Core.UI; using ABI_RC.Core.InteractionSystem.Base; using ABI_RC.Scripting.Attributes; using ABI_RC.Systems.InputManagement; using ABI_RC.Systems.RuntimeDebug; using ABI.Scripting.CVRSTL.Client; using HighlightPlus; using TMPro; using Unity.XR.OpenVR; using Unity.XR.PXR; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Serialization; using UnityEngine.UI; using UnityEngine.XR.Management; using Button = UnityEngine.UI.Button; using EventTrigger = UnityEngine.EventSystems.EventTrigger; using Slider = UnityEngine.UI.Slider; using Toggle = UnityEngine.UI.Toggle; namespace ABI_RC.Core.InteractionSystem { [AvailableToScripting( targetModule: TargetModule, inheritFrom: typeof(MonoBehaviour), optInMembers: true)] public class ControllerRay : MonoBehaviour { public const string TargetModule = LuaScriptFactory.ModuleNames.CVR; private const float MAX_RAYCAST_LENGTH = 100f; [AvailableToScripting(@readonly: true)] public Vector3 RayDirection = new Vector3(0,-0.2f,1f); [AvailableToScripting(@readonly: true)] public CVRHand hand; [AvailableToScripting(@readonly: true)] public bool isInteractionRay = true; [FormerlySerializedAs("triggerGazeEvents")] public bool triggerHoverEvents = true; public bool shouldPhysicalInteraction = false; public VelocityTracker pickupVelocityTracker; public LineRenderer lineRenderer; [FormerlySerializedAs("_vrActive")] [SerializeField] bool vrActive; [AvailableToScripting(@readonly: true)] [SerializeField] private Transform hitTransform; [SerializeField] float thickness = 0.002f; [AvailableToScripting(@readonly: true)] [SerializeField] internal Pickupable grabbedObject; public LayerMask uiMask; public LayerMask generalMask; [AvailableToScripting(@readonly: true)] public Transform controllerTransform; [AvailableToScripting(@readonly: true)] public bool enabled; [AvailableToScripting(@readonly: true)] public bool isDesktopRay; [AvailableToScripting(@readonly: true)] public bool isHeadRay; [AvailableToScripting(@readonly: true)] public ControllerRay otherRay; public bool uiActive; public Material highlightMaterial; private float _lastTrigger; private float _lastGrip; private bool _objectWasHit = false; private CohtmlControlledView _lastView; private Vector2 _lastUiCoords; private float _lastHitDistance; private GameObject lastTarget; private GameObject highlightGameObject; private GameObject lastTelepathicGrabTarget; private GameObject lastProximityGrabTarget; private Slider lastSlider; private RectTransform lastSliderRect; private EventTrigger lastEventTrigger; private ScrollRect lastScrollView; private RectTransform lastScrollViewRect; private Vector2 scrollStartPositionView; private Vector2 scrollStartPositionContent; private Button lastButton; private Dropdown lastDropdown; private Toggle lastToggle; private InputField lastInputField; private TMP_InputField lastTMPInputField; [SerializeField] private Interactable lastInteractable; [SerializeField] private Pickupable _telepathicPickupCandidate; private bool _telepathicPickupTargeted; private float _telepathicPickupTimer = 0f; private float _telepathicPickupResetTime = 0.5f; private bool _telepathicPickupLocked = false; private List _positionMemory = new List(); private List _timeMemory = new List(); private bool _enableHighlight; private bool _enableSmoothRay; public Transform rayDirectionTransform; public Transform attachmentPoint; public Transform pivotPoint; public float attachmentDistance = 0.2f; public float currentAttachmentDistance = 0.2f; public Transform backupRayFor; public GameObject backupCrossHair; private bool _enableTelepathicGrab = true; private float _telepathicGrabMaxDistance = 50f; private bool _gripToGrab; public Vector3 HitPoint => _hit.point; private RaycastHit _hit; private readonly RaycastHit[] _hits = new RaycastHit[10]; /// /// Comparer used to sort arrays of RaycastHits, it will result in an array where the first indexes are the closest ones. /// private readonly Comparer _hitsComparer = Comparer.Create((hit1, hit2) => hit1.distance.CompareTo(hit2.distance)); #region Proximity Grab Fields public const string ProximityGrabEnabled = "ExperimentalProximityGrabEnabled"; private bool _proximityGrabEnabled; public const string ProximityGrabVisualizers = "ExperimentalProximityGrabVisualizers"; public const string ProximityGrabRadiusScale = "ExperimentalProximityGrabRadiusScale"; public const float ProximityGrabRadiusScaleDefault = 0.1f; private bool _proximityGrabVisualizers; private float _proximityDetectionRadiusRelativeValue = ProximityGrabRadiusScaleDefault; private float ProximityDetectionRadius => _proximityDetectionRadiusRelativeValue * PlayerSetup.Instance.GetPlaySpaceScale(); private readonly Collider[] _proximityColliders = new Collider[15]; #endregion //Inputs private bool _interactDown = false; private Pickupable _proximityColliderClosestPickup; private bool _proximityCalculatedThisFrame; private bool _interactUp = false; private bool _interact = false; private bool _gripDown = false; private bool _gripUp = false; private bool _grip = false; private bool _isTryingToPickup = false; private bool _drop = false; private bool _hitUIInternal = false; private void Start() { vrActive = MetaPort.Instance.isUsingVr; #if !PLATFORM_ANDROID bool isOpenVR = XRGeneralSettings.Instance.Manager.activeLoader is OpenVRLoader; if (!isOpenVR) { transform.localPosition = new Vector3(0.001f, 0.021f, -0.009f); transform.localRotation = new Quaternion(0.50603801f, 0.0288440064f, 0.0147214793f, 0.861903071f); } #else bool isPxr = XRGeneralSettings.Instance.Manager.activeLoader is PXR_Loader; if (!isPxr) { transform.localPosition = new Vector3(0.001f, 0.021f, -0.009f); transform.localRotation = new Quaternion(0.50603801f, 0.0288440064f, 0.0147214793f, 0.861903071f); } else { transform.localPosition = new Vector3(0.001f, 0.021f, -0.009f); transform.localRotation = new Quaternion(0.30603801f, 0.0288440064f, 0.0147214793f, 0.861903071f); } #endif // Fallback if (controllerTransform == null) controllerTransform = transform; _enableHighlight = MetaPort.Instance.settings.GetSettingsBool("GeneralInteractableHighlight"); _enableTelepathicGrab = MetaPort.Instance.settings.GetSettingsBool("GeneralTelepathicGrip"); _gripToGrab = MetaPort.Instance.settings.GetSettingsBool("ControlUseGripToGrab"); _enableSmoothRay = MetaPort.Instance.settings.GetSettingsBool("ControlSmoothRaycast"); _proximityGrabEnabled = MetaPort.Instance.settings.GetSettingsBool(ProximityGrabEnabled); _proximityGrabVisualizers = MetaPort.Instance.settings.GetSettingsBool(ProximityGrabVisualizers) || MetaPort.Instance.showProximityGrabVisualizers; MetaPort.Instance.settings.settingBoolChanged.AddListener(SettingsBoolChanged); _proximityDetectionRadiusRelativeValue = MetaPort.Instance.settings.GetSettingsFloat(ProximityGrabRadiusScale); MetaPort.Instance.settings.settingFloatChanged.AddListener(SettingFloatChanged); // RayDirection object, we smooth to combat jitter hands for the ray, but not pickups rayDirectionTransform = new GameObject("RayDirection").transform; rayDirectionTransform.SetParent(transform); rayDirectionTransform.SetLocalPositionAndRotation(RayDirection * attachmentDistance, Quaternion.identity); rayDirectionTransform.localScale = Vector3.one; gameObject.BroadcastMessage("RayDirectionTransformUpdated",SendMessageOptions.DontRequireReceiver); // Attachment point, snaps to the object collider on grab, used for object push/pull attachmentPoint = new GameObject("AttachmentPoint").transform; attachmentPoint.SetParent(transform); attachmentPoint.SetLocalPositionAndRotation(RayDirection * attachmentDistance, Quaternion.identity); attachmentPoint.localScale = Vector3.one; // Pivot point, used purely for object rotation, makes the math easy pivotPoint = new GameObject("PivotPoint").transform; pivotPoint.SetParent(attachmentPoint); pivotPoint.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity); pivotPoint.localScale = Vector3.one; if (lineRenderer != null) { lineRenderer.sortingOrder = 10; } pickupVelocityTracker = pivotPoint.AddComponentIfMissing(); } private void OnDisable() { if (backupCrossHair) backupCrossHair.SetActive(true); } public void UpdateGrabDistance(float distance = 0.2f) { Vector3 lossyScale = attachmentPoint.lossyScale; Vector3 scale = new(1f/lossyScale.x, 1f/lossyScale.y, 1f/lossyScale.z); currentAttachmentDistance = distance; scale.Scale(RayDirection); attachmentPoint.localPosition = scale * distance; } public void SetPivotRotation(Quaternion rot) { pivotPoint.rotation = rot; } public void ResetPivotRotation() { pivotPoint.localRotation = Quaternion.identity; } private void SettingsBoolChanged(string name, bool value) { if (name == "GeneralInteractableHighlight") _enableHighlight = value; if (name == "GeneralTelepathicGrip") _enableTelepathicGrab = value; if (name == "ControlUseGripToGrab") _gripToGrab = value; if (name == "ControlSmoothRaycast") _enableSmoothRay = value; if (name == ProximityGrabEnabled) _proximityGrabEnabled = value; if (name == ProximityGrabVisualizers) _proximityGrabVisualizers = value; } private void SettingFloatChanged(string settingName, float value) { if (settingName == ProximityGrabRadiusScale) _proximityDetectionRadiusRelativeValue = value; } public bool IsInteracting() { bool shouldDisplay = _interact || _grip || _objectWasHit; return shouldDisplay; } public void ClearGrabbedObject() { grabbedObject = null; } private RaycastHit SphereCast(float offset, float radius, float maxDistance) { // Might be slower? //_proximityColliders[0] = new(); //Physics.SphereCastNonAlloc Physics.SphereCast (transform.TransformPoint(RayDirection * offset), radius, transform.TransformDirection(RayDirection), out RaycastHit output, maxDistance, (1 << CVRLayers.MirrorReflection), // generalMask |= (1 << CVRLayers.MirrorReflection), QueryTriggerInteraction.Collide); //return _proximityColliders[0]; return output; } private void UpdateBackupRay() { if (backupRayFor != null) { isInteractionRay = uiActive = backupRayFor.localPosition == Vector3.zero; backupCrossHair.SetActive(isInteractionRay); if (otherRay != null) otherRay.isInteractionRay = !isInteractionRay; } } private bool CanSelectPlayersAndProps() { if (ViewManager.Instance.IsAnyMenuOpen) return true; if (isDesktopRay && CVRInputManager.Instance.unlockMouse) return true; return false; } private void UpdateInteractionMask() { if (CanSelectPlayersAndProps()) { generalMask |= (1 << CVRLayers.PlayerNetwork); } else { generalMask &= ~(1 << CVRLayers.PlayerNetwork); } generalMask &= ~(1 << CVRLayers.Water); } private void ResetPointAtUI() { if (isInteractionRay && !isDesktopRay && !isHeadRay) { if (hand == CVRHand.Left) { CVRInputManager.Instance.leftControllerPointingMenu = false; CVRInputManager.Instance.leftControllerPointingCanvas = false; } else { CVRInputManager.Instance.rightControllerPointingMenu = false; CVRInputManager.Instance.rightControllerPointingCanvas = false; } } } private void UpdateInputs() { _interactDown = false; _interactUp = false; _interact = false; _gripDown = false; _gripUp = false; _grip = false; _isTryingToPickup = false; _drop = false; if (isInteractionRay) { _interactDown = hand == CVRHand.Left ? CVRInputManager.Instance.interactLeftDown : CVRInputManager.Instance.interactRightDown; _interactUp = hand == CVRHand.Left ? CVRInputManager.Instance.interactLeftUp : CVRInputManager.Instance.interactRightUp; _interact = (hand == CVRHand.Left ? CVRInputManager.Instance.interactLeftValue : CVRInputManager.Instance.interactRightValue) > 0.8f; _gripDown = hand == CVRHand.Left ? CVRInputManager.Instance.gripLeftDown : CVRInputManager.Instance.gripRightDown; _gripUp = hand == CVRHand.Left ? CVRInputManager.Instance.gripLeftUp : CVRInputManager.Instance.gripRightUp; _grip = (hand == CVRHand.Left ? CVRInputManager.Instance.gripLeftValue : CVRInputManager.Instance.gripRightValue) > 0.8f; if (!MetaPort.Instance.isUsingVr) { // Non-VR mode: Use grip for pickup and drop _isTryingToPickup = _gripDown; _drop = _gripUp || CVRInputManager.Instance.drop; } else if (_gripToGrab) { // VR mode with grip-to-grab enabled _isTryingToPickup = _gripDown; _drop = _gripUp; } else { // VR mode with interact button _isTryingToPickup = _interactDown; _drop = _gripDown; } } } private void CheckExitPropModes() { if (_gripDown && PlayerSetup.Instance.GetCurrentPropSelectionMode() != PlayerSetup.PropSelectionMode.None) { PlayerSetup.Instance.ClearPropToSpawn(); } } private void HandleTelepathicGrip() { if (!_telepathicPickupLocked) { if (_telepathicPickupCandidate != null && _telepathicPickupTargeted) { SetTelepathicGrabTargetHighlight(_telepathicPickupCandidate.gameObject); if (_interactDown) _telepathicPickupLocked = true; } else if (_telepathicPickupCandidate != null) { _telepathicPickupTimer += Time.deltaTime; if (_telepathicPickupTimer >= _telepathicPickupResetTime) { ClearTelepathicGrabTargetHighlight(); _telepathicPickupCandidate = null; _telepathicPickupTimer = 0f; } } else if (_telepathicPickupCandidate == null) { _telepathicPickupTimer = 0f; } _telepathicPickupTargeted = false; } else { if (_positionMemory.Count >= 5) { _positionMemory.RemoveAt(0); _timeMemory.RemoveAt(0); } _positionMemory.Add(transform.parent.localPosition); _timeMemory.Add(Time.deltaTime); if (_positionMemory.Count >= 5) { Vector3 velocity = _positionMemory[4] - _positionMemory[0]; float time = 0; foreach (float v in _timeMemory) { time += v; } if (MetaPort.Instance.isUsingVr) { if ((velocity * (1 / time)).magnitude >= 0.5f) { if (_telepathicPickupCandidate != null) { _telepathicPickupCandidate.FlingTowardsTarget(transform.position); ClearTelepathicGrabTargetHighlight(); } _telepathicPickupCandidate = null; _telepathicPickupTimer = 0f; _telepathicPickupLocked = false; _positionMemory.Clear(); _timeMemory.Clear(); } } else { if (CVRInputManager.Instance.objectPushPull <= -2) { if (_telepathicPickupCandidate != null) { _telepathicPickupCandidate.FlingTowardsTarget(transform.position); ClearTelepathicGrabTargetHighlight(); } _telepathicPickupCandidate = null; _telepathicPickupTimer = 0f; _telepathicPickupLocked = false; _positionMemory.Clear(); _timeMemory.Clear(); } } } if (_interactUp) { if (_telepathicPickupCandidate != null) ClearTelepathicGrabTargetHighlight(); _telepathicPickupCandidate = null; _telepathicPickupTimer = 0f; _telepathicPickupLocked = false; _positionMemory.Clear(); _timeMemory.Clear(); } } } private void HandleGrabbedObjects() { if (grabbedObject == null) return; // pickup rotation if (grabbedObject.IsObjectRotationAllowed) { Quaternion rotationX = Quaternion.AngleAxis(-CVRInputManager.Instance.objectRotationValue.x * 20f, transform.up); Quaternion rotationY = Quaternion.AngleAxis(CVRInputManager.Instance.objectRotationValue.y * 20f, transform.right); pivotPoint.rotation = rotationX * rotationY * pivotPoint.rotation; } // pickup push/pull if (grabbedObject.IsObjectPushPullAllowed) { float newDistance = currentAttachmentDistance + 0.1f * CVRInputManager.Instance.objectPushPull * PlayerSetup.Instance.GetPlaySpaceScale(); if (Mathf.Abs(newDistance - currentAttachmentDistance) > float.Epsilon) UpdateGrabDistance(Mathf.Clamp(newDistance, 0f, grabbedObject.MaxPushDistance)); } // pickup interaction if (_interactDown) grabbedObject.UseDown(new InteractionContext(this)); if (_interactUp) grabbedObject.UseUp(new InteractionContext(this)); Interactable interactable = grabbedObject.gameObject.GetComponent(); // interaction if (grabbedObject.IsObjectUseAllowed && interactable != null) { if (_interactDown) interactable.InteractDown(new InteractionContext(this), this); if (_interactUp) interactable.InteractUp(new InteractionContext(this), this); } if (!grabbedObject.IsAutoHold && _drop) DropObject(); else if (grabbedObject.IsAutoHold && CVRInputManager.Instance.drop) DropObject(true); DisableLineRenderer(); DisableProximityGrabHighlight(); if (_telepathicPickupCandidate != null) { ClearTelepathicGrabTargetHighlight(); _telepathicPickupCandidate = null; } } private bool HandleMenuUIInteraction() { //Swap Ray if applicable if (_interactDown && _hitUIInternal && !uiActive) { uiActive = true; if (otherRay) otherRay.uiActive = false; CVR_MenuManager.Instance.lastInteractedHand = hand; } if (!uiActive) return false; Ray menuRay = new Ray(rayDirectionTransform.position, rayDirectionTransform.TransformDirection(RayDirection)); CohtmlControlledView hitView = CohtmlViewInputHandler.Instance.RayToView(menuRay, out float hitDistance, out Vector2 hitCoords); if (hitView == null) return false; float minDistance = 0.15f * PlayerSetup.Instance.GetPlaySpaceScale(); // physical-like touch, very satisfying if (shouldPhysicalInteraction && (hitDistance < minDistance && _lastHitDistance >= minDistance)) CohtmlViewInputHandler.Instance.DoViewInput(hitView, hitCoords, true, false, 0f); else if (shouldPhysicalInteraction && (hitDistance >= minDistance && _lastHitDistance < minDistance)) CohtmlViewInputHandler.Instance.DoViewInput(hitView, hitCoords, false, true, 0f); else CohtmlViewInputHandler.Instance.DoViewInput(hitView, hitCoords, _interactDown, _interactUp, CVRInputManager.Instance.scrollValue); if (_lastView != null && hitView != _lastView) CohtmlViewInputHandler.Instance.ReleaseViewInput(_lastView, _lastUiCoords); _lastView = hitView; _lastUiCoords = hitCoords; _lastHitDistance = hitDistance; _objectWasHit = true; if (isInteractionRay && !isDesktopRay && !isHeadRay) { if (hand == CVRHand.Left) CVRInputManager.Instance.leftControllerPointingMenu = true; else CVRInputManager.Instance.rightControllerPointingMenu = true; } if (lineRenderer) { lineRenderer.enabled = true; lineRenderer.SetPosition(0, lineRenderer.transform.InverseTransformPoint(rayDirectionTransform.position)); lineRenderer.SetPosition(1, lineRenderer.transform.InverseTransformPoint(rayDirectionTransform.position + rayDirectionTransform.TransformDirection(RayDirection) * hitDistance)); } return true; } private void DisableLineRenderer() { if (lineRenderer) lineRenderer.enabled = false; } private void DisableProximityGrabHighlight() { if (!_proximityCalculatedThisFrame) ClearProximityGrabTargetHighlight(); _proximityCalculatedThisFrame = false; } private void HandleMenuUIInteractionRelease() { if (_lastView == null) return; if (!_interactUp && uiActive) return; CohtmlViewInputHandler.Instance.ReleaseViewInput(_lastView, _lastUiCoords); _lastView = null; } private bool FindTargets(out bool hitUiInternal) { const int uiInternalMask = 1 << CVRLayers.UIInternal; // Transform the point and direction from local to world space Vector3 origin = rayDirectionTransform.TransformPoint(RayDirection * -0.15f); Vector3 direction = rayDirectionTransform.TransformDirection(RayDirection); // Ray Cast to the internal UI layer int hitCount = Physics.RaycastNonAlloc(origin, direction, _hits, MAX_RAYCAST_LENGTH, uiInternalMask); // Sort hits by distance (closer hits first) Array.Sort(_hits, 0, hitCount, _hitsComparer); // Iterate through the internal UI layers for (int i = 0; i < hitCount; i++) { RaycastHit hit = _hits[i]; Collider hitCollider = hit.collider; // Ignore Quick Menu from the menu holding ray cast hand if (!isDesktopRay && !isHeadRay && CVR_MenuManager.Instance.IsQuickMenuOpen && hand == CVR_MenuManager.Instance.SelectedQuickMenuHand && hitCollider == CVR_MenuManager.Instance.quickMenuCollider && !CVRInputManager.Instance.oneHanded) continue; _hit = hit; hitTransform = hit.collider.transform; hitUiInternal = true; return true; } // General mask except Mirror Reflection and UI Internal (Since we already looked for it) int generalLayersMask = generalMask & ~(1 << CVRLayers.MirrorReflection) & ~uiInternalMask; // Ray Cast to the general layers if (Physics.Raycast(origin, direction, out _hit, MAX_RAYCAST_LENGTH, generalLayersMask)) { hitTransform = _hit.collider.transform; hitUiInternal = false; return true; } DisableLineRenderer(); _objectWasHit = false; if (lastInteractable != null) { if (triggerHoverEvents) lastInteractable.HoverExit(new InteractionContext(this, PlayerSetup.PlayerLocalId), this); // interactable handles ignoring if we didn't already interact lastInteractable.InteractUp(new InteractionContext(this, PlayerSetup.PlayerLocalId), this); lastInteractable = null; } lastSlider = null; if (isInteractionRay) CVR_MenuManager.Instance.SetHandTarget("", "", "", Vector3.zero, "", hand); if (triggerHoverEvents) CVR_MenuManager.Instance.SetViewTarget("", "", "", Vector3.zero, ""); ClearTargetHighlight(); hitUiInternal = false; return false; } private Pickupable CheckPickupDirect() { Pickupable pickup = hitTransform.GetComponent(); if (pickup == null || !pickup.CanPickup) return null; if (pickup.MaxGrabDistance < _hit.distance) return null; if (_enableTelepathicGrab && !pickup.IsGrabbedByMe) { _telepathicPickupCandidate = pickup; _telepathicPickupTargeted = true; } return pickup; } private PlayerDescriptor HandlePlayerClicked() { if (PlayerSetup.Instance.GetCurrentPropSelectionMode() != PlayerSetup.PropSelectionMode.None) return null; PlayerDescriptor descriptor = null; if (CanSelectPlayersAndProps() && hitTransform.TryGetComponent(out descriptor)) if (_interactDown) Users.ShowDetails(descriptor.ownerId); return descriptor; } private Interactable HandleInteractable() { Interactable interactable = hitTransform.GetComponent(); // this handles swapping interactables when the raycast hits anything, otherwise // FindObjects handles resetting when *nothing* is hit // TODO: rework this so lastInteractable cannot be updated if an interaction is in progress if (interactable != lastInteractable) { if (triggerHoverEvents) { if (lastInteractable) lastInteractable.HoverExit(new InteractionContext(this, PlayerSetup.PlayerLocalId), this); if (interactable) interactable.HoverEnter(new InteractionContext(this, PlayerSetup.PlayerLocalId), this); } // interactable handles ignoring if we didn't already interact if (lastInteractable) lastInteractable.InteractUp(new InteractionContext(this, PlayerSetup.PlayerLocalId), this); lastInteractable = interactable; } if (interactable == null) return null; // Ignore interactable interactions if in prop delete or spawn mode if (!isInteractionRay || PlayerSetup.Instance.GetCurrentPropSelectionMode() != PlayerSetup.PropSelectionMode.None) return interactable; if (interactable.IsInteractableWithinRange(transform.position)) { if (_interactDown) interactable.InteractDown(new InteractionContext(this, PlayerSetup.PlayerLocalId), this); if (_interactUp) interactable.InteractUp(new InteractionContext(this, PlayerSetup.PlayerLocalId), this); } return interactable; } private CVRSpawnable HandleSpawnableClicked() { CVRSpawnable spawnable = hitTransform.GetComponentInParent(); if (spawnable == null) return null; PlayerSetup.PropSelectionMode selectionMode = PlayerSetup.Instance.GetCurrentPropSelectionMode(); switch (selectionMode) { case PlayerSetup.PropSelectionMode.None: { // Click a prop while a menu is open to open the details menu if (_interactDown && CanSelectPlayersAndProps() && spawnable.TryGetComponent(out CVRAssetInfo assetInfo)) { // Open the details menu of the spawnable ViewManager.Instance.GetPropDetails(assetInfo.objectId); ViewManager.Instance.UiStateToggle(true); _interactDown = false; // Consume the click } break; } case PlayerSetup.PropSelectionMode.Delete: { // Click a prop while in delete mode to delete it if (_interactDown && spawnable.ownerId != "SYSTEM" && spawnable.ownerId != "LocalServer") { spawnable.Delete(); _interactDown = false; // Consume the click return null; // Don't return the spawnable, it's been deleted } break; } } // Return normal prop hover return spawnable; } private bool HandleUnityUI() { if (isDesktopRay && Cursor.lockState != CursorLockMode.Locked) return false; // Unity does this for us Canvas canvas = hitTransform.GetComponentInParent(); Button button = hitTransform.GetComponent