using System.Collections.Generic; using Unity.Netcode; using UnityEngine; public abstract class NetworkHeldInteractable : NetworkInteractable { protected const ulong NoHolder = ulong.MaxValue; [Header("Physics")] [SerializeField] private Rigidbody physicsBody; [SerializeField] private Collider[] objectColliders; [SerializeField, Min(0.05f)] private float collisionRefreshInterval = 0.25f; private readonly NetworkVariable holderClientId = new( NoHolder, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); private readonly HashSet ignoredPlayerColliders = new(); private float nextCollisionRefreshTime; protected abstract Vector3 HoldingOffset { get; } protected abstract string DropPrompt { get; } private bool IsHeld => holderClientId.Value != NoHolder; private bool IsHeldByLocalPlayer => NetworkManager != null && holderClientId.Value == NetworkManager.LocalClientId; public override string InteractionPrompt => !IsHeld ? InteractionLabel : IsHeldByLocalPlayer ? DropPrompt : "In use"; public override bool IsInteractionAvailable => base.IsInteractionAvailable && (!IsHeld || IsHeldByLocalPlayer); private void Awake() { if (physicsBody == null) physicsBody = GetComponent(); if (objectColliders == null || objectColliders.Length == 0) objectColliders = GetComponentsInChildren(true); } public override void OnNetworkSpawn() { holderClientId.OnValueChanged += HandleHolderChanged; ApplyHoldingState(IsHeld); } public override void OnNetworkDespawn() { holderClientId.OnValueChanged -= HandleHolderChanged; ApplyHoldingState(false); } private void LateUpdate() { if (!IsSpawned || !IsHeld) return; if (!TryGetPlayerObject(holderClientId.Value, out NetworkObject holder)) { if (IsServer) StopHolding(); return; } transform.SetPositionAndRotation( holder.transform.TransformPoint(HoldingOffset), holder.transform.rotation); if (Time.time >= nextCollisionRefreshTime) { nextCollisionRefreshTime = Time.time + collisionRefreshInterval; IgnoreCollisionsWithPlayers(); } } protected override bool CanInteractOnServer(ulong senderClientId) => !IsHeld || holderClientId.Value == senderClientId; protected override void InteractOnServer(ulong senderClientId, NetworkObject playerObject) { if (holderClientId.Value == senderClientId) StopHolding(); else if (!IsHeld) StartHolding(senderClientId); } private void StartHolding(ulong clientId) { holderClientId.Value = clientId; ApplyHoldingState(true); } private void StopHolding() { holderClientId.Value = NoHolder; ApplyHoldingState(false); } private void HandleHolderChanged(ulong _, ulong currentHolder) => ApplyHoldingState(currentHolder != NoHolder); private void ApplyHoldingState(bool isHeld) { if (physicsBody != null) { physicsBody.isKinematic = isHeld; if (!isHeld) physicsBody.linearVelocity = Vector3.zero; } if (isHeld) IgnoreCollisionsWithPlayers(); else RestorePlayerCollisions(); } private void IgnoreCollisionsWithPlayers() { foreach (CharacterController playerCollider in FindObjectsByType( FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { foreach (Collider objectCollider in objectColliders) { if (objectCollider == null || playerCollider == null) continue; Physics.IgnoreCollision(objectCollider, playerCollider, true); } ignoredPlayerColliders.Add(playerCollider); } } private void RestorePlayerCollisions() { foreach (Collider playerCollider in ignoredPlayerColliders) { if (playerCollider == null) continue; foreach (Collider objectCollider in objectColliders) { if (objectCollider != null) Physics.IgnoreCollision(objectCollider, playerCollider, false); } } ignoredPlayerColliders.Clear(); } }