Ui/BoardOverlay.cs

The BoardOverlay is a UI panel that visualizes match claims and cell highlights. It displays detailed information when the player hovers over a specific board cell.

using CitySim.Match;
using CitySim.Cards;
using Sandbox.UI;
using CitySim.DesignSystem;

namespace CitySim;

public sealed class BoardOverlay : MatchPanel
{
	readonly List<(TileClaim Claim,SceneLabel Label)> claims = new();
	readonly Dictionary<(int X,int Y),SceneLabel> highlightLabels = new();
	Panel inspection;
	Label inspectionOwner;
	PlotterCard inspectionCard;
	BoardCell inspectedCell;
	float hoverStarted;
	protected override void Subscribe() { if (BoundMatch != null) BoundMatch.Events.ClaimsChanged += Read; if (City?.Player != null) City.Player.PreviewChanged += Inspect; if (City != null) City.UiChanged += Inspect; Read(); Inspect(); }
	protected override void Unsubscribe() { if (BoundMatch != null) BoundMatch.Events.ClaimsChanged -= Read; if (City?.Player != null) City.Player.PreviewChanged -= Inspect; if (City != null) City.UiChanged -= Inspect; foreach (var item in claims) item.Label.Destroy(); claims.Clear(); foreach (var label in highlightLabels.Values) label.Destroy(); highlightLabels.Clear(); }
	void Inspect()
	{
		if (inspection == null)
		{
			inspection=AddChild<Panel>("board-inspect");
			inspectionOwner=inspection.AddChild<Label>("inspect-owner");
			inspectionCard=inspection.AddChild<PlotterCard>();
			inspectionCard.BuildOnly=true;
		}
		var hover=City?.Player?.Hovered;
		var cell=hover == null ? null : BoundMatch?.Get(hover.X,hover.Y);
		var allowed=City?.Screens.Top==GameScreen.Match && City.State==GameState.Playing &&
			(BoundMatch?.Phase != MatchPhase.Bid || Hud.MarketPeeking);
		if (!allowed) cell=null;
		if (cell != inspectedCell)
		{
			inspectedCell=cell;
			hoverStarted=Time.Now;
			inspection.SetClass("visible",false);
		}
		var visible=cell != null && Time.Now-hoverStarted >= 0.4f;
		inspection.SetClass("visible",visible);
		if (!visible) return;
		inspectionCard.City=City;
		inspectionCard.Card=CardLibrary.Get(cell.CardId);
		inspectionCard.Mature=Growth.IsMature(cell);
		inspectionOwner.Text=BoundMatch.Players.TryGetValue(cell.Owner,out var player) ? player.Name : GameText.Get("ui.board_overlay.city_road");
	}
	void Read()
	{
		foreach (var item in claims) item.Label.Destroy(); claims.Clear();
		if (BoundMatch == null) return;
		foreach (var claim in BoundMatch.Claims)
		{
			var label = new SceneLabel(City, "Claim", 0.10f); claims.Add((claim,label));
		}
	}
	public override void Tick()
	{
		base.Tick();
		Inspect();
		var showHighlights = City?.Player != null && City.State == GameState.Playing && City.Screens.Top == GameScreen.Match &&
			(City.Match?.Phase != MatchPhase.Bid || Hud.MarketPeeking);
		var cells = showHighlights ? City.HighlightedCells : Array.Empty<BoardCell>();
		var wanted = cells.Select(c => (c.X,c.Y)).ToHashSet();
		foreach (var key in highlightLabels.Keys.Where(key => !wanted.Contains(key)).ToArray()) { highlightLabels[key].Destroy(); highlightLabels.Remove(key); }
		foreach (var cell in cells)
		{
			var key=(cell.X,cell.Y);
			if (!highlightLabels.TryGetValue(key,out var label)) { label=new SceneLabel(City, "Highlight", 0.14f); highlightLabels[key]=label; }
			label.Set(SceneLabel.AboveTile(City.Grid.Get(cell.X,cell.Y), City.Grid.CellToWorld(cell.X,cell.Y))+Vector3.Up*18, City.HighlightIcon);
		}
		foreach (var item in claims)
		{
			var visible = City.State == GameState.Playing && City.Screens.Top == GameScreen.Match && (City.Match?.Phase != MatchPhase.Bid || Hud.MarketPeeking);
			item.Label.Set(visible ? SceneLabel.AboveTile(City.Grid.Get(item.Claim.X,item.Claim.Y), City.Grid.CellToWorld(item.Claim.X,item.Claim.Y))+Vector3.Up*18 : null, $"🚩 {BoundMatch.Players[item.Claim.Owner].Name}");
		}
	}
}