using AfterHours.Interaction;
using AfterHours.Shop;
using Unity.Netcode;
using UnityEngine;
namespace AfterHours.Networking
{
///
/// Was der Spieler gerade als physisches Objekt trägt (z. B. ein
/// Lieferkarton) sowie das serverseitig getaktete Ausschütten daraus in
/// ein IPourTarget. Getrennt von PlayerHands (abstrakte Stückzahl) –
/// beide sind gegenseitig exklusiv, siehe TryPickUp/StorageCrate.Interact.
///
public sealed class PlayerCarry : NetworkBehaviour
{
[SerializeField] private Transform _handSocket;
[SerializeField] private float _pourInterval = 0.4f;
[SerializeField] private float _maxPourReach = 2.5f;
[SerializeField] private float _flightDuration = 0.35f;
private readonly NetworkVariable _isCarrying = new(
false,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
private readonly NetworkVariable _carriedObject = new(
default,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
public Transform HandSocket => _handSocket;
public bool IsCarrying => _isCarrying.Value;
private IPourTarget _pourTarget;
private IPourSource _pourSource;
private NetworkObjectReference _pourTargetRef;
private float _pourTimer;
/// Server: versucht, aufzuheben.
public bool TryPickUp(Carryable target)
{
if (!IsServer || _isCarrying.Value || target == null) return false;
if (TryGetComponent(out var hands) && !hands.IsEmpty) return false;
if (!target.TryPickUp(NetworkObject)) return false;
_carriedObject.Value = target.NetworkObject;
_isCarrying.Value = true;
return true;
}
/// Server: legt das getragene Objekt vor dem Spieler ab.
[Rpc(SendTo.Server)]
public void RequestDropRpc()
{
if (!_isCarrying.Value) return;
StopPour();
if (_carriedObject.Value.TryGet(out var carried) && carried.TryGetComponent(out var carryable))
{
Vector3 dropPosition = transform.position + transform.forward * 1f;
carryable.Drop(dropPosition, transform.rotation);
}
_isCarrying.Value = false;
_carriedObject.Value = default;
}
/// Server: startet das Ausschütten in , falls möglich.
public bool BeginPour(IPourTarget target)
{
if (!IsServer || target == null) return false;
if (!_isCarrying.Value || !_carriedObject.Value.TryGet(out var carried)) return false;
if (!carried.TryGetComponent(out var source)) return false;
if (!source.IsOpen || source.IsEmpty || target.IsFull) return false;
if (!target.IsEmpty && target.ProductId != source.ProductId) return false;
if (!((Component)target).TryGetComponent(out var targetNo)) return false;
_pourSource = source;
_pourTarget = target;
_pourTargetRef = targetNo;
_pourTimer = 0f; // erste Einheit sofort, danach im Takt von _pourInterval
return true;
}
public void StopPour()
{
_pourSource = null;
_pourTarget = null;
}
private void Update()
{
if (!IsServer || _pourSource == null || _pourTarget == null) return;
if (!_isCarrying.Value || _pourSource.IsEmpty || _pourTarget.IsFull)
{
StopPour();
return;
}
if (Vector3.Distance(transform.position, _pourTarget.Transform.position) > _maxPourReach)
{
StopPour();
return;
}
_pourTimer -= Time.deltaTime;
if (_pourTimer > 0f) return;
_pourTimer = _pourInterval;
int landingIndex = _pourTarget.Quantity;
int productId = _pourSource.ProductId;
if (_pourSource.TryTakeOne() && _pourTarget.TryAddOne(productId))
PlayFlightRpc(_carriedObject.Value, _pourTargetRef, landingIndex);
}
/// Läuft auf jedem Peer: rein kosmetisches Flug-Visual für die soeben transferierte Einheit.
[Rpc(SendTo.Everyone)]
private void PlayFlightRpc(NetworkObjectReference sourceRef, NetworkObjectReference targetRef, int landingIndex)
{
if (!sourceRef.TryGet(out var sourceObj) || !sourceObj.TryGetComponent(out var box)) return;
if (!targetRef.TryGet(out var targetObj) || !targetObj.TryGetComponent(out var shelf)) return;
GameObject prefab = box.GetPackagePrefab();
Vector3 start = box.transform.position + Vector3.up * 0.3f;
Vector3 end = shelf.GetSlotWorldPosition(landingIndex);
FlyingPackage.Spawn(prefab, start, end, shelf.transform.rotation, _flightDuration);
}
}
}