Files
FORT-BO/Assets/CONTENT/Game/Scripts/Interaction/NetworkHeldInteractable.cs
T
2026-08-07 11:40:20 +03:00

229 lines
7.0 KiB
C#

using System.Collections.Generic;
using Unity.Netcode;
using Unity.Netcode.Components;
using UnityEngine;
[RequireComponent(typeof(Rigidbody), typeof(NetworkRigidbody))]
public abstract class NetworkHeldInteractable : NetworkInteractable
{
protected const ulong NoHolder = ulong.MaxValue;
private static readonly HashSet<NetworkHeldInteractable> SpawnedHeldObjects = new();
[Header("Physics")]
[SerializeField] private Rigidbody physicsBody;
[SerializeField] private Collider[] objectColliders;
[SerializeField, Min(0.05f)] private float collisionRefreshInterval = 0.25f;
[Header("Throw")]
[SerializeField, Min(0f)] private float throwForce = 6f;
[SerializeField, Min(0f)] private float throwUpwardForce = 0.5f;
private readonly NetworkVariable<ulong> holderClientId = new(
NoHolder,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server);
private readonly HashSet<Collider> ignoredPlayerColliders = new();
private float nextCollisionRefreshTime;
protected abstract Vector3 HoldingOffset { get; }
protected abstract string DropPrompt { get; }
public bool IsHeld => holderClientId.Value != NoHolder;
public bool IsHeldByLocalPlayer =>
NetworkManager != null && holderClientId.Value == NetworkManager.LocalClientId;
public string HeldObjectName => InteractionLabel;
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<Rigidbody>();
if (objectColliders == null || objectColliders.Length == 0)
objectColliders = GetComponentsInChildren<Collider>(true);
}
public override void OnNetworkSpawn()
{
SpawnedHeldObjects.Add(this);
holderClientId.OnValueChanged += HandleHolderChanged;
ApplyHoldingState(IsHeld);
}
public override void OnNetworkDespawn()
{
holderClientId.OnValueChanged -= HandleHolderChanged;
SpawnedHeldObjects.Remove(this);
ApplyHoldingState(false);
}
private void LateUpdate()
{
if (!IsSpawned || !IsHeld)
return;
if (!TryGetPlayerObject(holderClientId.Value, out NetworkObject holder))
{
if (IsServer)
StopHolding();
return;
}
Transform anchor = ResolveHoldingAnchor(holder);
transform.SetPositionAndRotation(
anchor.TransformPoint(HoldingOffset),
anchor.rotation);
if (Time.time >= nextCollisionRefreshTime)
{
nextCollisionRefreshTime = Time.time + collisionRefreshInterval;
IgnoreCollisionsWithPlayers();
}
}
public void RequestThrow()
{
if (IsSpawned && IsHeldByLocalPlayer)
RequestThrowServerRpc();
}
public static bool TryGetHeldObject(
ulong clientId,
out NetworkHeldInteractable heldObject)
{
foreach (NetworkHeldInteractable candidate in SpawnedHeldObjects)
{
if (candidate != null && candidate.holderClientId.Value == clientId)
{
heldObject = candidate;
return true;
}
}
heldObject = null;
return false;
}
protected override bool CanInteractOnServer(ulong senderClientId)
{
if (holderClientId.Value == senderClientId)
return true;
return !IsHeld &&
!TryGetHeldObject(senderClientId, out _) &&
!NetworkHandItemInteractable.TryGetHeldItem(senderClientId, out _);
}
protected override void InteractOnServer(ulong senderClientId, NetworkObject playerObject)
{
if (holderClientId.Value == senderClientId)
StopHolding();
else if (!IsHeld)
StartHolding(senderClientId);
}
[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<FirstPersonCameraController>();
Vector3 throwDirection = cameraController != null
? cameraController.AimForward
: playerObject.transform.forward;
Vector3 throwVelocity = throwDirection * throwForce +
Vector3.up * throwUpwardForce;
StopHolding();
if (physicsBody != null)
physicsBody.linearVelocity = throwVelocity;
}
private void StartHolding(ulong clientId)
{
holderClientId.Value = clientId;
ApplyHoldingState(true);
}
private void StopHolding()
{
holderClientId.Value = NoHolder;
ApplyHoldingState(false);
}
private Transform ResolveHoldingAnchor(NetworkObject holder)
{
FirstPersonCameraController cameraController =
holder.GetComponent<FirstPersonCameraController>();
return cameraController != null ? cameraController.AimTransform : holder.transform;
}
private void HandleHolderChanged(ulong _, ulong currentHolder) =>
ApplyHoldingState(currentHolder != NoHolder);
private void ApplyHoldingState(bool isHeld)
{
if (physicsBody != null)
{
physicsBody.isKinematic = isHeld || !IsServer;
physicsBody.linearVelocity = Vector3.zero;
physicsBody.angularVelocity = Vector3.zero;
}
if (isHeld)
IgnoreCollisionsWithPlayers();
else
RestorePlayerCollisions();
}
private void IgnoreCollisionsWithPlayers()
{
foreach (CharacterController playerCollider in FindObjectsByType<CharacterController>(
FindObjectsInactive.Exclude,
FindObjectsSortMode.None))
{
foreach (Collider objectCollider in objectColliders)
{
if (objectCollider != null && playerCollider != null)
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();
}
}