Decompiled source of TerraChat v1.2.1

plugins/TerraChat/TerraChat.dll

Decompiled a day ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Steamworks;
using TMPro;
using TerraChat.Chat;
using TerraChat.Net;
using TerraChat.UI;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.TextCore.LowLevel;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("TerraChat")]
[assembly: AssemblyProduct("TerraChat")]
[assembly: AssemblyFileVersion("1.2.1")]
[assembly: AssemblyDescription("Created by R4V9N1")]
[assembly: AssemblyVersion("1.2.1.0")]
namespace TerraChat
{
	[BepInPlugin("r4v9n1.terrachat", "TerraChat", "1.2.1")]
	public sealed class TerraChatPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "r4v9n1.terrachat";

		public const string PluginName = "TerraChat";

		public const string PluginVersion = "1.2.1";

		public const string CreatorCredit = "Created by R4V9N1";

		public const int ProtocolVersion = 1;

		public const string DefaultServerUrl = "wss://chat.100club.is";

		private static readonly Regex NamePattern = new Regex("^[A-Za-z0-9 _-]{3,24}$");

		internal ConfigEntry<bool> Enabled;

		internal ConfigEntry<bool> StayOnline;

		internal ConfigEntry<string> ServerUrl;

		internal ConfigEntry<string> Token;

		internal ConfigEntry<string> MutedChannels;

		internal ConfigEntry<float> WindowX;

		internal ConfigEntry<float> WindowY;

		internal ConfigEntry<float> WindowWidth;

		internal ConfigEntry<float> WindowHeight;

		internal ConfigEntry<float> FontSize;

		internal ConfigEntry<float> IdleOpacity;

		internal ConfigEntry<float> BackgroundOpacity;

		internal readonly ChatState State = new ChatState();

		internal RelayClient Relay;

		internal string ConnectedName = string.Empty;

		private PmHistory pmHistory;

		private readonly SteamTicket steamTicket = new SteamTicket();

		private bool waitingForTicket;

		private string characterKey;

		private ChatWindow window;

		internal static ManualLogSource Log { get; private set; }

		internal static TerraChatPlugin Instance { get; private set; }

		internal bool WindowActive
		{
			get
			{
				if (window != null)
				{
					return window.Active;
				}
				return false;
			}
		}

		internal bool HasWindow => window != null;

		private void Awake()
		{
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Use the TerraChat window instead of Valheim's chat box.");
			StayOnline = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Online", true, "Connect to TerraChat. Off = offline mode (Local chat only); toggle by clicking Online/Offline in the window.");
			ServerUrl = ((BaseUnityPlugin)this).Config.Bind<string>("General", "ServerUrl", "wss://chat.100club.is", "TerraChat relay address (wss://...).");
			Token = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Token", string.Empty, "Secret that owns your chat name. Generated automatically; never share it.");
			MutedChannels = ((BaseUnityPlugin)this).Config.Bind<string>("Channels", "Muted", string.Empty, "Muted global channel numbers, e.g. 2,3.");
			WindowX = ((BaseUnityPlugin)this).Config.Bind<float>("Window", "X", -1f, "Window left edge (-1 = bottom-right corner of the screen).");
			WindowY = ((BaseUnityPlugin)this).Config.Bind<float>("Window", "Y", -1f, "Window bottom edge.");
			WindowWidth = ((BaseUnityPlugin)this).Config.Bind<float>("Window", "Width", 520f, "Window width.");
			WindowHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Window", "Height", 260f, "Window height.");
			FontSize = ((BaseUnityPlugin)this).Config.Bind<float>("Window", "FontSize", 16f, "Chat text size.");
			IdleOpacity = ((BaseUnityPlugin)this).Config.Bind<float>("Window", "IdleOpacity", 0.55f, "Window opacity when idle (0.1-1).");
			BackgroundOpacity = ((BaseUnityPlugin)this).Config.Bind<float>("Window", "BackgroundOpacity", 0.8f, "Background opacity (0-1).");
			if (!Regex.IsMatch(Token.Value ?? string.Empty, "^[0-9a-f]{32}$"))
			{
				Token.Value = NewToken();
			}
			State.SetMutedFromConfig(MutedChannels.Value);
			Relay = new RelayClient(delegate(string line)
			{
				Log.LogWarning((object)line);
			});
			pmHistory = new PmHistory(Path.Combine(Paths.ConfigPath, "TerraChat", "pm-history"));
			new Harmony("r4v9n1.terrachat").PatchAll(typeof(TerraChatPlugin).Assembly);
			Log.LogInfo((object)"TerraChat 1.2.1 loaded.");
		}

		private void Update()
		{
			if (!Enabled.Value)
			{
				return;
			}
			if ((Object)(object)Player.m_localPlayer != (Object)null && !Relay.Running && StayOnline.Value)
			{
				Connect();
			}
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				LoadCharacterHistory();
			}
			for (int i = 0; i < 50; i++)
			{
				if (!Relay.TryReceive(out var message))
				{
					break;
				}
				HandleRelayMessage(message);
			}
			if (window != null && !window.IsValid)
			{
				window.Dispose();
				window = null;
			}
			if (window == null && (Object)(object)Chat.instance != (Object)null && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				window = ChatWindow.TryCreate(this);
			}
			if (window != null)
			{
				window.Tick();
			}
		}

		private void OnDestroy()
		{
			if (Relay != null)
			{
				Relay.Stop();
			}
			steamTicket.Dispose();
		}

		private void Connect()
		{
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Expected O, but got Unknown
			string text = ResolveName();
			if (string.IsNullOrEmpty(ServerUrl.Value) || ServerUrl.Value.Contains("example.com"))
			{
				Relay.Stop();
				return;
			}
			if (!waitingForTicket)
			{
				steamTicket.Request();
				waitingForTicket = true;
			}
			steamTicket.Tick();
			if (!steamTicket.Pending)
			{
				waitingForTicket = false;
				if (steamTicket.Ticket == null)
				{
					Log.LogWarning((object)"TerraChat could not get a Steam ticket; the server will only accept an already verified player.");
				}
				ConnectedName = text;
				JObject val = new JObject
				{
					["t"] = JToken.op_Implicit("hello"),
					["v"] = JToken.op_Implicit(1),
					["name"] = JToken.op_Implicit(text),
					["token"] = JToken.op_Implicit(Token.Value),
					["mod"] = JToken.op_Implicit("1.2.1")
				};
				if (steamTicket.Ticket != null)
				{
					val["steam"] = JToken.op_Implicit(steamTicket.Ticket);
				}
				Relay.Start(ServerUrl.Value, val);
			}
		}

		private string ResolveName()
		{
			string text = string.Empty;
			if ((Object)(object)Game.instance != (Object)null && Game.instance.GetPlayerProfile() != null)
			{
				text = Regex.Replace(Game.instance.GetPlayerProfile().GetName() ?? string.Empty, "[^A-Za-z0-9 _-]", "").Trim();
			}
			if (!NamePattern.IsMatch(text))
			{
				text = "Viking" + Token.Value.Substring(0, 4);
			}
			return text;
		}

		private void HandleRelayMessage(JObject m)
		{
			string text = (string)m["t"];
			if (text == null)
			{
				return;
			}
			switch (text.Length)
			{
			case 7:
				switch (text[0])
				{
				case 'w':
				{
					if (!(text == "welcome"))
					{
						break;
					}
					ConnectedName = ((string)m["name"]) ?? ConnectedName;
					JToken obj = m["channels"];
					JArray val = (JArray)(object)((obj is JArray) ? obj : null);
					if (val != null)
					{
						State.SetChannels(((IEnumerable<JToken>)val).Select((JToken c) => Tuple.Create((int)c[(object)"id"], (string)c[(object)"name"])));
						foreach (JToken item in val)
						{
							State.SetChannelInfo((int)item[(object)"id"], (string)item[(object)"label"], (string)item[(object)"bridge"] == "discord");
						}
					}
					State.SetDiscordLinked((string)m["discordLinked"]);
					State.AddSystem("TerraChat connected as " + ConnectedName + ".", "local");
					Log.LogInfo((object)("TerraChat connected as " + ConnectedName + "."));
					string text2 = (string)m["role"];
					if (text2 == "admin" || text2 == "mod")
					{
						State.AddSystem("You are a TerraChat " + ((text2 == "admin") ? "admin. Moderation: /tc mute Name [minutes], /tc unmute Name, /tc kick Name, /tc ban Name [minutes], /tc unban Name. Admins: /tc mod Name, /tc unmod Name." : "moderator. Moderation: /tc mute Name [minutes], /tc unmute Name, /tc kick Name, /tc ban Name [minutes], /tc unban Name."), "local");
					}
					string text3 = (string)m["motd"];
					if (!string.IsNullOrEmpty(text3))
					{
						State.AddSystem(text3, "local");
					}
					break;
				}
				case 'd':
					if (text == "discord")
					{
						State.AddPmNotice("Join our Discord: " + (string)m["invite"] + "  (click the link to copy it)");
						State.Select("pm");
					}
					break;
				}
				break;
			case 3:
				switch (text[0])
				{
				case 'm':
					if (text == "msg")
					{
						State.AddChannelMessage((int)m["ch"], (string)m["from"], (string)m["text"], (string)m["src"] == "discord");
					}
					break;
				case 'w':
					if (text == "who")
					{
						JToken obj2 = m["players"];
						string[] array = ((IEnumerable<JToken>)((obj2 is JArray) ? obj2 : null))?.Select((JToken p) => (string)p).ToArray() ?? new string[0];
						State.AddSystem("Online (" + array.Length + "): " + string.Join(", ", array));
					}
					break;
				}
				break;
			case 6:
				switch (text[0])
				{
				case 'l':
					if (text == "linked")
					{
						string text6 = (string)m["discord"];
						State.SetDiscordLinked(text6);
						State.AddSystem((text6 != null) ? ("Linked to Discord as " + text6 + ". Your messages in Discord now show your Discord name and picture.") : "Discord link removed.", (State.DiscordTab != null) ? State.DiscordTab.Id : null);
					}
					break;
				case 's':
					if (text == "system")
					{
						State.AddSystem((string)m["text"]);
					}
					break;
				}
				break;
			case 2:
				if (text == "pm")
				{
					State.AddPrivateMessage((string)m["from"], (string)m["to"], (string)m["text"], ConnectedName);
					SavePm(m);
				}
				break;
			case 4:
				if (text == "link")
				{
					string text4 = (GUIUtility.systemCopyBuffer = "!link " + (string)m["code"]);
					State.SetPendingLinkCode(text4);
					State.AddSystem("Link your Discord profile: " + text4 + " is copied. In Discord, open the channel linked to this tab, paste it (Ctrl+V) and send it within " + (int?)m["minutes"] + " minutes. You only do this once.", (State.DiscordTab != null) ? State.DiscordTab.Id : null);
				}
				break;
			case 5:
				if (text == "error")
				{
					State.AddSystem("TerraChat: " + (string)m["text"]);
					Log.LogWarning((object)("TerraChat server error " + (string)m["code"] + ": " + (string)m["text"]));
				}
				break;
			}
		}

		private void LoadCharacterHistory()
		{
			string text = (((Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile() : null)?.m_filename;
			if (string.IsNullOrEmpty(text) || text == characterKey)
			{
				return;
			}
			characterKey = text;
			try
			{
				State.LoadPmHistory(pmHistory.Load(text));
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("TerraChat could not read PM history: " + ex.Message));
			}
		}

		private void SavePm(JObject m)
		{
			if (string.IsNullOrEmpty(characterKey))
			{
				return;
			}
			try
			{
				string text = (string)m["from"];
				pmHistory.Append(characterKey, new PmEntry
				{
					Ts = (((long?)m["ts"]) ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds()),
					From = text,
					To = (string)m["to"],
					Text = (string)m["text"],
					Outgoing = string.Equals(text, ConnectedName, StringComparison.OrdinalIgnoreCase)
				});
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("TerraChat could not save PM history: " + ex.Message));
			}
		}

		internal void Submit(string input)
		{
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Expected O, but got Unknown
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Expected O, but got Unknown
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Expected O, but got Unknown
			//IL_0227: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0241: Unknown result type (might be due to invalid IL or missing references)
			//IL_0257: Unknown result type (might be due to invalid IL or missing references)
			//IL_026e: Expected O, but got Unknown
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Expected O, but got Unknown
			ChatCommand chatCommand = CommandParser.Parse(input, State.ActiveTab, State.LastPmPartner);
			switch (chatCommand.Kind)
			{
			case ChatCommandKind.None:
				return;
			case ChatCommandKind.Local:
				SendLocal(chatCommand.Text);
				return;
			case ChatCommandKind.Help:
				State.AddSystem("TerraChat: Enter opens chat, Tab switches tabs. /g1 /g2 /g3 text = global channel, /g4 text = Heim (linked to Discord), /pm Name text (\"quoted name\" if it has spaces), /r text = reply, /who = online players, /discord = our Discord invite, /tc mute 2 | /tc unmute 2 = mute a channel, /tc link | /tc unlink = Discord profile, /tc help. Other / commands go to Valheim chat.");
				return;
			case ChatCommandKind.Error:
				State.AddSystem(chatCommand.Text);
				return;
			case ChatCommandKind.MuteChannel:
			case ChatCommandKind.UnmuteChannel:
				SetChannelMuted(chatCommand.Channel, chatCommand.Kind == ChatCommandKind.MuteChannel);
				return;
			}
			if (Relay.Status != "Online")
			{
				State.AddSystem((!StayOnline.Value) ? "You are offline. Click Offline in the title bar to go online." : ((Relay.Status == "Stopped") ? "TerraChat is stopped (see the message above)." : "TerraChat is offline; trying to reconnect."));
				return;
			}
			switch (chatCommand.Kind)
			{
			case ChatCommandKind.Channel:
				Relay.Send(new JObject
				{
					["t"] = JToken.op_Implicit("say"),
					["ch"] = JToken.op_Implicit(chatCommand.Channel),
					["text"] = JToken.op_Implicit(chatCommand.Text)
				});
				break;
			case ChatCommandKind.PrivateMessage:
				Relay.Send(new JObject
				{
					["t"] = JToken.op_Implicit("pm"),
					["to"] = JToken.op_Implicit(chatCommand.Target),
					["text"] = JToken.op_Implicit(chatCommand.Text)
				});
				break;
			case ChatCommandKind.Who:
				Relay.Send(new JObject { ["t"] = JToken.op_Implicit("who") });
				break;
			case ChatCommandKind.DiscordInvite:
				RequestDiscordInvite();
				break;
			case ChatCommandKind.LinkDiscord:
				RequestDiscordLink();
				break;
			case ChatCommandKind.UnlinkDiscord:
				Relay.Send(new JObject { ["t"] = JToken.op_Implicit("unlink") });
				break;
			case ChatCommandKind.Admin:
			{
				JObject val = new JObject
				{
					["t"] = JToken.op_Implicit("admin"),
					["cmd"] = JToken.op_Implicit(chatCommand.AdminCommand),
					["target"] = JToken.op_Implicit(chatCommand.Target)
				};
				if (chatCommand.Minutes > 0.0)
				{
					val["minutes"] = JToken.op_Implicit(chatCommand.Minutes);
				}
				Relay.Send(val);
				break;
			}
			case ChatCommandKind.MuteChannel:
			case ChatCommandKind.UnmuteChannel:
				break;
			}
		}

		internal void SetOnline(bool online)
		{
			StayOnline.Value = online;
			if (online)
			{
				State.AddSystem("Going online...");
				if ((Object)(object)Player.m_localPlayer != (Object)null && !Relay.Running)
				{
					Connect();
				}
			}
			else
			{
				Relay.Stop();
				State.AddSystem("You are offline. Click Offline in the title bar to go online again.");
			}
		}

		internal void RequestDiscordLink()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			if (Relay.Status != "Online")
			{
				State.AddSystem("TerraChat is offline; try linking again when it shows Online.");
				return;
			}
			Relay.Send(new JObject { ["t"] = JToken.op_Implicit("link") });
		}

		internal void RequestDiscordInvite()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			if (Relay.Status != "Online")
			{
				State.AddSystem("TerraChat is offline; try /discord again when it shows Online.");
				return;
			}
			Relay.Send(new JObject { ["t"] = JToken.op_Implicit("discord") });
		}

		internal void SetChannelMuted(int channel, bool muted)
		{
			if (State.ChannelTab(channel) == null)
			{
				State.AddSystem("No channel " + channel + ".");
				return;
			}
			State.SetMuted(channel, muted);
			MutedChannels.Value = State.MutedToConfig();
		}

		private static void SendLocal(string text)
		{
			Chat instance = Chat.instance;
			if (!((Object)(object)instance == (Object)null) && !((Object)(object)((Terminal)instance).m_input == (Object)null))
			{
				((TMP_InputField)((Terminal)instance).m_input).text = text;
				instance.SendInput();
			}
		}

		private static string NewToken()
		{
			byte[] array = new byte[16];
			using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create())
			{
				randomNumberGenerator.GetBytes(array);
			}
			StringBuilder stringBuilder = new StringBuilder(32);
			byte[] array2 = array;
			foreach (byte b in array2)
			{
				stringBuilder.Append(b.ToString("x2"));
			}
			return stringBuilder.ToString();
		}
	}
}
namespace TerraChat.UI
{
	internal sealed class ChatWindow
	{
		private sealed class SettingsRow
		{
			private const float Height = 24f;

