Files
DepthsOfGreed/Assets/Scripts/HandbookUI/SupplyBasementController.cs
T

207 lines
11 KiB
C#

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<long, bool> ConnectionChanged;
public event System.Action<int> 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<long, bool> connections = new Dictionary<long, bool>();
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<Image>().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;
}
}
}
}