AndrewLin.Fomo.dll

Decompiled a month ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Alpha;
using Alpha.Core.Command;
using Alpha.Core.Util;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Fomo.Core;
using Fomo.Core.Commands;
using Fomo.Patches;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PurrNet;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("AndrewLin")]
[assembly: AssemblyConfiguration("Publish")]
[assembly: AssemblyDescription("FOMO: A mod for On-Together to expand chat size, log chats, and relay messages via WebSocket. Use /fomohelp")]
[assembly: AssemblyFileVersion("1.2.9.0")]
[assembly: AssemblyInformationalVersion("1.2.9+93bf6c254192ca3bde9494f062dab68782362cda")]
[assembly: AssemblyProduct("AndrewLin.Fomo")]
[assembly: AssemblyTitle("AndrewLin.Fomo")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/andrewlimforfun/ot-mods")]
[assembly: AssemblyVersion("1.2.9.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace Fomo
{
	public static class BuildInfo
	{
		public const string Version = "1.2.9";
	}
	[BepInPlugin("com.andrewlin.ontogether.fomo", "Fomo", "1.2.9")]
	public class FomoPlugin : BaseUnityPlugin
	{
		public const int OriginalNotificationLimit = 99;

		public const int DefaultNotificationLimit = 300;

		public const int MaxNotificationLimit = 999;

		public const int DefaultChatSinkLocalRange = 5;

		public const int DefaultGlobalChatMessageLimit = 50;

		public const int DefaultLocalChatMessageLimit = 25;

		private static readonly ConcurrentQueue<Action> _mainThreadQueue = new ConcurrentQueue<Action>();

		public const string ModGUID = "com.andrewlin.ontogether.fomo";

		public const string ModName = "Fomo";

		public const string ModVersion = "1.2.9";

		public static ConfigEntry<bool>? EnableFeature { get; private set; }

		public static ConfigEntry<bool>? ShowCommand { get; private set; }

		public static ConfigEntry<bool>? CleanChatSinkTags { get; private set; }

		public static ConfigEntry<int>? GlobalMessageLimitCount { get; private set; }

		public static ConfigEntry<int>? LocalMessageLimitCount { get; private set; }

		public static ConfigEntry<int>? ChatSinkLocalRange { get; private set; }

		public static ConfigEntry<bool>? DispatchIncomingLocal { get; private set; }

		public static ChatSinkManager? SinkManager { get; private set; }

		public static void RunOnMainThread(Action action)
		{
			_mainThreadQueue.Enqueue(action);
		}

		public static void DispatchIncomingText(string text)
		{
			string text2 = text;
			if (string.IsNullOrWhiteSpace(text2))
			{
				return;
			}
			text2 = text2.Trim();
			if (AlphaPlugin.CommandManager != null && text2.StartsWith("/") && text2.Length > 2)
			{
				RunOnMainThread(delegate
				{
					ChatCommandManager commandManager = AlphaPlugin.CommandManager;
					string text3 = text2.Split(' ')[0];
					string text4 = text3.Substring(1, text3.Length - 1).ToLower();
					if (string.IsNullOrWhiteSpace(text4) || !commandManager.ContainsCommand(text4) || !commandManager.ProcessInput(text2))
					{
						ChatUtils.UISendMessage(text2);
					}
				});
			}
			else
			{
				string userName = PlayerUtils.GetUserName();
				bool flag = DispatchIncomingLocal?.Value ?? false;
				ChatUtils.SendMessageAsync(userName, text2, flag, "word", (Vector3?)null);
			}
		}

		private void Awake()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Fomo v1.2.9 is loaded!");
			InitConfig();
			Harmony val = new Harmony("com.andrewlin.ontogether.fomo");
			val.PatchAll(typeof(TextChannelManagerPatch));
			val.PatchAll(typeof(GameSettingsPatch));
			val.PatchAll(typeof(MainSceneManagerPatch));
			val.PatchAll(typeof(PlayerCustomizationControllerPatch));
			ChatCommandManager commandManager = AlphaPlugin.CommandManager;
			if (commandManager != null)
			{
				commandManager.Register((IChatCommand)(object)new FomoChatLocalCommand());
			}
			ChatCommandManager commandManager2 = AlphaPlugin.CommandManager;
			if (commandManager2 != null)
			{
				commandManager2.Register((IChatCommand)(object)new FomoChatGlobalCommand());
			}
			ChatCommandManager commandManager3 = AlphaPlugin.CommandManager;
			if (commandManager3 != null)
			{
				commandManager3.Register((IChatCommand)(object)new FomoChatSinkCleanTagsCommand());
			}
			ChatCommandManager commandManager4 = AlphaPlugin.CommandManager;
			if (commandManager4 != null)
			{
				commandManager4.Register((IChatCommand)(object)new FomoChatSinkLocalRangeCommand());
			}
			ChatCommandManager commandManager5 = AlphaPlugin.CommandManager;
			if (commandManager5 != null)
			{
				commandManager5.Register((IChatCommand)(object)new FomoIncomingModeCommand());
			}
			ChatCommandManager commandManager6 = AlphaPlugin.CommandManager;
			if (commandManager6 != null)
			{
				commandManager6.Register((IChatCommand)(object)new FomoMessageLimitCommand());
			}
			ChatCommandManager commandManager7 = AlphaPlugin.CommandManager;
			if (commandManager7 != null)
			{
				commandManager7.Register((IChatCommand)(object)new FomoToggleCommand());
			}
			SinkManager = new ChatSinkManager();
		}

		private void InitConfig()
		{
			EnableFeature = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableFeature", true, "Enable or disable the mod feature.");
			ShowCommand = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowCommand", false, "Show the command in chat when used.");
			CleanChatSinkTags = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "CleanChatSinkTags", false, "Strip TMP color/formatting tags (e.g. <#ff0000>) from chat messages before dispatching to sinks (affects all sinks).");
			GlobalMessageLimitCount = ((BaseUnityPlugin)this).Config.Bind<int>("Chat", "GlobalMessageLimitCount", 50, "Max number of messages shown in the global chat window. Game default is 50.");
			LocalMessageLimitCount = ((BaseUnityPlugin)this).Config.Bind<int>("Chat", "LocalMessageLimitCount", 25, "Max number of messages shown in the local chat window. Game default is 25.");
			ChatSinkLocalRange = ((BaseUnityPlugin)this).Config.Bind<int>("Chat", "ChatLogLocalRange", 5, "Local range for chat log messages.");
			DispatchIncomingLocal = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "DispatchIncomingLocal", false, "When true, messages from external sources (Telegram, WebSocket, etc.) are dispatched as local chat instead of global.");
		}

		private void Update()
		{
			Action result;
			while (_mainThreadQueue.TryDequeue(out result))
			{
				try
				{
					result();
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Main thread action failed: " + ex.Message));
				}
			}
		}

		private void OnDestroy()
		{
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "AndrewLin.Fomo";

		public const string PLUGIN_NAME = "AndrewLin.Fomo";

		public const string PLUGIN_VERSION = "1.2.9";
	}
}
namespace Fomo.Patches
{
	[HarmonyPatch(typeof(GameSettings))]
	public class GameSettingsPatch
	{
		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPostfix]
		public static void GlobalMessageLimitCountPostfix(ref int __result)
		{
			int num = FomoPlugin.GlobalMessageLimitCount?.Value ?? __result;
			if (num > 0)
			{
				__result = num;
			}
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPostfix]
		public static void LocalMessageLimitCountPostfix(ref int __result)
		{
			int num = FomoPlugin.LocalMessageLimitCount?.Value ?? __result;
			if (num > 0)
			{
				__result = num;
			}
		}
	}
	[HarmonyPatch(typeof(MainSceneManager))]
	internal class MainSceneManagerPatch
	{
		private static readonly ManualLogSource _log = Logger.CreateLogSource("Fomo.MSMP");

		[HarmonyPatch("ConnectionLost")]
		[HarmonyPostfix]
		public static void ConnectionLostPostfix(NotificationStatus notificationStatus)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			_log.LogWarning((object)$"Disconnected: {notificationStatus} at {DateTime.Now}");
			ChatEntry entry = new ChatEntry(DateTime.Now, $"Disconnected: {notificationStatus}");
			if (FomoPlugin.SinkManager != null)
			{
				FomoPlugin.SinkManager.BroadcastAsync(entry);
			}
		}

		[HarmonyPatch("ReturnMenu", new Type[] { typeof(bool) })]
		[HarmonyPostfix]
		public static void ReturnMenuPostfix(bool wishListCheck)
		{
			_log.LogInfo((object)$"Returned to menu at {DateTime.Now}");
			ChatEntry entry = new ChatEntry(DateTime.Now, "Returned to menu");
			if (FomoPlugin.SinkManager != null)
			{
				FomoPlugin.SinkManager.BroadcastAsync(entry);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerCustomizationController))]
	public static class PlayerCustomizationControllerPatch
	{
		private static readonly ManualLogSource _log = Logger.CreateLogSource("Fomo.PCCP");

		[HarmonyPostfix]
		[HarmonyPatch("UpdateOnNewPeopleJoin_Original_0")]
		public static void UpdateOnNewPeopleJoin_Original_0_Postfix(string playerSteamID, PlayerIDInfo playerIDInfo)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			string @string = Encoding.Unicode.GetString(playerIDInfo.Name);
			NetworkSingleton<TextChannelManager>.I.AddNotification(@string + " - " + playerSteamID);
		}
	}
	[HarmonyPatch(typeof(TextChannelManager))]
	public class TextChannelManagerPatch
	{
		private const int _selfDistance = 0;

		[HarmonyPatch("AddNotification", new Type[] { typeof(string) })]
		[HarmonyPostfix]
		public static void AddNotificationPostfix(string text)
		{
			string text2 = text;
			ConfigEntry<bool>? enableFeature = FomoPlugin.EnableFeature;
			if (enableFeature == null || enableFeature.Value)
			{
				Task.Run(async delegate
				{
					ChatEntry entry = new ChatEntry(message: ChatUtils.CleanTMPTags(text2), timestamp: DateTime.Now, channel: null, userName: null, source: "AN");
					await (FomoPlugin.SinkManager?.BroadcastAsync(entry) ?? Task.CompletedTask);
				});
			}
		}

		[HarmonyPatch("SendMessageAsync", new Type[]
		{
			typeof(byte[]),
			typeof(byte[]),
			typeof(bool),
			typeof(Vector3),
			typeof(string),
			typeof(RPCInfo)
		})]
		[HarmonyPostfix]
		public static void SendMessageAsyncPostfix(byte[] textBytes, byte[] userName, bool isLocal, Vector3 pos, string playerID, RPCInfo info = default(RPCInfo))
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			byte[] userName2 = userName;
			byte[] textBytes2 = textBytes;
			string playerID2 = playerID;
			ConfigEntry<bool>? enableFeature = FomoPlugin.EnableFeature;
			if (enableFeature == null || enableFeature.Value)
			{
				Task.Run(async delegate
				{
					string channel = (isLocal ? "Local" : "Global");
					string cleanUserName = ChatUtils.CleanTMPTags(Encoding.Unicode.GetString(userName2));
					string rawMessage = Encoding.Unicode.GetString(textBytes2);
					ChatEntry entry = new ChatEntry(message: (FomoPlugin.CleanChatSinkTags?.Value ?? false) ? ChatUtils.CleanTMPTags(rawMessage) : rawMessage, timestamp: DateTime.Now, channel: channel, userName: cleanUserName, source: "SM", playerID: playerID2, distance: isLocal ? ((int)Vector3.Distance(pos, NetworkSingleton<TextChannelManager>.I.MainPlayer.position)) : 0);
					await (FomoPlugin.SinkManager?.BroadcastAsync(entry) ?? Task.CompletedTask);
				});
			}
		}

		[HarmonyPatch("OnChannelMessageReceived", new Type[]
		{
			typeof(string),
			typeof(string),
			typeof(Vector3),
			typeof(bool),
			typeof(int),
			typeof(string)
		})]
		[HarmonyPostfix]
		public static void OnChannelMessageReceivedPostfix(string userName, string message, Vector3 senderPosition, bool isLocal, int senderIndex, string playerID)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			string userName2 = userName;
			string message2 = message;
			string playerID2 = playerID;
			ConfigEntry<bool>? enableFeature = FomoPlugin.EnableFeature;
			if ((enableFeature != null && !enableFeature.Value) || MonoSingleton<DataManager>.I.BanData.IgnorePlayers.Contains(playerID2) || MonoSingleton<DataManager>.I.BanData.MutedPlayers.Contains(playerID2) || SteamUtils.GetPlayerSteamID() == playerID2)
			{
				return;
			}
			Task.Run(async delegate
			{
				string channel = (isLocal ? "Local" : "Global");
				int? distance = (int)Vector3.Distance(senderPosition, NetworkSingleton<TextChannelManager>.I.MainPlayer.position);
				int localRange = FomoPlugin.ChatSinkLocalRange?.Value ?? 5;
				if (!isLocal || !(distance > localRange))
				{
					ChatEntry entry = new ChatEntry(userName: ChatUtils.CleanTMPTags(userName2), message: (FomoPlugin.CleanChatSinkTags?.Value ?? false) ? ChatUtils.CleanTMPTags(message2) : message2, timestamp: DateTime.Now, channel: channel, source: "OC", playerID: playerID2, distance: distance);
					await (FomoPlugin.SinkManager?.BroadcastAsync(entry) ?? Task.CompletedTask);
				}
			});
		}
	}
}
namespace Fomo.Core
{
	public class ChatEntry
	{
		public DateTime Timestamp { get; }