			private readonly ChatWindow window;

			private readonly float y;

			private float x = 10f;

			internal SettingsRow(ChatWindow window, float y)
			{
				this.window = window;
				this.y = y;
				Divider();
			}

			internal void Button(string text, Action action, float width = 0f)
			{
				//IL_0017: Unknown result type (might be due to invalid IL or missing references)
				TextMeshProUGUI val = Text(text, width, TabColor);
				((Graphic)val).raycastTarget = true;
				window.MakeClickable(((Component)val).gameObject, delegate
				{
					action();
					window.BuildSettings();
					window.RefocusIfActive();
				});
			}

			internal void Value(string text, float width)
			{
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				Text("<b>" + text + "</b>", width, new Color(1f, 0.85f, 0.55f));
			}

			internal float End()
			{
				return y - 24f - 6f;
			}

			private TextMeshProUGUI Text(string text, float width, Color color)
			{
				//IL_002e: Unknown result type (might be due to invalid IL or missing references)
				//IL_005c: Unknown result type (might be due to invalid IL or missing references)
				//IL_006b: Unknown result type (might be due to invalid IL or missing references)
				//IL_007a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0091: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ae: Expected O, but got Unknown
				TextMeshProUGUI val = window.NewText("Item", (Transform)(object)window.settings, 14f, (TextAlignmentOptions)514);
				((TMP_Text)val).text = text;
				((Graphic)val).color = color;
				((TMP_Text)val).textWrappingMode = (TextWrappingModes)0;
				if (width <= 0f)
				{
					width = Mathf.Ceil(((TMP_Text)val).preferredWidth) + 12f;
				}
				Stretch((RectTransform)((TMP_Text)val).transform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(x, y - 24f), new Vector2(x + width, y));
				x += width;
				Divider();
				return val;
			}

			private void Divider()
			{
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0052: Unknown result type (might be due to invalid IL or missing references)
				//IL_006f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0098: Unknown result type (might be due to invalid IL or missing references)
				RectTransform obj = NewRect("Divider", (Transform)(object)window.settings);
				Stretch(obj, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(x + 2f, y - 24f + 4f), new Vector2(x + 4f, y - 4f));
				Image obj2 = ((Component)obj).gameObject.AddComponent<Image>();
				((Graphic)obj2).color = new Color(0.75f, 0.6f, 0.4f, 0.8f);
				((Graphic)obj2).raycastTarget = false;
				x += 6f;
			}
		}

		private const float HeaderHeight = 28f;

		private const float InputHeight = 32f;

		private const float MinWidth = 320f;

		private const float MinHeight = 160f;

		private const float MaxWidth = 1400f;

		private const float MaxHeight = 900f;

		private const int ShownLines = 120;

		private const float HintHeight = 20f;

		private static readonly Color TabColor = new Color(0.85f, 0.8f, 0.7f);

		private static bool s_failed;

		private readonly TerraChatPlugin plugin;

		private readonly Chat chat;

		private readonly Canvas canvas;

		private readonly Canvas hudCanvas;

		private readonly RectTransform parent;

		private readonly RectTransform root;

		private readonly CanvasGroup group;

		private readonly Image background;

		private readonly RectTransform tabBar;

		private readonly TextMeshProUGUI output;

		private readonly ScrollRect scroll;

		private readonly TMP_InputField input;

		private readonly RectTransform settings;

		private readonly RectTransform settingsPanel;

		private readonly RectTransform statusMenu;

		private readonly TextMeshProUGUI statusMenuLabel;

		private readonly TextMeshProUGUI status;

		private readonly WindowPointer pointer;

		private readonly TMP_FontAsset font;

		private readonly Sprite buttonSprite;

		private readonly Type buttonSpriteType;

		private readonly List<KeyValuePair<string, TextMeshProUGUI>> tabLabels = new List<KeyValuePair<string, TextMeshProUGUI>>();

		private string renderedTabs = string.Empty;

		private int renderedVersion = -1;

		private bool settingsOpen;

		private int checkFocusFrame = -1;

		private int caretToEndFrame = -1;

		private int submitFrame = -10;

		private bool defaultPlacement;

		private bool runeTabs;

		private readonly RectTransform viewport;

		private readonly TextMeshProUGUI linkHint;

		internal bool Active { get; private set; }

		internal bool IsValid
		{
			get
			{
				if ((Object)(object)root != (Object)null && (Object)(object)canvas != (Object)null && (Object)(object)chat != (Object)null)
				{
					return (Object)(object)chat == (Object)(object)Chat.instance;
				}
				return false;
			}
		}

