Ui/Chat.razor.cs
using System;
using Clover.Player;
using Sandbox.UI;

namespace Clover;

public partial class Chat
{
	public static Chat Instance { get; private set; }

	public List<ChatMessage> Messages { get; set; } = new();

	public List<ChatMessage> DistantMessages => Messages.Where( x => !x.IsNearby ).ToList();

	private bool ShouldShowMessages => DistantMessages.Count > 0 || ShouldShowInput;

	private bool ShouldShowInput;

	public record ChatMessage
	{
		public string Text { get; init; }
		public ulong Author { get; init; }
		public string PlayerId { get; init; }
		public DateTime Time { get; init; }

		public string Name => IsYou ? "You" : (Player.IsValid() ? Player.PlayerName : "Unknown");

		public bool IsYou => PlayerId == PlayerCharacter.Local?.PlayerId;

		public PlayerCharacter Player => Game.ActiveScene.GetAllComponents<PlayerCharacter>()
			.FirstOrDefault( x => x.PlayerId == PlayerId /*|| x.Network.Owner.SteamId == Author*/ );

		public bool IsNearby => Player.IsValid() && PlayerCharacter.Local.IsValid() &&
		                        PlayerCharacter.Local.WorldPosition.Distance( Player.WorldPosition ) < 300;

		public string Location { get; set; } = "Unknown";
	}


	private string MessageText { get; set; } = "";

	private TextEntry InputBox;


	protected override int BuildHash() =>
		System.HashCode.Combine( Messages, DistantMessages, ShouldShowInput, MessageText );

	protected override void OnAwake()
	{
		Instance = this;
	}

	protected override void OnDestroy()
	{
		Instance = null;
	}

	protected override void OnFixedUpdate()
	{
		if ( Input.EscapePressed )
		{
			ShouldShowInput = false;
			StateHasChanged();
		}

		if ( Input.Pressed( "Chat" ) )
		{
			if ( !ShouldShowInput )
			{
				Log.Info( "Enable Chat" );
				ShouldShowInput = true;
				StateHasChanged();
				InputBox?.Focus();
				return;
			}

			SendMessage();
		}

		if ( Messages.RemoveAll( x => x.Time < DateTime.Now.AddSeconds( -30 ) ) > 0 )
		{
			StateHasChanged();
		}
	}

	private void HideInput()
	{
		ShouldShowInput = false;
		StateHasChanged();
	}

	private void SendMessage()
	{
		if ( !string.IsNullOrWhiteSpace( MessageText ) )
		{
			var location = PlayerCharacter.Local.World.Title;
			AddMessage( MessageText, PlayerCharacter.Local.PlayerId, location );
			MessageText = "";
		}

		ShouldShowInput = false;
		StateHasChanged();
	}

	[Rpc.Broadcast]
	public void AddMessage( string message, string playerId, string location )
	{
		var msg = new ChatMessage()
		{
			Text = message,
			Author = Rpc.Caller.SteamId,
			Time = DateTime.Now,
			PlayerId = playerId,
			Location = location
		};
		Messages.Add( msg );
		Log.Info( $"> {msg.Name} ({Rpc.Caller.DisplayName}): {msg.Text}" );
		StateHasChanged();
	}

	[ConCmd( "say" )]
	public static void Say( string message )
	{
		Instance.AddMessage( message, PlayerCharacter.Local.PlayerId, PlayerCharacter.Local.World.Title );
	}
}