UI/GamePlayer.Ui.cs

UI and player UI logic for the Game player. Defines FPlayerUiInfo which packages displayed player data and selection/hover state, and parts of GamePlayer that produce and refresh GUI elements and handle upgrade UI interactions; also defines GamePlayerGUi component for upgrade button behavior and shown upgrade clones.

File Access
using Forp.Object;
using Forp.Object.Building;
using Forp.Object.Unit;
using Sandbox;
using Sandbox.Diagnostics;
using Sandbox.Services;
using System;
using static Sandbox.Material;

namespace Forp.Game;

public struct FPlayerUiInfo
{
	public FPlayerUiInfo() { }

	public string Name = "NONE";
	public int Gold = 0;
	public int Territory = 0;
	public int Production = 0;

	public Object.Obj HoveredObject = null;
	public ObjectUnit SelectedUnit = null;
	public Hex SelectedHex = null;
	public List<string> BuildObjects = new();

	public readonly List<string> GetTopBarStrings()
	{
		GameManager.Instance.GetPlayerBoardStats(out var BoardStats);
		return BoardStats.Select(Player => $"{Player.Key.SteamName} \u00A3{Player.Key.Gold} {Player.Value.Production}⬡ {Player.Value.TerritoryPercentage}%").ToList();
	}

	public readonly string GetBottomBarString()
	{
		var BottomString = String.Empty;

		if (SelectedHex.IsValid() && SelectedHex.IsRevealed)
		{
			var OwnerName = "None";
			var IsLocallyOwner = false;

			if (GameManager.Instance.GetGamePlayer(SelectedHex.GetOwnerId()) is { } Owner)
			{
				OwnerName = Owner.SteamName;
				IsLocallyOwner = SelectedHex.IsLocallyOwned();
			}

			BottomString += $"type {SelectedHex.Type} with {SelectedHex.Production} resources owned by {OwnerName}\n";

			if (SelectedHex.UnitObject.IsValid())
			{
				bool IsAi = SelectedHex.UnitObject.GetComponent<AiUnit>() != null;
				BottomString += $"occupied by {SelectedHex.UnitObject.DisplayName} ai={IsAi}\n";
			}

			if (SelectedHex.BuildingObject.IsValid())
			{
				BottomString += $"with building {SelectedHex.BuildingObject.DisplayName} {IsLocallyOwner}\n";
			}
		}
		else if (SelectedUnit.IsValid())
		{
			BottomString += $"unit {SelectedUnit.DisplayName} {SelectedUnit.Attack}A {SelectedUnit.Health}HP\n";

			if (HoveredObject is ObjectUnit { } HoveredUnit && HoveredUnit != SelectedUnit)
			{
				BottomString += $"attack {HoveredUnit.DisplayName}";
			}
		}
		else if (HoveredObject.IsValid())
		{
			BottomString += $"{HoveredObject.DisplayName}\n";
		}

		return BottomString;
	}

	public readonly bool AnythingSelected()
	{
		return SelectedUnit != null || SelectedHex != null;
	}
}

public sealed partial class GamePlayer : Component
{
	public bool IsDead()
	{
		foreach (var Hex in GameManager.Instance.BoardHexes)
		{
			if (Hex.UnitData?.OwnerGuid == ConnectionId || Hex.BuildingData?.OwnerGuid == ConnectionId)
			{
				return false;
			}
		}

		return true;
	}

	public void GetPlayerUiInfo(out FPlayerUiInfo OutPlayerUiInfo)
	{
		OutPlayerUiInfo = new();
		OutPlayerUiInfo.Name = Local?.SteamName;
		OutPlayerUiInfo.Gold = Gold;

		GameManager.Instance.GetPlayerBoardStats(out var PlayerBoardStats);
		if (PlayerBoardStats.TryGetValue(Local, out var PlayerStats))
		{
			OutPlayerUiInfo.Territory = PlayerStats.TerritoryPercentage;
			OutPlayerUiInfo.Production = PlayerStats.Production;
		}

		OutPlayerUiInfo.SelectedHex = SelectedHex;
		OutPlayerUiInfo.SelectedUnit = SelectedUnit;
		OutPlayerUiInfo.HoveredObject = HoveredObject;

		// if we've got a valid hex what can we build
		if (SelectedHex.IsValid() && SelectedHex.BuildingObject.IsValid())
		{
			foreach (var BuildUnit in SelectedHex.BuildingObject.BuildableUnits)
			{
				if (BuildUnit.GetComponent<ObjectUnit>() is { } BuildObjectUnit)
				{
					OutPlayerUiInfo.BuildObjects.Add($"{BuildObjectUnit.ObjectId} £{BuildObjectUnit.GoldToBuild}");
				}
			}
		}
	}