		internal static ChatWindow TryCreate(TerraChatPlugin plugin)
		{
			if (s_failed)
			{
				return null;
			}
			Chat instance = Chat.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)((Terminal)instance).m_chatWindow == (Object)null || (Object)(object)((Terminal)instance).m_output == (Object)null || (Object)(object)((Terminal)instance).m_input == (Object)null)
			{
				return null;
			}
			try
			{
				return new ChatWindow(plugin, instance);
			}
			catch (Exception ex)
			{
				s_failed = true;
				TerraChatPlugin.Log.LogError((object)("TerraChat window could not be created; Valheim chat stays in use. " + ex));
				return null;
			}
		}

		private ChatWindow(TerraChatPlugin plugin, Chat chat)
		{
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Expected O, but got Unknown
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Expected O, but got Unknown
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: 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_01dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_029e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0346: Unknown result type (might be due to invalid IL or missing references)
			//IL_034b: Unknown result type (might be due to invalid IL or missing references)
			//IL_035a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0369: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f2: Expected O, but got Unknown
			//IL_044c: 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_046b: Unknown result type (might be due to invalid IL or missing references)
			//IL_047a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0489: Unknown result type (might be due to invalid IL or missing references)
			//IL_0498: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a2: Expected O, but got Unknown
			//IL_04b6: 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_04d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e1: Expected O, but got Unknown
			//IL_04ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_050a: Unknown result type (might be due to invalid IL or missing references)
			//IL_052d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0532: Unknown result type (might be due to invalid IL or missing references)
			//IL_0541: Unknown result type (might be due to invalid IL or missing references)
			//IL_0550: Unknown result type (might be due to invalid IL or missing references)
			//IL_058e: Unknown result type (might be due to invalid IL or missing references)
			//IL_059d: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_05bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_05d4: Expected O, but got Unknown
			//IL_067a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0681: Expected O, but got Unknown
			//IL_068d: Unknown result type (might be due to invalid IL or missing references)
			//IL_06a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_06b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_06c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_06cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_06cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_06d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_07cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_07de: Unknown result type (might be due to invalid IL or missing references)
			//IL_07ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_07fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_080b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0815: Expected O, but got Unknown
			//IL_08d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_08e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_08f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0901: Unknown result type (might be due to invalid IL or missing references)
			//IL_0973: Unknown result type (might be due to invalid IL or missing references)
			//IL_0936: Unknown result type (might be due to invalid IL or missing references)
			//IL_0951: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Unknown result type (might be due to invalid IL or missing references)
			//IL_09c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_09cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_09da: Unknown result type (might be due to invalid IL or missing references)
			//IL_09e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a52: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a93: Unknown result type (might be due to invalid IL or missing references)
			//IL_0aad: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ac7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0add: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ae2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ae3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0aea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b68: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b77: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b86: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b95: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a32: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bfe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c34: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c39: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c3e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c4d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c5c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c66: Expected O, but got Unknown
			//IL_0c78: Unknown result type (might be due to invalid IL or missing references)
			//IL_0cef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d07: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d1f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d37: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d4f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d54: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d6b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0d70: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bde: Unknown result type (might be due to invalid IL or missing references)
			ChatWindow chatWindow = this;
			this.plugin = plugin;
			this.chat = chat;
			font = ((TMP_Text)((Terminal)chat).m_output).font;
			Canvas componentInParent = ((Component)((Terminal)chat).m_chatWindow).GetComponentInParent<Canvas>(true);
			hudCanvas = (((Object)(object)componentInParent != (Object)null) ? componentInParent.rootCanvas : null);
			GameObject val = new GameObject("TerraChatCanvas", new Type[3]
			{
				typeof(RectTransform),
				typeof(Canvas),
				typeof(GraphicRaycaster)
			});
			val.layer = ((Component)((Terminal)chat).m_chatWindow).gameObject.layer;
			canvas = val.GetComponent<Canvas>();
			canvas.renderMode = (RenderMode)0;
			canvas.sortingOrder = (((Object)(object)hudCanvas != (Object)null) ? (hudCanvas.sortingOrder + 1) : 10);
			val.AddComponent<CanvasScaler>();
			val.AddComponent<GuiScaler>();
			parent = (RectTransform)val.transform;
			Button val2 = (((Object)(object)InventoryGui.instance != (Object)null) ? InventoryGui.instance.m_takeAllButton : null);
			Image val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponent<Image>() : null);
			buttonSprite = (((Object)(object)val3 != (Object)null) ? val3.sprite : null);
			buttonSpriteType = (Type)(((Object)(object)val3 != (Object)null) ? ((int)val3.type) : 0);
			root = NewRect("TerraChatWindow", (Transform)(object)parent);
			root.anchorMin = (root.anchorMax = (root.pivot = Vector2.zero));
			background = ((Component)root).gameObject.AddComponent<Image>();
			Image component = ((Component)((Terminal)chat).m_chatWindow).GetComponent<Image>();
			if ((Object)(object)component != (Object)null && (Object)(object)component.sprite != (Object)null)
			{
				background.sprite = component.sprite;
				background.type = component.type;
			}
			group = ((Component)root).gameObject.AddComponent<CanvasGroup>();
			pointer = ((Component)root).gameObject.AddComponent<WindowPointer>();
			PlaceFromConfig();
			RectTransform val5 = NewRect("Header", (Transform)(object)root);
			Stretch(val5, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(4f, -28f), new Vector2(-4f, -2f));
			((Graphic)((Component)val5).gameObject.AddComponent<Image>()).color = new Color(0f, 0f, 0f, 0.25f);
			DragHandle dragHandle = ((Component)val5).gameObject.AddComponent<DragHandle>();
			dragHandle.Dragged = delegate(Vector2 delta)
			{
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0032: Unknown result type (might be due to invalid IL or missing references)
				//IL_0037: Unknown result type (might be due to invalid IL or missing references)
				chatWindow.root.anchoredPosition = chatWindow.ClampPosition(chatWindow.root.anchoredPosition + delta / chatWindow.Scale());
			};
			dragHandle.Finished = SaveWindow;
			tabBar = NewRect("Tabs", (Transform)(object)val5);
			Stretch(tabBar, Vector2.zero, Vector2.one, new Vector2(2f, 2f), new Vector2(-190f, -2f));
			((Component)tabBar).gameObject.AddComponent<RectMask2D>();
			status = NewText("Status", (Transform)(object)val5, 12f, (TextAlignmentOptions)516);
			Stretch((RectTransform)((TMP_Text)status).transform, new Vector2(1f, 0f), new Vector2(1f, 1f), new Vector2(-186f, 0f), new Vector2(-112f, 0f));
			((Graphic)status).raycastTarget = true;
			MakeClickable(((Component)status).gameObject, ToggleStatusMenu);
			TextMeshProUGUI obj = NewText("Settings", (Transform)(object)val5, 15f, (TextAlignmentOptions)514);
			((Graphic)obj).raycastTarget = true;
			((TMP_Text)obj).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)obj).text = "Settings";
			((Graphic)obj).color = TabColor;
			Stretch((RectTransform)((TMP_Text)obj).transform, new Vector2(1f, 0f), new Vector2(1f, 1f), new Vector2(-104f, 0f), new Vector2(-28f, 0f));
			Button obj2 = ((Component)obj).gameObject.AddComponent<Button>();
			((Selectable)obj2).transition = (Transition)0;
			Navigation navigation = default(Navigation);
			((Navigation)(ref navigation)).mode = (Mode)0;
			((Selectable)obj2).navigation = navigation;
			((UnityEvent)obj2.onClick).AddListener(new UnityAction(ToggleSettings));
			AddDivider(val5, new Vector2(1f, 0f), -108f);
			AddDivider(val5, new Vector2(1f, 0f), -26f);
			RectTransform val6 = NewRect("Viewport", (Transform)(object)root);
			Stretch(val6, Vector2.zero, Vector2.one, new Vector2(8f, 42f), new Vector2(-8f, -32f));
			viewport = val6;
			linkHint = NewText("DiscordHint", (Transform)(object)root, 13f, (TextAlignmentOptions)513);
			Stretch((RectTransform)((TMP_Text)linkHint).transform, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(10f, -52f), new Vector2(-10f, -32f));
			((TMP_Text)linkHint).text = string.Empty;
			((TMP_Text)linkHint).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)linkHint).overflowMode = (TextOverflowModes)1;
			((Graphic)linkHint).raycastTarget = true;
			((Component)linkHint).gameObject.AddComponent<NameLinkClick>().Clicked = delegate(string id)
			{
				if (id == "cmd:discord")
				{
					plugin.RequestDiscordInvite();
				}
				else if (id == "cmd:link")
				{
					plugin.RequestDiscordLink();
				}
				chatWindow.RefocusIfActive();
			};
			((Component)linkHint).gameObject.SetActive(false);
			((Component)val6).gameObject.AddComponent<RectMask2D>();
			output = NewText("Output", (Transform)(object)val6, plugin.FontSize.Value, (TextAlignmentOptions)1025);
			RectTransform val7 = (RectTransform)((TMP_Text)output).transform;
			val7.anchorMin = new Vector2(0f, 0f);
			val7.anchorMax = new Vector2(1f, 0f);
			val7.pivot = new Vector2(0.5f, 0f);
			val7.offsetMin = (val7.offsetMax = Vector2.zero);
			((TMP_Text)output).textWrappingMode = (TextWrappingModes)1;
			((Graphic)output).raycastTarget = true;
			((Component)output).gameObject.AddComponent<NameLinkClick>().Clicked = OnLinkClicked;
			((Component)output).gameObject.AddComponent<ContentSizeFitter>().verticalFit = (FitMode)2;
			scroll = ((Component)root).gameObject.AddComponent<ScrollRect>();
			scroll.viewport = val6;
			scroll.content = val7;
			scroll.horizontal = false;
			scroll.vertical = true;
			scroll.movementType = (MovementType)2;
			scroll.scrollSensitivity = 25f;
			GameObject val8 = Object.Instantiate<GameObject>(((Component)((Terminal)chat).m_input).gameObject, (Transform)(object)root, false);
			((Object)val8).name = "TerraChatInput";
			val8.SetActive(true);
			input = val8.GetComponent<TMP_InputField>();
			Stretch((RectTransform)val8.transform, new Vector2(0f, 0f), new Vector2(1f, 0f), new Vector2(6f, 6f), new Vector2(-24f, 38f));
			((UnityEventBase)input.onSubmit).RemoveAllListeners();
			((UnityEventBase)input.onEndEdit).RemoveAllListeners();
			((UnityEventBase)input.onSelect).RemoveAllListeners();
			((UnityEventBase)input.onDeselect).RemoveAllListeners();
			((UnityEventBase)input.onValueChanged).RemoveAllListeners();
			input.text = string.Empty;
			input.onFocusSelectAll = false;
			((UnityEvent<string>)(object)input.onSubmit).AddListener((UnityAction<string>)OnSubmit);
			((UnityEvent<string>)(object)input.onDeselect).AddListener((UnityAction<string>)delegate
			{
				if (chatWindow.Active)
				{
					chatWindow.checkFocusFrame = Time.frameCount + 1;
				}
			});
			RectTransform obj3 = NewRect("Resize", (Transform)(object)root);
			Stretch(obj3, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-20f, -21f), new Vector2(-6f, -7f));
			Image val9 = ((Component)obj3).gameObject.AddComponent<Image>();
			if ((Object)(object)buttonSprite != (Object)null)
			{
				val9.sprite = buttonSprite;
				val9.type = buttonSpriteType;
				((Graphic)val9).color = new Color(1f, 0.85f, 0.6f);
			}
			else
			{
				((Graphic)val9).color = new Color(0.35f, 0.25f, 0.15f, 0.9f);
			}
			DragHandle dragHandle2 = ((Component)obj3).gameObject.AddComponent<DragHandle>();
			dragHandle2.Dragged = delegate(Vector2 delta)
			{
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				//IL_0032: Unknown result type (might be due to invalid IL or missing references)
				//IL_0047: Unknown result type (might be due to invalid IL or missing references)
				//IL_005c: Unknown result type (might be due to invalid IL or missing references)
				Vector2 val12 = chatWindow.root.sizeDelta + delta / chatWindow.Scale();
				chatWindow.root.sizeDelta = new Vector2(Mathf.Clamp(val12.x, 320f, 1400f), Mathf.Clamp(val12.y, 160f, 900f));
			};
			dragHandle2.Finished = SaveWindow;
			settingsPanel = NewRect("Settings", (Transform)(object)root);
			Stretch(settingsPanel, Vector2.zero, Vector2.one, new Vector2(6f, 6f), new Vector2(-6f, -32f));
			Image val10 = ((Component)settingsPanel).gameObject.AddComponent<Image>();
			if ((Object)(object)background.sprite != (Object)null)
			{
				val10.sprite = background.sprite;
				val10.type = background.type;
			}
			((Graphic)val10).color = new Color(0.12f, 0.1f, 0.08f, 0.97f);
			((Component)settingsPanel).gameObject.AddComponent<RectMask2D>();
			settings = NewRect("Content", (Transform)(object)settingsPanel);
			settings.anchorMin = new Vector2(0f, 1f);
			settings.anchorMax = new Vector2(1f, 1f);
			settings.pivot = new Vector2(0.5f, 1f);
			settings.offsetMin = (settings.offsetMax = Vector2.zero);
			ScrollRect obj4 = ((Component)settingsPanel).gameObject.AddComponent<ScrollRect>();
			obj4.viewport = settingsPanel;
			obj4.content = settings;
			obj4.horizontal = false;
			obj4.movementType = (MovementType)2;
			obj4.scrollSensitivity = 25f;
			((Component)settingsPanel).gameObject.SetActive(false);
			statusMenu = NewRect("StatusMenu", (Transform)(object)root);
			Stretch(statusMenu, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-196f, -58f), new Vector2(-102f, -30f));
			Image val11 = ((Component)statusMenu).gameObject.AddComponent<Image>();
			if ((Object)(object)background.sprite != (Object)null)
			{
				val11.sprite = background.sprite;
				val11.type = background.type;
			}
			((Graphic)val11).color = new Color(0.12f, 0.1f, 0.08f, 0.98f);
			statusMenuLabel = NewText("Option", (Transform)(object)statusMenu, 14f, (TextAlignmentOptions)514);
			Stretch((RectTransform)((TMP_Text)statusMenuLabel).transform, Vector2.zero, Vector2.one, new Vector2(4f, 0f), new Vector2(-4f, 0f));
			((Graphic)statusMenuLabel).raycastTarget = true;
			((Graphic)statusMenuLabel).color = TabColor;
			MakeClickable(((Component)statusMenuLabel).gameObject, delegate
			{
				plugin.SetOnline(!plugin.StayOnline.Value);
				((Component)chatWindow.statusMenu).gameObject.SetActive(false);
				chatWindow.RefocusIfActive();
			});
			((Component)statusMenu).gameObject.SetActive(false);
			ApplyStyle();
			SetLayerRecursively(((Component)root).gameObject, ((Component)parent).gameObject.layer);
			ManualLogSource log = TerraChatPlugin.Log;
			object[] obj5 = new object[9]
			{
				root.anchoredPosition.x,
				root.anchoredPosition.y,
				root.sizeDelta.x,
				root.sizeDelta.y,
				null,
				null,
				null,
				null,
				null
			};
			Rect rect = parent.rect;
			obj5[4] = ((Rect)(ref rect)).width;
			rect = parent.rect;
			obj5[5] = ((Rect)(ref rect)).height;
			obj5[6] = canvas.scaleFactor;
			obj5[7] = canvas.sortingOrder;
			obj5[8] = (((Object)(object)hudCanvas != (Object)null) ? ((Object)hudCanvas).name : "none");
			log.LogInfo((object)string.Format("TerraChat window created: position {0:0},{1:0} size {2:0}x{3:0}; canvas {4:0}x{5:0} scale {6:0.##} sort {7}; HUD canvas {8}.", obj5));
		}

		internal void Dispose()
		{
			Active = false;
			if ((Object)(object)canvas != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)canvas).gameObject);
			}
		}

		internal void Tick()
		{
			//IL_001c: 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)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = (defaultPlacement ? DefaultPosition() : ClampPosition(root.anchoredPosition));
			if (val != root.anchoredPosition)
			{
				root.anchoredPosition = val;
			}
			if (caretToEndFrame >= 0 && Time.frameCount >= caretToEndFrame)
			{
				caretToEndFrame = -1;
				input.MoveTextEnd(false);
			}
			bool flag = (Object)(object)Player.m_localPlayer != (Object)null;
			if (((Component)root).gameObject.activeSelf != flag)
			{
				((Component)root).gameObject.SetActive(flag);
			}
			if (!flag)
			{
				Active = false;
				return;
			}
			if (!Active && ZInput.GetButtonDown("Chat") && Time.frameCount > submitFrame + 1 && CanOpen())
			{
				Open();
			}
			if (Active && ZInput.GetKeyDown((KeyCode)27, true))
			{
				Close();
			}
			if (Active && ZInput.GetKeyDown((KeyCode)9, true))
			{
				NextTab();
			}
			if (checkFocusFrame >= 0 && Time.frameCount >= checkFocusFrame)
			{
				checkFocusFrame = -1;
				if (Active && !input.isFocused)
				{
					if (pointer.Over || pointer.LastPressFrame >= Time.frameCount - 2)
					{
						Focus();
					}
					else
					{
						Close();
					}
				}
			}
			ChatState state = plugin.State;
			if (state.Version != renderedVersion)
			{
				Render(state);
			}
			((TMP_Text)status).text = ((!plugin.StayOnline.Value) ? "<color=#a09a90><u>Offline</u></color>" : ((plugin.Relay.Status == "Online") ? "<color=#8fe08f><u>Online</u></color>" : ("<color=#e0a080><u>" + plugin.Relay.Status + "</u></color>")));
			bool flag2 = (DateTime.UtcNow - state.LastMessageUtc).TotalSeconds < 10.0;
			float num = ((Active || pointer.Over || flag2) ? 1f : Mathf.Clamp(plugin.IdleOpacity.Value, 0.1f, 1f));
			group.alpha = Mathf.MoveTowards(group.alpha, num, Time.deltaTime * 2f);
		}

		private bool CanOpen()
		{
			if (!Console.IsVisible() && !TextInput.IsVisible() && !Minimap.InTextInput() && !Menu.IsVisible())
			{
				return !Hud.IsPieceSelectionVisible();
			}
			return false;
		}

		private void Open()
		{
			Active = true;
			((Transform)root).SetAsLastSibling();
			Focus();
		}

		private void Focus()
		{
			((Component)input).gameObject.SetActive(true);
			((Selectable)input).Select();
			input.ActivateInputField();
		}

		internal void Close()
		{
			Active = false;
			checkFocusFrame = -1;
			if (settingsOpen)
			{
				ToggleSettings();
			}
			((Component)statusMenu).gameObject.SetActive(false);
			input.DeactivateInputField(false);
			if ((Object)(object)EventSystem.current != (Object)null && (Object)(object)EventSystem.current.currentSelectedGameObject == (Object)(object)((Component)input).gameObject)
			{
				EventSystem.current.SetSelectedGameObject((GameObject)null);
			}
		}

		private void OnLinkClicked(string id)
		{
			if (id.StartsWith("pm:", StringComparison.Ordinal))
			{
				StartPm(id.Substring(3));
			}
			else if (id.StartsWith("url:", StringComparison.Ordinal))
			{
				GUIUtility.systemCopyBuffer = id.Substring(4);
				plugin.State.AddSystem("Link copied: " + id.Substring(4) + "  (paste it in your browser if you trust the sender)");
			}
		}

		private void StartPm(string name)
		{
			if (!string.IsNullOrEmpty(name))
			{
				InsertCommand("/pm " + (name.Contains(" ") ? ("\"" + name + "\"") : name) + " ");
			}
		}

		private void InsertCommand(string text)
		{
			input.text = text;
			if (!Active)
			{
				Open();
			}
			else
			{
				Focus();
			}
			caretToEndFrame = Time.frameCount + 1;
		}

		private void OnSubmit(string text)
		{
			submitFrame = Time.frameCount;
			input.text = string.Empty;
			plugin.Submit(text);
			Close();
		}

		private void NextTab()
		{
			List<ChatTab> list = plugin.State.VisibleTabs.ToList();
			int num = list.FindIndex((ChatTab t) => t.Id == plugin.State.ActiveTabId);
			plugin.State.Select(list[(num + 1) % list.Count].Id);
			Focus();
		}

		private void Render(ChatState state)
		{
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			renderedVersion = state.Version;
			string text = string.Join("|", state.VisibleTabs.Select((ChatTab t) => t.Id + ":" + t.Title).ToArray());
			if (text != renderedTabs)
			{
				BuildTabs(state, text);
			}
			foreach (KeyValuePair<string, TextMeshProUGUI> tabLabel in tabLabels)
			{
				ChatTab chatTab = state.Tab(tabLabel.Key);
				bool flag = chatTab.Id == state.ActiveTabId;
				((TMP_Text)tabLabel.Value).text = (flag ? "<b>" : "") + TabName(chatTab) + (chatTab.Unread ? " <color=#ffd479>*</color>" : "") + (flag ? "</b>" : "");
				((Graphic)tabLabel.Value).color = (Color)(flag ? new Color(1f, 0.85f, 0.55f) : TabColor);
			}
			bool flag2 = state.ActiveTab.Discord && state.DiscordLinked == null;
			((TMP_Text)linkHint).text = ((state.PendingLinkCode == null) ? "<color=#a09080>Join our Discord: type</color> <link=\"cmd:discord\"><color=#8ea1e1><u>/discord</u></color></link>  <color=#a09080>|</color>  <link=\"cmd:link\"><color=#8ea1e1><u>Link your Discord profile</u></color></link>" : ("<color=#8ea1e1>" + state.PendingLinkCode + "</color> <color=#a09080>copied: paste it in the Discord channel.</color> <link=\"cmd:link\"><u>New code</u></link>  <color=#a09080>|</color>  <color=#a09080>Join our Discord: type</color> <link=\"cmd:discord\"><color=#8ea1e1><u>/discord</u></color></link>"));
			if (((Component)linkHint).gameObject.activeSelf != flag2)
			{
				((Component)linkHint).gameObject.SetActive(flag2);
				viewport.offsetMax = new Vector2(viewport.offsetMax.x, -32f - (flag2 ? 20f : 0f));
			}
			int num;
			if (!(scroll.verticalNormalizedPosition <= 0.01f))
			{
				Rect rect = scroll.content.rect;
				float height = ((Rect)(ref rect)).height;
				rect = scroll.viewport.rect;
				num = ((height <= ((Rect)(ref rect)).height) ? 1 : 0);
			}
			else
			{
				num = 1;
			}
			List<string> lines = state.ActiveTab.Lines;
			((TMP_Text)output).text = string.Join("\n", lines.Skip(Math.Max(0, lines.Count - 120)).ToArray());
			if (num != 0)
			{
				Canvas.ForceUpdateCanvases();
				scroll.verticalNormalizedPosition = 0f;
			}
		}

		private void BuildTabs(ChatState state, string key)
		{
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Expected O, but got Unknown
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Expected O, but got Unknown
			renderedTabs = key;
			if (settingsOpen)
			{
				BuildSettings();
			}
			for (int num = ((Transform)tabBar).childCount - 1; num >= 0; num--)
			{
				Object.Destroy((Object)(object)((Component)((Transform)tabBar).GetChild(num)).gameObject);
			}
			tabLabels.Clear();
			TMP_FontAsset val = RuneFont.Get(font);
			runeTabs = (Object)(object)val != (Object)null;
			float num2 = AddTabSeparator(4f);
			foreach (ChatTab visibleTab in state.VisibleTabs)
			{
				string id = visibleTab.Id;
				TextMeshProUGUI val2 = NewText("Tab", (Transform)(object)tabBar, 15f, (TextAlignmentOptions)514);
				if ((Object)(object)val != (Object)null)
				{
					((TMP_Text)val2).font = val;
				}
				((TMP_Text)val2).textWrappingMode = (TextWrappingModes)0;
				((Graphic)val2).raycastTarget = true;
				((TMP_Text)val2).text = "<b>" + TabName(visibleTab) + " *</b>";
				float num3 = Mathf.Ceil(((TMP_Text)val2).preferredWidth) + 6f;
				Stretch((RectTransform)((TMP_Text)val2).transform, new Vector2(0f, 0f), new Vector2(0f, 1f), new Vector2(num2, 0f), new Vector2(num2 + num3, 0f));
				Button obj = ((Component)val2).gameObject.AddComponent<Button>();
				((Selectable)obj).transition = (Transition)0;
				Navigation navigation = default(Navigation);
				((Navigation)(ref navigation)).mode = (Mode)0;
				((Selectable)obj).navigation = navigation;
				((UnityEvent)obj.onClick).AddListener((UnityAction)delegate
				{
					plugin.State.Select(id);
					RefocusIfActive();
				});
				tabLabels.Add(new KeyValuePair<string, TextMeshProUGUI>(id, val2));
				num2 = AddTabSeparator(num2 + num3);
			}
			SetLayerRecursively(((Component)tabBar).gameObject, ((Component)parent).gameObject.layer);
		}

		private float AddTabSeparator(float x)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			AddDivider(tabBar, new Vector2(0f, 0f), x + 3f);
			return x + 8f;
		}

		private void AddDivider(RectTransform owner, Vector2 anchor, float x)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			RectTransform obj = NewRect("Divider", (Transform)(object)owner);
			Stretch(obj, new Vector2(anchor.x, 0.15f), new Vector2(anchor.x, 0.85f), new Vector2(x, 0f), new Vector2(x + 2f, 0f));
			Image obj2 = ((Component)obj).gameObject.AddComponent<Image>();
			((Graphic)obj2).color = new Color(0.75f, 0.6f, 0.4f, 0.8f);
			((Graphic)obj2).raycastTarget = false;
		}

		private string TabName(ChatTab tab)
		{
			if (tab.Id == "local")
			{
				if (!runeTabs)
				{
					return "Local";
				}
				return "ᛊᛖᚷᛃᚨ";
			}
			if (tab.Id == "pm")
			{
				if (!runeTabs)
				{
					return "PM";
				}
				return "ᛈᛗ";
			}
			if (tab.Label != null)
			{
				if (!runeTabs)
				{
					return tab.Title;
				}
				return tab.Label;
			}
			return (runeTabs ? "ᛒᛟᚱᚦ " : "Ch ") + tab.Channel;
		}

		private void ToggleSettings()
		{
			settingsOpen = !settingsOpen;
			((Component)settingsPanel).gameObject.SetActive(settingsOpen);
			if (settingsOpen)
			{
				BuildSettings();
			}
			RefocusIfActive();
		}

		private void ToggleStatusMenu()
		{
			((TMP_Text)statusMenuLabel).text = (plugin.StayOnline.Value ? "Go offline" : "Go online");
			((Component)statusMenu).gameObject.SetActive(!((Component)statusMenu).gameObject.activeSelf);
			((Transform)statusMenu).SetAsLastSibling();
			RefocusIfActive();
		}

		private void MakeClickable(GameObject target, Action action)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			Button obj = target.AddComponent<Button>();
			((Selectable)obj).transition = (Transition)0;
			Navigation navigation = default(Navigation);
			((Navigation)(ref navigation)).mode = (Mode)0;
			((Selectable)obj).navigation = navigation;
			((UnityEvent)obj.onClick).AddListener((UnityAction)delegate
			{
				action();
			});
		}

		private void BuildSettings()
		{
			//IL_02fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_030b: Unknown result type (might be due to invalid IL or missing references)
			//IL_031a: Unknown result type (might be due to invalid IL or missing references)
			//IL_032b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0336: Unknown result type (might be due to invalid IL or missing references)
			//IL_0340: Expected O, but got Unknown
			//IL_036c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0378: Unknown result type (might be due to invalid IL or missing references)
			for (int num = ((Transform)settings).childCount - 1; num >= 0; num--)
			{
				Object.Destroy((Object)(object)((Component)((Transform)settings).GetChild(num)).gameObject);
			}
			float y = -6f;
			y = SettingsHeading("Channels (click to mute or unmute)", y);
			SettingsRow settingsRow = new SettingsRow(this, y);
			foreach (ChatTab item in from t in (from t in plugin.State.VisibleTabs.Concat(plugin.State.MutedChannels.Select((int c) => plugin.State.ChannelTab(c)))
					where t != null && t.Channel > 0
					select t).Distinct()
				orderby t.Channel
				select t)
			{
				int channel = item.Channel;
				bool muted = plugin.State.IsMuted(channel);
				settingsRow.Button((muted ? "<color=#e0a080><s>" : "<color=#8fe08f>") + item.Title + (muted ? "</s></color>" : "</color>"), delegate
				{
					plugin.SetChannelMuted(channel, !muted);
				});
			}
			y = settingsRow.End();
			y = SettingsHeading("Text size", y);
			y = Stepper(y, plugin.FontSize.Value.ToString("0"), delegate
			{
				Adjust(plugin.FontSize, -1f, 10f, 28f);
			}, delegate
			{
				Adjust(plugin.FontSize, 1f, 10f, 28f);
			});
			y = SettingsHeading("Opacity when idle", y);
			y = Stepper(y, Mathf.RoundToInt(plugin.IdleOpacity.Value * 100f) + "%", delegate
			{
				Adjust(plugin.IdleOpacity, -0.1f, 0.1f, 1f);
			}, delegate
			{
				Adjust(plugin.IdleOpacity, 0.1f, 0.1f, 1f);
			});
			y = SettingsHeading("Background opacity", y);
			y = Stepper(y, Mathf.RoundToInt(plugin.BackgroundOpacity.Value * 100f) + "%", delegate
			{
				Adjust(plugin.BackgroundOpacity, -0.1f, 0f, 1f);
			}, delegate
			{
				Adjust(plugin.BackgroundOpacity, 0.1f, 0f, 1f);
			});
			y = SettingsHeading("Window", y);
			settingsRow = new SettingsRow(this, y);
			settingsRow.Button("Reset position and size", ResetWindow);
			settingsRow.Button("Close settings", ToggleSettings);
			y = settingsRow.End();
			TextMeshProUGUI obj = NewText("Hint", (Transform)(object)settings, 11f, (TextAlignmentOptions)513);
			Stretch((RectTransform)((TMP_Text)obj).transform, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(10f, y - 16f), new Vector2(-10f, y));
			((TMP_Text)obj).text = "<color=#a09080>Drag the title bar to move, the dot to resize. Click a name to PM. Click Online to go offline.</color>";
			((TMP_Text)obj).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)obj).overflowMode = (TextOverflowModes)1;
			y -= 22f;
			settings.sizeDelta = new Vector2(settings.sizeDelta.x, 0f - y);
			SetLayerRecursively(((Component)settings).gameObject, ((Component)parent).gameObject.layer);
		}

		private float SettingsHeading(string text, float y)
		{
			//IL_0021: 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)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Expected O, but got Unknown
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI obj = NewText("Heading", (Transform)(object)settings, 13f, (TextAlignmentOptions)513);
			Stretch((RectTransform)((TMP_Text)obj).transform, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(10f, y - 18f), new Vector2(-10f, y));
			((TMP_Text)obj).text = text;
			((Graphic)obj).color = new Color(0.75f, 0.68f, 0.55f);
			((TMP_Text)obj).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)obj).overflowMode = (TextOverflowModes)1;
			return y - 18f;
		}

		private float Stepper(float y, string value, Action down, Action up)
		{
			SettingsRow settingsRow = new SettingsRow(this, y);
			settingsRow.Button("-", down, 30f);
			settingsRow.Value(value, 48f);
			settingsRow.Button("+", up, 30f);
			return settingsRow.End();
		}

		private void Adjust(ConfigEntry<float> entry, float step, float min, float max)
		{
			entry.Value = Mathf.Clamp(Mathf.Round((entry.Value + step) * 100f) / 100f, min, max);
			ApplyStyle();
		}

		private void ApplyStyle()
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			((TMP_Text)output).fontSize = Mathf.Clamp(plugin.FontSize.Value, 10f, 28f);
			Color color = (Color)(((Object)(object)background.sprite != (Object)null) ? Color.white : new Color(0f, 0f, 0f, 1f));
			color.a = Mathf.Clamp01(plugin.BackgroundOpacity.Value);
			((Graphic)background).color = color;
			renderedVersion = -1;
		}

		private void RefocusIfActive()
		{
			if (Active)
			{
				Focus();
			}
		}

		private void PlaceFromConfig()
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Clamp(plugin.WindowWidth.Value, 320f, 1400f);
			float num2 = Mathf.Clamp(plugin.WindowHeight.Value, 160f, 900f);
			root.sizeDelta = new Vector2(num, num2);
			if (plugin.WindowX.Value >= 0f && plugin.WindowY.Value >= 0f)
			{
				root.anchoredPosition = ClampPosition(new Vector2(plugin.WindowX.Value, plugin.WindowY.Value));
				return;
			}
			defaultPlacement = true;
			root.anchoredPosition = DefaultPosition();
		}

		private Vector2 DefaultPosition()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: 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)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			Rect rect = parent.rect;
			return ClampPosition(new Vector2(((Rect)(ref rect)).width - root.sizeDelta.x - 16f, 16f));
		}

		private void ResetWindow()
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			plugin.WindowX.Value = -1f;
			plugin.WindowY.Value = -1f;
			plugin.WindowWidth.Value = (float)((ConfigEntryBase)plugin.WindowWidth).DefaultValue;
			plugin.WindowHeight.Value = (float)((ConfigEntryBase)plugin.WindowHeight).DefaultValue;
			root.sizeDelta = new Vector2(plugin.WindowWidth.Value, plugin.WindowHeight.Value);
			defaultPlacement = true;
			root.anchoredPosition = DefaultPosition();
		}

		private void SaveWindow()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			defaultPlacement = false;
			plugin.WindowX.Value = root.anchoredPosition.x;
			plugin.WindowY.Value = root.anchoredPosition.y;
			plugin.WindowWidth.Value = root.sizeDelta.x;
			plugin.WindowHeight.Value = root.sizeDelta.y;
		}

		private Vector2 ClampPosition(Vector2 position)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			Rect rect = parent.rect;
			return new Vector2(Mathf.Clamp(position.x, 0f, Mathf.Max(0f, ((Rect)(ref rect)).width - 60f)), Mathf.Clamp(position.y, 0f, Mathf.Max(0f, ((Rect)(ref rect)).height - 28f)));
		}

		private float Scale()
		{
			Canvas componentInParent = ((Component)root).GetComponentInParent<Canvas>();
			if (!((Object)(object)componentInParent != (Object)null) || !(componentInParent.scaleFactor > 0f))
			{
				return 1f;
			}
			return componentInParent.scaleFactor;
		}

		private TextMeshProUGUI NewText(string name, Transform owner, float size, TextAlignmentOptions alignment)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) });
			val.SetActive(false);
			val.transform.SetParent(owner, false);
			TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val2).font = font;
			val.SetActive(true);
			((TMP_Text)val2).fontSize = size;
			((TMP_Text)val2).alignment = alignment;
			((TMP_Text)val2).richText = true;
			((Graphic)val2).raycastTarget = false;
			((Graphic)val2).color = new Color(0.95f, 0.92f, 0.85f);
			return val2;
		}

		private static RectTransform NewRect(string name, Transform owner)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(owner, false);
			return (RectTransform)val.transform;
		}

		private static void Stretch(RectTransform rect, Vector2 anchorMin, Vector2 anchorMax, Vector2 offsetMin, Vector2 offsetMax)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			rect.anchorMin = anchorMin;
			rect.anchorMax = anchorMax;
			rect.pivot = new Vector2(0.5f, 0.5f);
			rect.offsetMin = offsetMin;
			rect.offsetMax = offsetMax;
		}

		private static void SetLayerRecursively(GameObject go, int layer)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			go.layer = layer;
			foreach (Transform item in go.transform)
			{
				SetLayerRecursively(((Component)item).gameObject, layer);
			}
		}
	}
	internal static class RuneFont
	{
		internal const string Probe = "ᛊᛖᚷᛃᚨᛒᛟᚱᚦᚺᛁᛈᛗ";

		private const string ResourceName = "TerraChat.Fonts.NotoSansRunic-Regular.ttf";

		private static bool s_resolved;

		private static TMP_FontAsset s_font;

		internal static TMP_FontAsset Get(TMP_FontAsset primary)
		{
			if (s_resolved)
			{
				return s_font;
			}
			s_resolved = true;
			try
			{
				string text = ExtractFont();
				TMP_FontAsset val = ((text != null) ? TMP_FontAsset.CreateFontAsset(text, 0, 90, 9, (GlyphRenderMode)4165, 512, 512) : null);
				uint[] array = default(uint[]);
				if ((Object)(object)val != (Object)null && val.HasCharacters("ᛊᛖᚷᛃᚨᛒᛟᚱᚦᚺᛁᛈᛗ", ref array, false, true))
				{
					((Object)val).name = "TerraChat Runes";
					if ((Object)(object)primary != (Object)null)
					{
						val.fallbackFontAssetTable = new List<TMP_FontAsset> { primary };
					}
					s_font = val;
				}
			}
			catch (Exception ex)
			{
				TerraChatPlugin.Log.LogWarning((object)("TerraChat rune font could not be loaded: " + ex.Message));
				s_font = null;
			}
			TerraChatPlugin.Log.LogInfo((object)(((Object)(object)s_font != (Object)null) ? "TerraChat rune tabs use the embedded Noto Sans Runic font." : "TerraChat rune font unavailable; tabs use Latin names."));
			return s_font;
		}

		private static string ExtractFont()
		{
			using Stream stream = typeof(RuneFont).Assembly.GetManifestResourceStream("TerraChat.Fonts.NotoSansRunic-Regular.ttf");
			if (stream == null)
			{
				return null;
			}
			byte[] array = new byte[stream.Length];
			int num;
			for (int i = 0; i < array.Length; i += num)
			{
				num = stream.Read(array, i, array.Length - i);
				if (num <= 0)
				{
					break;
				}
			}
			string text = Path.Combine(Paths.CachePath, "TerraChat");
			string text2 = Path.Combine(text, "NotoSansRunic-Regular.ttf");
			Directory.CreateDirectory(text);
			if (!File.Exists(text2) || new FileInfo(text2).Length != array.Length)
			{
				File.WriteAllBytes(text2, array);
			}
			return text2;
		}
	}
	internal sealed class DragHandle : MonoBehaviour, IDragHandler, IEventSystemHandler, IEndDragHandler
	{
		internal Action<Vector2> Dragged;

		internal Action Finished;

		public void OnDrag(PointerEventData eventData)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			Dragged?.Invoke(eventData.delta);
		}

		public void OnEndDrag(PointerEventData eventData)
		{
			Finished?.Invoke();
		}
	}
	internal sealed class WindowPointer : MonoBehaviour, IPointerEnterHandler, IEventSystemHandler, IPointerExitHandler, IPointerDownHandler
	{
		internal bool Over;

		internal int LastPressFrame = -1;

		public void OnPointerEnter(PointerEventData eventData)
		{
			Over = true;
		}

		public void OnPointerExit(PointerEventData eventData)
		{
			Over = false;
		}

		public void OnPointerDown(PointerEventData eventData)
		{
			LastPressFrame = Time.frameCount;
		}
	}
	internal sealed class NameLinkClick : MonoBehaviour, IPointerClickHandler, IEventSystemHandler
	{
		internal Action<string> Clicked;

		public void OnPointerClick(PointerEventData eventData)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if ((int)eventData.button == 0)
			{
				TMP_Text component = ((Component)this).GetComponent<TMP_Text>();
				Camera pressEventCamera = eventData.pressEventCamera;
				int num = TMP_TextUtilities.FindIntersectingLink(component, Vector2.op_Implicit(eventData.position), pressEventCamera);
				if (num >= 0)
				{
					string linkID = ((TMP_LinkInfo)(ref component.textInfo.linkInfo[num])).GetLinkID();
					Clicked?.Invoke(linkID);
				}
			}
		}
	}
}
namespace TerraChat.Patches
{
	[HarmonyPatch(typeof(Chat), "HasFocus")]
	internal static class ChatHasFocusPatch
	{
		private static void Postfix(ref bool __result)
		{
			TerraChatPlugin instance = TerraChatPlugin.Instance;
			if ((Object)(object)instance != (Object)null && instance.WindowActive)
			{
				__result = true;
			}
		}
	}
	[HarmonyPatch(typeof(Chat), "Update")]
	internal static class ChatUpdatePatch
	{
		private static readonly FieldRef<Terminal, bool> Focused = AccessTools.FieldRefAccess<Terminal, bool>("m_focused");