		public string Message { get; }

		public string? Channel { get; }

		public string? UserName { get; }

		public string? Source { get; }

		public string? PlayerID { get; }

		public int? Distance { get; }

		public bool IsNotification => Channel == null || UserName == null;

		public ChatEntry(DateTime timestamp, string message, string? channel = null, string? userName = null, string? source = null, string? playerID = null, int? distance = null)
		{
			Timestamp = timestamp;
			Message = message;
			Channel = channel;
			UserName = userName;
			Source = source;
			PlayerID = playerID;
			Distance = distance;
		}
	}
	public static class ChatEntryFormatter
	{
		public const string DefaultMessageFormat = "[{timestamp:HH:mm}] [{channel:short}{distance}] {username}: {message}";

		public const string DefaultNotificationFormat = "[{timestamp:HH:mm}] {message}";

		public const string PlaceholdersCsv = "{timestamp[:fmt]}, {channel[:short]}, {username}, {message}, {distance}, {source}, {playerid}";

		private static readonly Regex TokenRegex = new Regex("\\{(\\w+)(?::([^}]*))?\\}", RegexOptions.Compiled);

		public static string Format(ChatEntry entry, string? format = null)
		{
			ChatEntry entry2 = entry;
			string text = (entry2.IsNotification ? "[{timestamp:HH:mm}] {message}" : "[{timestamp:HH:mm}] [{channel:short}{distance}] {username}: {message}");
			string input = (string.IsNullOrEmpty(format) ? text : format);
			return TokenRegex.Replace(input, delegate(Match m)
			{
				string value = m.Groups[1].Value;
				string text2 = (m.Groups[2].Success ? m.Groups[2].Value : string.Empty);
				return value switch
				{
					"timestamp" => entry2.Timestamp.ToString(string.IsNullOrEmpty(text2) ? "HH:mm" : text2), 
					"channel" => FormatChannelLabel(entry2, text2), 
					"username" => entry2.UserName ?? string.Empty, 
					"source" => entry2.Source ?? string.Empty, 
					"playerid" => FormatPlayerID(entry2, text2), 
					"message" => entry2.Message, 
					"distance" => FormatDistanceLabel(entry2, text2), 
					_ => m.Value, 
				};
			});
		}

