using AfterHours.Data; using AfterHours.Interaction; using AfterHours.Networking; using Unity.Netcode; using UnityEngine; namespace AfterHours.Shop { [RequireComponent(typeof(NetworkObject))] [RequireComponent(typeof(Carryable))] public class Lieferkarton : NetworkBehaviour, IInteractable, IPourSource { [Header("Deckel Objekte")] [SerializeField] private Transform deckelLinks; [SerializeField] private Transform deckelRechts; [Header("Rotations-Einstellungen")] [SerializeField] private float oeffnungsWinkel = 110f; // Winkel in Grad nach außen [SerializeField] private float geschwindigkeit = 5f; // Geschwindigkeit des Aufklappens [Header("Inhalt")] [SerializeField] private ProductCatalog _catalog; [SerializeField] private int _productId = -1; [SerializeField] private int _startingQuantity = 20; [SerializeField] private Transform _contentsRoot; [SerializeField] private int _maxVisiblePackages = 12; [Header("Anzeige-Raster (sauber gestapelt, so wie Platz ist)")] [SerializeField] private int _displayColumns = 3; [SerializeField] private int _displayRows = 3; [SerializeField] private Vector3 _displaySpacing = new(0.12f, 0.1f, 0.12f); // Eine synchronisierte Variable, die den Zustand des Kartons speichert private NetworkVariable istOffen = new NetworkVariable( false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server // Nur der Server darf den Zustand ändern ); private readonly NetworkVariable _quantity = new( 0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); public bool IsOpen => istOffen.Value; public int ProductId => _productId; public int Quantity => _quantity.Value; public bool IsEmpty => _quantity.Value <= 0; // Interne Variablen zum Speichern der Ziel-Rotationen private Quaternion linksGeschlossen; private Quaternion linksOffen; private Quaternion rechtsGeschlossen; private Quaternion rechtsOffen; private Carryable _carryable; private PackageDisplay _packageDisplay; private void Awake() { _carryable = GetComponent(); _packageDisplay = new PackageDisplay(_contentsRoot, _displayColumns, _displayRows, _displaySpacing); // Start-Rotationen (Geschlossen) direkt beim Laden sichern if (deckelLinks != null) linksGeschlossen = deckelLinks.localRotation; if (deckelRechts != null) rechtsGeschlossen = deckelRechts.localRotation; // Ziel-Rotationen berechnen (X-Achse = Scharnierkante entlang der Längsseite) linksOffen = linksGeschlossen * Quaternion.Euler(-oeffnungsWinkel, 0, 0); rechtsOffen = rechtsGeschlossen * Quaternion.Euler(oeffnungsWinkel, 0, 0); } public override void OnNetworkSpawn() { if (IsServer) _quantity.Value = _startingQuantity; _quantity.OnValueChanged += (_, _) => RefreshVisuals(); istOffen.OnValueChanged += (_, _) => RefreshVisuals(); RefreshVisuals(); } private void Update() { // Beide Deckel weich auf jedem Client rotieren lassen basierend auf der NetworkVariable if (deckelLinks != null) { Quaternion zielLinks = istOffen.Value ? linksOffen : linksGeschlossen; deckelLinks.localRotation = Quaternion.Slerp(deckelLinks.localRotation, zielLinks, Time.deltaTime * geschwindigkeit); } if (deckelRechts != null) { Quaternion zielRechts = istOffen.Value ? rechtsOffen : rechtsGeschlossen; deckelRechts.localRotation = Quaternion.Slerp(deckelRechts.localRotation, zielRechts, Time.deltaTime * geschwindigkeit); } } /// /// Wird von deinem Interaktionssystem aufgerufen (z. B. wenn "E" gedrückt wird). /// Geschlossen -> öffnen. Offen und nicht getragen -> aufheben. /// public void Interact(NetworkObject actor) { if (!IsServer) return; if (!istOffen.Value) { istOffen.Value = true; return; } if (!_carryable.IsCarried && actor.TryGetComponent(out var carry)) carry.TryPickUp(_carryable); } /// Server: nimmt ein Stück Ware aus dem Karton. Für das Ausschütten (siehe PlayerCarry). public bool TryTakeOne() { if (!IsServer || IsEmpty) return false; _quantity.Value--; return true; } // Gibt den dynamischen Text für das UI-Prompt zurück public string GetPrompt(ulong clientId) { if (!istOffen.Value) return "Karton öffnen"; var product = _catalog != null ? _catalog.Get(_productId) : null; string name = product != null ? product.DisplayName : "?"; return IsEmpty ? $"{name} — leer" : $"{name} aufheben ({_quantity.Value} Stk.)"; } /// Verpackungs-Prefab des aktuellen Produkts (auch für das Flug-Visual beim Ausschütten, siehe PlayerCarry). public GameObject GetPackagePrefab() { var product = _catalog != null ? _catalog.Get(_productId) : null; return product != null ? product.PackagePrefab : null; } private void RefreshVisuals() { int visibleCount = istOffen.Value ? _quantity.Value : 0; _packageDisplay.SetTarget(GetPackagePrefab(), visibleCount, _maxVisiblePackages); } } }