		private static readonly Dictionary<KeyCode, List<string>> Binds = (Dictionary<KeyCode, List<string>>)AccessTools.Field(typeof(Terminal), "m_binds").GetValue(null);

		private static bool Prefix(Chat __instance)
		{
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			TerraChatPlugin instance = TerraChatPlugin.Instance;
			if ((Object)(object)instance == (Object)null || !instance.Enabled.Value || !instance.HasWindow)
			{
				return true;
			}
			Focused.Invoke((Terminal)(object)__instance) = false;
			__instance.m_wasFocused = false;
			if ((Object)(object)((Terminal)__instance).m_chatWindow != (Object)null && ((Component)((Terminal)__instance).m_chatWindow).gameObject.activeSelf)
			{
				((Component)((Terminal)__instance).m_chatWindow).gameObject.SetActive(false);
			}
			if ((Object)(object)((Terminal)__instance).m_input != (Object)null && ((Component)((Terminal)__instance).m_input).gameObject.activeSelf)
			{
				((TMP_InputField)((Terminal)__instance).m_input).DeactivateInputField(false);
				((Component)((Terminal)__instance).m_input).gameObject.SetActive(false);
			}
			if (!instance.WindowActive && Binds != null && !Console.IsVisible() && !TextInput.IsVisible() && !Minimap.InTextInput())
			{
				foreach (KeyValuePair<KeyCode, List<string>> bind in Binds)
				{
					if (!ZInput.GetKeyDown(bind.Key, true))
					{
						continue;
					}
					foreach (string item in bind.Value)
					{
						((Terminal)__instance).TryRunCommand(item, true, true);
					}
				}
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Terminal), "AddString", new Type[] { typeof(string) })]
	internal static class TerminalAddStringPatch
	{
		private static void Postfix(Terminal __instance, string text)
		{
			TerraChatPlugin instance = TerraChatPlugin.Instance;
			if ((Object)(object)instance != (Object)null && __instance is Chat)
			{
				instance.State.AddLocal(text);
			}
		}
	}
	[HarmonyPatch(typeof(GameCamera), "UpdateMouseCapture")]
	internal static class GameCameraMouseCapturePatch
	{
		private static void Postfix()
		{
			TerraChatPlugin instance = TerraChatPlugin.Instance;
			if (!((Object)(object)instance == (Object)null) && instance.WindowActive)
			{
				ZCursor.LockState = (CursorLockMode)0;
				ZCursor.Show();
			}
		}
	}
}
namespace TerraChat.Net
{
	public sealed class RelayClient
	{
		private static readonly string[] FatalErrors = new string[5] { "bad_version", "bad_name", "name_taken", "banned", "steam_required" };