	private void RefreshGUi()
	{
		if (GUi == null)
		{
			return; // :(
		}

		var UpgradeModel = GUi.Upgrade.GetComponent<SkinnedModelRenderer>();
		Assert.NotNull(UpgradeModel);
		UpgradeModel.Tint = Colour;
		var Outline = UpgradeModel.GetOrAddComponent<HighlightOutline>();
		Outline.Color = Colour;

		var AvatarDresser = GUi.Avatar.GetComponent<Dresser>();
		Assert.NotNull(AvatarDresser);
		var AvatarModel = GUi.Avatar.GetComponent<SkinnedModelRenderer>();
		Assert.NotNull(AvatarModel);

		if (HoveredObject is ObjectUnit HoveredUnit && HoveredObject.IsValid())
		{
			AvatarModel.Tint = HoveredUnit.ModelRenderer.Tint;
			AvatarDresser.Clothing = HoveredUnit.GetComponent<Dresser>()?.Clothing;
			AvatarDresser.Source = Dresser.ClothingSource.Manual;
			AvatarDresser.Apply();
		}
		else if (SelectedUnit is ObjectUnit CastSelectedUnit && SelectedUnit.IsValid())
		{
			AvatarModel.Tint = CastSelectedUnit.ModelRenderer.Tint;
			AvatarDresser.Clothing = CastSelectedUnit.GetComponent<Dresser>()?.Clothing; // ?
			AvatarDresser.Source = Dresser.ClothingSource.Manual;
			AvatarDresser.Apply();
		}
		else
		{
			AvatarModel.Tint = Color.White;
			AvatarDresser.Source = Dresser.ClothingSource.LocalUser;
			AvatarDresser.Apply();
		}

		SetGuiStrings();
	}

	private void SetGuiStrings()
	{
		var BottomTextString = $"\u2800";

		if (HoveredObject is ObjectUnit ObjectUnit)
		{
			var UnitPrefix = ObjectUnit.IsAi ? "Native" : $"{ObjectUnit.OwnerPlayer.SteamName}'s";
			BottomTextString = $"{UnitPrefix} {ObjectUnit.DisplayName}\n{ObjectUnit.Health}hp";

			if (ObjectUnit.OwnerPlayer?.ConnectionId == Local.ConnectionId)
			{
				BottomTextString += $"\n{ObjectUnit.Attack} Attack";
			}
		}
		else if (HoveredObject is ObjectBuilding ObjectBuilding)
		{
			BottomTextString = $"{ObjectBuilding.DisplayName} {ObjectBuilding.Health}hp";
		}
		else if (HoveredObject is Upgrade ObjectUpgrade)
		{
			BottomTextString = $"{ObjectUpgrade.GetDesciption()}";
		}
		else
		{
			GameManager.Instance.GetPlayerBoardStats(out var BoardStats);
			if (BoardStats.ContainsKey(this))
			{
				BottomTextString = $"\u00A3{Gold}\n{BoardStats[this].Production}⬡\n{BoardStats[this].TerritoryPercentage}%";
			}
		}

		var BottomTextRenderer = GUi.BottomText.GetComponent<TextRenderer>();
		Assert.NotNull(BottomTextRenderer);
		BottomTextRenderer.Text = $"{BottomTextString}";
	}
}

public sealed class GamePlayerGUi : Component
{
	[Property] public GameObject Upgrade { get; private set; }
	[Property] public GameObject BottomText { get; private set; }
	[Property] public GameObject Avatar { get; private set; }

	private bool ShowUpgrades = false;
	private List<GameObject> ShownUpgrades = new();
	public void OnUpgradeClicked(GamePlayer UpgradePlayer)
	{
		ShowUpgrades = !ShowUpgrades;
		if (!ShowUpgrades)
		{
			foreach (var ShownUpgrade in ShownUpgrades)
			{
				ShownUpgrade.Destroy();
			}
			ShownUpgrades.Clear();
			return;
		}
		
		if (UpgradePlayer == null)
		{
			Log.Warning("upgrade pressed by no-one (spooky)");
			return;
		}

		float XOffset = 7.77f;
		foreach (var Upgrade in UpgradePlayer.Upgrades)
		{
			var UpgradeObject = GameManager.Instance.GetObject(Upgrade.ObjectId);

			CloneConfig CloneConfig = new()
			{
				Parent = this.Upgrade,
				StartEnabled = true,
			};
			
			var ShownUpgrade = UpgradeObject.Clone(CloneConfig);
			ShownUpgrade.LocalScale = Vector3.One;
			ShownUpgrade.WorldPosition += Vector3.Right * XOffset;
			ShownUpgrades.Add(ShownUpgrade);

			XOffset += 5f;
		}
	}
}