		private static string FormatPlayerID(ChatEntry entry, string specifier)
		{
			if (entry.PlayerID != null && entry.PlayerID.Length >= 3)
			{
				if (specifier == "")
				{
					return entry.PlayerID;
				}
				if (specifier == "short")
				{
					string? playerID = entry.PlayerID;
					int length = playerID.Length;
					int num = length - 3;
					return playerID.Substring(num, length - num);
				}
				if (int.TryParse(specifier, out var result) && result > 0 && result <= entry.PlayerID.Length)
				{
					return entry.PlayerID.Substring(entry.PlayerID.Length - result);
				}
				return entry.PlayerID;
			}
			return string.Empty;
		}

		public static string FormatDistanceLabel(ChatEntry entry, string specifier)
		{
			if (entry.Distance.HasValue && entry.Distance.Value > 0)
			{
				return $"{entry.Distance.Value}";
			}
			return string.Empty;
		}

		public static string FormatChannelLabel(ChatEntry entry, string specifier = "")
		{
			string text = entry.Channel ?? "System";
			if (specifier == "short")
			{
				text = ((text.Length > 0) ? text.Substring(0, 1) : "?");
			}
			return text;
		}
	}
	public class ChatSinkManager
	{
		private readonly ManualLogSource _log = Logger.CreateLogSource("Fomo.CSM");

