using System.Collections.Generic; using Unity.Netcode; using UnityEngine; public interface IUsableHandItem { string ItemName { get; } bool IsHeld { get; } bool IsHeldByLocalPlayer { get; } void RequestUse(); void RequestThrow(); } public abstract class NetworkHandItemInteractable : NetworkInteractable, IUsableHandItem { private const ulong NoHolder = ulong.MaxValue; private static readonly HashSet SpawnedItems = new(); [Header("Hand item physics")] [SerializeField] private Rigidbody physicsBody; [SerializeField] private Collider[] itemColliders; [SerializeField, Min(0f)] private float throwForce = 4f; [SerializeField, Min(0f)] private float throwUpwardForce = 1f; private readonly NetworkVariable holderClientId = new( NoHolder, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); public string ItemName => InteractionLabel; public bool IsHeld => holderClientId.Value != NoHolder; public bool IsHeldByLocalPlayer => NetworkManager != null && holderClientId.Value == NetworkManager.LocalClientId; public override bool IsInteractionAvailable => base.IsInteractionAvailable && !IsHeld; protected virtual void Awake() { if (physicsBody == null) physicsBody = GetComponent(); if (itemColliders == null || itemColliders.Length == 0) itemColliders = GetComponentsInChildren(true); } public override void OnNetworkSpawn() { SpawnedItems.Add(this); holderClientId.OnValueChanged += HandleHolderChanged; ApplyHeldState(IsHeld); } public override void OnNetworkDespawn() { holderClientId.OnValueChanged -= HandleHolderChanged; SpawnedItems.Remove(this); ApplyHeldState(false); } private void LateUpdate() { if (!IsSpawned || !IsHeld) return; if (!TryGetPlayerObject(holderClientId.Value, out NetworkObject holder)) { if (IsServer) DropWithoutThrow(); return; } PlayerHandController hand = holder.GetComponent(); if (hand == null || hand.ItemSocket == null) return; transform.SetPositionAndRotation( hand.ItemSocket.position, hand.ItemSocket.rotation); } public void RequestUse() { if (IsSpawned && IsHeldByLocalPlayer) RequestUseServerRpc(); } public void RequestThrow() { if (IsSpawned && IsHeldByLocalPlayer) RequestThrowServerRpc(); } protected override bool CanInteractOnServer(ulong senderClientId) => !IsHeld && !TryGetHeldItem(senderClientId, out _) && !NetworkHeldInteractable.TryGetHeldObject(senderClientId, out _); protected override void InteractOnServer(ulong senderClientId, NetworkObject playerObject) { holderClientId.Value = senderClientId; ApplyHeldState(true); } [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] private void RequestUseServerRpc(RpcParams rpcParams = default) { ulong senderClientId = rpcParams.Receive.SenderClientId; if (holderClientId.Value != senderClientId || !TryGetPlayerObject(senderClientId, out NetworkObject playerObject)) { return; } UseOnServer(playerObject); } [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] private void RequestThrowServerRpc(RpcParams rpcParams = default) { ulong senderClientId = rpcParams.Receive.SenderClientId; if (holderClientId.Value != senderClientId || !TryGetPlayerObject(senderClientId, out NetworkObject playerObject)) { return; } FirstPersonCameraController cameraController = playerObject.GetComponent(); Vector3 throwDirection = cameraController != null ? cameraController.AimForward : playerObject.transform.forward; Vector3 throwVelocity = throwDirection * throwForce + Vector3.up * throwUpwardForce; holderClientId.Value = NoHolder; ApplyHeldState(false); if (physicsBody != null) physicsBody.linearVelocity = throwVelocity; } protected abstract void UseOnServer(NetworkObject playerObject); public static bool TryGetHeldItem( ulong clientId, out NetworkHandItemInteractable heldItem) { foreach (NetworkHandItemInteractable item in SpawnedItems) { if (item != null && item.holderClientId.Value == clientId) { heldItem = item; return true; } } heldItem = null; return false; } private void DropWithoutThrow() { holderClientId.Value = NoHolder; ApplyHeldState(false); } private void HandleHolderChanged(ulong _, ulong currentHolder) => ApplyHeldState(currentHolder != NoHolder); private void ApplyHeldState(bool held) { if (physicsBody != null) { physicsBody.isKinematic = held || !IsServer; if (held) { physicsBody.linearVelocity = Vector3.zero; physicsBody.angularVelocity = Vector3.zero; } } foreach (Collider itemCollider in itemColliders) { if (itemCollider != null) itemCollider.enabled = !held; } } }