		private readonly ConcurrentQueue<JObject> incoming = new ConcurrentQueue<JObject>();

		private readonly ConcurrentQueue<string> outgoing = new ConcurrentQueue<string>();

		private readonly SemaphoreSlim outgoingSignal = new SemaphoreSlim(0);

		private readonly Action<string> log;

		private CancellationTokenSource stop;

		private volatile bool fatal;

		private volatile string status = "Offline";

		public string Status => status;

		public bool Running
		{
			get
			{
				if (stop != null)
				{
					return !stop.IsCancellationRequested;
				}
				return false;
			}
		}

		public RelayClient(Action<string> log)
		{
			this.log = log ?? ((Action<string>)delegate
			{
			});
		}

		public void Start(string url, JObject hello)
		{
			Stop();
			fatal = false;
			stop = new CancellationTokenSource();
			CancellationToken token = stop.Token;
			Task.Run(() => RunAsync(url, ((JToken)hello).ToString((Formatting)0, Array.Empty<JsonConverter>()), token));
		}

		public void Stop()
		{
			if (stop != null)
			{
				stop.Cancel();
				stop = null;
				status = "Offline";
			}
		}

		public void Send(JObject message)
		{
			outgoing.Enqueue(((JToken)message).ToString((Formatting)0, Array.Empty<JsonConverter>()));
			outgoingSignal.Release();
		}

