using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace DepthsOfGreed.HandbookUI { // Presentation prototype. Connection intent is separate from real ammunition accounting. public sealed partial class SupplyBasementController : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler { public bool IsInBasement { get; private set; } public bool IsTravelling { get; private set; } public int Priority { get; private set; } = -1; public event System.Action ConnectionChanged; public event System.Action PriorityChanged; [Header("Authored scene references")] public GameObject basementView; public GameObject handbookRoot; public CanvasGroup handbookVisibility; public HandbookController handbook; public TestPlacementController placement; public Button liftButton; public RectTransform liftSign; public TMP_Text liftLabel; public CanvasGroup doorGroup; public RectTransform leftDoor, rightDoor; public Button[] connectionButtons; public TMP_Text[] connectionNames, connectionStates; public Image[] connectionIcons, cables; public Button[] priorityButtons; public TMP_Text[] priorityLabels; public RectTransform truck; public TMP_Text truckStatus; [Header("Lift timing")] [Min(0.05f)] public float doorDuration = 0.3f; [Min(0)] public float travelDuration = 0.35f; [Min(0.05f)] public float signHalfFlipDuration = 0.1f; [Header("Truck display test (no ammunition is consumed)")] public bool animateTruckPreview = true; public float truckSpeed = 100f; public float[] loadingPositions = { -530f, 0f, 530f }; public float unloadingPosition = 710f; public float loadingPause = 1.2f; public Color physicalColor = new Color(.35f,.35f,.35f); public Color cellColor = new Color(.48f,.48f,.48f); public Color specialColor = new Color(.60f,.60f,.60f); private readonly long[] slotIds = new long[8]; private readonly Dictionary connections = new Dictionary(); private bool hovered, labelBack; private Coroutine flipRoutine; private float nextRefresh, truckPauseUntil; private int previewKind; private bool unloading; private Vector3 signScale; private Vector3 truckScale; private readonly string[] kinds = { "Physisch", "Energiezellen", "Sondermunition" }; private void Awake() { signScale = liftSign.localScale; truckScale = truck.localScale; basementView.SetActive(false); doorGroup.gameObject.SetActive(false); liftButton.onClick.AddListener(UseLift); for (int i = 0; i < connectionButtons.Length; i++) { int slot = i; connectionButtons[i].onClick.AddListener(() => ToggleConnection(slot)); } for (int i = 0; i < priorityButtons.Length; i++) { int kind = i; priorityButtons[i].onClick.AddListener(() => SetPriority(kind)); } if (!HasSupplyBackend) SetPriority(-1); else if (supplyBackend && !(supplyBackend is ISupplyGameplay)) Debug.LogError("Versorgung: ISupplyGameplay fehlt.", this); RefreshSign(); } public void OnPointerEnter(PointerEventData e) { hovered = true; FlipSign(); } public void OnPointerExit(PointerEventData e) { hovered = false; FlipSign(); } private void FlipSign() { if (flipRoutine != null) StopCoroutine(flipRoutine); flipRoutine = StartCoroutine(Flip()); } private IEnumerator Flip() { float start = liftSign.localScale.x; for (float t = 0; t < signHalfFlipDuration; t += Time.unscaledDeltaTime) { liftSign.localScale = new Vector3(Mathf.Lerp(start, 0.02f, t / signHalfFlipDuration), signScale.y, signScale.z); yield return null; } labelBack = hovered; RefreshSign(); for (float t = 0; t < signHalfFlipDuration; t += Time.unscaledDeltaTime) { liftSign.localScale = new Vector3(Mathf.Lerp(0.02f, signScale.x, t / signHalfFlipDuration), signScale.y, signScale.z); yield return null; } liftSign.localScale = signScale; flipRoutine = null; } private void RefreshSign() => liftLabel.text = labelBack ? (IsInBasement ? "ZUR\nTURMEBENE" : "ZUM\nVERSORGUNGS-\nKELLER") : (IsInBasement ? "▲\nAUFZUG" : "▼\nAUFZUG"); public void UseLift() { if (IsTravelling) return; if (placement && (placement.IsDragging || placement.IsWaitingForReturn)) return; StartCoroutine(Travel()); } private IEnumerator Travel() { IsTravelling = true; liftButton.interactable = false; doorGroup.gameObject.SetActive(true); doorGroup.blocksRaycasts = true; yield return Doors(true); if (handbook) handbook.Close(); IsInBasement = !IsInBasement; basementView.SetActive(IsInBasement); // Hide only the presentation. The backend on HandbookRoot must keep updating. handbookVisibility.alpha = IsInBasement ? 0f : 1f; handbookVisibility.interactable = !IsInBasement; handbookVisibility.blocksRaycasts = !IsInBasement; hovered = false; labelBack = false; RefreshSign(); RefreshConnections(); yield return new WaitForSecondsRealtime(travelDuration); yield return Doors(false); doorGroup.blocksRaycasts = false; doorGroup.gameObject.SetActive(false); liftButton.interactable = true; IsTravelling = false; } private IEnumerator Doors(bool close) { float width = ((RectTransform)leftDoor.parent).rect.width * 0.5f; for (float t = 0; t < doorDuration; t += Time.unscaledDeltaTime) { float p = Mathf.SmoothStep(0, 1, t / doorDuration); if (!close) p = 1 - p; leftDoor.anchoredPosition = new Vector2(-width * (1 - p), 0); rightDoor.anchoredPosition = new Vector2(width * (1 - p), 0); yield return null; } leftDoor.anchoredPosition = new Vector2(close ? 0 : -width, 0); rightDoor.anchoredPosition = new Vector2(close ? 0 : width, 0); } public bool IsConnectionEnabled(long instanceId) => HasSupplyBackend ? FindTower(instanceId)?.connected == true : !connections.TryGetValue(instanceId, out bool enabled) || enabled; public void ToggleConnection(int slot) { if (slot < 0 || slot >= slotIds.Length || slotIds[slot] == 0) return; long id = slotIds[slot]; if (TrySetBackendConnection(id, !IsConnectionEnabled(id))) return; connections[id] = !IsConnectionEnabled(id); ConnectionChanged?.Invoke(id, connections[id]); RefreshConnections(); } public void SetPriority(int kind) { if (kind < -1 || kind > 2) return; if (TrySetBackendPriority(kind)) return; Priority = Priority == kind ? -1 : kind; PriorityChanged?.Invoke(Priority); for (int i = 0; i < priorityButtons.Length; i++) { priorityButtons[i].GetComponent().color = Priority == i ? ColorFor(i) : new Color(.72f,.72f,.72f); priorityLabels[i].color = new Color(.18f,.18f,.18f); priorityLabels[i].text = Priority == i ? "Vorrang aktiv" : "Vorrang geben"; } } private Color ColorFor(int kind) => kind == 0 ? physicalColor : kind == 1 ? cellColor : specialColor; private static bool UsesSupply(HandbookTowerEntry t) => t && t.use == BuildUse.Structure && t.supply != null && t.supply.kind != SupplyKind.None; private void RefreshConnections() { var list = GetSupplyObjects(); for (int i = 0; i < slotIds.Length; i++) { bool exists = false; if (list != null) foreach (var obj in list) if (obj.instanceId == slotIds[i] && UsesSupply(obj.type)) { exists = true; break; } if (!exists) { connections.Remove(slotIds[i]); slotIds[i] = 0; } } if (list != null) foreach (var obj in list) { if (!UsesSupply(obj.type) || System.Array.IndexOf(slotIds, obj.instanceId) >= 0) continue; int free = System.Array.IndexOf(slotIds, 0L); if (free >= 0) slotIds[free] = obj.instanceId; } for (int i = 0; i < connectionButtons.Length; i++) { BuiltObjectSnapshot found = null; if (list != null) foreach (var obj in list) if (obj.instanceId == slotIds[i]) { found = obj; break; } RenderMagazine(i, found == null ? 0 : found.instanceId); connectionButtons[i].interactable = found != null; connectionIcons[i].gameObject.SetActive(found != null && found.type.icon); if (found == null) { connectionNames[i].text = ""; connectionStates[i].text = "Frei"; cables[i].color = new Color(.48f,.48f,.48f); continue; } bool enabled = IsConnectionEnabled(found.instanceId); int kind = Mathf.Clamp((int)found.type.supply.kind, 0, 2); connectionIcons[i].sprite = found.type.icon; connectionNames[i].text = found.type.displayName + " #" + found.instanceId; connectionStates[i].text = (enabled ? "Verbunden" : "Getrennt") + "\n" + kinds[kind]; cables[i].color = enabled ? ColorFor(kind) : new Color(.48f,.48f,.48f); } } private void Update() { if (!IsInBasement) return; if (Time.unscaledTime > nextRefresh) { nextRefresh = Time.unscaledTime + 0.25f; RefreshConnections(); } if (HasSupplyBackend) { RenderBackendTruck(); return; } if (!animateTruckPreview) { truckStatus.text = "LKW bereit"; return; } if (Time.unscaledTime < truckPauseUntil) return; float destination = unloading ? unloadingPosition : loadingPositions[previewKind]; var p = truck.anchoredPosition; float direction = destination - p.x; if (Mathf.Abs(direction) > 0.1f) truck.localScale = new Vector3(Mathf.Abs(truckScale.x) * Mathf.Sign(direction), truckScale.y, truckScale.z); p.x = Mathf.MoveTowards(p.x, destination, Mathf.Max(0, truckSpeed) * Time.unscaledDeltaTime); truck.anchoredPosition = p; truckStatus.text = (unloading ? "Zur Verteilerstation" : "Zum Behälter: " + kinds[previewKind]); if (Mathf.Abs(p.x - destination) < 0.1f) { truckStatus.text = (unloading ? "Abladen" : "Beladen: " + kinds[previewKind]); truckPauseUntil = Time.unscaledTime + loadingPause; if (unloading) previewKind = Priority >= 0 ? Priority : (previewKind + 1) % 3; unloading = !unloading; } } } }