		private readonly List<IChatSink> _sinks = new List<IChatSink>();

		public void Register(IChatSink sink)
		{
			_sinks.Add(sink);
			_log.LogInfo((object)("Registered chat sink: " + sink.GetType().Name));
		}

		public async Task BroadcastAsync(ChatEntry entry)
		{
			foreach (IChatSink sink in _sinks)
			{
				try
				{
					await sink.SendAsync(entry);
				}
				catch (Exception ex)
				{
					_log.LogWarning((object)("[" + sink.GetType().Name + "] Unhandled error: " + ex.Message));
				}
			}
		}
	}
	public interface IChatSink
	{
		Task SendAsync(ChatEntry entry);
	}
}
namespace Fomo.Core.Commands
{
	public class FomoChatGlobalCommand : IChatCommand, IComparable<IChatCommand>
	{
		public const string CMD = "fomochatglobal";

		public string Name => "fomochatglobal";

		public string ShortName => "fcg";

		public string Description => "Send message in global chat. ";

		public string Namespace => "fomo";

		public void Execute(string[] args)
		{
			if (args.Length < 1)
			{
				ChatUtils.AddGlobalNotification("Usage: /fomochatglobal [message]");
				return;
			}
			string text = string.Join(" ", args);
			if (string.IsNullOrWhiteSpace(text))
			{
				ChatUtils.AddGlobalNotification("Please enter a valid message.");
				return;
			}
			string userName = PlayerUtils.GetUserName();
			ChatUtils.SendMessageAsync(userName, text, false, "word", (Vector3?)null);
		}
	}
	public class FomoChatLocalCommand : IChatCommand, IComparable<IChatCommand>
	{
		public const string CMD = "fomochatlocal";