		public bool TryReceive(out JObject message)
		{
			return incoming.TryDequeue(out message);
		}

		private async Task RunAsync(string url, string hello, CancellationToken token)
		{
			int delaySeconds = 2;
			while (!token.IsCancellationRequested && !fatal)
			{
				status = "Connecting";
				try
				{
					using ClientWebSocket socket = new ClientWebSocket();
					socket.Options.KeepAliveInterval = TimeSpan.FromSeconds(20.0);
					await socket.ConnectAsync(new Uri(url), token).ConfigureAwait(continueOnCapturedContext: false);
					await SendRawAsync(socket, hello, token).ConfigureAwait(continueOnCapturedContext: false);
					status = "Online";
					delaySeconds = 2;
					using CancellationTokenSource connection = CancellationTokenSource.CreateLinkedTokenSource(new CancellationToken[1] { token });
					Task sender = SendLoopAsync(socket, connection.Token);
					Task pinger = PingLoopAsync(connection.Token);
					await ReceiveLoopAsync(socket, connection.Token).ConfigureAwait(continueOnCapturedContext: false);
					connection.Cancel();
					try
					{
						await Task.WhenAll(sender, pinger).ConfigureAwait(continueOnCapturedContext: false);
					}
					catch
					{
					}
				}
				catch (OperationCanceledException) when (token.IsCancellationRequested)
				{
					break;
				}
				catch (Exception ex2)
				{
					log("TerraChat connection: " + ex2.GetType().Name + ": " + ex2.Message);
				}
				status = (fatal ? "Stopped" : "Offline");
				if (!token.IsCancellationRequested && !fatal)
				{
					try
					{
						await Task.Delay(TimeSpan.FromSeconds(delaySeconds), token).ConfigureAwait(continueOnCapturedContext: false);
					}
					catch (OperationCanceledException)
					{
						break;
					}
					delaySeconds = Math.Min(60, delaySeconds * 2);
					continue;
				}
				break;
			}
		}

