Player component for the game. Manages local player state, camera and GUI creation, input for movement, clicking, dragging upgrades, unit selection and unit/hex interactions, plus some server-only initialization and utility spawn helpers.
using Forp.Object;
using Forp.Object.Building;
using Forp.Object.Unit;
using Sandbox.Diagnostics;
using System;
using System.Linq.Expressions;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Threading;
using static Sandbox.Services.Stats;
namespace Forp.Game;
public enum ECameraMode
{
Normal,
Build,
Combat,
}
public record FPlayerBoardStats
{
public int Production { get; set; }
public int TerritoryCount { get; set; }
public int TerritoryPercentage { get => (int)(((float)TerritoryCount / GameManager.Instance.BoardHexes.Count) * 100f); }
}
public sealed partial class GamePlayer : Component
{
public static GamePlayer Local { get; private set; } = null;
[Property] public GameObject PlayerCameraPrefab { get; private set; }
public CameraComponent Camera { get; private set; }
public GamePlayerGUi GUi { get; private set; }
[Sync(SyncFlags.FromHost), Property] public ulong SteamId { get; private set; }
[Sync(SyncFlags.FromHost), Property] public string SteamName { get; private set; }
[Sync(SyncFlags.FromHost), Property] public Guid ConnectionId { get; private set; }
[Sync(SyncFlags.FromHost), Property] public NetList<FUpgrade> Upgrades { get; private set; }
[Sync(SyncFlags.FromHost), Property] public Color Colour { get; set; }
[Sync(SyncFlags.FromHost), Property] public int Gold { get; set; }
public override string ToString()
{
return $"GamePlayer : {SteamName} : c={IsConnected}?{(IsConnected ? Connection : string.Empty)} ai={IsAi}";
}
public Connection Connection { get; private set; }
public bool IsConnected => Connection != null && Connection.IsActive;
private ECameraMode _CameraMode = ECameraMode.Build;
public ECameraMode CameraMode
{
get => _CameraMode;
set
{
if (_CameraMode == value)
{
return;
}
_CameraMode = value;
OnCameraModeChange();
}
}
public GameObject DraggedObject { get; private set; }
public Obj HoveredObject { get; private set; }
public Hex SelectedHex { get; private set; }
private ObjectUnit _SelectedUnit = null;
public ObjectUnit SelectedUnit
{
get => _SelectedUnit;
set
{
if (value != null)
{
_SelectedUnit = value;
SelectUnit();
}
else
{
DeselectUnit();
_SelectedUnit = value;
}
}
}
private void OnCameraModeChange()
{
foreach (var Hexogon in GameManager.Instance.BoardHexes)
{
Hexogon.LocalCameraMode = CameraMode;
}
}
private void SelectUnit()
{
if (SelectedUnit.OwnerPlayer?.ConnectionId != Local.ConnectionId)
{
return;
}
if (GameManager.Instance.HACK_GetHexFromUnit(SelectedUnit) is { } UnitHex)
{
SelectedUnit.HighlightOutline.Enabled = true;
var MoveRange = UnitHex.UnitData.ActionPoints - UnitHex.UnitData.ActionPointsSpent + 1;
Hex.HighlightHexesRecusrive(UnitHex, true, MoveRange);
}
}
private void DeselectUnit()
{
if (GameManager.Instance.HACK_GetHexFromUnit(SelectedUnit) is { } UnitHex)
{
SelectedUnit.HighlightOutline.Enabled = false;
Hex.HighlightHexesRecusrive(UnitHex, false, SelectedUnit.ActionPoints + 1);
}
foreach (var Hex in GameManager.Instance.BoardHexes)
{
Hex.IsHighlighted = false;
}
}
protected override void OnStart()
{
base.OnStart();
if (!Network.IsOwner)
{
return;
}
foreach (var Hex in GameManager.Instance.BoardHexes)
{
if (Hex.UnitData == null)
{
continue;
}
if (Hex.UnitData.OwnerGuid == ConnectionId && Hex.UnitData.ObjectId == "unit-settler")
{
SelectedUnit = Hex.UnitObject;
break;
}
}
}
protected override void OnAwake()
{
base.OnAwake();
if (!Network.IsOwner)
{
return; // !
}
Log.Info($"player awoke ai={IsAi}");
Connection = Connection.Local; // TODO : this is only set on server & local client per player...
// why dont connections replicate ? [may/26]
if (IsAi)
{
return; // !
}
Local = this;
Mouse.Visibility = MouseVisibility.Visible;
Assert.True(CreateCamera());
RefreshGUi();
}
private bool CreateCamera()
{
Transform CloneTransform = new();
var Cloneconfig = new CloneConfig();
Cloneconfig.Parent = GameObject;
Cloneconfig.StartEnabled = true;
Cloneconfig.Transform = CloneTransform;
var CameraObject = PlayerCameraPrefab.Clone(Cloneconfig);
Camera = CameraObject.GetComponentInChildren<CameraComponent>();
if (Camera == null)
{
Log.Error("failed to create camera for local player!");
return Camera != null;
}
GUi = CameraObject.GetComponentInChildren<GamePlayerGUi>(true);
if (GUi == null)
{
Log.Error("failed to create gui for local player!");
return GUi != null;
}
return true;
}
protected override void OnUpdate()
{
if (IsProxy || IsAi)
{
return;
}
DoMovement();
DoAction();
var HoverRay = Camera.ScreenPixelToRay(Mouse.Position);
var HoverTraces = Scene.Trace.Ray(HoverRay.Position, HoverRay.Position + HoverRay.Forward * 10000f).RunAll();
foreach (var HoverTrace in HoverTraces)
{
if (!HoverTrace.Hit)
{
continue;
}
if (HoverTrace.GameObject.GetComponent<Obj>() is { } HitObject)
{
// special case to make upgrades work
// TODO : this should be a list!
if (HitObject is Upgrade && DraggedObject != null)
{
continue;
}
HoveredObject = HitObject;
break;
}
}
RefreshGUi();
}
public bool Initilize_ServerOnly(Connection ConnectionIn, Hex SpawnHex, bool IsAiIn)
{
Assert.True(Networking.IsHost);
Assert.NotNull(ConnectionIn);
if (IsAiIn)
{
IsAi = IsAiIn;
Connection = ConnectionIn;
ConnectionId = Guid.NewGuid();
SteamId = 0;
SteamName = $"{Random.Shared.Next()}";
GameManager.Instance.Server_CreateHexBuildingObject("building-city", SpawnHex, false, ConnectionId);
}
else
{
Connection = ConnectionIn;
ConnectionId = ConnectionIn.Id;
SteamId = ConnectionIn.SteamId;
SteamName = ConnectionIn.DisplayName;
GameManager.Instance.Server_CreateHexUnitObject("unit-settler", SpawnHex, ConnectionId, false);
}
Log.Info($"{this} is initing");
var ValidBrothers = SpawnHex.AllBrothers.Where(Hex => Hex != null && Hex.ObjectData == null && Hex.CanWalkOn()).OrderBy(Hex => Random.Shared.Next());
if (!ValidBrothers.Any())
{
Log.Warning($"urrr, couldn't spawn a brother unit... this isn't good...");
return false;
}
var Brother = ValidBrothers.First();
GameManager.Instance.Server_CreateHexUnitObject("unit-combat", Brother, ConnectionId, false);
return true;
}
const float MovementExp = 2f;
private void DoMovement()
{
Camera.WorldPosition += Camera.WorldRotation.Forward * Input.MouseWheel.y * 50;
Vector3 FlatForward = Camera.WorldRotation.Forward.WithZ(0).Normal;
Vector3 FlatRight = Camera.WorldRotation.Right.WithZ(0).Normal;
if (Input.Down("forward"))
{
Camera.WorldPosition += FlatForward * MovementExp;
}
if (Input.Down("backward"))
{
Camera.WorldPosition -= FlatForward * MovementExp;
}
if (Input.Down("left"))
{
Camera.WorldPosition -= FlatRight * MovementExp;
}
if (Input.Down("right"))
{
Camera.WorldPosition += FlatRight * MovementExp;
}
if (Input.Down("mouse2"))
{
Camera.WorldPosition += FlatForward * Mouse.Delta.y + FlatRight * -Mouse.Delta.x;
}
if (Input.Down("mouse3"))
{
float Distance = Camera.WorldPosition.z / -Camera.WorldRotation.Forward.z;
Vector3 Pivot = Camera.WorldPosition + Camera.WorldRotation.Forward * Distance;
var Rotation = global::Rotation.FromAxis(Vector3.Up, -Mouse.Delta.x * 0.2f);
Camera.WorldPosition = Pivot + Rotation * (Camera.WorldPosition - Pivot);
Camera.WorldRotation = Rotation * Camera.WorldRotation;
}
if (DraggedObject != null)
{
if (!Input.Down("mouse1"))
{
if (HoveredObject is ObjectUnit UpgradeUnit)
{
var Upgrade = DraggedObject.GetComponent<Upgrade>();
GameManager.Instance.Server_UpgradeObject(Upgrade.GetUpgradeData(), UpgradeUnit.OwnerHex, ConnectionId);
for (int Index = Upgrades.Count - 1; Index >= 0; --Index)
{
if (Upgrades[Index].ObjectId == Upgrade.ObjectId)
{
Upgrades.RemoveAt(Index);
break;
}
}
DraggedObject.Destroy();
}
GUi.OnUpgradeClicked(this); // 1
GUi.OnUpgradeClicked(this); // 2
DraggedObject = null; // this code is poo
return;
}
var Yaw = Scene.Camera.ScreenPixelToRay(Mouse.Position);
var Hit = DragPlane.Trace(Yaw, true);
if (Hit.HasValue)
{
DraggedObject.WorldPosition = Hit.Value + GrabOffset;
}
}
}
private Plane DragPlane;
private Vector3 GrabOffset;
private void DoAction()
{
if (Input.Pressed("mouse1"))
{
Mouse.CursorType = "crosshair";
List<Obj> ClickedObjects = new();
var ClickRay = Camera.ScreenPixelToRay(Mouse.Position);
var ClickTraces = Scene.Trace.Ray(ClickRay.Position, ClickRay.Position + ClickRay.Forward * 10000f).RunAll();
foreach (var ClickTrace in ClickTraces)
{
if (!ClickTrace.Hit)
{
continue;
}
if (ClickTrace.GameObject.GetComponent<Upgrade>() is { })
{
DraggedObject = ClickTrace.GameObject;
DraggedObject.WorldRotation = new();
DragPlane = new Plane(DraggedObject.WorldPosition, -Scene.Camera.WorldRotation.Forward);
var Ray = Scene.Camera.ScreenPixelToRay(Mouse.Position);
var Hit = DragPlane.Trace(Ray, true);
if (Hit.HasValue)
{
GrabOffset = DraggedObject.WorldPosition - Hit.Value;
}
}
else if (GUi != null && ClickTrace.GameObject == GUi.Upgrade)
{
GUi.OnUpgradeClicked(Local);
}
else if (ClickTrace.GameObject.GetComponent<Obj>() is { } HitObject)
{
HitObject.OnClick();
ClickedObjects.Add(HitObject);
}
}
///////////////////////////////
// TODO : sort these & use virt
foreach (var ClickedObject in ClickedObjects)
{
if (ClickedObject is ObjectUnit ObjectUnit)
{
DeselectHex();
if (SelectedUnit == ObjectUnit)
{
SelectedUnit = null;
return;
}
if (SelectedUnit.IsValid())
{
if (GameManager.Instance.CanAttack(SelectedUnit.OwnerHex, ObjectUnit.OwnerHex, ObjectUnit.OwnerHex.UnitData, Connection.Local.Id, out var _, out var _))
{
GameManager.Instance.Server_UnitAttackUnit(SelectedUnit.OwnerHex, ObjectUnit.OwnerHex, Connection.Local.Id);
}
SelectedUnit = null;
return;
}
SelectedUnit = ObjectUnit;
return;
}
if (ClickedObject is ObjectBuilding ObjectBuilding)
{
if (SelectedUnit.IsValid())
{
if (GameManager.Instance.CanAttack(SelectedUnit.OwnerHex, ObjectBuilding.OwnerHex, ObjectBuilding.OwnerHex.BuildingData, Connection.Local.Id, out var _, out var _))
{
GameManager.Instance.Server_UnitAttackBuilding(SelectedUnit.OwnerHex, ObjectBuilding.OwnerHex, Connection.Local.Id);
}
SelectedUnit = null;
}
}
}
///////////////////////////////
var PriorHex = SelectedHex;
DeselectHex();
var FoundHex = GetHexFromMousePos(Mouse.Position);
if (!FoundHex.IsValid() || PriorHex == FoundHex)
{
return;
}
if (SelectedUnit.IsValid() && SelectedUnit.GetComponent<AiUnit>() == null)
{
if (GameManager.Instance.HACK_GetHexFromUnit(SelectedUnit) is var UnitHex && UnitHex.IsValid())
{
Hex.HighlightHexesRecusrive(UnitHex, false, SelectedUnit.ActionPoints + 1);
GameManager.Instance.Server_MoveUnitToHex(UnitHex, FoundHex, Connection.Id);
}
}
else
{
SelectedHex = FoundHex;
SelectedHex.OnClick();
}
SelectedUnit = null;
}
if (Input.Pressed("camera_combat"))
{
CameraMode = ECameraMode.Combat;
}
if (Input.Pressed("camera_normal"))
{
CameraMode = ECameraMode.Normal;
}
if (Input.Pressed("camera_build"))
{
CameraMode = ECameraMode.Build;
}
}
private void DeselectHex()
{
if (!SelectedHex.IsValid())
{
return;
}
SelectedHex.IsSelected = false;
SelectedHex = null;
}
private Hex GetHexFromMousePos(Vector2 MousePos)
{
var ClickRay = Camera.ScreenPixelToRay(MousePos);
var ClickTraces = Scene.Trace.Ray(ClickRay.Position, ClickRay.Position + ClickRay.Forward * 10000f).RunAll();
foreach (var ClickTrace in ClickTraces)
{
if (!ClickTrace.Hit)
{
continue;
}
if (ClickTrace.GameObject.GetComponent<Object.Hex>() is { } HitHex)
{
return HitHex;
}
}
return null;
}
//////////////////////////////////////////////////////////////////////////////
// TODO : move me
// & clean this shi up
public static T SpawnObject<T>(GameObject SpawnGameObject, Transform SpawnTransform, Connection Connection, GameObject Parent = null, bool NetworkSpawn = false) where T : new()
{
var SpawnedGameObject = SpawnObject(SpawnGameObject, SpawnTransform, Connection, Parent, NetworkSpawn);
var SpawnedComponent = SpawnedGameObject.GetComponent<T>();
Assert.NotNull(SpawnedComponent, $"FAILED TO SPAWN OBJECT {SpawnObject} FOR CONNECTION {Connection}");
return SpawnedComponent;
}
public static GameObject SpawnObject(GameObject SpawnObject, Transform SpawnTransform, Connection Connection, GameObject Parent = null, bool NetworkSpawn = false)
{
Assert.NotNull(Connection, $"SPAWN CONNECTION NULL");
Assert.NotNull(SpawnObject, $"SPAWN OBJECT NULL");
SpawnObject.NetworkMode = NetworkSpawn ? NetworkMode.Object : NetworkMode.Never;
CloneConfig SpawningConfig = new(SpawnTransform);
SpawningConfig.Parent = Parent;
var SpawnedObject = SpawnObject.Clone(SpawningConfig);
Assert.NotNull(SpawnedObject, $"FAILED TO SPAWN OBJECT {SpawnObject} FOR CONNECTION {Connection}");
if (NetworkSpawn)
{
SpawnedObject.Network.SetOrphanedMode(NetworkOrphaned.Destroy);
SpawnedObject.NetworkSpawn(Connection);
SpawnedObject.NetworkSpawn();
}
return SpawnedObject;
}
}