191 lines
5.6 KiB
C#
191 lines
5.6 KiB
C#
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<NetworkHandItemInteractable> 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<ulong> 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<Rigidbody>();
|
|
|
|
if (itemColliders == null || itemColliders.Length == 0)
|
|
itemColliders = GetComponentsInChildren<Collider>(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<PlayerHandController>();
|
|
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<FirstPersonCameraController>();
|
|
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;
|
|
}
|
|
}
|
|
}
|