		private async Task ReceiveLoopAsync(ClientWebSocket socket, CancellationToken token)
		{
			ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[4096]);
			using MemoryStream message = new MemoryStream();
			while (socket.State == WebSocketState.Open && !token.IsCancellationRequested)
			{
				WebSocketReceiveResult webSocketReceiveResult = await socket.ReceiveAsync(buffer, token).ConfigureAwait(continueOnCapturedContext: false);
				if (webSocketReceiveResult.MessageType == WebSocketMessageType.Close)
				{
					return;
				}
				message.Write(buffer.Array, 0, webSocketReceiveResult.Count);
				if (message.Length > 65536)
				{
					return;
				}
				if (webSocketReceiveResult.EndOfMessage)
				{
					string text = Encoding.UTF8.GetString(message.ToArray());
					message.SetLength(0L);
					JObject val;
					try
					{
						val = JObject.Parse(text);
					}
					catch
					{
						continue;
					}
					if ((string)val["t"] == "error" && Array.IndexOf(FatalErrors, (string)val["code"]) >= 0)
					{
						fatal = true;
					}
					incoming.Enqueue(val);
				}
			}
		}

		private async Task SendLoopAsync(ClientWebSocket socket, CancellationToken token)
		{
			while (!token.IsCancellationRequested)
			{
				await outgoingSignal.WaitAsync(token).ConfigureAwait(continueOnCapturedContext: false);
				string result;
				while (socket.State == WebSocketState.Open && outgoing.TryDequeue(out result))
				{
					await SendRawAsync(socket, result, token).ConfigureAwait(continueOnCapturedContext: false);
				}
			}
		}

		private async Task PingLoopAsync(CancellationToken token)
		{
			while (!token.IsCancellationRequested)
			{
				await Task.Delay(TimeSpan.FromSeconds(30.0), token).ConfigureAwait(continueOnCapturedContext: false);
				Send(new JObject { ["t"] = JToken.op_Implicit("ping") });
			}
		}

		private static Task SendRawAsync(ClientWebSocket socket, string text, CancellationToken token)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(text);
			return socket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, endOfMessage: true, token);
		}
	}
	internal sealed class SteamTicket : IDisposable
	{
		internal const string Identity = "terrachat";

		private const float TimeoutSeconds = 15f;

		private Callback<GetTicketForWebApiResponse_t> callback;

		private HAuthTicket handle = HAuthTicket.Invalid;

		private float requestedAt = -1f;

		internal string Ticket { get; private set; }

		internal bool Failed { get; private set; }

		internal bool Pending
		{
			get
			{
				if (requestedAt >= 0f && Ticket == null)
				{
					return !Failed;
				}
				return false;
			}
		}

		internal void Request()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			Cancel();
			Ticket = null;
			Failed = false;
			try
			{
				if (!SteamAPI.IsSteamRunning() || !SteamUser.BLoggedOn())
				{
					Failed = true;
					return;
				}
				if (callback == null)
				{
					callback = Callback<GetTicketForWebApiResponse_t>.Create((DispatchDelegate<GetTicketForWebApiResponse_t>)OnTicket);
				}
				handle = SteamUser.GetAuthTicketForWebApi("terrachat");
				requestedAt = Time.realtimeSinceStartup;
				if (handle == HAuthTicket.Invalid)
				{
					Failed = true;
				}
			}
			catch (Exception ex)
			{
				TerraChatPlugin.Log.LogWarning((object)("TerraChat Steam ticket unavailable: " + ex.Message));
				Failed = true;
			}
		}

		internal void Tick()
		{
			if (Pending && Time.realtimeSinceStartup - requestedAt > 15f)
			{
				Failed = true;
			}
		}

		private unsafe void OnTicket(GetTicketForWebApiResponse_t response)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Invalid comparison between Unknown and I4
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			if (response.m_hAuthTicket != handle)
			{
				return;
			}
			if ((int)response.m_eResult != 1 || response.m_cubTicket <= 0)
			{
				TerraChatPlugin.Log.LogWarning((object)("TerraChat Steam ticket request failed: " + ((object)(*(EResult*)(&response.m_eResult))/*cast due to .constrained prefix*/).ToString()));
				Failed = true;
				return;
			}
			StringBuilder stringBuilder = new StringBuilder(response.m_cubTicket * 2);
			for (int i = 0; i < response.m_cubTicket; i++)
			{
				stringBuilder.Append(response.m_rgubTicket[i].ToString("x2"));
			}
			Ticket = stringBuilder.ToString();
		}

		private void Cancel()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (handle != HAuthTicket.Invalid)
				{
					SteamUser.CancelAuthTicket(handle);
				}
			}
			catch
			{
			}
			handle = HAuthTicket.Invalid;
			requestedAt = -1f;
		}

		public void Dispose()
		{
			Cancel();
			if (callback != null)
			{
				callback.Dispose();
				callback = null;
			}
		}
	}
}
namespace TerraChat.Chat
{
	public sealed class ChatTab
	{
		public string Id { get; }

		public string Title { get; internal set; }

		public string Label { get; internal set; }

		public bool Discord { get; internal set; }

		public int Channel { get; }

		public bool Unread { get; internal set; }

		public List<string> Lines { get; } = new List<string>();

		public ChatTab(string id, string title, int channel)
		{
			Id = id;
			Title = title;
			Channel = channel;
		}
	}
	public sealed class ChatState
	{
		public const string LocalTab = "local";

		public const string PmTab = "pm";

		public const int MaxLines = 200;

		private static readonly string[] ChannelColors = new string[4] { "#9fd3ff", "#b5ff9f", "#ffc89f", "#e0e0a0" };

		private const string PmColor = "#e6a8ff";

		private const string SystemColor = "#c8c8c8";

		private const string NameColor = "orange";

		private const string DiscordColor = "#8ea1e1";

		private readonly List<ChatTab> tabs = new List<ChatTab>();

		private readonly HashSet<int> muted = new HashSet<int>();

		private static readonly Regex WebAddress = new Regex("(https?://|www\\.)\\S+|\\b[a-z0-9-]+(\\.[a-z0-9-]+)*\\.(com|net|org|io|gg|is|co|me|xyz|ru|tv|info|biz|app|dev|link|site|online|top|shop|store|club|live|fun|uk|de|eu|us|ly|to|cc|pw|tk|ml|ga|cf|gq)\\b(/\\S*)?", RegexOptions.IgnoreCase);

		public int Version { get; private set; }

		public string ActiveTabId { get; private set; }

		public string LastPmPartner { get; private set; }

		public DateTime LastMessageUtc { get; private set; }

		public string DiscordLinked { get; private set; }

		public ChatTab DiscordTab => tabs.FirstOrDefault((ChatTab t) => t.Discord);

		public string PendingLinkCode { get; private set; }

		public IEnumerable<ChatTab> VisibleTabs => tabs.Where((ChatTab t) => t.Channel == 0 || !muted.Contains(t.Channel));

		public ChatTab ActiveTab => tabs.First((ChatTab t) => t.Id == ActiveTabId);

		public IEnumerable<int> MutedChannels => muted.OrderBy((int c) => c);

		public ChatState()
		{
			tabs.Add(new ChatTab("local", "Local", 0));
			SetChannels(new Tuple<int, string>[3]
			{
				Tuple.Create(1, "Global #1"),
				Tuple.Create(2, "Global #2"),
				Tuple.Create(3, "Global #3")
			});
			tabs.Add(new ChatTab("pm", "PM", 0));
			ActiveTabId = "local";
		}

		public void SetDiscordLinked(string name)
		{
			DiscordLinked = (string.IsNullOrEmpty(name) ? null : name);
			if (DiscordLinked != null)
			{
				PendingLinkCode = null;
			}
			Changed();
		}

		public void SetPendingLinkCode(string command)
		{
			PendingLinkCode = (string.IsNullOrEmpty(command) ? null : command);
			Changed();
		}

		public ChatTab Tab(string id)
		{
			return tabs.FirstOrDefault((ChatTab t) => t.Id == id);
		}

		public ChatTab ChannelTab(int channel)
		{
			return tabs.FirstOrDefault((ChatTab t) => t.Channel == channel && channel != 0);
		}

		public void SetChannels(IEnumerable<Tuple<int, string>> channels)
		{
			foreach (Tuple<int, string> channel in channels)
			{
				ChatTab chatTab = ChannelTab(channel.Item1);
				if (chatTab != null)
				{
					chatTab.Title = channel.Item2;
					continue;
				}
				int num = tabs.FindIndex((ChatTab t) => t.Id == "pm");
				ChatTab item = new ChatTab("g" + channel.Item1, channel.Item2, channel.Item1);
				if (num >= 0)
				{
					tabs.Insert(num, item);
				}
				else
				{
					tabs.Add(item);
				}
			}
			Changed();
		}

		public void SetChannelInfo(int channel, string label, bool discord)
		{
			ChatTab chatTab = ChannelTab(channel);
			if (chatTab != null)
			{
				chatTab.Label = (string.IsNullOrEmpty(label) ? null : label);
				chatTab.Discord = discord;
				Changed();
			}
		}

		public void Select(string tabId)
		{
			ChatTab chatTab = Tab(tabId);
			if (chatTab != null && (chatTab.Channel == 0 || !muted.Contains(chatTab.Channel)))
			{
				ActiveTabId = tabId;
				chatTab.Unread = false;
				Changed();
			}
		}

		public bool IsMuted(int channel)
		{
			return muted.Contains(channel);
		}

		public void SetMuted(int channel, bool value)
		{
			if (ChannelTab(channel) != null)
			{
				if (value)
				{
					muted.Add(channel);
				}
				else
				{
					muted.Remove(channel);
				}
				if (value && ActiveTab.Channel == channel)
				{
					ActiveTabId = "local";
				}
				Changed();
			}
		}

		public void SetMutedFromConfig(string text)
		{
			muted.Clear();
			string[] array = (text ?? string.Empty).Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				if (int.TryParse(array[i].Trim(), out var result))
				{
					muted.Add(result);
				}
			}
			Changed();
		}

		public string MutedToConfig()
		{
			return string.Join(",", MutedChannels.Select((int c) => c.ToString()).ToArray());
		}

		public void AddLocal(string formatted)
		{
			Append(Tab("local"), formatted);
		}

