Object/ObjectUnit.cs

Component and data definitions for a game unit. AiUnit is an AI component that can order its unit to move to a random neighboring hex on the server. FUnit is a plain record used for serialization/representation of a unit. ObjectUnit is the game object class for a unit, handles visuals, upgrades, camera-mode-dependent UI (health and build previews), animation gaze, and spawning text-building previews.

NetworkingFile Access
using Forp.Game;
using Sandbox;
using Sandbox.Citizen;
using Sandbox.Diagnostics;
using Sandbox.Resources;
using System;

namespace Forp.Object.Unit;

public class AiUnit : Component
{
	private ObjectUnit ObjectUnit { get => GetComponent<ObjectUnit>(); }

	public void MoveRandomly_ServerOnly()
	{
		Assert.True(Networking.IsHost);

		if (Random.Shared.FromList(ObjectUnit.OwnerHex.AllBrothers.ToList()) is { } MoveHex)
		{
			GameManager.Instance.Server_MoveUnitToHex(ObjectUnit.OwnerHex, MoveHex, Connection.Local.Id);
		}
	}
}

public record FUnit : IObj
{
	public string ObjectId { get; set; }

	public string Name { get; set; }
	public Transform Transform { get; set; }
	public Guid OwnerGuid { get; set; }

	public Hex Hex { get; set; }
	public int TurnsAlive { get; set; }
	public int ViewRange { get; set; }

	public int Health { get; set; }
	public int Attack { get; set; }

	public int ActionPoints { get; set; }
	public int ActionPointsSpent { get; set; }

	public FUpgrade Upgrade { get; set; }
	public bool IsAi { get; set; }
	public Hex HomeHex { get; set; } // TODO : AI ONLY DATA
}

public class ObjectUnit : Obj
{
	[RequireComponent] public HighlightOutline HighlightOutline { get; set; }
	[RequireComponent] public CitizenAnimationHelper Animation { get; set; }

	[Property] public List<GameObject> Buildings { get; set; }
	[Property] public int ViewRange { get; set; }
	[Property] public int ActionPoints { get; set; }
	[Property] public int ProductionToBuild { get; set; }
	[Property] public int GoldToBuild { get; set; }


	public bool IsAi { get => GetComponent<AiUnit>() != null; }

	protected override void OnStart()
	{
		base.OnStart();

		HighlightOutline.Enabled = false;

		if (OwnerPlayer != null)
		{
			ModelRenderer.Tint = GameManager.Instance.GetTintColour(OwnerPlayer.ConnectionId);
		}

		if (IsAi)
		{
			ModelRenderer.Tint = GameManager.Instance.GetTintColour(GameManager.AiGuid);
			return;
		}

		if (GamePlayer.Local == null)
		{
			if (GameManager.Instance.Mode != EGameManagerMode.Menu)
			{
				Log.Warning("game player somehow not valid, not good!");
			}
			return;
		}

		SetCameraMode(GamePlayer.Local.CameraMode);
	}

	protected override void OnDestroy()
	{
		ShowHealth = false;
		ShowBuildings = false;

		base.OnDestroy();
	}

	protected override void OnUpdate()
	{
		base.OnUpdate();

		if (GameManager.Instance.Mode != EGameManagerMode.Menu)
		{	
			Animation.LookAtEnabled = true;
			Animation.LookAt = GamePlayer.Local.Camera.GameObject;
		}
	}

	public FUpgrade Upgrade { get; private set; } = null;
	public void ApplyUpgrade(FUpgrade InUpgrade)
	{
		Assert.NotNull(InUpgrade);

		if (Upgrade != null)
		{
			return;
		}

		Upgrade = InUpgrade;

		Attack += Upgrade.AttackModifyer;
		Health += Upgrade.HealthModifyer;

		foreach (var BuildingId in Upgrade.BuildingIds)
		{
			Buildings.Add(BuildingId);
		}

		SetCameraMode(GamePlayer.Local.CameraMode);
	}


	protected override void OnDamageTaken_Internal()
	{
		base.OnDamageTaken_Internal();

		ToggleHealth(ShowHealth);
	}

	public void SetCameraMode(ECameraMode CameraMode)
	{
		ShowHealth = CameraMode == ECameraMode.Combat;
		ShowBuildings = CameraMode == ECameraMode.Build;
	}

	private bool _showBuildings = false;
	public bool ShowBuildings
	{
		get => _showBuildings;
		set
		{
			if (OwnerPlayer == null || GamePlayer.Local == null)
			{
				return;
			}

			if (GamePlayer.Local.ConnectionId != OwnerPlayer.ConnectionId)
			{
				return;
			}

			_showBuildings = value;
			ToggleBuidlings(_showBuildings);
		}
	}

	private readonly List<GameObject> TextBuildingObjects = new();
	private void ToggleBuidlings(bool Show)
	{
		foreach (var TextBuilding in TextBuildingObjects)
		{
			TextBuilding.Destroy();
		}
		TextBuildingObjects.Clear();

		if (!Show)
		{
			return;
		}
		List<GameObject> ValidBuildings = new();
		foreach (var Building in Buildings)
		{
			if (Building.IsValid() && Building.GetComponent<TextBuilding>().CanBeBuilt(OwnerHex))
			{
				ValidBuildings.Add(Building);
			}
		}

		for (int BuildingIndex = 0; BuildingIndex < ValidBuildings.Count; ++BuildingIndex)
		{
			var TextBuilding = ValidBuildings[BuildingIndex];
			var TextBuildingComponent = TextBuilding.GetComponent<TextBuilding>();

			Transform Transform = new();

			float HorizontalOffset = (BuildingIndex - (ValidBuildings.Count - 1) / 2f) * 100f;
			Transform.Position += new Vector3(0f, HorizontalOffset, 150f);
			
			var BuildingComponent = GamePlayer.SpawnObject<TextBuilding>(TextBuilding, Transform, Connection.Local, GameObject);

			BuildingComponent.BelongingHex = OwnerHex;
			TextBuildingObjects.Add(BuildingComponent.GameObject);
		}
	}
}