GameMode/Rules/Dm/MgeGamemode.cs
using KOTH.World;
using Sandbox;
using Sandbox.Diagnostics;
using Sandbox.Events;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;

namespace KOTH;

public readonly struct FDMUIData
{
	public FDMUIData() { }

	public string Player1Name { get; init; } = string.Empty;
	public string Player2Name { get; init; } = string.Empty;
	public string Player3Name { get; init; } = string.Empty;
	public int Player1Score { get; init; } = -1;
	public int Player2Score { get; init; } = -1;
	public int Player3Score { get; init; } = -1;

	public readonly bool IsValid()
	{
		return Player1Name != string.Empty && Player2Name != string.Empty;
	}

	public readonly bool IsThree()
	{
		return IsValid() && Player3Name != string.Empty;
	}
}

public sealed class DMUI
{
	public static DmGamemode CurrentDmGamemode => GameMode.Instance?.StateMachine.CurrentState.GameObject.Components.Get<DmGamemode>();
}

public sealed class DmGamemode : Component,
	IGameEventHandler<EnterStateEvent>,
	IGameEventHandler<UpdateStateEvent>,
	IGameEventHandler<KillBroadcastEvent>
{
	[Property] public int KillLimit { get; set; } = 100;

	[Sync(SyncFlags.FromHost)] public NetDictionary<PlayerState, int> PlayerScores { get; set; }

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

		if (Networking.IsHost)
		{
			PlayerScores = new();
		}
	}

	public bool QueryUIData(out FDMUIData UiData)
	{
		if (PlayerScores.Count < 2)
		{
			UiData = new();
			return false;
		}

		var Top = PlayerScores.OrderByDescending(Kvp => Kvp.Value).Take(3).ToArray();

		var P1 = Top.ElementAt(0);
		var P2 = Top.ElementAt(1);
		if (PlayerScores.Count > 2)
		{
			var P3 = Top.ElementAt(2);

			UiData = new()
			{
				Player1Name = P1.Key.SteamName,
				Player1Score = P1.Value,
				Player2Name = P2.Key.SteamName,
				Player2Score = P2.Value,
				Player3Name = P3.Key.SteamName,
				Player3Score = P3.Value,
			};
		}
		else
		{
			UiData = new()
			{
				Player1Name = P1.Key.SteamName,
				Player1Score = P1.Value,
				Player2Name = P2.Key.SteamName,
				Player2Score = P2.Value,
			};
		}

		Log.Info(PlayerScores.Count);

		return true;
	}

	public void OnGameEvent(KillBroadcastEvent KillEvent)
	{
		if (!Networking.IsHost)
		{
			return;
		}

		// HACK, connection events too fast, maybe need a player state active event?
		foreach (var PlayerState in GameNetworkManager.PlayerStates)
		{
			if (!PlayerScores.ContainsKey(PlayerState))
			{
				PlayerScores.Add(PlayerState, 0);
			}
		}
		foreach (var (Player, _) in PlayerScores)
		{
			if (!GameNetworkManager.PlayerStates.Contains(Player))
			{
				PlayerScores.Remove(Player);
			}
		}

		var DamageEvent = KillEvent.DamageEvent;
		if (DamageEvent.VictimPlayerState == null)
		{
			return;
		}

		var Attacker = DamageEvent.AttackerPlayerState;

		if (!Attacker.IsValid())
		{
			return;
		}

		if (!PlayerScores.TryGetValue(Attacker, out var Score))
		{
			return;
		}

		PlayerScores[Attacker] = ++Score;

		if (Score < KillLimit)
		{
			return;
		}

		// TODO : make a song & dance
		if (GetComponent<StateComponent>() is { } ParentState)
		{
			Assert.IsValid(ParentState.DefaultNextState);
			GameMode.Instance.StateMachine.Transition(ParentState.DefaultNextState);
		}
	}

	void IGameEventHandler<EnterStateEvent>.OnGameEvent(EnterStateEvent eventArgs)
	{
		PlayerScores.Clear();

		foreach (var PlayerState in GameNetworkManager.PlayerStates)
		{
			PlayerScores.Add(PlayerState, 0);
		}
	}

	public void OnGameEvent(UpdateStateEvent eventArgs)
	{
		if (GameNetworkManager.PlayerStates.Count < 2)
		{
			if (GameObject.GetComponent<StateComponent>() is { } ParentState)
			{
				Assert.IsValid(ParentState.DefaultNextState);
				GameMode.Instance.StateMachine.Transition(ParentState.DefaultNextState);
			}
		}
	}
}