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.GetSlotWorldRotation(), _flightDuration); } } }