Decompiled source of GlobalChat v1.0.1

plugins/GlobalChat.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using HarmonyLib;
using SOD.Common;
using SOD.Common.BepInEx;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("GlobalChat")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+d0e60919a46380d27189dbc92c5189a9765ec9bd")]
[assembly: AssemblyProduct("GlobalChat")]
[assembly: AssemblyTitle("GlobalChat")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace GlobalChat;

public sealed class ChatCommandService
{
	private static readonly Random Random = new Random();

	public ChatCommandResult Resolve(string input)
	{
		if (string.IsNullOrWhiteSpace(input))
		{
			return new ChatCommandResult
			{
				Handled = true,
				ShouldSend = false,
				LocalFeedback = string.Empty
			};
		}
		string text = input.Trim();
		if (!text.StartsWith("//", StringComparison.Ordinal))
		{
			return new ChatCommandResult
			{
				Handled = false,
				ShouldSend = true,
				MessageType = "chat",
				RawText = input,
				FormattedText = "[" + GlobalChatRuntime.DisplayName + "] " + input
			};
		}
		string text2 = text.Substring(2);
		string[] array = text2.Split(new char[1] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
		string text3 = ((array.Length != 0) ? array[0].ToLowerInvariant() : string.Empty);
		string text4 = ((array.Length > 1) ? array[1] : string.Empty);
		switch (text3)
		{
		case "wealth":
		case "money":
			return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] has <color=#D4AF37>{PlayerContextService.GetWealthText()}</color>.");
		case "location":
			return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] is at <color=#00FFFF>{PlayerContextService.GetLocationText()}</color>.");
		case "case":
			return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] is working on case: <color=#BF40BF>{PlayerContextService.GetCaseText()}</color>.");
		case "time":
			return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] says the time is <color=#7FDBFF>{PlayerContextService.GetTimeText()}</color>.");
		case "social":
			return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] has social rating: <color=#7CFC00>{PlayerContextService.GetSocialText()}</color>.");
		case "roll":
			return SendFormatted("command", input, $"[{GlobalChatRuntime.DisplayName}] rolled <color=#FFA500>{ResolveRollValue(text4)}</color>.");
		case "mute":
			return ResolveMute(text4);
		case "unmute":
			return ResolveUnmute(text4);
		case "color":
		case "colour":
			return ResolveColor(text4);
		case "muted":
			return new ChatCommandResult
			{
				Handled = true,
				ShouldSend = false,
				LocalFeedback = ((GlobalChatRuntime.Instance != null) ? GlobalChatRuntime.Instance.GetMutedUsersSummary() : "Muted users: none")
			};
		case "help":
			return new ChatCommandResult
			{
				Handled = true,
				ShouldSend = false,
				LocalFeedback = "Commands: //wealth, //money, //location, //case, //time, //social, //roll [max], //mute <name>, //unmute <name>, //muted, //color [#RRGGBB|reset], //help, //me <text>"
			};
		case "me":
			if (string.IsNullOrWhiteSpace(text4))
			{
				return new ChatCommandResult
				{
					Handled = true,
					ShouldSend = false,
					LocalFeedback = "Usage: //me <text>"
				};
			}
			return SendFormatted("emote", input, "* " + GlobalChatRuntime.DisplayName + " " + text4);
		default:
			return new ChatCommandResult
			{
				Handled = true,
				ShouldSend = false,
				LocalFeedback = "Unknown command: //" + text3
			};
		}
	}

	private static ChatCommandResult SendFormatted(string messageType, string rawText, string formattedText)
	{
		return new ChatCommandResult
		{
			Handled = true,
			ShouldSend = true,
			MessageType = messageType,
			RawText = rawText,
			FormattedText = formattedText
		};
	}

	private static ChatCommandResult ResolveColor(string remainder)
	{
		if (GlobalChatRuntime.Instance == null)
		{
			return new ChatCommandResult
			{
				Handled = true,
				ShouldSend = false,
				LocalFeedback = "Color system is unavailable."
			};
		}
		string text = NormalizeNameArgument(remainder);
		if (string.IsNullOrWhiteSpace(text))
		{
			string userNameColorSummary = GlobalChatRuntime.Instance.GetUserNameColorSummary();
			return new ChatCommandResult
			{
				Handled = true,
				ShouldSend = false,
				LocalFeedback = userNameColorSummary
			};
		}
		if (text.Equals("reset", StringComparison.OrdinalIgnoreCase) || text.Equals("clear", StringComparison.OrdinalIgnoreCase))
		{
			GlobalChatRuntime.Instance.ResetUserNameColor();
			return new ChatCommandResult
			{
				Handled = true,
				ShouldSend = false,
				LocalFeedback = "Username color reset."
			};
		}
		if (!GlobalChatRuntime.Instance.TrySetUserNameColor(text, out var normalizedColor, out var error))
		{
			return new ChatCommandResult
			{
				Handled = true,
				ShouldSend = false,
				LocalFeedback = (string.IsNullOrWhiteSpace(error) ? "Usage: //color <#RRGGBB|reset>" : error)
			};
		}
		return new ChatCommandResult
		{
			Handled = true,
			ShouldSend = false,
			LocalFeedback = "Username color set to " + normalizedColor + "."
		};
	}

	private static ChatCommandResult ResolveMute(string remainder)
	{
		string text = NormalizeNameArgument(remainder);
		if (string.IsNullOrWhiteSpace(text))
		{
			return new ChatCommandResult
			{
				Handled = true,
				ShouldSend = false,
				LocalFeedback = "Usage: //mute <name>"
			};
		}
		bool flag = GlobalChatRuntime.Instance != null && GlobalChatRuntime.Instance.AddMutedUser(text);
		return new ChatCommandResult
		{
			Handled = true,
			ShouldSend = false,
			LocalFeedback = (flag ? ("Muted " + text + ".") : (text + " is already muted."))
		};
	}

	private static ChatCommandResult ResolveUnmute(string remainder)
	{
		string text = NormalizeNameArgument(remainder);
		if (string.IsNullOrWhiteSpace(text))
		{
			return new ChatCommandResult
			{
				Handled = true,
				ShouldSend = false,
				LocalFeedback = "Usage: //unmute <name>"
			};
		}
		bool flag = GlobalChatRuntime.Instance != null && GlobalChatRuntime.Instance.RemoveMutedUser(text);
		return new ChatCommandResult
		{
			Handled = true,
			ShouldSend = false,
			LocalFeedback = (flag ? ("Unmuted " + text + ".") : (text + " is not muted."))
		};
	}

	private static string NormalizeNameArgument(string value)
	{
		if (string.IsNullOrWhiteSpace(value))
		{
			return string.Empty;
		}
		string text = value.Trim();
		if (text.Length >= 2 && text.StartsWith("\"", StringComparison.Ordinal) && text.EndsWith("\"", StringComparison.Ordinal))
		{
			text = text.Substring(1, text.Length - 2).Trim();
		}
		return text;
	}

	private static string ResolveRollValue(string remainder)
	{
		int num = 100;
		if (!string.IsNullOrWhiteSpace(remainder))
		{
			string text = remainder.Trim();
			int result3;
			if (text.Contains("d", StringComparison.OrdinalIgnoreCase))
			{
				string[] array = text.Split(new char[2] { 'd', 'D' }, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length == 2 && int.TryParse(array[0], out var result) && int.TryParse(array[1], out var result2) && result > 0 && result <= 20 && result2 > 1 && result2 <= 1000)
				{
					int num2 = 0;
					for (int i = 0; i < result; i++)
					{
						num2 += Random.Next(1, result2 + 1);
					}
					return $"{num2} ({result}d{result2})";
				}
			}
			else if (int.TryParse(text, out result3) && result3 > 1)
			{
				num = Mathf.Min(result3, 1000);
			}
		}
		return $"{Random.Next(1, num + 1)} (1-{num})";
	}
}
public static class PlayerContextService
{
	public static string GetWealthText()
	{
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Expected O, but got Unknown
		try
		{
			if ((Object)(object)GameplayController.Instance != (Object)null)
			{
				return $"${GameplayController.Instance.money}";
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
			bool flag = default(bool);
			BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(23, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to read wealth: ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
			}
			log.LogWarning(val);
		}
		return "unknown wealth";
	}

	public static string GetLocationText()
	{
		//IL_0178: Unknown result type (might be due to invalid IL or missing references)
		//IL_017f: Expected O, but got Unknown
		try
		{
			if ((Object)(object)Player.Instance == (Object)null || ((Actor)Player.Instance).currentNode == null)
			{
				return "an unknown location";
			}
			NewNode currentNode = ((Actor)Player.Instance).currentNode;
			if ((Object)(object)currentNode.room != (Object)null)
			{
				string text = ((!string.IsNullOrWhiteSpace(currentNode.room.name)) ? currentNode.room.name : currentNode.room.GetName());
				string text2 = (((Object)(object)currentNode.building != (Object)null) ? ((Object)currentNode.building).name : "Unknown Building");
				return text + ", " + text2;
			}
			if ((Object)(object)currentNode.building != (Object)null && !string.IsNullOrWhiteSpace(((Object)currentNode.building).name))
			{
				return ((Object)currentNode.building).name;
			}
			StreetController val = (((Object)(object)currentNode.building != (Object)null) ? currentNode.building.street : null);
			if ((Object)(object)val == (Object)null && (Object)(object)((Actor)Player.Instance).currentGameLocation != (Object)null)
			{
				val = ((Actor)Player.Instance).currentGameLocation.thisAsStreet;
			}
			if ((Object)(object)val != (Object)null && !string.IsNullOrWhiteSpace(((Object)val).name))
			{
				return ((Object)val).name;
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
			bool flag = default(bool);
			BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(25, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Failed to read location: ");
				((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.Message);
			}
			log.LogWarning(val2);
		}
		return "an unknown location";
	}

	public static string GetCaseText()
	{
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Expected O, but got Unknown
		try
		{
			if ((Object)(object)CasePanelController.Instance == (Object)null || CasePanelController.Instance.activeCase == null)
			{
				return "no active case";
			}
			Case activeCase = CasePanelController.Instance.activeCase;
			if (!string.IsNullOrWhiteSpace(activeCase.name))
			{
				return activeCase.name;
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
			bool flag = default(bool);
			BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(28, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to read active case: ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
			}
			log.LogWarning(val);
		}
		return "an unknown case";
	}

	public static string GetTimeText()
	{
		//IL_0187: Unknown result type (might be due to invalid IL or missing references)
		//IL_018e: Expected O, but got Unknown
		try
		{
			object obj = ReadStaticProperty(typeof(SessionData), "Instance") ?? ReadStaticField(typeof(SessionData), "instance");
			if (obj != null)
			{
				string text = ReadFirstString(obj, "day", "dayName", "weekday", "weekdayName", "currentDayName");
				string text2 = ReadFirstString(obj, "gameTimeString", "timeString", "formattedTime", "currentTimeString");
				if (string.IsNullOrWhiteSpace(text2))
				{
					object obj2 = ReadFirstValue(obj, "decimalClock", "gameTime", "timeOfDay", "decimalTime");
					if (obj2 is float num)
					{
						text2 = num.ToString("0.00");
					}
					else if (obj2 is double num2)
					{
						text2 = num2.ToString("0.00");
					}
				}
				if (!string.IsNullOrWhiteSpace(text) && !string.IsNullOrWhiteSpace(text2))
				{
					return text2 + " on " + text;
				}
				if (!string.IsNullOrWhiteSpace(text2))
				{
					return text2;
				}
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
			bool flag = default(bool);
			BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(21, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to read time: ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
			}
			log.LogWarning(val);
		}
		return "an unknown time";
	}

	public static string GetSocialText()
	{
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dd: Expected O, but got Unknown
		try
		{
			if ((Object)(object)GameplayController.Instance != (Object)null)
			{
				object obj = ReadFirstValue(GameplayController.Instance, "socialCredit", "socialScore", "creditRating", "citizenRank", "socialRating");
				if (obj != null)
				{
					return obj.ToString();
				}
			}
			if ((Object)(object)Player.Instance != (Object)null)
			{
				object obj2 = ReadFirstValue(Player.Instance, "socialCredit", "socialScore", "creditRating", "citizenRank", "socialRating");
				if (obj2 != null)
				{
					return obj2.ToString();
				}
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
			bool flag = default(bool);
			BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(29, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to read social score: ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
			}
			log.LogWarning(val);
		}
		return "unknown";
	}

	private static object ReadStaticProperty(Type type, string propertyName)
	{
		PropertyInfo property = type.GetProperty(propertyName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		return (property != null) ? property.GetValue(null, null) : null;
	}

	private static object ReadStaticField(Type type, string fieldName)
	{
		FieldInfo field = type.GetField(fieldName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		return (field != null) ? field.GetValue(null) : null;
	}

	private static object ReadFirstValue(object target, params string[] memberNames)
	{
		if (target == null)
		{
			return null;
		}
		Type type = target.GetType();
		for (int i = 0; i < memberNames.Length; i++)
		{
			FieldInfo field = type.GetField(memberNames[i], BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field.GetValue(target);
			}
			PropertyInfo property = type.GetProperty(memberNames[i], BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null)
			{
				return property.GetValue(target, null);
			}
		}
		return null;
	}

	private static string ReadFirstString(object target, params string[] memberNames)
	{
		return ReadFirstValue(target, memberNames)?.ToString();
	}
}
[Serializable]
public class ChatMessageDto
{
	public string messageId;

	public string playerId;

	public string displayName;

	public string resolvedDisplayName;

	public string usernameColorHex;

	public string channelId;

	public string messageType;

	public string rawText;

	public string formattedText;

	public string filteredRawText;

	public string filteredFormattedText;

	public bool containsProfanity;

	public string createdAt;

	public string clientVersion;
}
[Serializable]
public class SendChatMessageRequest
{
	public string playerId;

	public string displayName;

	public string usernameColorHex;

	public string channelId;

	public string messageType;

	public string rawText;

	public string formattedText;

	public string clientVersion;

	public bool applyProfanityFilter = true;
}
[Serializable]
public class UpdatePresenceRequest
{
	public string playerId;

	public string displayName;

	public string usernameColorHex;

	public string clientVersion;

	public string currentStatus;
}
[Serializable]
public class ChatMessagesResponse
{
	public List<ChatMessageDto> messages = new List<ChatMessageDto>();

	public string nextCursor;

	public int onlineCount;

	public List<OnlinePlayerDto> onlinePlayers = new List<OnlinePlayerDto>();

	public bool success = true;

	public string error;
}
[Serializable]
public class ChatSendResponse
{
	public bool success = true;

	public string messageId;

	public string createdAt;

	public string error;
}
[Serializable]
public class PresenceUpdateResponse
{
	public bool success = true;

	public string updatedAt;

	public int onlineCount;

	public List<OnlinePlayerDto> onlinePlayers = new List<OnlinePlayerDto>();

	public string error;
}
[Serializable]
public class PresenceDisconnectResponse
{
	public bool success = true;

	public int onlineCount;

	public List<OnlinePlayerDto> onlinePlayers = new List<OnlinePlayerDto>();

	public string error;
}
[Serializable]
public class OnlinePlayerDto
{
	public string playerId;

	public string displayName;

	public string usernameColorHex;

	public string updatedAt;
}
public sealed class OnlinePlayerViewModel
{
	public string PlayerId { get; set; }

	public string DisplayName { get; set; }

	public string UsernameColorHex { get; set; }
}
public sealed class ChatMessageViewModel
{
	public string Id { get; set; }

	public string DisplayName { get; set; }

	public string ResolvedDisplayName { get; set; }

	public string UsernameColorHex { get; set; }

	public string DisplayText { get; set; }

	public string FilteredDisplayText { get; set; }

	public string RawText { get; set; }

	public string MessageType { get; set; }

	public bool ContainsProfanity { get; set; }

	public DateTime CreatedAtUtc { get; set; }
}
public sealed class ChatCommandResult
{
	public bool Handled { get; set; }

	public bool ShouldSend { get; set; }

	public string MessageType { get; set; }

	public string RawText { get; set; }

	public string FormattedText { get; set; }

	public string LocalFeedback { get; set; }
}
public enum ChatConnectionState
{
	Offline,
	Connecting,
	Online,
	Error
}
public interface IChatTransport
{
	ChatConnectionState ConnectionState { get; }

	string StatusText { get; }

	int OnlineUserCount { get; }

	IReadOnlyList<OnlinePlayerViewModel> OnlinePlayers { get; }

	void Initialize();

	void Tick(float unscaledDeltaTime);

	void QueueMessage(SendChatMessageRequest request);

	void UpdatePresence(string playerId, string displayName);

	void DisconnectPresence(string playerId);

	List<ChatMessageViewModel> DrainIncomingMessages();
}
public sealed class HttpPollingChatTransport : IChatTransport
{
	private static readonly HttpClient HttpClient = new HttpClient();

	private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
	{
		IncludeFields = true,
		PropertyNameCaseInsensitive = true
	};

	private readonly Queue<SendChatMessageRequest> _outbox = new Queue<SendChatMessageRequest>();

	private readonly List<ChatMessageViewModel> _inbox = new List<ChatMessageViewModel>();

	private readonly HashSet<string> _seenMessageIds = new HashSet<string>();

	private readonly List<OnlinePlayerViewModel> _onlinePlayers = new List<OnlinePlayerViewModel>();

	private float _pollTimer;

	private float _presenceTimer;

	private bool _initialized;

	private bool _pollInFlight;

	private bool _sendInFlight;

	private bool _presenceInFlight;

	private bool _disconnectSent;

	private float _retryDelayTimer;

	private string _cursor;

	private string _statusText = "Offline";

	private ChatConnectionState _connectionState = ChatConnectionState.Offline;

	private int _onlineUserCount;

	public ChatConnectionState ConnectionState => _connectionState;

	public string StatusText => _statusText;

	public int OnlineUserCount => _onlineUserCount;

	public IReadOnlyList<OnlinePlayerViewModel> OnlinePlayers => _onlinePlayers;

	public void Initialize()
	{
		_initialized = true;
		_connectionState = ChatConnectionState.Connecting;
		_statusText = "Connecting";
		_pollTimer = 0f;
		_presenceTimer = 0f;
		_retryDelayTimer = 0f;
		_disconnectSent = false;
	}

	public void Tick(float unscaledDeltaTime)
	{
		if (_initialized && GlobalChatRuntime.IsConfigured)
		{
			_pollTimer -= unscaledDeltaTime;
			_presenceTimer -= unscaledDeltaTime;
			if (_retryDelayTimer > 0f)
			{
				_retryDelayTimer -= unscaledDeltaTime;
			}
			if (_pollTimer <= 0f && !_pollInFlight)
			{
				_pollTimer = Mathf.Max(1f, GlobalChatRuntime.PollIntervalSeconds);
				PollMessagesAsync();
			}
			if (_presenceTimer <= 0f && !_presenceInFlight)
			{
				_presenceTimer = Mathf.Max(5f, GlobalChatRuntime.PresenceIntervalSeconds);
				UpdatePresence(GlobalChatRuntime.PlayerId, GlobalChatRuntime.DisplayName);
			}
			if (_outbox.Count > 0 && !_sendInFlight && _retryDelayTimer <= 0f)
			{
				SendChatMessageRequest request = _outbox.Dequeue();
				SendMessageAsync(request);
			}
		}
	}

	public void QueueMessage(SendChatMessageRequest request)
	{
		if (request != null)
		{
			_outbox.Enqueue(request);
		}
	}

	public void UpdatePresence(string playerId, string displayName)
	{
		if (!_presenceInFlight && GlobalChatRuntime.IsConfigured)
		{
			UpdatePresenceAsync(new UpdatePresenceRequest
			{
				playerId = playerId,
				displayName = displayName,
				usernameColorHex = GlobalChatRuntime.UserNameColorHex,
				clientVersion = "1.0.0",
				currentStatus = "online"
			});
		}
	}

	public void DisconnectPresence(string playerId)
	{
		if (!string.IsNullOrWhiteSpace(playerId) && GlobalChatRuntime.IsConfigured && !_disconnectSent)
		{
			DisconnectPresenceBlocking(playerId);
		}
	}

	public List<ChatMessageViewModel> DrainIncomingMessages()
	{
		List<ChatMessageViewModel> result = new List<ChatMessageViewModel>(_inbox);
		_inbox.Clear();
		return result;
	}

	private async Task PollMessagesAsync()
	{
		_pollInFlight = true;
		try
		{
			string endpoint = (string.IsNullOrWhiteSpace(_cursor) ? (GlobalChatRuntime.ApiBaseUrl.TrimEnd('/') + "/api/chat/messages") : (GlobalChatRuntime.ApiBaseUrl.TrimEnd('/') + "/api/chat/messages?after=" + Uri.EscapeDataString(_cursor)));
			HttpResponseMessage response = await HttpClient.GetAsync(endpoint).ConfigureAwait(continueOnCapturedContext: false);
			string json = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false);
			if (!response.IsSuccessStatusCode)
			{
				SetErrorState($"HTTP {response.StatusCode}");
				return;
			}
			ChatMessagesResponse payload = JsonSerializer.Deserialize<ChatMessagesResponse>(json, JsonOptions);
			if (payload == null)
			{
				SetErrorState("Bad response");
				return;
			}
			if (!payload.success)
			{
				SetErrorState(string.IsNullOrWhiteSpace(payload.error) ? "Poll failed" : payload.error);
				return;
			}
			if (!string.IsNullOrWhiteSpace(payload.nextCursor))
			{
				_cursor = payload.nextCursor;
			}
			_onlineUserCount = Mathf.Max(0, payload.onlineCount);
			UpdateOnlinePlayers(payload.onlinePlayers);
			if (payload.messages != null)
			{
				for (int i = 0; i < payload.messages.Count; i++)
				{
					ChatMessageDto dto = payload.messages[i];
					if (dto != null)
					{
						string id = (string.IsNullOrWhiteSpace(dto.messageId) ? Guid.NewGuid().ToString("N") : dto.messageId);
						if (_seenMessageIds.Add(id))
						{
							_inbox.Add(Map(dto, id));
						}
					}
				}
			}
			_connectionState = ChatConnectionState.Online;
			_statusText = "Online";
		}
		catch (Exception ex2)
		{
			Exception ex = ex2;
			ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
			bool flag = default(bool);
			BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(24, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("GlobalChat poll failed: ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex);
			}
			log.LogError(val);
			SetErrorState("Offline");
		}
		finally
		{
			_pollInFlight = false;
		}
	}

	private async Task SendMessageAsync(SendChatMessageRequest request)
	{
		_sendInFlight = true;
		try
		{
			string endpoint = GlobalChatRuntime.ApiBaseUrl.TrimEnd('/') + "/api/chat/send";
			string json = JsonSerializer.Serialize(request, JsonOptions);
			StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
			HttpResponseMessage response = await HttpClient.PostAsync(endpoint, content).ConfigureAwait(continueOnCapturedContext: false);
			string responseJson = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false);
			if (!response.IsSuccessStatusCode)
			{
				RequeueFailedMessage(request);
				SetErrorState($"Send {response.StatusCode}");
				return;
			}
			ChatSendResponse payload = JsonSerializer.Deserialize<ChatSendResponse>(responseJson, JsonOptions);
			if (payload != null && !payload.success)
			{
				RequeueFailedMessage(request);
				SetErrorState(string.IsNullOrWhiteSpace(payload.error) ? "Send failed" : payload.error);
			}
			else
			{
				_connectionState = ChatConnectionState.Online;
				_statusText = "Online";
				_retryDelayTimer = 0f;
			}
		}
		catch (Exception ex2)
		{
			Exception ex = ex2;
			ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
			bool flag = default(bool);
			BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(24, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("GlobalChat send failed: ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex);
			}
			log.LogError(val);
			RequeueFailedMessage(request);
			SetErrorState("Offline");
		}
		finally
		{
			_sendInFlight = false;
		}
	}

	private async Task UpdatePresenceAsync(UpdatePresenceRequest request)
	{
		_presenceInFlight = true;
		try
		{
			string endpoint = GlobalChatRuntime.ApiBaseUrl.TrimEnd('/') + "/api/presence/update";
			string json = JsonSerializer.Serialize(request, JsonOptions);
			StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
			HttpResponseMessage response = await HttpClient.PostAsync(endpoint, content).ConfigureAwait(continueOnCapturedContext: false);
			if (response.IsSuccessStatusCode)
			{
				PresenceUpdateResponse payload = JsonSerializer.Deserialize<PresenceUpdateResponse>(await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false), JsonOptions);
				if (payload != null)
				{
					_onlineUserCount = Mathf.Max(0, payload.onlineCount);
					UpdateOnlinePlayers(payload.onlinePlayers);
				}
				else
				{
					_onlineUserCount = Mathf.Max(0, _onlineUserCount);
				}
				if (_connectionState != ChatConnectionState.Error)
				{
					_connectionState = ChatConnectionState.Online;
					_statusText = "Online";
				}
			}
		}
		catch (Exception ex2)
		{
			Exception ex = ex2;
			ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
			bool flag = default(bool);
			BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(35, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("GlobalChat presence update failed: ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
			}
			log.LogWarning(val);
		}
		finally
		{
			_presenceInFlight = false;
		}
	}

	private void DisconnectPresenceBlocking(string playerId)
	{
		//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Expected O, but got Unknown
		try
		{
			_disconnectSent = true;
			string requestUri = GlobalChatRuntime.ApiBaseUrl.TrimEnd('/') + "/api/presence/disconnect";
			string content = JsonSerializer.Serialize(new { playerId }, JsonOptions);
			StringContent content2 = new StringContent(content, Encoding.UTF8, "application/json");
			HttpResponseMessage result = HttpClient.PostAsync(requestUri, content2).GetAwaiter().GetResult();
			if (result.IsSuccessStatusCode)
			{
				string result2 = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
				PresenceDisconnectResponse presenceDisconnectResponse = JsonSerializer.Deserialize<PresenceDisconnectResponse>(result2, JsonOptions);
				if (presenceDisconnectResponse != null && presenceDisconnectResponse.success)
				{
					_onlineUserCount = Mathf.Max(0, presenceDisconnectResponse.onlineCount);
					UpdateOnlinePlayers(presenceDisconnectResponse.onlinePlayers);
				}
			}
		}
		catch (Exception ex)
		{
			_disconnectSent = false;
			ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
			bool flag = default(bool);
			BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(39, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("GlobalChat presence disconnect failed: ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
			}
			log.LogWarning(val);
		}
	}

	private static ChatMessageViewModel Map(ChatMessageDto dto, string id)
	{
		DateTime result = DateTime.UtcNow;
		if (!string.IsNullOrWhiteSpace(dto.createdAt))
		{
			DateTime.TryParse(dto.createdAt, out result);
		}
		string text = (string.IsNullOrWhiteSpace(dto.formattedText) ? dto.rawText : dto.formattedText);
		string text2 = (string.IsNullOrWhiteSpace(dto.filteredFormattedText) ? dto.filteredRawText : dto.filteredFormattedText);
		if (string.IsNullOrWhiteSpace(text))
		{
			text = "[empty]";
		}
		if (string.IsNullOrWhiteSpace(text2))
		{
			text2 = text;
		}
		return new ChatMessageViewModel
		{
			Id = id,
			DisplayName = dto.displayName,
			ResolvedDisplayName = (string.IsNullOrWhiteSpace(dto.resolvedDisplayName) ? dto.displayName : dto.resolvedDisplayName),
			UsernameColorHex = dto.usernameColorHex,
			RawText = dto.rawText,
			DisplayText = text,
			FilteredDisplayText = text2,
			MessageType = (string.IsNullOrWhiteSpace(dto.messageType) ? "chat" : dto.messageType),
			ContainsProfanity = dto.containsProfanity,
			CreatedAtUtc = ((result == default(DateTime)) ? DateTime.UtcNow : result)
		};
	}

	private void UpdateOnlinePlayers(List<OnlinePlayerDto> players)
	{
		_onlinePlayers.Clear();
		if (players == null)
		{
			return;
		}
		for (int i = 0; i < players.Count; i++)
		{
			OnlinePlayerDto onlinePlayerDto = players[i];
			if (onlinePlayerDto != null && !string.IsNullOrWhiteSpace(onlinePlayerDto.displayName))
			{
				_onlinePlayers.Add(new OnlinePlayerViewModel
				{
					PlayerId = onlinePlayerDto.playerId,
					DisplayName = onlinePlayerDto.displayName.Trim(),
					UsernameColorHex = onlinePlayerDto.usernameColorHex
				});
			}
		}
	}

	private void RequeueFailedMessage(SendChatMessageRequest request)
	{
		if (request != null)
		{
			_retryDelayTimer = Mathf.Max(_retryDelayTimer, 2f);
			_outbox.Enqueue(request);
			_statusText = $"Offline ({_outbox.Count} queued)";
		}
	}

	private void SetErrorState(string error)
	{
		_connectionState = ChatConnectionState.Error;
		string text = (string.IsNullOrWhiteSpace(error) ? "Error" : error);
		_statusText = ((_outbox.Count > 0) ? $"{text} ({_outbox.Count} queued)" : text);
	}
}
[HarmonyPatch(typeof(Player), "Update")]
public class GlobalChatPatch
{
	public static void Postfix()
	{
		if (SaveGameHandlers.IsGameActive)
		{
			GlobalChatRuntime.EnsureCreated();
			if (GlobalChatRuntime.Instance != null)
			{
				GlobalChatRuntime.Instance.Tick();
			}
		}
	}
}
public sealed class GlobalChatRuntime
{
	private static readonly Regex UserNameColorRegex = new Regex("^#?[0-9A-Fa-f]{6}$", RegexOptions.Compiled);

	private static GlobalChatRuntime _instance;

	private readonly List<ChatMessageViewModel> _messages = new List<ChatMessageViewModel>();

	private readonly HashSet<string> _localEchoPayloads = new HashSet<string>();

	private readonly HashSet<string> _mutedUsers = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private IChatTransport _transport;

	private ChatCommandService _commandService;

	private ChatUiController _ui;

	private float _localMessageCooldown;

	private bool _isPaused;

	private int _unreadCount;

	public static GlobalChatRuntime Instance => _instance;

	public static string ApiBaseUrl => Plugin.ApiBaseUrl.Value;

	public static string DisplayName => Plugin.DisplayName.Value;

	public static string PlayerId => Plugin.PlayerId.Value;

	public static string UserNameColorHex => (Plugin.UserNameColorHex != null) ? Plugin.UserNameColorHex.Value : string.Empty;

	public static float PollIntervalSeconds => Plugin.PollIntervalSeconds.Value;

	public static float PresenceIntervalSeconds => Plugin.PresenceIntervalSeconds.Value;

	public static bool IsConfigured => !string.IsNullOrWhiteSpace(ApiBaseUrl) && !string.IsNullOrWhiteSpace(DisplayName) && !string.IsNullOrWhiteSpace(PlayerId);

	public IReadOnlyList<ChatMessageViewModel> Messages => _messages;

	public static void EnsureCreated()
	{
		if (_instance == null)
		{
			_instance = new GlobalChatRuntime();
		}
	}

	private GlobalChatRuntime()
	{
		LoadMutedUsers();
		_commandService = new ChatCommandService();
		_transport = new HttpPollingChatTransport();
		_transport.Initialize();
		_ui = new ChatUiController();
		_ui.Initialize(this);
	}

	public void Tick()
	{
		if (!SaveGameHandlers.IsGameActive)
		{
			if (_ui != null && _ui.IsVisible)
			{
				_ui.Hide();
			}
			return;
		}
		if (_transport != null)
		{
			_transport.Tick(Time.unscaledDeltaTime);
			List<ChatMessageViewModel> list = _transport.DrainIncomingMessages();
			if (list.Count > 0)
			{
				for (int i = 0; i < list.Count; i++)
				{
					AddMessage(list[i]);
				}
			}
		}
		if (_localMessageCooldown > 0f)
		{
			_localMessageCooldown -= Time.unscaledDeltaTime;
		}
		if (_ui != null)
		{
			_ui.SetStatus((_transport != null) ? _transport.StatusText : "Offline");
			_ui.SetOnlineCount((_transport != null) ? _transport.OnlineUserCount : 0);
			ChatUiController ui = _ui;
			IReadOnlyList<OnlinePlayerViewModel> onlinePlayers;
			if (_transport == null)
			{
				IReadOnlyList<OnlinePlayerViewModel> readOnlyList = Array.Empty<OnlinePlayerViewModel>();
				onlinePlayers = readOnlyList;
			}
			else
			{
				onlinePlayers = _transport.OnlinePlayers;
			}
			ui.SetOnlinePlayers(onlinePlayers);
			_ui.SetUnreadCount(_unreadCount);
		}
		if (ShouldToggleChat())
		{
			ToggleChat();
		}
		if (_ui != null)
		{
			_ui.Tick();
		}
	}

	private void SyncProfileColor()
	{
		if (_transport != null)
		{
			_transport.UpdatePresence(PlayerId, DisplayName);
		}
		RefreshVisibleMessages();
	}

	private static string NormalizeUserNameColor(string value)
	{
		if (string.IsNullOrWhiteSpace(value))
		{
			return string.Empty;
		}
		string text = value.Trim();
		if (!UserNameColorRegex.IsMatch(text))
		{
			return string.Empty;
		}
		if (!text.StartsWith("#", StringComparison.Ordinal))
		{
			text = "#" + text;
		}
		return text.ToUpperInvariant();
	}

	public bool AddMutedUser(string displayName)
	{
		string text = NormalizeMutedUser(displayName);
		if (string.IsNullOrWhiteSpace(text) || !_mutedUsers.Add(text))
		{
			return false;
		}
		SaveMutedUsers();
		RefreshVisibleMessages();
		return true;
	}

	public bool RemoveMutedUser(string displayName)
	{
		string text = NormalizeMutedUser(displayName);
		if (string.IsNullOrWhiteSpace(text) || !_mutedUsers.Remove(text))
		{
			return false;
		}
		SaveMutedUsers();
		RefreshVisibleMessages();
		return true;
	}

	public string GetMutedUsersSummary()
	{
		if (_mutedUsers.Count == 0)
		{
			return "Muted users: none";
		}
		return "Muted users: " + string.Join(", ", _mutedUsers.OrderBy<string, string>((string name) => name, StringComparer.OrdinalIgnoreCase));
	}

	public string GetUserNameColorSummary()
	{
		string text = NormalizeUserNameColor(UserNameColorHex);
		return string.IsNullOrWhiteSpace(text) ? "Username color: default" : ("Username color: " + text);
	}

	public bool TrySetUserNameColor(string value, out string normalizedColor, out string error)
	{
		normalizedColor = NormalizeUserNameColor(value);
		if (string.IsNullOrWhiteSpace(normalizedColor))
		{
			error = "Usage: //color <#RRGGBB|reset>";
			return false;
		}
		if (Plugin.UserNameColorHex == null)
		{
			error = "Username color setting is unavailable.";
			return false;
		}
		Plugin.UserNameColorHex.Value = normalizedColor;
		((ConfigEntryBase)Plugin.UserNameColorHex).ConfigFile.Save();
		SyncProfileColor();
		error = string.Empty;
		return true;
	}

	public void ResetUserNameColor()
	{
		if (Plugin.UserNameColorHex != null)
		{
			Plugin.UserNameColorHex.Value = string.Empty;
			((ConfigEntryBase)Plugin.UserNameColorHex).ConfigFile.Save();
			SyncProfileColor();
		}
	}

	public void HandlePauseStateChanged(bool isPaused)
	{
		_isPaused = isPaused;
		if (_isPaused && _ui != null)
		{
			_ui.Hide();
		}
	}

	public void Shutdown()
	{
		if (_transport != null && !string.IsNullOrWhiteSpace(PlayerId))
		{
			_transport.DisconnectPresence(PlayerId);
		}
		if (_ui != null)
		{
			_ui.Hide();
		}
	}

	public void SubmitInput(string input)
	{
		if (!SaveGameHandlers.IsGameActive)
		{
			return;
		}
		if (_localMessageCooldown > 0f)
		{
			AddSystemMessage("You are sending messages too quickly.");
			return;
		}
		ChatCommandResult chatCommandResult = _commandService.Resolve(input);
		if (!string.IsNullOrWhiteSpace(chatCommandResult.LocalFeedback))
		{
			AddSystemMessage(chatCommandResult.LocalFeedback);
		}
		if (chatCommandResult.ShouldSend)
		{
			SendChatMessageRequest sendChatMessageRequest = new SendChatMessageRequest
			{
				playerId = PlayerId,
				displayName = DisplayName,
				usernameColorHex = UserNameColorHex,
				channelId = "global",
				messageType = (string.IsNullOrWhiteSpace(chatCommandResult.MessageType) ? "chat" : chatCommandResult.MessageType),
				rawText = (string.IsNullOrWhiteSpace(chatCommandResult.RawText) ? input : chatCommandResult.RawText),
				formattedText = chatCommandResult.FormattedText,
				clientVersion = "1.0.0"
			};
			_transport.QueueMessage(sendChatMessageRequest);
			string text = BuildEchoKey(sendChatMessageRequest.formattedText, sendChatMessageRequest.rawText);
			if (!string.IsNullOrWhiteSpace(text))
			{
				_localEchoPayloads.Add(text);
			}
			string text2 = (string.IsNullOrWhiteSpace(sendChatMessageRequest.formattedText) ? sendChatMessageRequest.rawText : sendChatMessageRequest.formattedText);
			string text3 = ProfanityFilterService.Apply(text2);
			bool containsProfanity = !string.Equals(text2, text3, StringComparison.Ordinal);
			AddMessage(new ChatMessageViewModel
			{
				Id = Guid.NewGuid().ToString("N"),
				DisplayName = DisplayName,
				ResolvedDisplayName = DisplayName,
				UsernameColorHex = UserNameColorHex,
				RawText = sendChatMessageRequest.rawText,
				DisplayText = text2,
				FilteredDisplayText = text3,
				MessageType = "local_echo",
				ContainsProfanity = containsProfanity,
				CreatedAtUtc = DateTime.UtcNow
			});
			_localMessageCooldown = Plugin.LocalSendCooldownSeconds.Value;
		}
	}

	public void AddSystemMessage(string text)
	{
		AddMessage(new ChatMessageViewModel
		{
			Id = Guid.NewGuid().ToString("N"),
			DisplayName = "System",
			ResolvedDisplayName = "System",
			UsernameColorHex = string.Empty,
			RawText = text,
			DisplayText = "[System] " + text,
			MessageType = "system",
			CreatedAtUtc = DateTime.UtcNow
		});
	}

	private void AddMessage(ChatMessageViewModel message)
	{
		string text = BuildEchoKey(message.DisplayText, message.RawText);
		if (message != null && message.MessageType != "local_echo" && !string.IsNullOrWhiteSpace(text) && _localEchoPayloads.Remove(text))
		{
			for (int num = _messages.Count - 1; num >= 0; num--)
			{
				ChatMessageViewModel chatMessageViewModel = _messages[num];
				if (chatMessageViewModel != null && !(chatMessageViewModel.MessageType != "local_echo"))
				{
					string a = BuildEchoKey(chatMessageViewModel.DisplayText, chatMessageViewModel.RawText);
					if (string.Equals(a, text, StringComparison.Ordinal))
					{
						chatMessageViewModel.Id = message.Id;
						chatMessageViewModel.DisplayName = message.DisplayName;
						chatMessageViewModel.ResolvedDisplayName = message.ResolvedDisplayName;
						chatMessageViewModel.UsernameColorHex = message.UsernameColorHex;
						chatMessageViewModel.DisplayText = message.DisplayText;
						chatMessageViewModel.FilteredDisplayText = message.FilteredDisplayText;
						chatMessageViewModel.RawText = message.RawText;
						chatMessageViewModel.MessageType = message.MessageType;
						chatMessageViewModel.ContainsProfanity = message.ContainsProfanity;
						chatMessageViewModel.CreatedAtUtc = message.CreatedAtUtc;
						RefreshVisibleMessages();
						break;
					}
				}
			}
		}
		else
		{
			_messages.Add(message);
			while (_messages.Count > Plugin.MaxMessages.Value)
			{
				_messages.RemoveAt(0);
			}
			if (ShouldCountAsUnread(message))
			{
				_unreadCount++;
			}
			RefreshVisibleMessages();
		}
	}

	private void RefreshVisibleMessages()
	{
		if (_ui != null)
		{
			_ui.RefreshMessages(GetVisibleMessages());
			_ui.SetUnreadCount(_unreadCount);
			if (!_isPaused)
			{
				_ui.ShowMessagePreview();
			}
		}
	}

	private bool ShouldToggleChat()
	{
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		if (!SaveGameHandlers.IsGameActive)
		{
			return false;
		}
		if (_isPaused)
		{
			return false;
		}
		if (_ui != null && _ui.IsVisible)
		{
			return false;
		}
		if (string.IsNullOrWhiteSpace(Plugin.ToggleChatKey.Value))
		{
			return Input.GetKeyDown((KeyCode)13);
		}
		if (Enum.TryParse<KeyCode>(Plugin.ToggleChatKey.Value, ignoreCase: true, out KeyCode result))
		{
			return Input.GetKeyDown(result);
		}
		return Input.GetKeyDown((KeyCode)13);
	}

	private void ToggleChat()
	{
		if (_ui == null)
		{
			return;
		}
		if (!SaveGameHandlers.IsGameActive)
		{
			_ui.Hide();
			return;
		}
		if (!_ui.IsVisible)
		{
			_unreadCount = 0;
			_ui.SetUnreadCount(_unreadCount);
		}
		_ui.ToggleVisibility();
	}

	private IReadOnlyList<ChatMessageViewModel> GetVisibleMessages()
	{
		if (_mutedUsers.Count == 0)
		{
			return _messages;
		}
		List<ChatMessageViewModel> list = new List<ChatMessageViewModel>(_messages.Count);
		for (int i = 0; i < _messages.Count; i++)
		{
			ChatMessageViewModel chatMessageViewModel = _messages[i];
			if (chatMessageViewModel != null && !IsMuted(chatMessageViewModel.DisplayName))
			{
				list.Add(chatMessageViewModel);
			}
		}
		return list;
	}

	private bool ShouldCountAsUnread(ChatMessageViewModel message)
	{
		if (_ui == null || _ui.IsVisible || !Plugin.EnableUnreadBadge.Value || message == null)
		{
			return false;
		}
		if (message.MessageType == "system" || message.MessageType == "local_echo")
		{
			return false;
		}
		return !IsMuted(message.DisplayName);
	}

	private bool IsMuted(string displayName)
	{
		string text = NormalizeMutedUser(displayName);
		return !string.IsNullOrWhiteSpace(text) && _mutedUsers.Contains(text);
	}

	private void LoadMutedUsers()
	{
		_mutedUsers.Clear();
		if (Plugin.MutedUsers == null || string.IsNullOrWhiteSpace(Plugin.MutedUsers.Value))
		{
			return;
		}
		string[] array = Plugin.MutedUsers.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
		for (int i = 0; i < array.Length; i++)
		{
			string text = NormalizeMutedUser(array[i]);
			if (!string.IsNullOrWhiteSpace(text))
			{
				_mutedUsers.Add(text);
			}
		}
	}

	private void SaveMutedUsers()
	{
		if (Plugin.MutedUsers != null)
		{
			Plugin.MutedUsers.Value = string.Join(",", _mutedUsers.OrderBy<string, string>((string name) => name, StringComparer.OrdinalIgnoreCase));
			((ConfigEntryBase)Plugin.MutedUsers).ConfigFile.Save();
		}
	}

	private static string NormalizeMutedUser(string value)
	{
		return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
	}

	private static string BuildEchoKey(string displayText, string rawText)
	{
		if (string.IsNullOrWhiteSpace(displayText) && string.IsNullOrWhiteSpace(rawText))
		{
			return string.Empty;
		}
		return displayText + "|" + rawText;
	}
}
public sealed class ChatUiController
{
	private static TMP_FontAsset _cachedFont;

	private static Material _cachedFontMaterial;

	private GlobalChatRuntime _runtime;

	private GameObject _root;

	private Canvas _canvas;

	private GameObject _panel;

	private CanvasGroup _panelCanvasGroup;

	private GameObject _inputRoot;

	private ScrollRect _messagesScrollRect;

	private RectTransform _messagesViewport;

	private RectTransform _messagesContent;

	private TextMeshProUGUI _messagesText;

	private TextMeshProUGUI _statusText;

	private TextMeshProUGUI _onlineCountText;

	private TextMeshProUGUI _onlinePlayersText;

	private TextMeshProUGUI _unreadText;

	private TextMeshProUGUI _placeholderText;

	private TextMeshProUGUI _inputText;

	private TMP_InputField _inputField;

	private bool _visible;

	private bool _previousPlayerTextInputActive;

	private bool _previousInputControllerEnabled = true;

	private bool _previousMouseLookEnabled = true;

	private bool _previousCharacterControllerEnabled = true;

	private int _openedFrame = -1;

	private bool _fontRefreshAttemptedInGame;

	private bool _previewVisible;

	private float _previewTimer;

	private const float PreviewOpacity = 0.2f;

	private const float PreviewDurationSeconds = 5f;

	private const float PreviewFadeSpeed = 2.5f;

	public bool IsVisible => _visible;

	public void Initialize(GlobalChatRuntime runtime)
	{
		_runtime = runtime;
		BuildUi();
		SetVisible(visible: false);
	}

	public void Tick()
	{
		RefreshFontsIfNeeded();
		if (!_visible)
		{
			TickPreview();
		}
		if (!_visible || (Object)(object)_inputField == (Object)null || Time.frameCount == _openedFrame)
		{
			return;
		}
		if (Input.GetKeyDown((KeyCode)27))
		{
			SetVisible(visible: false);
		}
		else if (Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271))
		{
			string text = _inputField.text;
			if (string.IsNullOrWhiteSpace(text))
			{
				_inputField.text = string.Empty;
				SetVisible(visible: false);
			}
			else
			{
				_inputField.text = string.Empty;
				_runtime.SubmitInput(text);
				_inputField.ActivateInputField();
			}
		}
	}

	public void ToggleVisibility()
	{
		SetVisible(!_visible);
	}

	public void Hide()
	{
		SetVisible(visible: false);
	}

	public void ShowMessagePreview()
	{
		if (!_visible)
		{
			_previewVisible = true;
			_previewTimer = 5f;
			UpdatePanelState();
		}
	}

	public void SetStatus(string status)
	{
		if ((Object)(object)_statusText != (Object)null)
		{
			((TMP_Text)_statusText).text = "GlobalChat: " + status;
		}
	}

	public void SetOnlineCount(int onlineCount)
	{
		if ((Object)(object)_onlineCountText != (Object)null)
		{
			((TMP_Text)_onlineCountText).text = $"Online: {Mathf.Max(0, onlineCount)}";
		}
	}

	public void SetUnreadCount(int unreadCount)
	{
		if (!((Object)(object)_unreadText == (Object)null))
		{
			bool flag = Plugin.EnableUnreadBadge != null && Plugin.EnableUnreadBadge.Value;
			int num = Mathf.Max(0, unreadCount);
			((Component)_unreadText).gameObject.SetActive(flag && num > 0);
			if (flag && num > 0)
			{
				((TMP_Text)_unreadText).text = $"Unread: {num}";
			}
		}
	}

	public void SetOnlinePlayers(IReadOnlyList<OnlinePlayerViewModel> onlinePlayers)
	{
		if ((Object)(object)_onlinePlayersText == (Object)null)
		{
			return;
		}
		if (onlinePlayers == null || onlinePlayers.Count == 0)
		{
			((TMP_Text)_onlinePlayersText).text = string.Empty;
			return;
		}
		int num = Mathf.Min(6, onlinePlayers.Count);
		StringBuilder stringBuilder = new StringBuilder();
		for (int i = 0; i < num; i++)
		{
			OnlinePlayerViewModel onlinePlayerViewModel = onlinePlayers[i];
			if (onlinePlayerViewModel != null && !string.IsNullOrWhiteSpace(onlinePlayerViewModel.DisplayName))
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.AppendLine();
				}
				stringBuilder.Append(FormatUsername(onlinePlayerViewModel.DisplayName.Trim(), onlinePlayerViewModel.UsernameColorHex));
			}
		}
		if (onlinePlayers.Count > num)
		{
			if (stringBuilder.Length > 0)
			{
				stringBuilder.AppendLine();
			}
			StringBuilder stringBuilder2 = stringBuilder;
			StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(6, 1, stringBuilder2);
			handler.AppendLiteral("+");
			handler.AppendFormatted(onlinePlayers.Count - num);
			handler.AppendLiteral(" more");
			stringBuilder2.Append(ref handler);
		}
		((TMP_Text)_onlinePlayersText).text = stringBuilder.ToString();
	}

	private static string FormatMessageText(string text, ChatMessageViewModel message)
	{
		if (string.IsNullOrWhiteSpace(text) || message == null)
		{
			return text;
		}
		string text2 = ((!string.IsNullOrWhiteSpace(message.ResolvedDisplayName)) ? message.ResolvedDisplayName : message.DisplayName);
		if (string.IsNullOrWhiteSpace(text2))
		{
			return text;
		}
		if (message.MessageType == "chat" || message.MessageType == "command")
		{
			string text3 = "[" + text2 + "]";
			if (text.StartsWith(text3, StringComparison.Ordinal))
			{
				return "[" + FormatUsername(text2, message.UsernameColorHex) + "]" + text.Substring(text3.Length);
			}
		}
		if (message.MessageType == "emote")
		{
			string text4 = "* " + text2 + " ";
			if (text.StartsWith(text4, StringComparison.Ordinal))
			{
				return "* " + FormatUsername(text2, message.UsernameColorHex) + " " + text.Substring(text4.Length);
			}
		}
		return text;
	}

	private static string FormatUsername(string username, string usernameColorHex)
	{
		if (string.IsNullOrWhiteSpace(username))
		{
			return username;
		}
		if (Plugin.EnableColoredUsernames == null || !Plugin.EnableColoredUsernames.Value)
		{
			return username;
		}
		if (string.IsNullOrWhiteSpace(usernameColorHex))
		{
			return username;
		}
		string text = usernameColorHex.Trim();
		if (!text.StartsWith("#", StringComparison.Ordinal))
		{
			text = "#" + text;
		}
		return $"<color={text}>{username}</color>";
	}

	public void RefreshMessages(IReadOnlyList<ChatMessageViewModel> messages)
	{
		//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_messagesText == (Object)null)
		{
			return;
		}
		StringBuilder stringBuilder = new StringBuilder();
		for (int i = 0; i < messages.Count; i++)
		{
			ChatMessageViewModel chatMessageViewModel = messages[i];
			string text = ((chatMessageViewModel != null && chatMessageViewModel.ContainsProfanity && Plugin.EnableProfanityFilter != null && Plugin.EnableProfanityFilter.Value) ? chatMessageViewModel.FilteredDisplayText : chatMessageViewModel.DisplayText);
			stringBuilder.AppendLine(FormatMessageText(text, chatMessageViewModel));
		}
		((TMP_Text)_messagesText).text = stringBuilder.ToString();
		((TMP_Text)_messagesText).ForceMeshUpdate(false, false);
		if ((Object)(object)_messagesContent != (Object)null)
		{
			float preferredHeight = ((TMP_Text)_messagesText).preferredHeight;
			float num;
			if (!((Object)(object)_messagesViewport != (Object)null))
			{
				num = 0f;
			}
			else
			{
				Rect rect = _messagesViewport.rect;
				num = ((Rect)(ref rect)).height;
			}
			float num2 = num;
			_messagesContent.SetSizeWithCurrentAnchors((Axis)1, Mathf.Max(preferredHeight, num2));
			LayoutRebuilder.ForceRebuildLayoutImmediate(_messagesContent);
		}
		Canvas.ForceUpdateCanvases();
		if ((Object)(object)_messagesScrollRect != (Object)null)
		{
			_messagesScrollRect.verticalNormalizedPosition = 0f;
		}
	}

	private void TickPreview()
	{
		if (!_previewVisible || (Object)(object)_panelCanvasGroup == (Object)null)
		{
			return;
		}
		if (_previewTimer > 0f)
		{
			_previewTimer -= Time.unscaledDeltaTime;
			_panelCanvasGroup.alpha = 0.2f;
			return;
		}
		_panelCanvasGroup.alpha = Mathf.MoveTowards(_panelCanvasGroup.alpha, 0f, Time.unscaledDeltaTime * 2.5f);
		if (_panelCanvasGroup.alpha <= 0.001f)
		{
			_previewVisible = false;
			UpdatePanelState();
		}
	}

	private void SetVisible(bool visible)
	{
		if (visible)
		{
			_previewVisible = false;
			_previewTimer = 0f;
		}
		_visible = visible;
		UpdatePanelState();
		if ((Object)(object)InterfaceController.Instance != (Object)null)
		{
			if (visible)
			{
				_previousPlayerTextInputActive = InterfaceController.Instance.playerTextInputActive;
				InterfaceController.Instance.playerTextInputActive = true;
			}
			else
			{
				InterfaceController.Instance.playerTextInputActive = _previousPlayerTextInputActive;
			}
		}
		if ((Object)(object)Player.Instance != (Object)null)
		{
			if (visible)
			{
				if ((Object)(object)InputController.Instance != (Object)null)
				{
					_previousInputControllerEnabled = ((Behaviour)InputController.Instance).enabled;
				}
				if ((Object)(object)Player.Instance.fps != (Object)null)
				{
					_previousMouseLookEnabled = Player.Instance.fps.enableLook;
					_previousCharacterControllerEnabled = (Object)(object)Player.Instance.fps.m_CharacterController != (Object)null && ((Collider)Player.Instance.fps.m_CharacterController).enabled;
				}
				Player.Instance.EnablePlayerMouseLook(false, false);
				Player.Instance.EnableCharacterController(false);
				if ((Object)(object)InputController.Instance != (Object)null)
				{
					((Behaviour)InputController.Instance).enabled = false;
				}
			}
			else
			{
				Player.Instance.EnableCharacterController(_previousCharacterControllerEnabled);
				Player.Instance.EnablePlayerMouseLook(_previousMouseLookEnabled, _previousMouseLookEnabled);
				if ((Object)(object)InputController.Instance != (Object)null)
				{
					((Behaviour)InputController.Instance).enabled = _previousInputControllerEnabled;
				}
			}
		}
		if (visible)
		{
			_openedFrame = Time.frameCount;
			EnsureEventSystem();
			_inputField.text = string.Empty;
			((Selectable)_inputField).interactable = true;
			_inputField.ActivateInputField();
			((Selectable)_inputField).Select();
			if ((Object)(object)_messagesScrollRect != (Object)null)
			{
				Canvas.ForceUpdateCanvases();
				_messagesScrollRect.verticalNormalizedPosition = 0f;
			}
		}
		else
		{
			if ((Object)(object)_inputField != (Object)null)
			{
				_inputField.DeactivateInputField(false);
				_inputField.ReleaseSelection();
				((Selectable)_inputField).interactable = false;
			}
			if ((Object)(object)EventSystem.current != (Object)null)
			{
				EventSystem.current.SetSelectedGameObject((GameObject)null);
			}
		}
	}

	private void UpdatePanelState()
	{
		bool active = _visible || _previewVisible;
		if ((Object)(object)_panel != (Object)null)
		{
			_panel.SetActive(active);
		}
		if ((Object)(object)_inputRoot != (Object)null)
		{
			_inputRoot.SetActive(_visible);
		}
		if ((Object)(object)_panelCanvasGroup != (Object)null)
		{
			_panelCanvasGroup.alpha = (_visible ? 1f : (_previewVisible ? 0.2f : 0f));
			_panelCanvasGroup.blocksRaycasts = _visible;
			_panelCanvasGroup.interactable = _visible;
		}
	}

	private void BuildUi()
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0118: Unknown result type (might be due to invalid IL or missing references)
		//IL_0124: Unknown result type (might be due to invalid IL or missing references)
		//IL_0175: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_023d: Unknown result type (might be due to invalid IL or missing references)
		//IL_027f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0296: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_0317: Unknown result type (might be due to invalid IL or missing references)
		//IL_036b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0382: Unknown result type (might be due to invalid IL or missing references)
		//IL_038f: Unknown result type (might be due to invalid IL or missing references)
		//IL_039c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0403: Unknown result type (might be due to invalid IL or missing references)
		//IL_0445: Unknown result type (might be due to invalid IL or missing references)
		//IL_045c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0469: Unknown result type (might be due to invalid IL or missing references)
		//IL_0476: Unknown result type (might be due to invalid IL or missing references)
		//IL_04ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_04c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_04d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_04de: Unknown result type (might be due to invalid IL or missing references)
		//IL_0508: Unknown result type (might be due to invalid IL or missing references)
		//IL_0598: Unknown result type (might be due to invalid IL or missing references)
		//IL_05b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_05ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_05e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0604: Unknown result type (might be due to invalid IL or missing references)
		//IL_0652: Unknown result type (might be due to invalid IL or missing references)
		//IL_06af: Unknown result type (might be due to invalid IL or missing references)
		//IL_06cf: Unknown result type (might be due to invalid IL or missing references)
		//IL_06e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_06fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_070a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0721: Unknown result type (might be due to invalid IL or missing references)
		//IL_078e: Unknown result type (might be due to invalid IL or missing references)
		//IL_07b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_07c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_07d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_07e3: Unknown result type (might be due to invalid IL or missing references)
		//IL_085b: Unknown result type (might be due to invalid IL or missing references)
		//IL_088c: Unknown result type (might be due to invalid IL or missing references)
		//IL_08a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_08b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_08bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0910: Unknown result type (might be due to invalid IL or missing references)
		//IL_0941: Unknown result type (might be due to invalid IL or missing references)
		//IL_0958: Unknown result type (might be due to invalid IL or missing references)
		//IL_0965: Unknown result type (might be due to invalid IL or missing references)
		//IL_0972: Unknown result type (might be due to invalid IL or missing references)
		//IL_09ed: Unknown result type (might be due to invalid IL or missing references)
		_root = new GameObject("GlobalChatUI");
		Object.DontDestroyOnLoad((Object)(object)_root);
		_canvas = _root.AddComponent<Canvas>();
		_canvas.renderMode = (RenderMode)0;
		_canvas.sortingOrder = 9000;
		CanvasScaler val = _root.AddComponent<CanvasScaler>();
		val.uiScaleMode = (ScaleMode)1;
		val.referenceResolution = new Vector2(1920f, 1080f);
		_root.AddComponent<GraphicRaycaster>();
		_panel = CreateUiObject("Panel", _root.transform);
		_panelCanvasGroup = _panel.AddComponent<CanvasGroup>();
		Image val2 = _panel.AddComponent<Image>();
		((Graphic)val2).color = new Color(0f, 0f, 0f, Plugin.ChatOpacity.Value);
		RectTransform component = _panel.GetComponent<RectTransform>();
		component.anchorMin = new Vector2(0.02f, 0.02f);
		component.anchorMax = new Vector2(0.42f, 0.38f);
		component.offsetMin = Vector2.zero;
		component.offsetMax = Vector2.zero;
		GameObject val3 = CreateUiObject("Status", _panel.transform);
		_statusText = val3.AddComponent<TextMeshProUGUI>();
		ApplyGameFont(_statusText, preferTitleFont: true);
		((TMP_Text)_statusText).fontSize = 20f;
		((Graphic)_statusText).color = Color.white;
		((TMP_Text)_statusText).alignment = (TextAlignmentOptions)513;
		RectTransform component2 = val3.GetComponent<RectTransform>();
		component2.anchorMin = new Vector2(0.03f, 0.88f);
		component2.anchorMax = new Vector2(0.52f, 0.97f);
		component2.offsetMin = Vector2.zero;
		component2.offsetMax = Vector2.zero;
		GameObject val4 = CreateUiObject("OnlineCount", _panel.transform);
		_onlineCountText = val4.AddComponent<TextMeshProUGUI>();
		ApplyGameFont(_onlineCountText, preferTitleFont: false);
		((TMP_Text)_onlineCountText).fontSize = 18f;
		((Graphic)_onlineCountText).color = new Color(0.85f, 0.95f, 1f, 1f);
		((TMP_Text)_onlineCountText).alignment = (TextAlignmentOptions)516;
		((TMP_Text)_onlineCountText).text = "Online: 0";
		RectTransform component3 = val4.GetComponent<RectTransform>();
		component3.anchorMin = new Vector2(0.56f, 0.88f);
		component3.anchorMax = new Vector2(0.97f, 0.97f);
		component3.offsetMin = Vector2.zero;
		component3.offsetMax = Vector2.zero;
		GameObject val5 = CreateUiObject("UnreadCount", _panel.transform);
		_unreadText = val5.AddComponent<TextMeshProUGUI>();
		ApplyGameFont(_unreadText, preferTitleFont: false);
		((TMP_Text)_unreadText).fontSize = 16f;
		((Graphic)_unreadText).color = new Color(1f, 0.45f, 0.45f, 1f);
		((TMP_Text)_unreadText).alignment = (TextAlignmentOptions)516;
		((TMP_Text)_unreadText).text = "Unread: 0";
		((Component)_unreadText).gameObject.SetActive(false);
		RectTransform component4 = val5.GetComponent<RectTransform>();
		component4.anchorMin = new Vector2(0.56f, 0.83f);
		component4.anchorMax = new Vector2(0.97f, 0.89f);
		component4.offsetMin = Vector2.zero;
		component4.offsetMax = Vector2.zero;
		GameObject val6 = CreateUiObject("OnlinePlayers", _panel.transform);
		_onlinePlayersText = val6.AddComponent<TextMeshProUGUI>();
		ApplyGameFont(_onlinePlayersText, preferTitleFont: false);
		((TMP_Text)_onlinePlayersText).fontSize = 14f;
		((Graphic)_onlinePlayersText).color = new Color(0.82f, 0.9f, 1f, 0.95f);
		((TMP_Text)_onlinePlayersText).alignment = (TextAlignmentOptions)260;
		((TMP_Text)_onlinePlayersText).text = string.Empty;
		RectTransform component5 = val6.GetComponent<RectTransform>();
		component5.anchorMin = new Vector2(0.62f, 0.58f);
		component5.anchorMax = new Vector2(0.97f, 0.82f);
		component5.offsetMin = Vector2.zero;
		component5.offsetMax = Vector2.zero;
		GameObject val7 = CreateUiObject("MessagesRoot", _panel.transform);
		RectTransform component6 = val7.GetComponent<RectTransform>();
		component6.anchorMin = new Vector2(0.03f, 0.22f);
		component6.anchorMax = new Vector2(0.6f, 0.84f);
		component6.offsetMin = Vector2.zero;
		component6.offsetMax = Vector2.zero;
		Image val8 = val7.AddComponent<Image>();
		((Graphic)val8).color = new Color(0f, 0f, 0f, 0.12f);
		val7.AddComponent<RectMask2D>();
		_messagesViewport = component6;
		_messagesScrollRect = val7.AddComponent<ScrollRect>();
		_messagesScrollRect.horizontal = false;
		_messagesScrollRect.vertical = true;
		_messagesScrollRect.movementType = (MovementType)2;
		_messagesScrollRect.scrollSensitivity = 18f;
		GameObject val9 = CreateUiObject("MessagesContent", val7.transform);
		_messagesContent = val9.GetComponent<RectTransform>();
		_messagesContent.anchorMin = new Vector2(0f, 1f);
		_messagesContent.anchorMax = new Vector2(1f, 1f);
		_messagesContent.pivot = new Vector2(0.5f, 1f);
		_messagesContent.offsetMin = new Vector2(8f, 0f);
		_messagesContent.offsetMax = new Vector2(-8f, 0f);
		ContentSizeFitter val10 = val9.AddComponent<ContentSizeFitter>();
		val10.verticalFit = (FitMode)2;
		_messagesText = val9.AddComponent<TextMeshProUGUI>();
		ApplyGameFont(_messagesText, preferTitleFont: false);
		((TMP_Text)_messagesText).fontSize = 18f;
		((Graphic)_messagesText).color = Color.white;
		((TMP_Text)_messagesText).enableWordWrapping = true;
		((TMP_Text)_messagesText).alignment = (TextAlignmentOptions)257;
		((TMP_Text)_messagesText).overflowMode = (TextOverflowModes)2;
		((MaskableGraphic)_messagesText).maskable = true;
		((TMP_Text)_messagesText).margin = new Vector4(6f, 6f, 6f, 6f);
		RectTransform component7 = val9.GetComponent<RectTransform>();
		component7.anchorMin = new Vector2(0f, 1f);
		component7.anchorMax = new Vector2(1f, 1f);
		component7.pivot = new Vector2(0.5f, 1f);
		component7.anchoredPosition = Vector2.zero;
		component7.sizeDelta = new Vector2(0f, 0f);
		_messagesScrollRect.viewport = _messagesViewport;
		_messagesScrollRect.content = _messagesContent;
		_inputRoot = CreateUiObject("InputRoot", _panel.transform);
		Image val11 = _inputRoot.AddComponent<Image>();
		((Graphic)val11).color = new Color(0.12f, 0.12f, 0.12f, 0.95f);
		RectTransform component8 = _inputRoot.GetComponent<RectTransform>();
		component8.anchorMin = new Vector2(0.03f, 0.04f);
		component8.anchorMax = new Vector2(0.97f, 0.18f);
		component8.offsetMin = Vector2.zero;
		component8.offsetMax = Vector2.zero;
		GameObject val12 = CreateUiObject("Placeholder", _inputRoot.transform);
		_placeholderText = val12.AddComponent<TextMeshProUGUI>();
		ApplyGameFont(_placeholderText, preferTitleFont: false);
		((TMP_Text)_placeholderText).text = "Type message or //command...";
		((TMP_Text)_placeholderText).fontSize = 18f;
		((Graphic)_placeholderText).color = new Color(0.75f, 0.75f, 0.75f, 0.8f);
		((TMP_Text)_placeholderText).alignment = (TextAlignmentOptions)4097;
		RectTransform component9 = val12.GetComponent<RectTransform>();
		component9.anchorMin = new Vector2(0.02f, 0.1f);
		component9.anchorMax = new Vector2(0.98f, 0.9f);
		component9.offsetMin = Vector2.zero;
		component9.offsetMax = Vector2.zero;
		GameObject val13 = CreateUiObject("Text", _inputRoot.transform);
		_inputText = val13.AddComponent<TextMeshProUGUI>();
		ApplyGameFont(_inputText, preferTitleFont: false);
		((TMP_Text)_inputText).fontSize = 18f;
		((Graphic)_inputText).color = Color.white;
		((TMP_Text)_inputText).alignment = (TextAlignmentOptions)4097;
		RectTransform component10 = val13.GetComponent<RectTransform>();
		component10.anchorMin = new Vector2(0.02f, 0.1f);
		component10.anchorMax = new Vector2(0.98f, 0.9f);
		component10.offsetMin = Vector2.zero;
		component10.offsetMax = Vector2.zero;
		_inputField = _inputRoot.AddComponent<TMP_InputField>();
		_inputField.textViewport = component8;
		_inputField.textComponent = (TMP_Text)(object)_inputText;
		_inputField.placeholder = (Graphic)(object)_placeholderText;
		_inputField.lineType = (LineType)0;
		_inputField.caretWidth = 2;
		_inputField.customCaretColor = true;
		_inputField.caretColor = Color.white;
		_inputField.richText = false;
		ApplyResolvedFontsToUi();
	}

	private static GameObject CreateUiObject(string name, Transform parent)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Expected O, but got Unknown
		GameObject val = new GameObject(name);
		val.transform.SetParent(parent, false);
		val.AddComponent<RectTransform>();
		return val;
	}

	private static void ApplyGameFont(TextMeshProUGUI text, bool preferTitleFont)
	{
		if (!((Object)(object)text == (Object)null))
		{
			ResolveGameFontAssets();
			if ((Object)(object)_cachedFont != (Object)null)
			{
				((TMP_Text)text).font = _cachedFont;
			}
			if ((Object)(object)_cachedFontMaterial != (Object)null)
			{
				((TMP_Text)text).fontSharedMaterial = _cachedFontMaterial;
			}
			((TMP_Text)text).richText = true;
			((TMP_Text)text).isOverlay = true;
			((Graphic)text).raycastTarget = false;
			((TMP_Text)text).extraPadding = true;
			((TMP_Text)text).enableVertexGradient = false;
			((MaskableGraphic)text).maskable = true;
		}
	}

	private void RefreshFontsIfNeeded()
	{
		if (!SaveGameHandlers.IsGameActive)
		{
			_fontRefreshAttemptedInGame = false;
		}
		else if (!_fontRefreshAttemptedInGame)
		{
			_fontRefreshAttemptedInGame = true;
			_cachedFont = null;
			_cachedFontMaterial = null;
			ApplyResolvedFontsToUi();
		}
	}

	private void ApplyResolvedFontsToUi()
	{
		ApplyGameFont(_statusText, preferTitleFont: true);
		ApplyGameFont(_onlineCountText, preferTitleFont: false);
		ApplyGameFont(_onlinePlayersText, preferTitleFont: false);
		ApplyGameFont(_unreadText, preferTitleFont: false);
		ApplyGameFont(_messagesText, preferTitleFont: false);
		ApplyGameFont(_placeholderText, preferTitleFont: false);
		ApplyGameFont(_inputText, preferTitleFont: false);
	}

	private static void ResolveGameFontAssets()
	{
		//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d5: Expected O, but got Unknown
		//IL_0145: Unknown result type (might be due to invalid IL or missing references)
		//IL_014c: Expected O, but got Unknown
		//IL_01be: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c5: Expected O, but got Unknown
		if (((Object)(object)_cachedFont != (Object)null && (Object)(object)_cachedFontMaterial != (Object)null) || !SaveGameHandlers.IsGameActive)
		{
			return;
		}
		object instance = InterfaceController.Instance;
		bool flag = default(bool);
		if (instance != null)
		{
			try
			{
				Type type = instance.GetType();
				_cachedFont = ReadFieldValue<TMP_FontAsset>(type, instance, "mainFont") ?? ReadFieldValue<TMP_FontAsset>(type, instance, "bodyFont") ?? ReadFieldValue<TMP_FontAsset>(type, instance, "titleFont") ?? _cachedFont;
				_cachedFontMaterial = ReadFieldValue<Material>(type, instance, "fontMaterial") ?? ReadFieldValue<Material>(type, instance, "fontMaterialOutline") ?? _cachedFontMaterial;
			}
			catch (Exception ex)
			{
				ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(51, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to resolve InterfaceController font assets: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
				}
				log.LogWarning(val);
			}
		}
		if ((Object)(object)_cachedFont == (Object)null)
		{
			try
			{
				if ((Object)(object)TMP_Settings.defaultFontAsset != (Object)null)
				{
					_cachedFont = TMP_Settings.defaultFontAsset;
				}
			}
			catch (Exception ex2)
			{
				ManualLogSource log2 = PluginController<Plugin, IEmptyBindings>.Log;
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(42, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to resolve default TMP font asset: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex2.Message);
				}
				log2.LogWarning(val);
			}
		}
		if (!((Object)(object)_cachedFontMaterial == (Object)null) || !((Object)(object)_cachedFont != (Object)null))
		{
			return;
		}
		try
		{
			_cachedFontMaterial = ((TMP_Asset)_cachedFont).material;
		}
		catch (Exception ex3)
		{
			ManualLogSource log3 = PluginController<Plugin, IEmptyBindings>.Log;
			BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(45, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to resolve default TMP font material: ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex3.Message);
			}
			log3.LogWarning(val);
		}
	}

	private static T ReadFieldValue<T>(Type type, object instance, string fieldName) where T : class
	{
		if (type == null || instance == null || string.IsNullOrWhiteSpace(fieldName))
		{
			return null;
		}
		FieldInfo field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (field == null)
		{
			return null;
		}
		return field.GetValue(instance) as T;
	}

	private static void EnsureEventSystem()
	{
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Expected O, but got Unknown
		if (!((Object)(object)EventSystem.current != (Object)null))
		{
			GameObject val = new GameObject("GlobalChatEventSystem");
			Object.DontDestroyOnLoad((Object)(object)val);
			val.AddComponent<EventSystem>();
		}
	}
}
[BepInPlugin("ShaneeexD.GlobalChat", "GlobalChat", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : PluginController<Plugin>
{
	public const string PLUGIN_GUID = "ShaneeexD.GlobalChat";

	public const string PLUGIN_NAME = "GlobalChat";

	public const string PLUGIN_VERSION = "1.0.0";

	public static ConfigEntry<string> ApiBaseUrl;

	public static ConfigEntry<string> DisplayName;

	public static ConfigEntry<string> PlayerId;

	public static ConfigEntry<string> ToggleChatKey;

	public static ConfigEntry<float> PollIntervalSeconds;

	public static ConfigEntry<float> PresenceIntervalSeconds;

	public static ConfigEntry<float> LocalSendCooldownSeconds;

	public static ConfigEntry<float> ChatOpacity;

	public static ConfigEntry<int> MaxMessages;

	public static ConfigEntry<bool> EnableProfanityFilter;

	public static ConfigEntry<string> MutedUsers;

	public static ConfigEntry<bool> EnableUnreadBadge;

	public static ConfigEntry<bool> EnableColoredUsernames;

	public static ConfigEntry<string> UserNameColorHex;

	public override void Load()
	{
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Expected O, but got Unknown
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Expected O, but got Unknown
		//IL_008c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Expected O, but got Unknown
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Expected O, but got Unknown
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f4: Expected O, but got Unknown
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		//IL_0123: Expected O, but got Unknown
		//IL_0148: Unknown result type (might be due to invalid IL or missing references)
		//IL_0152: Expected O, but got Unknown
		//IL_0177: Unknown result type (might be due to invalid IL or missing references)
		//IL_0181: Expected O, but got Unknown
		//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ac: Expected O, but got Unknown
		//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01db: Expected O, but got Unknown
		//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0207: Expected O, but got Unknown
		//IL_0228: Unknown result type (might be due to invalid IL or missing references)
		//IL_0232: Expected O, but got Unknown
		//IL_0253: Unknown result type (might be due to invalid IL or missing references)
		//IL_025d: Expected O, but got Unknown
		//IL_0282: Unknown result type (might be due to invalid IL or missing references)
		//IL_028c: Expected O, but got Unknown
		ApiBaseUrl = base.Config.Bind<string>("Network", "ApiBaseUrl", "https://globalchat-wkrf.onrender.com", new ConfigDescription("Base URL for the GlobalChat Python backend.", (AcceptableValueBase)null, Array.Empty<object>()));
		DisplayName = base.Config.Bind<string>("Profile", "DisplayName", Environment.UserName, new ConfigDescription("Display name shown in global chat.", (AcceptableValueBase)null, Array.Empty<object>()));
		PlayerId = base.Config.Bind<string>("Profile", "PlayerId", Guid.NewGuid().ToString("N"), new ConfigDescription("Stable unique ID for this player installation.", (AcceptableValueBase)null, Array.Empty<object>()));
		MutedUsers = base.Config.Bind<string>("Profile", "MutedUsers", string.Empty, new ConfigDescription("Comma-separated list of muted display names for local chat filtering.", (AcceptableValueBase)null, Array.Empty<object>()));
		ToggleChatKey = base.Config.Bind<string>("Input", "ToggleChatKey", "Return", new ConfigDescription("Keyboard key used to open and close the chat overlay.", (AcceptableValueBase)null, Array.Empty<object>()));
		PollIntervalSeconds = base.Config.Bind<float>("Network", "PollIntervalSeconds", 3f, new ConfigDescription("How often to poll the backend for new messages.", (AcceptableValueBase)null, Array.Empty<object>()));
		PresenceIntervalSeconds = base.Config.Bind<float>("Network", "PresenceIntervalSeconds", 30f, new ConfigDescription("How often to refresh online presence with the backend.", (AcceptableValueBase)null, Array.Empty<object>()));
		LocalSendCooldownSeconds = base.Config.Bind<float>("Safety", "LocalSendCooldownSeconds", 1.5f, new ConfigDescription("Client-side minimum delay between sent messages.", (AcceptableValueBase)null, Array.Empty<object>()));
		EnableProfanityFilter = base.Config.Bind<bool>("Safety", "EnableProfanityFilter", true, new ConfigDescription("If enabled, outgoing messages request backend profanity filtering.", (AcceptableValueBase)null, Array.Empty<object>()));
		ChatOpacity = base.Config.Bind<float>("UI", "ChatOpacity", 0.85f, new ConfigDescription("Opacity of the chat panel background.", (AcceptableValueBase)null, Array.Empty<object>()));
		MaxMessages = base.Config.Bind<int>("UI", "MaxMessages", 40, new ConfigDescription("Maximum number of chat lines kept in the on-screen buffer.", (AcceptableValueBase)null, Array.Empty<object>()));
		EnableUnreadBadge = base.Config.Bind<bool>("UI", "EnableUnreadBadge", true, new ConfigDescription("If enabled, show an unread count in the chat header while chat is closed.", (AcceptableValueBase)null, Array.Empty<object>()));
		EnableColoredUsernames = base.Config.Bind<bool>("UI", "EnableColoredUsernames", true, new ConfigDescription("If enabled, color usernames in chat and the online player list.", (AcceptableValueBase)null, Array.Empty<object>()));
		UserNameColorHex = base.Config.Bind<string>("Profile", "UserNameColorHex", "#7FDBFF", new ConfigDescription("Hex color for your username that is shared with the backend and visible to other players.", (AcceptableValueBase)null, Array.Empty<object>()));
		((PluginController<Plugin, IEmptyBindings>)(object)this).Harmony.PatchAll(Assembly.GetExecutingAssembly());
		SaveGameHandlers.Initialize();
		PluginController<Plugin, IEmptyBindings>.Log.LogInfo((object)"GlobalChat loaded.");
	}

	private void OnDestroy()
	{
		if (GlobalChatRuntime.Instance != null)
		{
			GlobalChatRuntime.Instance.Shutdown();
		}
	}

	private void OnApplicationQuit()
	{
		if (GlobalChatRuntime.Instance != null)
		{
			GlobalChatRuntime.Instance.Shutdown();
		}
	}
}
[HarmonyPatch(typeof(SessionData))]
[HarmonyPatch("PauseGame")]
public class PauseManager
{
	public static void Prefix(ref bool showPauseText, ref bool delayOverride, ref bool openDesktopMode)
	{
		if (GlobalChatRuntime.Instance != null)
		{
			GlobalChatRuntime.Instance.HandlePauseStateChanged(isPaused: true);
		}
	}
}
[HarmonyPatch(typeof(SessionData))]
[HarmonyPatch("ResumeGame")]
public class ResumeGameManager
{
	public static void Prefix()
	{
		if (GlobalChatRuntime.Instance != null)
		{
			GlobalChatRuntime.Instance.HandlePauseStateChanged(isPaused: false);
		}
	}
}
public static class ProfanityFilterService
{
	private static readonly object Sync = new object();

	private static bool _loaded;

	private static List<string> _words = new List<string>();

	public static string FilterIfEnabled(string text)
	{
		if (Plugin.EnableProfanityFilter != null && !Plugin.EnableProfanityFilter.Value)
		{
			return text;
		}
		return Apply(text);
	}

	public static string Apply(string text)
	{
		if (string.IsNullOrWhiteSpace(text))
		{
			return text;
		}
		EnsureLoaded();
		if (_words.Count == 0)
		{
			return text;
		}
		string text2 = text;
		for (int i = 0; i < _words.Count; i++)
		{
			string word = _words[i];
			text2 = ReplaceContains(text2, word);
		}
		return text2;
	}

	private static void EnsureLoaded()
	{
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Expected O, but got Unknown
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_009b: Expected O, but got Unknown
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e8: Expected O, but got Unknown
		//IL_012e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0135: Expected O, but got Unknown
		if (_loaded)
		{
			return;
		}
		lock (Sync)
		{
			if (!_loaded)
			{
				_words = LoadWords();
				ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(37, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Profanity filter ready with ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(_words.Count);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" entries.");
				}
				log.LogInfo(val);
				ManualLogSource log2 = PluginController<Plugin, IEmptyBindings>.Log;
				val = new BepInExInfoLogInterpolatedStringHandler(39, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Profanity filter self-test 'wank' => '");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ApplyPreview("wank"));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'");
				}
				log2.LogInfo(val);
				ManualLogSource log3 = PluginController<Plugin, IEmptyBindings>.Log;
				val = new BepInExInfoLogInterpolatedStringHandler(42, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Profanity filter self-test 'vjayjay' => '");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ApplyPreview("vjayjay"));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'");
				}
				log3.LogInfo(val);
				ManualLogSource log4 = PluginController<Plugin, IEmptyBindings>.Log;
				val = new BepInExInfoLogInterpolatedStringHandler(44, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Profanity filter self-test 'wet dream' => '");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ApplyPreview("wet dream"));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'");
				}
				log4.LogInfo(val);
				_loaded = true;
			}
		}
	}

	private static List<string> LoadWords()
	{
		//IL_0136: Unknown result type (might be due to invalid IL or missing references)
		//IL_013d: Expected O, but got Unknown
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ce: Expected O, but got Unknown
		List<string> list = new List<string>();
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		bool flag = default(bool);
		try
		{
			string[] array = BuildCandidatePaths();
			foreach (string text in array)
			{
				if (!File.Exists(text))
				{
					continue;
				}
				string[] array2 = File.ReadAllLines(text);
				for (int j = 0; j < array2.Length; j++)
				{
					string text2 = array2[j]?.Trim();
					if (!string.IsNullOrWhiteSpace(text2) && !text2.StartsWith("#", StringComparison.Ordinal) && hashSet.Add(text2))
					{
						list.Add(text2);
					}
				}
				if (list.Count > 0)
				{
					ManualLogSource log = PluginController<Plugin, IEmptyBindings>.Log;
					BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(38, 2, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Loaded ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(list.Count);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" profanity filter entries from ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text);
					}
					log.LogInfo(val);
					break;
				}
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log2 = PluginController<Plugin, IEmptyBindings>.Log;
			BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(36, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Failed to load profanity_words.txt: ");
				((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.Message);
			}
			log2.LogWarning(val2);
		}
		if (list.Count == 0)
		{
			string[] array3 = new string[11]
			{
				"fuck", "fucking", "shit", "bitch", "cunt", "asshole", "dick", "piss", "bastard", "wanker",
				"pussy"
			};
			for (int k = 0; k < array3.Length; k++)
			{
				if (hashSet.Add(array3[k]))
				{
					list.Add(array3[k]);
				}
			}
		}
		return list.OrderByDescending((string word) => word.Length).ThenBy<string, string>((string word) => word, StringComparer.OrdinalIgnoreCase).ToList();
	}

	private static string ApplyPreview(string text)
	{
		if (string.IsNullOrWhiteSpace(text) || _words.Count == 0)
		{
			return text;
		}
		string text2 = text;
		for (int i = 0; i < _words.Count; i++)
		{
			text2 = ReplaceContains(text2, _words[i]);
		}
		return text2;
	}

	private static string ReplaceContains(string text, string word)
	{
		if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(word))
		{
			return text;
		}
		int num = text.IndexOf(word, StringComparison.OrdinalIgnoreCase);
		if (num < 0)
		{
			return text;
		}
		string value = new string('*', word.Length);
		StringBuilder stringBuilder = new StringBuilder(text.Length);
		int num2 = 0;
		while (num >= 0)
		{
			stringBuilder.Append(text, num2, num - num2);
			stringBuilder.Append(value);
			num2 = num + word.Length;
			num = text.IndexOf(word, num2, StringComparison.OrdinalIgnoreCase);
		}
		stringBuilder.Append(text, num2, text.Length - num2);
		return stringBuilder.ToString();
	}

	private static string[] BuildCandidatePaths()
	{
		string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty;
		List<string> list = new List<string>
		{
			Path.Combine(path, "profanity_words.txt"),
			Path.Combine(Paths.PluginPath, "profanity_words.txt"),
			Path.Combine(Paths.BepInExRootPath, "plugins", "profanity_words.txt"),
			Path.Combine(Environment.CurrentDirectory, "profanity_words.txt")
		};
		return list.ToArray();
	}
}
public static class SaveGameHandlers
{
	private static bool _initialized;

	public static bool IsGameActive { get; private set; }

	private static void DisconnectGlobalChat()
	{
		if (GlobalChatRuntime.Instance != null)
		{
			GlobalChatRuntime.Instance.Shutdown();
		}
	}

	public static void Initialize()
	{
		if (!_initialized)
		{
			_initialized = true;
			Lib.SaveGame.OnAfterLoad += HandleGameLoaded;
			Lib.SaveGame.OnAfterNewGame += HandleNewGameStarted;
			Lib.SaveGame.OnBeforeNewGame += HandleGameBeforeNewGame;
			Lib.SaveGame.OnBeforeLoad += HandleGameBeforeLoad;
			Lib.SaveGame.OnBeforeDelete += HandleGameBeforeDelete;
			Lib.SaveGame.OnAfterDelete += HandleGameAfterDelete;
		}
	}

	private static void HandleNewGameStarted(object sender, EventArgs e)
	{
		IsGameActive = true;
		GlobalChatRuntime.EnsureCreated();
	}

	private static void HandleGameLoaded(object sender, EventArgs e)
	{
		IsGameActive = true;
		GlobalChatRuntime.EnsureCreated();
	}

	private static void HandleGameBeforeNewGame(object sender, EventArgs e)
	{
		IsGameActive = false;
		DisconnectGlobalChat();
	}

	private static void HandleGameBeforeLoad(object sender, EventArgs e)
	{
		IsGameActive = false;
		DisconnectGlobalChat();
	}

	private static void HandleGameBeforeDelete(object sender, EventArgs e)
	{
		IsGameActive = false;
		DisconnectGlobalChat();
	}

	private static void HandleGameAfterDelete(object sender, EventArgs e)
	{
		IsGameActive = false;
		DisconnectGlobalChat();
	}
}