Files
SixShopSimulator/Assets/Scripts/Networking/PlayerCarry.cs
T
svenm_adminandClaude Sonnet 5 9eb588e491 feat(shop): configurable package display rotation, pocoregal shelf model
Add _displayRotationEuler to Lieferkarton/ShelfSlot so package prefabs
with a non-upright export orientation can be corrected per-container
without touching PackageDisplay code. Fixes a bug where flying packages
landed with a different rotation than the resting shelf display
(PlayerCarry used the raw shelf transform instead of
ShelfSlot.GetSlotWorldRotation()).

Also adds the pocoregal shelf model (Blender export) and its materials,
places it in the Shop scene, registers ShelfSlot as a network prefab,
and rescales EmptyBox. See docs/PACKAGE_DISPLAY_SETUP.md for the
display-grid setup/troubleshooting reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 16:13:32 +02:00

135 lines
5.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using AfterHours.Interaction;
using AfterHours.Shop;
using Unity.Netcode;
using UnityEngine;
namespace AfterHours.Networking
{
/// <summary>
/// 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.
/// </summary>
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<bool> _isCarrying = new(
false,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
private readonly NetworkVariable<NetworkObjectReference> _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;
/// <summary>Server: versucht, <paramref name="target"/> aufzuheben.</summary>
public bool TryPickUp(Carryable target)
{
if (!IsServer || _isCarrying.Value || target == null) return false;
if (TryGetComponent<PlayerHands>(out var hands) && !hands.IsEmpty) return false;
if (!target.TryPickUp(NetworkObject)) return false;
_carriedObject.Value = target.NetworkObject;
_isCarrying.Value = true;
return true;
}
/// <summary>Server: legt das getragene Objekt vor dem Spieler ab.</summary>
[Rpc(SendTo.Server)]
public void RequestDropRpc()
{
if (!_isCarrying.Value) return;
StopPour();
if (_carriedObject.Value.TryGet(out var carried) && carried.TryGetComponent<Carryable>(out var carryable))
{
Vector3 dropPosition = transform.position + transform.forward * 1f;
carryable.Drop(dropPosition, transform.rotation);
}
_isCarrying.Value = false;
_carriedObject.Value = default;
}
/// <summary>Server: startet das Ausschütten in <paramref name="target"/>, falls möglich.</summary>
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<IPourSource>(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<NetworkObject>(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);
}
/// <summary>Läuft auf jedem Peer: rein kosmetisches Flug-Visual für die soeben transferierte Einheit.</summary>
[Rpc(SendTo.Everyone)]
private void PlayFlightRpc(NetworkObjectReference sourceRef, NetworkObjectReference targetRef, int landingIndex)
{
if (!sourceRef.TryGet(out var sourceObj) || !sourceObj.TryGetComponent<Lieferkarton>(out var box)) return;
if (!targetRef.TryGet(out var targetObj) || !targetObj.TryGetComponent<ShelfSlot>(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);
}
}
}