		public string Name => "fomochatlocal";

		public string ShortName => "fcl";

		public string Description => "Send message in local chat. ";

		public string Namespace => "fomo";

		public void Execute(string[] args)
		{
			if (args.Length < 1)
			{
				ChatUtils.AddGlobalNotification("Usage: /fomochatlocal [message]");
				return;
			}
			string text = string.Join(" ", args);
			if (string.IsNullOrWhiteSpace(text))
			{
				ChatUtils.AddGlobalNotification("Please enter a valid message.");
				return;
			}
			string userName = PlayerUtils.GetUserName();
			ChatUtils.SendMessageAsync(userName, text, true, "word", (Vector3?)null);
		}
	}
	public class FomoChatSinkCleanTagsCommand : IChatCommand, IComparable<IChatCommand>
	{
		public const string CMD = "fomochatsinkcleantags";

		public string Name => "fomochatsinkcleantags";

		public string ShortName => "fcsct";

		public string Description
		{
			get
			{
				ConfigEntry<bool>? cleanChatSinkTags = FomoPlugin.CleanChatSinkTags;
				return "Toggle cleaning TMP tags in the chat sink e.g. <noparse><#ff0000></noparse>. Currently: " + ((cleanChatSinkTags != null && cleanChatSinkTags.Value) ? "enabled" : "disabled") + ". Usage: /fomocleanchatsinktags";
			}
		}

		public string Namespace => "fomo";

		public void Execute(string[] args)
		{
			if (FomoPlugin.CleanChatSinkTags != null)
			{
				FomoPlugin.CleanChatSinkTags.Value = !FomoPlugin.CleanChatSinkTags.Value;
				ChatUtils.AddGlobalNotification("Cleaning TMP tags in chat sink is now " + (FomoPlugin.CleanChatSinkTags.Value ? "enabled" : "disabled") + ".");
			}
		}
	}
	public class FomoChatSinkLocalRangeCommand : IChatCommand, IComparable<IChatCommand>
	{
		public const string CMD = "fomochatsinklocalrange";

		public string Name => "fomochatsinklocalrange";

		public string ShortName => "fcslr";

		public string Description => "Get or set the local range for the chat sink. No args = get current value. Pass a number to set it. " + $"Current: {FomoPlugin.ChatSinkLocalRange?.Value ?? 5}";

		public bool IsHidden => true;

		public string Namespace => "fomo";