		public void AddChannelMessage(int channel, string from, string text, bool fromDiscord = false)
		{
			if (!muted.Contains(channel))
			{
				ChatTab chatTab = ChannelTab(channel);
				if (chatTab != null)
				{
					string text2 = ChannelColors[Math.Max(0, channel - 1) % ChannelColors.Length];
					string text3 = (fromDiscord ? ("<color=#8ea1e1>[D] " + Clean(from) + "</color>") : ("<color=orange>" + NameLink(from) + "</color>"));
					Append(chatTab, text3 + ": <color=" + text2 + ">" + Clean(text) + "</color>");
				}
			}
		}

		public void AddPrivateMessage(string from, string to, string text, string myName, bool fromHistory = false)
		{
			bool flag = string.Equals(from, myName, StringComparison.OrdinalIgnoreCase);
			LastPmPartner = (flag ? to : from);
			string text2 = (flag ? ("To " + NameLink(to)) : ("From " + NameLink(from)));
			Append(Tab("pm"), "<color=#e6a8ff>[" + text2 + "]</color> " + WebLinks(Clean(text)), !fromHistory);
		}

		public void AddPmNotice(string text)
		{
			Append(Tab("pm"), "<color=#e6a8ff>[TerraChat]</color> " + WebLinks(Clean(text)));
		}

		public void LoadPmHistory(IEnumerable<PmEntry> entries)
		{
			ChatTab chatTab = Tab("pm");
			chatTab.Lines.Clear();
			chatTab.Unread = false;
			LastPmPartner = null;
			foreach (PmEntry entry in entries)
			{
				AddPrivateMessage(entry.From, entry.To, entry.Text, entry.Outgoing ? entry.From : null, fromHistory: true);
			}
			Changed();
		}

		public void AddSystem(string text, string tabId = null)
		{
			ChatTab tab = ((tabId != null) ? Tab(tabId) : null) ?? ActiveTab;
			Append(tab, "<color=#c8c8c8>" + Clean(text) + "</color>", markUnread: false);
		}

		public static string WebLinks(string cleaned)
		{
			return WebAddress.Replace(cleaned, delegate(Match m)
			{
				string text = m.Value.Replace("\"", "");
				return "<link=\"url:" + text + "\"><color=#7fc8ff><u>" + text + "</u></color></link>";
			});
		}

		public static string NameLink(string name)
		{
			string text = Clean(name).Replace("\"", "");
			return "<link=\"pm:" + text + "\"><u>" + text + "</u></link>";
		}

		public static string Clean(string text)
		{
			return (text ?? string.Empty).Replace('<', ' ').Replace('>', ' ');
		}

		private void Append(ChatTab tab, string line, bool markUnread = true)
		{
			if (tab != null)
			{
				tab.Lines.Add(line);
				if (tab.Lines.Count > 200)
				{
					tab.Lines.RemoveRange(0, tab.Lines.Count - 200);
				}
				if (markUnread && tab.Id != ActiveTabId)
				{
					tab.Unread = true;
				}
				LastMessageUtc = DateTime.UtcNow;
				Changed();
			}
		}

		private void Changed()
		{
			Version++;
		}
	}
	public enum ChatCommandKind
	{
		None,
		Local,
		Channel,
		PrivateMessage,
		Who,
		MuteChannel,
		UnmuteChannel,
		Admin,
		LinkDiscord,
		DiscordInvite,
		UnlinkDiscord,
		Help,
		Error
	}
	public sealed class ChatCommand
	{
		public ChatCommandKind Kind;

		public int Channel;

		public string Target;

		public string Text;

		public string AdminCommand;

		public double Minutes;

		public static ChatCommand Error(string text)
		{
			return new ChatCommand
			{
				Kind = ChatCommandKind.Error,
				Text = text
			};
		}
	}
	public static class CommandParser
	{
		public const string HelpText = "TerraChat: Enter opens chat, Tab switches tabs. /g1 /g2 /g3 text = global channel, /g4 text = Heim (linked to Discord), /pm Name text (\"quoted name\" if it has spaces), /r text = reply, /who = online players, /discord = our Discord invite, /tc mute 2 | /tc unmute 2 = mute a channel, /tc link | /tc unlink = Discord profile, /tc help. Other / commands go to Valheim chat.";

		public const string ModeratorHelp = "Moderation: /tc mute Name [minutes], /tc unmute Name, /tc kick Name, /tc ban Name [minutes], /tc unban Name.";

		public const string AdminHelp = "Moderation: /tc mute Name [minutes], /tc unmute Name, /tc kick Name, /tc ban Name [minutes], /tc unban Name. Admins: /tc mod Name, /tc unmod Name.";

		public static ChatCommand Parse(string input, ChatTab activeTab, string lastPmPartner)
		{
			string text = (input ?? string.Empty).Trim();
			if (text.Length == 0)
			{
				return new ChatCommand
				{
					Kind = ChatCommandKind.None
				};
			}
			if (text[0] == '/')
			{
				string rest;
				string text2 = FirstWord(text, out rest);
				switch (text2.ToLowerInvariant())
				{
				case "/g1":
				case "/g2":
				case "/g3":
				case "/g4":
					return Channel(int.Parse(text2.Substring(2)), rest);
				case "/pm":
					return PrivateMessage(rest);
				case "/r":
					if (string.IsNullOrEmpty(lastPmPartner))
					{
						return ChatCommand.Error("Nobody to reply to yet. Use /pm Name text.");
					}
					return Pm(lastPmPartner, rest);
				case "/who":
					return new ChatCommand
					{
						Kind = ChatCommandKind.Who
					};
				case "/discord":
					return new ChatCommand
					{
						Kind = ChatCommandKind.DiscordInvite
					};
				case "/tc":
					return TerraChatCommand(rest);
				default:
					return new ChatCommand
					{
						Kind = ChatCommandKind.Local,
						Text = text
					};
				}
			}
			if (activeTab == null || activeTab.Id == "local")
			{
				return new ChatCommand
				{
					Kind = ChatCommandKind.Local,
					Text = text
				};
			}
			if (activeTab.Id == "pm")
			{
				if (string.IsNullOrEmpty(lastPmPartner))
				{
					return ChatCommand.Error("Start a conversation with /pm Name text.");
				}
				return Pm(lastPmPartner, text);
			}
			return Channel(activeTab.Channel, text);
		}

		private static ChatCommand TerraChatCommand(string rest)
		{
			string rest2;
			string text = FirstWord(rest, out rest2).ToLowerInvariant();
			switch (text)
			{
			case "help":
			case "":
				return new ChatCommand
				{
					Kind = ChatCommandKind.Help
				};
			case "mute":
			case "unmute":
				if (rest2.Length == 1 && char.IsDigit(rest2[0]))
				{
					return new ChatCommand
					{
						Kind = ((text == "mute") ? ChatCommandKind.MuteChannel : ChatCommandKind.UnmuteChannel),
						Channel = rest2[0] - 48
					};
				}
				return Admin(text, rest2);
			case "link":
				return new ChatCommand
				{
					Kind = ChatCommandKind.LinkDiscord
				};
			case "unlink":
				return new ChatCommand
				{
					Kind = ChatCommandKind.UnlinkDiscord
				};
			case "kick":
			case "ban":
			case "mod":
			case "unban":
			case "unmod":
				return Admin(text, rest2);
			default:
				return ChatCommand.Error("Unknown /tc command. Try /tc help.");
			}
		}

		private static ChatCommand Admin(string command, string args)
		{
			string rest;
			string text = ReadName(args, out rest);
			if (text.Length == 0)
			{
				return ChatCommand.Error("Usage: /tc " + command + " Name" + ((command == "mute" || command == "ban") ? " [minutes]" : ""));
			}
			double result = 0.0;
			if (rest.Length > 0 && !double.TryParse(rest, NumberStyles.Float, CultureInfo.InvariantCulture, out result))
			{
				return ChatCommand.Error("Minutes must be a number.");
			}
			return new ChatCommand
			{
				Kind = ChatCommandKind.Admin,
				AdminCommand = command,
				Target = text,
				Minutes = result
			};
		}

		private static ChatCommand PrivateMessage(string rest)
		{
			string rest2;
			string text = ReadName(rest, out rest2);
			if (text.Length == 0 || rest2.Length == 0)
			{
				return ChatCommand.Error("Usage: /pm Name text");
			}
			return Pm(text, rest2);
		}

		private static ChatCommand Pm(string target, string text)
		{
			if (text.Length == 0)
			{
				return new ChatCommand
				{
					Kind = ChatCommandKind.None
				};
			}
			return new ChatCommand
			{
				Kind = ChatCommandKind.PrivateMessage,
				Target = target,
				Text = text
			};
		}

		private static ChatCommand Channel(int channel, string text)
		{
			if (text.Length == 0)
			{
				return new ChatCommand
				{
					Kind = ChatCommandKind.None
				};
			}
			return new ChatCommand
			{
				Kind = ChatCommandKind.Channel,
				Channel = channel,
				Text = text
			};
		}

		public static string ReadName(string text, out string rest)
		{
			text = (text ?? string.Empty).TrimStart(Array.Empty<char>());
			if (text.StartsWith("\"", StringComparison.Ordinal))
			{
				int num = text.IndexOf('"', 1);
				if (num > 1)
				{
					rest = text.Substring(num + 1).Trim();
					return text.Substring(1, num - 1).Trim();
				}
			}
			return FirstWord(text, out rest);
		}

		private static string FirstWord(string text, out string rest)
		{
			text = (text ?? string.Empty).TrimStart(Array.Empty<char>());
			int num = text.IndexOf(' ');
			if (num < 0)
			{
				rest = string.Empty;
				return text;
			}
			rest = text.Substring(num + 1).Trim();
			return text.Substring(0, num);
		}
	}
	public sealed class PmEntry
	{
		public long Ts;

		public string From;

		public string To;

		public string Text;

		public bool Outgoing;
	}
	public sealed class PmHistory
	{
		public const int MaxEntries = 500;

		private readonly string folder;

		public PmHistory(string folder)
		{
			this.folder = folder;
		}

		public string FileFor(string characterKey)
		{
			StringBuilder stringBuilder = new StringBuilder();
			string text = characterKey ?? string.Empty;
			foreach (char c in text)
			{
				stringBuilder.Append((char.IsLetterOrDigit(c) || c == '-' || c == '_') ? c : '_');
			}
			return Path.Combine(folder, ((stringBuilder.Length > 0) ? stringBuilder.ToString() : "unknown") + ".jsonl");
		}

		public List<PmEntry> Load(string characterKey)
		{
			List<PmEntry> list = new List<PmEntry>();
			string path = FileFor(characterKey);
			if (!File.Exists(path))
			{
				return list;
			}
			string[] array = File.ReadAllLines(path, Encoding.UTF8);
			foreach (string text in array)
			{
				try
				{
					JObject val = JObject.Parse(text);
					list.Add(new PmEntry
					{
						Ts = ((long?)val["ts"]).GetValueOrDefault(),
						From = (string)val["from"],
						To = (string)val["to"],
						Text = (string)val["text"],
						Outgoing = ((bool?)val["out"] == true)
					});
				}
				catch
				{
				}
			}
			return list.Skip(Math.Max(0, list.Count - 500)).ToList();
		}

		public void Append(string characterKey, PmEntry entry)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			Directory.CreateDirectory(folder);
			string text = FileFor(characterKey);
			JObject val = new JObject
			{
				["ts"] = JToken.op_Implicit(entry.Ts),
				["from"] = JToken.op_Implicit(entry.From),
				["to"] = JToken.op_Implicit(entry