		public void Execute(string[] args)
		{
			if (FomoPlugin.ChatSinkLocalRange == null)
			{
				ChatUtils.AddGlobalNotification("Chat sink local range config is not initialized yet.");
				return;
			}
			if (args.Length == 0)
			{
				ChatUtils.AddGlobalNotification($"Chat sink local range is currently set to {FomoPlugin.ChatSinkLocalRange.Value}.");
				return;
			}
			if (!int.TryParse(args[0], out var result) || result <= 0)
			{
				ChatUtils.AddGlobalNotification("Please enter a valid positive integer.");
				return;
			}
			FomoPlugin.ChatSinkLocalRange.Value = result;
			ChatUtils.AddGlobalNotification($"Chat sink local range is now set to {result}.");
		}
	}
	public class FomoIncomingModeCommand : IChatCommand, IComparable<IChatCommand>
	{
		public const string CMD = "fomoincomingmode";

		public string Name => "fomoincomingmode";

		public string ShortName => "fim";

		public string Description
		{
			get
			{
				ConfigEntry<bool>? dispatchIncomingLocal = FomoPlugin.DispatchIncomingLocal;
				return "Toggle incoming message channel between global and local. Current: " + ((dispatchIncomingLocal != null && dispatchIncomingLocal.Value) ? "local" : "global") + ". Usage: /fomoincomingmode [global|local]";
			}
		}

		public string Namespace => "fomo";

		public void Execute(string[] args)
		{
			if (FomoPlugin.DispatchIncomingLocal == null)
			{
				return;
			}
			if (args.Length == 0)
			{
				FomoPlugin.DispatchIncomingLocal.Value = !FomoPlugin.DispatchIncomingLocal.Value;
			}
			else
			{
				string text = args[0].ToLower();
				if (text == "local")
				{
					FomoPlugin.DispatchIncomingLocal.Value = true;
				}
				else
				{
					if (!(text == "global"))
					{
						ChatUtils.AddGlobalNotification("Unknown mode '" + args[0] + "'. Use 'global' or 'local'.");
						return;
					}
					FomoPlugin.DispatchIncomingLocal.Value = false;
				}
			}
			string text2 = (FomoPlugin.DispatchIncomingLocal.Value ? "local" : "global");
			ChatUtils.AddGlobalNotification("Incoming messages will now be sent to " + text2 + " chat.");
		}
	}
	public class FomoMessageLimitCommand : IChatCommand, IComparable<IChatCommand>
	{
		public const string CMD = "fomomessagelimit";

		public string Name => "fomomessagelimit";

		public string ShortName => "fml";

		public string Description => "Set the global or local chat message window size. Usage: /fomomessagelimit [global|local] [number]. " + $"Current: global={FomoPlugin.GlobalMessageLimitCount?.Value}, local={FomoPlugin.LocalMessageLimitCount?.Value}";

		public string Namespace => "fomo";

		public void Execute(string[] args)
		{
			if (FomoPlugin.GlobalMessageLimitCount == null || FomoPlugin.LocalMessageLimitCount == null)
			{
				return;
			}
			if (args.Length != 2)
			{
				ChatUtils.AddGlobalNotification("Usage: /fomomessagelimit [global|local] [number]");
				return;
			}
			string text = args[0].ToLower();
			if (!int.TryParse(args[1], out var result) || result <= 0)
			{
				ChatUtils.AddGlobalNotification("Invalid number. Please enter a positive integer.");
			}
			else if (text == "global")
			{
				FomoPlugin.GlobalMessageLimitCount.Value = result;
				ChatUtils.AddGlobalNotification($"Global message limit set to {result}.");
			}
			else if (text == "local")
			{
				FomoPlugin.LocalMessageLimitCount.Value = result;
				ChatUtils.AddGlobalNotification($"Local message limit set to {result}.");
			}
			else
			{
				ChatUtils.AddGlobalNotification("Unknown type '" + text + "'. Use 'global' or 'local'.");
			}
		}
	}
	public class FomoToggleCommand : IChatCommand, IComparable<IChatCommand>
	{
		public const string CMD = "fomotoggle";

		public string Name => "fomotoggle";

		public string ShortName => "ft";

		public string Description => "Toggle Fomo feature on/off. ";

		public string Namespace => "fomo";

		public void Execute(string[] args)
		{
			if (FomoPlugin.EnableFeature != null)
			{
				FomoPlugin.EnableFeature.Value = !FomoPlugin.EnableFeature.Value;
				ChatUtils.AddGlobalNotification("Fomo feature is now " + (FomoPlugin.EnableFeature.Value ? "enabled" : "disabled") + ".");
			}
		}
	}
}