Decompiled source of LabLink v1.1.1

Mods/LabLink.dll

Decompiled 5 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.Json;
using BoneLib.BoneMenu;
using LabFusion.Network;
using LabFusion.Network.Serialization;
using LabFusion.Player;
using LabFusion.SDK.Modules;
using LabFusion.UI.Popups;
using LabFusion.Utilities;
using LabLink;
using LabLink.Data;
using LabLink.Net;
using LabLink.Runtime;
using LabLink.UI;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using Steamworks;
using Steamworks.Data;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(ModMain), "LabLink", "1.1.0", "Dynamic Team", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("LabLink")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("LabLink")]
[assembly: AssemblyTitle("LabLink")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace LabLink
{
	public class ModMain : MelonMod
	{
		public static Instance Log { get; private set; }

		public override void OnInitializeMelon()
		{
			Log = ((MelonBase)this).LoggerInstance;
			Settings.Init();
			FriendStore.Load();
			Menu.Build();
			try
			{
				ModuleManager.RegisterModule<LinkModule>();
				Log.Msg("LabLink Fusion module registered.");
			}
			catch (Exception ex)
			{
				Log.Warning("Fusion module not registered (LabFusion missing?): " + ex.Message);
			}
			LobbyTracker.Hook();
			Log.Msg("LabLink loaded.");
		}
	}
	public static class Settings
	{
		public const string Category = "LabLink";

		public static MelonPreferences_Category Cat { get; private set; }

		public static MelonPreferences_Entry<bool> PresenceToasts { get; private set; }

		public static MelonPreferences_Entry<bool> PopupOnRequest { get; private set; }

		public static MelonPreferences_Entry<bool> PopupOnMessage { get; private set; }

		public static MelonPreferences_Entry<bool> MessagesFromFriendsOnly { get; private set; }

		public static MelonPreferences_Entry<float> PopupDuration { get; private set; }

		public static MelonPreferences_Entry<string> ComposedMessage { get; private set; }

		public static void Init()
		{
			Cat = MelonPreferences.CreateCategory("LabLink", "LabLink");
			PresenceToasts = Cat.CreateEntry<bool>("PresenceToasts", true, "Toast when a friend joins your lobby", (string)null, false, false, (ValueValidator)null, (string)null);
			PopupOnRequest = Cat.CreateEntry<bool>("PopupOnRequest", true, "Popup when a friend request arrives", (string)null, false, false, (ValueValidator)null, (string)null);
			PopupOnMessage = Cat.CreateEntry<bool>("PopupOnMessage", true, "Popup when a message arrives", (string)null, false, false, (ValueValidator)null, (string)null);
			MessagesFromFriendsOnly = Cat.CreateEntry<bool>("MessagesFromFriendsOnly", true, "Only accept messages from friends", (string)null, false, false, (ValueValidator)null, (string)null);
			PopupDuration = Cat.CreateEntry<float>("PopupDuration", 5f, "Popup duration in seconds", (string)null, false, false, (ValueValidator)null, (string)null);
			ComposedMessage = Cat.CreateEntry<string>("ComposedMessage", "", "Current composed message", (string)null, false, false, (ValueValidator)null, (string)null);
			try
			{
				if (PopupDuration.Value < 1f || PopupDuration.Value > 30f)
				{
					PopupDuration.Value = 5f;
				}
			}
			catch
			{
			}
			Cat.SaveToFile(false);
		}

		public static void Save()
		{
			MelonPreferences_Category cat = Cat;
			if (cat != null)
			{
				cat.SaveToFile(false);
			}
		}
	}
}
namespace LabLink.UI
{
	public static class Menu
	{
		private static readonly Color _green = new Color(0.3f, 0.85f, 0.4f);

		private static readonly Color _cyan = new Color(0.2f, 0.8f, 0.9f);

		private static readonly Color _gold = new Color(1f, 0.84f, 0f);

		private static readonly Color _gray = new Color(0.6f, 0.6f, 0.6f);

		private static readonly Color _red = new Color(0.9f, 0.3f, 0.3f);

		private static readonly Color _blue = new Color(0.3f, 0.6f, 1f);

		private static Page _root;

		private static Page _friendsPage;

		private static Page _lobbyPage;

		private static Page _requestsPage;

		private static Page _messagesPage;

		private static Page _blockedPage;

		private static Page _recipientPage;

		private static Page _inboxPage;

		private static readonly string[] _quick = new string[6] { "gg", "follow me", "help!", "afk", "nice", "lol" };

		public static void Build()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: 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_0076: 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)
			_root = Page.Root.CreatePage("LabLink", _green, 0, true);
			_friendsPage = _root.CreatePage("Friends", _green, 0, true);
			_lobbyPage = _root.CreatePage("Lobby", _blue, 0, true);
			_requestsPage = _root.CreatePage("Requests", _gold, 0, true);
			_messagesPage = _root.CreatePage("Messages", _cyan, 0, true);
			_blockedPage = _root.CreatePage("Blocked", _red, 0, true);
			BuildSettingsPage(_root);
			RefreshDynamic();
			ModMain.Log.Msg("LabLink menu built.");
		}

		public static void RefreshDynamic()
		{
			try
			{
				RebuildFriends();
				RebuildLobby();
				RebuildRequests();
				RebuildMessages();
				RebuildBlocked();
			}
			catch (Exception ex)
			{
				Instance log = ModMain.Log;
				if (log != null)
				{
					log.Warning("Menu refresh failed: " + ex.Message);
				}
			}
		}

		private static void RebuildFriends()
		{
			//IL_0016: 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_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			Page friendsPage = _friendsPage;
			if (friendsPage == null)
			{
				return;
			}
			friendsPage.RemoveAll();
			friendsPage.CreateFunction("Refresh", _green, (Action)RefreshDynamic);
			IReadOnlyList<Friend> friends = FriendStore.Friends;
			if (friends.Count == 0)
			{
				friendsPage.CreateFunction("(no friends yet - add from Lobby)", _gray, (Action)delegate
				{
				});
				return;
			}
			List<Friend> list = new List<Friend>(friends);
			list.Sort((Friend a, Friend b) => (a.Favorite != b.Favorite) ? ((!a.Favorite) ? 1 : (-1)) : string.Compare(a.Display(), b.Display(), StringComparison.OrdinalIgnoreCase));
			foreach (Friend item in list)
			{
				ulong platformId = item.PlatformId;
				FriendPresence pres = SteamPresence.Get(platformId);
				bool flag = pres.Status != PresenceStatus.Offline && pres.Status != PresenceStatus.Unknown;
				string value = (item.Favorite ? "* " : "");
				BuildFriendActions(friendsPage.CreatePage($"{value}{item.Display()} [{pres.Label}]", flag ? _green : _gray, 0, true), platformId, pres);
			}
		}

		private static void BuildFriendActions(Page page, ulong id, FriendPresence pres)
		{
			//IL_002c: 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_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			Friend f = FriendStore.GetFriend(id);
			if (f == null)
			{
				page.CreateFunction("(removed)", _gray, (Action)delegate
				{
				});
				return;
			}
			if (pres.Status == PresenceStatus.InLobby)
			{
				page.CreateFunction("Join their lobby", _green, (Action)delegate
				{
					string message = SteamPresence.JoinFriend(id);
					Social.Toast("LabLink", message, (NotificationType)0);
				});
			}
			bool flag = pres.Status == PresenceStatus.InYourLobby;
			page.CreateFunction(flag ? "Set as message recipient" : "Message (needs same lobby)", flag ? _cyan : _gray, (Action)delegate
			{
				Social.CurrentRecipient = id;
				Social.Toast("LabLink", "Recipient set to " + f.Display() + ". Open Messages.", (NotificationType)0);
			});
			page.CreateString("Nickname", _gold, f.Nickname ?? "", (Action<string>)delegate(string v)
			{
				f.Nickname = v ?? "";
				FriendStore.Save();
			});
			page.CreateString("Note", _gold, f.Note ?? "", (Action<string>)delegate(string v)
			{
				f.Note = v ?? "";
				FriendStore.Save();
			});
			page.CreateBool("Favorite", _gold, f.Favorite, (Action<bool>)delegate(bool v)
			{
				f.Favorite = v;
				FriendStore.Save();
			});
			Page obj = page.CreatePage("Remove / Block", _red, 0, true);
			obj.CreateFunction("Unfriend", _red, (Action)delegate
			{
				Social.Unfriend(id);
			});
			obj.CreateFunction("Block", _red, (Action)delegate
			{
				Social.Block(id);
			});
		}

		private static void RebuildLobby()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			Page lobbyPage = _lobbyPage;
			if (lobbyPage == null)
			{
				return;
			}
			lobbyPage.RemoveAll();
			lobbyPage.CreateFunction("Refresh", _green, (Action)RefreshDynamic);
			if (!Fusion.HasServer)
			{
				lobbyPage.CreateFunction("(not in a lobby)", _gray, (Action)delegate
				{
				});
				return;
			}
			int num = 0;
			foreach (Fusion.PlayerEntry player in Fusion.GetPlayers())
			{
				if (player.IsLocal)
				{
					continue;
				}
				num++;
				ulong id = player.PlatformId;
				string name = player.Name;
				if (FriendStore.IsBlocked(id))
				{
					lobbyPage.CreateFunction(name + " [blocked]", _gray, (Action)delegate
					{
					});
				}
				else if (FriendStore.IsFriend(id))
				{
					lobbyPage.CreateFunction(name + " [friend]", _green, (Action)delegate
					{
					});
				}
				else if (LobbyTracker.HasMod(id))
				{
					lobbyPage.CreateFunction("Add Friend: " + name, _cyan, (Action)delegate
					{
						Social.SendFriendRequest(id, name);
					});
				}
				else
				{
					lobbyPage.CreateFunction(name + " (no LabLink)", _gray, (Action)delegate
					{
					});
				}
			}
			if (num == 0)
			{
				lobbyPage.CreateFunction("(no other players here)", _gray, (Action)delegate
				{
				});
			}
		}

		private static void RebuildRequests()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d4: 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_0279: Unknown result type (might be due to invalid IL or missing references)
			Page requestsPage = _requestsPage;
			if (requestsPage == null)
			{
				return;
			}
			requestsPage.RemoveAll();
			requestsPage.CreateFunction("Refresh", _green, (Action)RefreshDynamic);
			IReadOnlyList<PendingRequest> incoming = FriendStore.Incoming;
			IReadOnlyList<PendingRequest> outgoing = FriendStore.Outgoing;
			requestsPage.CreateFunction($"-- Incoming ({incoming.Count}) --", _gold, (Action)delegate
			{
			});
			if (incoming.Count == 0)
			{
				requestsPage.CreateFunction("(none)", _gray, (Action)delegate
				{
				});
			}
			foreach (PendingRequest item in incoming)
			{
				ulong id = item.PlatformId;
				Page obj = requestsPage.CreatePage("From " + item.Display(), _gold, 0, true);
				obj.CreateFunction("Accept", _green, (Action)delegate
				{
					Social.AcceptRequest(id);
				});
				obj.CreateFunction("Decline", _red, (Action)delegate
				{
					Social.DeclineRequest(id);
				});
				obj.CreateFunction("Block", _red, (Action)delegate
				{
					Social.Block(id);
				});
			}
			requestsPage.CreateFunction($"-- Outgoing ({outgoing.Count}) --", _cyan, (Action)delegate
			{
			});
			if (outgoing.Count == 0)
			{
				requestsPage.CreateFunction("(none)", _gray, (Action)delegate
				{
				});
			}
			foreach (PendingRequest item2 in outgoing)
			{
				string text = (Fusion.IsOnline(item2.PlatformId) ? "pending" : "pending (offline)");
				requestsPage.CreateFunction(item2.Display() + " - " + text, _gray, (Action)delegate
				{
				});
			}
		}

		private static void RebuildMessages()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			Page messagesPage = _messagesPage;
			if (messagesPage != null)
			{
				messagesPage.RemoveAll();
				messagesPage.CreateFunction("Refresh", _green, (Action)RefreshDynamic);
				string text = ((Social.CurrentRecipient == 0L) ? "none" : (FriendStore.GetFriend(Social.CurrentRecipient)?.Display() ?? Fusion.NameOf(Social.CurrentRecipient) ?? "unknown"));
				messagesPage.CreateFunction("To: " + text, _gold, (Action)delegate
				{
				});
				BuildComposePage(messagesPage);
				BuildRecipientPage(messagesPage);
				BuildQuickSendPage(messagesPage);
				BuildInboxPage(messagesPage);
			}
		}

		private static void BuildComposePage(Page parent)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: 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_0081: Unknown result type (might be due to invalid IL or missing references)
			Page obj = parent.CreatePage("Compose & Send", _cyan, 0, true);
			obj.CreateString("Type Message (VR keyboard)", _gold, Settings.ComposedMessage.Value, (Action<string>)delegate(string v)
			{
				Settings.ComposedMessage.Value = v ?? "";
				Settings.Save();
			});
			obj.CreateFunction("Send", _green, (Action)delegate
			{
				Social.SendMessageTo(Social.CurrentRecipient, Settings.ComposedMessage.Value);
			});
			obj.CreateFunction("Clear", _gray, (Action)delegate
			{
				Settings.ComposedMessage.Value = "";
				Settings.Save();
			});
		}

		private static void BuildRecipientPage(Page parent)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			_recipientPage = parent.CreatePage("Choose Recipient", _blue, 0, true);
			RebuildRecipient();
		}

		private static void RebuildRecipient()
		{
			//IL_0016: 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_00c1: Unknown result type (might be due to invalid IL or missing references)
			Page recipientPage = _recipientPage;
			if (recipientPage == null)
			{
				return;
			}
			recipientPage.RemoveAll();
			recipientPage.CreateFunction("Refresh", _green, (Action)RebuildRecipient);
			int num = 0;
			foreach (Friend friend in FriendStore.Friends)
			{
				if (Fusion.IsOnline(friend.PlatformId))
				{
					ulong id = friend.PlatformId;
					string label = friend.Display();
					recipientPage.CreateFunction(label, _cyan, (Action)delegate
					{
						Social.CurrentRecipient = id;
						Social.Toast("LabLink", "Recipient: " + label, (NotificationType)0);
						RebuildMessages();
					});
					num++;
				}
			}
			if (num == 0)
			{
				recipientPage.CreateFunction("(no friends online)", _gray, (Action)delegate
				{
				});
			}
		}

		private static void BuildQuickSendPage(Page parent)
		{
			//IL_0006: 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)
			Page val = parent.CreatePage("Quick Send", _green, 0, true);
			string[] quick = _quick;
			foreach (string text in quick)
			{
				string m = text;
				val.CreateFunction(m, _green, (Action)delegate
				{
					Social.SendMessageTo(Social.CurrentRecipient, m);
				});
			}
		}

		private static void BuildInboxPage(Page parent)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			_inboxPage = parent.CreatePage("Inbox", _gray, 0, true);
			RebuildInbox();
		}

		private static void RebuildInbox()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: 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)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			Page inboxPage = _inboxPage;
			if (inboxPage == null)
			{
				return;
			}
			inboxPage.RemoveAll();
			inboxPage.CreateFunction("Refresh", _green, (Action)RebuildInbox);
			inboxPage.CreateFunction("Clear", _gray, (Action)delegate
			{
				Social.ClearInbox();
				RebuildInbox();
			});
			IReadOnlyList<Social.InboxEntry> inbox = Social.Inbox;
			if (inbox.Count == 0)
			{
				inboxPage.CreateFunction("(no messages)", _gray, (Action)delegate
				{
				});
				return;
			}
			for (int num = inbox.Count - 1; num >= 0; num--)
			{
				Social.InboxEntry inboxEntry = inbox[num];
				inboxPage.CreateFunction(inboxEntry.Name + ": " + Trunc(inboxEntry.Body, 28), _gray, (Action)delegate
				{
				});
			}
		}

		private static void RebuildBlocked()
		{
			//IL_0016: 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_00e3: Unknown result type (might be due to invalid IL or missing references)
			Page blockedPage = _blockedPage;
			if (blockedPage == null)
			{
				return;
			}
			blockedPage.RemoveAll();
			blockedPage.CreateFunction("Refresh", _green, (Action)RefreshDynamic);
			IReadOnlyList<ulong> blocked = FriendStore.Blocked;
			if (blocked.Count == 0)
			{
				blockedPage.CreateFunction("(no one blocked)", _gray, (Action)delegate
				{
				});
				return;
			}
			foreach (ulong item in blocked)
			{
				ulong bid = item;
				string text = Fusion.NameOf(bid) ?? $"Player {bid}";
				blockedPage.CreateFunction("Unblock " + text, _green, (Action)delegate
				{
					FriendStore.Unblock(bid);
					RefreshDynamic();
				});
			}
		}

		private static void BuildSettingsPage(Page parent)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: 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_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			Page obj = parent.CreatePage("Settings", _gray, 0, true);
			obj.CreateBool("Friend-join toasts", _cyan, Settings.PresenceToasts.Value, (Action<bool>)delegate(bool v)
			{
				Settings.PresenceToasts.Value = v;
				Settings.Save();
			});
			obj.CreateBool("Popup on friend request", _cyan, Settings.PopupOnRequest.Value, (Action<bool>)delegate(bool v)
			{
				Settings.PopupOnRequest.Value = v;
				Settings.Save();
			});
			obj.CreateBool("Popup on message", _cyan, Settings.PopupOnMessage.Value, (Action<bool>)delegate(bool v)
			{
				Settings.PopupOnMessage.Value = v;
				Settings.Save();
			});
			obj.CreateBool("Messages from friends only", _cyan, Settings.MessagesFromFriendsOnly.Value, (Action<bool>)delegate(bool v)
			{
				Settings.MessagesFromFriendsOnly.Value = v;
				Settings.Save();
			});
			obj.CreateFloat("Popup duration (s)", _gold, Settings.PopupDuration.Value, 1f, 1f, 30f, (Action<float>)delegate(float v)
			{
				Settings.PopupDuration.Value = v;
				Settings.Save();
			});
		}

		private static string Trunc(string s, int max)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "(empty)";
			}
			if (s.Length > max)
			{
				return s.Substring(0, max - 1) + "…";
			}
			return s;
		}
	}
}
namespace LabLink.Runtime
{
	public static class Fusion
	{
		public struct PlayerEntry
		{
			public ulong PlatformId;

			public byte SmallId;

			public string Name;

			public bool IsLocal;

			public bool IsHost;
		}

		public static bool HasServer
		{
			get
			{
				try
				{
					return NetworkInfo.HasServer;
				}
				catch
				{
					return false;
				}
			}
		}

		public static bool IsHost
		{
			get
			{
				try
				{
					return NetworkInfo.IsHost;
				}
				catch
				{
					return false;
				}
			}
		}

		public static ulong LocalPlatformId
		{
			get
			{
				try
				{
					return PlayerIDManager.LocalPlatformID;
				}
				catch
				{
					return 0uL;
				}
			}
		}

		public static string LocalName()
		{
			try
			{
				return NameOf(PlayerIDManager.LocalID) ?? "You";
			}
			catch
			{
				return "You";
			}
		}

		public static List<PlayerEntry> GetPlayers()
		{
			List<PlayerEntry> list = new List<PlayerEntry>();
			if (!HasServer)
			{
				return list;
			}
			try
			{
				ulong localPlatformId = LocalPlatformId;
				foreach (PlayerID playerID in PlayerIDManager.PlayerIDs)
				{
					if (playerID != null)
					{
						list.Add(new PlayerEntry
						{
							PlatformId = playerID.PlatformID,
							SmallId = playerID.SmallID,
							Name = (NameOf(playerID) ?? $"Player {playerID.SmallID}"),
							IsLocal = (playerID.PlatformID == localPlatformId),
							IsHost = playerID.IsHost
						});
					}
				}
			}
			catch (Exception ex)
			{
				ModMain.Log.Warning("Player list failed: " + ex.Message);
			}
			return list;
		}

		public static string NameOf(PlayerID pid)
		{
			if (pid == null)
			{
				return null;
			}
			try
			{
				string valueOrEmpty = pid.Metadata.Nickname.GetValueOrEmpty();
				if (!string.IsNullOrEmpty(valueOrEmpty))
				{
					return valueOrEmpty;
				}
				string valueOrEmpty2 = pid.Metadata.Username.GetValueOrEmpty();
				if (!string.IsNullOrEmpty(valueOrEmpty2))
				{
					return valueOrEmpty2;
				}
			}
			catch
			{
			}
			return null;
		}

		public static string NameOf(ulong platformId)
		{
			try
			{
				return NameOf(PlayerIDManager.GetPlayerID(platformId));
			}
			catch
			{
				return null;
			}
		}

		public static bool IsOnline(ulong platformId)
		{
			try
			{
				return PlayerIDManager.GetPlayerID(platformId) != null;
			}
			catch
			{
				return false;
			}
		}
	}
	public static class LobbyTracker
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ServerEvent <0>__HandleSessionStart;

			public static ServerEvent <1>__HandleSessionEnd;

			public static PlayerUpdate <2>__HandlePlayerJoined;

			public static PlayerUpdate <3>__HandlePlayerLeft;
		}

		private static readonly HashSet<ulong> _modUsers = new HashSet<ulong>();

		private static bool _hooked;

		public static bool HasMod(ulong platformId)
		{
			return _modUsers.Contains(platformId);
		}

		public static void MarkModUser(ulong platformId, string name)
		{
			if (platformId != 0L && platformId != Fusion.LocalPlatformId)
			{
				_modUsers.Add(platformId);
			}
		}

		public static void Hook()
		{
			//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)
			//IL_0024: Expected O, but got Unknown
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Expected O, but got Unknown
			//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_00a4: Expected O, but got Unknown
			if (_hooked)
			{
				return;
			}
			try
			{
				object obj = <>O.<0>__HandleSessionStart;
				if (obj == null)
				{
					ServerEvent val = HandleSessionStart;
					<>O.<0>__HandleSessionStart = val;
					obj = (object)val;
				}
				MultiplayerHooking.OnJoinedServer += (ServerEvent)obj;
				object obj2 = <>O.<0>__HandleSessionStart;
				if (obj2 == null)
				{
					ServerEvent val2 = HandleSessionStart;
					<>O.<0>__HandleSessionStart = val2;
					obj2 = (object)val2;
				}
				MultiplayerHooking.OnStartedServer += (ServerEvent)obj2;
				object obj3 = <>O.<1>__HandleSessionEnd;
				if (obj3 == null)
				{
					ServerEvent val3 = HandleSessionEnd;
					<>O.<1>__HandleSessionEnd = val3;
					obj3 = (object)val3;
				}
				MultiplayerHooking.OnDisconnected += (ServerEvent)obj3;
				object obj4 = <>O.<2>__HandlePlayerJoined;
				if (obj4 == null)
				{
					PlayerUpdate val4 = HandlePlayerJoined;
					<>O.<2>__HandlePlayerJoined = val4;
					obj4 = (object)val4;
				}
				MultiplayerHooking.OnPlayerJoined += (PlayerUpdate)obj4;
				object obj5 = <>O.<3>__HandlePlayerLeft;
				if (obj5 == null)
				{
					PlayerUpdate val5 = HandlePlayerLeft;
					<>O.<3>__HandlePlayerLeft = val5;
					obj5 = (object)val5;
				}
				MultiplayerHooking.OnPlayerLeft += (PlayerUpdate)obj5;
				_hooked = true;
			}
			catch (Exception ex)
			{
				ModMain.Log.Warning("LabLink hooks failed: " + ex.Message);
			}
		}

		private static void HandleSessionStart()
		{
			_modUsers.Clear();
			LinkNet.Broadcast(LinkKind.CapabilityPing);
			Menu.RefreshDynamic();
		}

		private static void HandleSessionEnd()
		{
			_modUsers.Clear();
			Menu.RefreshDynamic();
		}

		private static void HandlePlayerJoined(PlayerID id)
		{
			LinkNet.Broadcast(LinkKind.CapabilityPing);
			try
			{
				if (id != null && Settings.PresenceToasts.Value && FriendStore.IsFriend(id.PlatformID))
				{
					string text = FriendStore.GetFriend(id.PlatformID)?.Display() ?? Fusion.NameOf(id) ?? "A friend";
					Social.Toast("Friend online", text + " joined the lobby.", (NotificationType)3);
				}
			}
			catch
			{
			}
			Menu.RefreshDynamic();
		}

		private static void HandlePlayerLeft(PlayerID id)
		{
			try
			{
				if (id != null)
				{
					_modUsers.Remove(id.PlatformID);
				}
			}
			catch
			{
			}
			Menu.RefreshDynamic();
		}
	}
	public static class Social
	{
		public struct InboxEntry
		{
			public float Time;

			public ulong FromId;

			public string Name;

			public string Body;
		}

		private const int InboxCapacity = 20;

		private static readonly List<InboxEntry> _inbox = new List<InboxEntry>();

		public static IReadOnlyList<InboxEntry> Inbox => _inbox;

		public static ulong CurrentRecipient { get; set; }

		public static void OnReceive(LinkData data)
		{
			if (data == null)
			{
				return;
			}
			ulong fromId = data.FromId;
			if (fromId == Fusion.LocalPlatformId || (data.ToId != 0L && data.ToId != Fusion.LocalPlatformId) || FriendStore.IsBlocked(fromId))
			{
				return;
			}
			string text = ((!string.IsNullOrEmpty(data.FromName)) ? data.FromName : (Fusion.NameOf(fromId) ?? $"Player {fromId}"));
			FriendStore.UpdateName(fromId, text);
			switch ((LinkKind)data.Kind)
			{
			case LinkKind.CapabilityPing:
				LobbyTracker.MarkModUser(fromId, text);
				LinkNet.Send(LinkKind.CapabilityAck, fromId);
				Menu.RefreshDynamic();
				break;
			case LinkKind.CapabilityAck:
				LobbyTracker.MarkModUser(fromId, text);
				Menu.RefreshDynamic();
				break;
			case LinkKind.FriendRequest:
				OnFriendRequest(fromId, text);
				break;
			case LinkKind.RequestAccept:
				OnRequestAccepted(fromId, text);
				break;
			case LinkKind.RequestDecline:
				FriendStore.RemoveOutgoing(fromId);
				Toast("Request declined", text + " declined your friend request.", (NotificationType)0);
				Menu.RefreshDynamic();
				break;
			case LinkKind.DirectMessage:
				OnDirectMessage(fromId, text, data.Body);
				break;
			case LinkKind.Unfriend:
				if (FriendStore.IsFriend(fromId))
				{
					FriendStore.RemoveFriend(fromId);
					Toast("Unfriended", text + " removed you as a friend.", (NotificationType)0);
					Menu.RefreshDynamic();
				}
				break;
			}
		}

		private static void OnFriendRequest(ulong from, string name)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: 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_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: 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_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Expected O, but got Unknown
			if (FriendStore.IsFriend(from))
			{
				LinkNet.Send(LinkKind.RequestAccept, from);
				return;
			}
			FriendStore.AddIncoming(from, name);
			Menu.RefreshDynamic();
			if (Settings.PopupOnRequest.Value)
			{
				ulong who = from;
				Notify(new Notification
				{
					Title = NotificationText.op_Implicit("Friend request"),
					Message = NotificationText.op_Implicit(name + " wants to add you. Accept?"),
					Type = (NotificationType)0,
					PopupLength = Mathf.Clamp(Settings.PopupDuration.Value + 5f, 5f, 30f),
					ShowPopup = true,
					SaveToMenu = true,
					OnAccepted = delegate
					{
						AcceptRequest(who);
					},
					OnDeclined = delegate
					{
						DeclineRequest(who);
					}
				});
			}
		}

		private static void OnRequestAccepted(ulong from, string name)
		{
			bool num = FriendStore.GetOutgoing(from) != null;
			FriendStore.AddFriend(from, name);
			if (num)
			{
				Toast("Friend added", name + " accepted your friend request.", (NotificationType)3);
			}
			Menu.RefreshDynamic();
		}

		private static void OnDirectMessage(ulong from, string name, string body)
		{
			if (!Settings.MessagesFromFriendsOnly.Value || FriendStore.IsFriend(from))
			{
				RecordInbox(from, name, body);
				if (Settings.PopupOnMessage.Value)
				{
					Toast(name, string.IsNullOrEmpty(body) ? "(empty)" : body, (NotificationType)0);
				}
				Menu.RefreshDynamic();
			}
		}

		public static void SendFriendRequest(ulong toId, string name)
		{
			if (!Fusion.HasServer)
			{
				Toast("LabLink", "You're not in a lobby.", (NotificationType)1);
			}
			else if (toId != 0L && toId != Fusion.LocalPlatformId)
			{
				if (FriendStore.IsFriend(toId))
				{
					Toast("LabLink", "Already friends.", (NotificationType)0);
					return;
				}
				FriendStore.AddOutgoing(toId, name);
				LinkNet.Send(LinkKind.FriendRequest, toId);
				Toast("Request sent", "Friend request sent to " + name + ".", (NotificationType)3);
				Menu.RefreshDynamic();
			}
		}

		public static void AcceptRequest(ulong fromId)
		{
			PendingRequest incoming = FriendStore.GetIncoming(fromId);
			string text = ((incoming != null) ? incoming.Display() : (Fusion.NameOf(fromId) ?? $"Player {fromId}"));
			FriendStore.AddFriend(fromId, text);
			LinkNet.Send(LinkKind.RequestAccept, fromId);
			Toast("Friend added", "You and " + text + " are now friends.", (NotificationType)3);
			Menu.RefreshDynamic();
		}

		public static void DeclineRequest(ulong fromId)
		{
			FriendStore.RemoveIncoming(fromId);
			LinkNet.Send(LinkKind.RequestDecline, fromId);
			Menu.RefreshDynamic();
		}

		public static void Unfriend(ulong id)
		{
			string text = FriendStore.GetFriend(id)?.Display() ?? "friend";
			LinkNet.Send(LinkKind.Unfriend, id);
			FriendStore.RemoveFriend(id);
			Toast("Unfriended", "Removed " + text + ".", (NotificationType)0);
			Menu.RefreshDynamic();
		}

		public static void Block(ulong id)
		{
			LinkNet.Send(LinkKind.Unfriend, id);
			FriendStore.Block(id);
			Toast("Blocked", "They can no longer reach you.", (NotificationType)0);
			Menu.RefreshDynamic();
		}

		public static void SendMessageTo(ulong toId, string body)
		{
			if (string.IsNullOrWhiteSpace(body))
			{
				Toast("LabLink", "Type a message first.", (NotificationType)1);
				return;
			}
			if (!Fusion.HasServer)
			{
				Toast("LabLink", "You're not in a lobby.", (NotificationType)1);
				return;
			}
			if (toId == 0L)
			{
				Toast("LabLink", "Pick a recipient first.", (NotificationType)1);
				return;
			}
			if (!Fusion.IsOnline(toId))
			{
				Toast("LabLink", "That friend isn't in this lobby.", (NotificationType)1);
				return;
			}
			LinkNet.Send(LinkKind.DirectMessage, toId, body);
			string text = FriendStore.GetFriend(toId)?.Display() ?? Fusion.NameOf(toId) ?? "them";
			Toast("Sent", "Message sent to " + text + ".", (NotificationType)3);
		}

		private static void RecordInbox(ulong from, string name, string body)
		{
			_inbox.Add(new InboxEntry
			{
				Time = Time.unscaledTime,
				FromId = from,
				Name = name,
				Body = (body ?? "")
			});
			while (_inbox.Count > 20)
			{
				_inbox.RemoveAt(0);
			}
		}

		public static void ClearInbox()
		{
			_inbox.Clear();
		}

		public static void Toast(string title, string message, NotificationType type)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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_0011: 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_001d: 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)
			//IL_0024: 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_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			Notify(new Notification
			{
				Title = NotificationText.op_Implicit(title),
				Message = NotificationText.op_Implicit(message),
				Type = type,
				PopupLength = Mathf.Clamp(Settings.PopupDuration.Value, 1f, 30f),
				ShowPopup = true,
				SaveToMenu = false
			});
		}

		private static void Notify(Notification n)
		{
			try
			{
				Notifier.Send(n);
			}
			catch (Exception ex)
			{
				ModMain.Log.Warning("Notifier failed: " + ex.Message);
			}
		}
	}
	public enum PresenceStatus
	{
		Unknown,
		Offline,
		Online,
		InGame,
		InLobby,
		InYourLobby
	}
	public struct FriendPresence
	{
		public PresenceStatus Status;

		public bool Joinable;

		public ulong HostId;

		public string Label;
	}
	public static class SteamPresence
	{
		public static bool SteamAvailable
		{
			get
			{
				try
				{
					return SteamClient.IsValid;
				}
				catch
				{
					return false;
				}
			}
		}

		public static FriendPresence Get(ulong platformId)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: 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)
			if (Fusion.IsOnline(platformId))
			{
				return new FriendPresence
				{
					Status = PresenceStatus.InYourLobby,
					Label = "in your lobby"
				};
			}
			if (!SteamAvailable)
			{
				return new FriendPresence
				{
					Status = PresenceStatus.Unknown,
					Label = "unknown"
				};
			}
			try
			{
				Friend val = default(Friend);
				((Friend)(ref val))..ctor(SteamId.op_Implicit(platformId));
				if (((Friend)(ref val)).IsPlayingThisGame)
				{
					FriendGameInfo? gameInfo = ((Friend)(ref val)).GameInfo;
					Lobby? obj;
					if (!gameInfo.HasValue)
					{
						obj = null;
					}
					else
					{
						FriendGameInfo valueOrDefault = gameInfo.GetValueOrDefault();
						obj = ((FriendGameInfo)(ref valueOrDefault)).Lobby;
					}
					Lobby? val2 = obj;
					if (val2.HasValue)
					{
						ulong num = 0uL;
						try
						{
							Lobby value = val2.Value;
							num = SteamId.op_Implicit(((Lobby)(ref value)).Owner.Id);
						}
						catch
						{
						}
						if (num != 0L)
						{
							return new FriendPresence
							{
								Status = PresenceStatus.InLobby,
								Joinable = true,
								HostId = num,
								Label = "in a lobby"
							};
						}
						return new FriendPresence
						{
							Status = PresenceStatus.InLobby,
							Joinable = false,
							Label = "in a lobby"
						};
					}
					return new FriendPresence
					{
						Status = PresenceStatus.InGame,
						Label = "in BONELAB"
					};
				}
				if (((Friend)(ref val)).IsOnline)
				{
					return new FriendPresence
					{
						Status = PresenceStatus.Online,
						Label = "online"
					};
				}
				if (((Friend)(ref val)).IsFriend)
				{
					return new FriendPresence
					{
						Status = PresenceStatus.Offline,
						Label = "offline"
					};
				}
				return new FriendPresence
				{
					Status = PresenceStatus.Unknown,
					Label = "not a Steam friend"
				};
			}
			catch (Exception ex)
			{
				ModMain.Log.Warning("[LabLink] presence read failed: " + ex.Message);
				return new FriendPresence
				{
					Status = PresenceStatus.Unknown,
					Label = "unknown"
				};
			}
		}

		public static string JoinFriend(ulong platformId)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: 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_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			if (!SteamAvailable)
			{
				return "Joining only works on the Steam version.";
			}
			try
			{
				Friend val = default(Friend);
				((Friend)(ref val))..ctor(SteamId.op_Implicit(platformId));
				if (!((Friend)(ref val)).IsPlayingThisGame)
				{
					return "They're not in BONELAB right now.";
				}
				FriendGameInfo? gameInfo = ((Friend)(ref val)).GameInfo;
				Lobby? obj;
				if (!gameInfo.HasValue)
				{
					obj = null;
				}
				else
				{
					FriendGameInfo valueOrDefault = gameInfo.GetValueOrDefault();
					obj = ((FriendGameInfo)(ref valueOrDefault)).Lobby;
				}
				Lobby? val2 = obj;
				if (!val2.HasValue)
				{
					return "They're not in a joinable lobby.";
				}
				Lobby value = val2.Value;
				ulong num = 0uL;
				try
				{
					num = SteamId.op_Implicit(((Lobby)(ref value)).Owner.Id);
				}
				catch
				{
				}
				if (num == 0L)
				{
					try
					{
						((Lobby)(ref value)).Refresh();
					}
					catch
					{
					}
					return "Fetching their lobby... press Join again in a moment.";
				}
				NetworkLayer layer = NetworkLayerManager.Layer;
				SteamNetworkLayer val3 = (SteamNetworkLayer)(object)((layer is SteamNetworkLayer) ? layer : null);
				if (val3 != null)
				{
					val3.JoinServer(SteamId.op_Implicit(num));
					return "Joining " + ((Friend)(ref val)).Name + "...";
				}
				return "Joining requires the Steam network layer.";
			}
			catch (Exception ex)
			{
				ModMain.Log.Warning("[LabLink] join failed: " + ex.Message);
				return "Couldn't join (see console).";
			}
		}
	}
}
namespace LabLink.Net
{
	public class LinkData : INetSerializable
	{
		public byte Kind;

		public ulong FromId;

		public string FromName;

		public ulong ToId;

		public string Body;

		public LinkData()
		{
			FromName = "";
			Body = "";
		}

		public int? GetSize()
		{
			return 17 + SizeExtensions.GetSize(FromName) + SizeExtensions.GetSize(Body);
		}

		public void Serialize(INetSerializer serializer)
		{
			serializer.SerializeValue(ref Kind);
			serializer.SerializeValue(ref FromId);
			serializer.SerializeValue(ref FromName);
			serializer.SerializeValue(ref ToId);
			serializer.SerializeValue(ref Body);
		}
	}
	public class LinkHandler : ModuleMessageHandler
	{
		protected override void OnHandleMessage(ReceivedMessage received)
		{
			try
			{
				LinkData linkData = ((ReceivedMessage)(ref received)).ReadData<LinkData>();
				if (linkData != null)
				{
					Social.OnReceive(linkData);
				}
			}
			catch (Exception value)
			{
				ModMain.Log.Error($"[LabLink] failed to handle packet: {value}");
			}
		}
	}
	public enum LinkKind : byte
	{
		CapabilityPing,
		CapabilityAck,
		FriendRequest,
		RequestAccept,
		RequestDecline,
		DirectMessage,
		Unfriend
	}
	public class LinkModule : Module
	{
		public override string Name => "LabLink";

		public override string Author => "Dynamic Team";

		public override Version Version => new Version(1, 0, 0);

		public override ConsoleColor Color => ConsoleColor.Green;

		protected override void OnModuleRegistered()
		{
			ModuleMessageManager.RegisterHandler<LinkHandler>();
			ModMain.Log.Msg("LabLink message handler registered.");
		}
	}
	public static class LinkNet
	{
		public static void Send(LinkKind kind, ulong toId, string body = "")
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			if (!Fusion.HasServer)
			{
				return;
			}
			try
			{
				LinkData obj = new LinkData
				{
					Kind = (byte)kind,
					FromId = Fusion.LocalPlatformId,
					FromName = Fusion.LocalName(),
					ToId = toId,
					Body = (body ?? "")
				};
				MessageRoute val = default(MessageRoute);
				((MessageRoute)(ref val))..ctor((RelayType)2, (NetworkChannel)0);
				MessageRelay.RelayModule<LinkHandler, LinkData>(obj, val);
			}
			catch (Exception ex)
			{
				ModMain.Log.Warning($"[LabLink] send {kind} failed: {ex.Message}");
			}
		}

		public static void Broadcast(LinkKind kind, string body = "")
		{
			Send(kind, 0uL, body);
		}
	}
}
namespace LabLink.Data
{
	public class Friend
	{
		public ulong PlatformId { get; set; }

		public string LastName { get; set; } = "";

		public string Nickname { get; set; } = "";

		public string Note { get; set; } = "";

		public bool Favorite { get; set; }

		public long AddedUnix { get; set; }

		public string Display()
		{
			if (!string.IsNullOrEmpty(Nickname))
			{
				return Nickname;
			}
			if (!string.IsNullOrEmpty(LastName))
			{
				return LastName;
			}
			return $"Player {PlatformId}";
		}
	}
	public class PendingRequest
	{
		public ulong PlatformId { get; set; }

		public string LastName { get; set; } = "";

		public long Unix { get; set; }

		public string Display()
		{
			if (!string.IsNullOrEmpty(LastName))
			{
				return LastName;
			}
			return $"Player {PlatformId}";
		}
	}
	public static class FriendStore
	{
		public sealed class StoreData
		{
			public List<Friend> Friends { get; set; } = new List<Friend>();

			public List<ulong> Blocked { get; set; } = new List<ulong>();

			public List<PendingRequest> Incoming { get; set; } = new List<PendingRequest>();

			public List<PendingRequest> Outgoing { get; set; } = new List<PendingRequest>();
		}

		private static StoreData _data = new StoreData();

		public static IReadOnlyList<Friend> Friends => _data.Friends;

		public static IReadOnlyList<ulong> Blocked => _data.Blocked;

		public static IReadOnlyList<PendingRequest> Incoming => _data.Incoming;

		public static IReadOnlyList<PendingRequest> Outgoing => _data.Outgoing;

		private static string FilePath()
		{
			string text;
			try
			{
				text = Path.Combine(MelonEnvironment.UserDataDirectory, "LabLink");
			}
			catch
			{
				text = Path.Combine("UserData", "LabLink");
			}
			Directory.CreateDirectory(text);
			return Path.Combine(text, "labllink.json");
		}

		public static void Load()
		{
			try
			{
				string path = FilePath();
				if (File.Exists(path))
				{
					_data = JsonSerializer.Deserialize<StoreData>(File.ReadAllText(path)) ?? new StoreData();
				}
			}
			catch (Exception ex)
			{
				ModMain.Log.Warning("Failed to load friends: " + ex.Message);
				_data = new StoreData();
			}
			if (_data.Friends == null)
			{
				_data.Friends = new List<Friend>();
			}
			if (_data.Blocked == null)
			{
				_data.Blocked = new List<ulong>();
			}
			if (_data.Incoming == null)
			{
				_data.Incoming = new List<PendingRequest>();
			}
			if (_data.Outgoing == null)
			{
				_data.Outgoing = new List<PendingRequest>();
			}
		}

		public static void Save()
		{
			try
			{
				string contents = JsonSerializer.Serialize(_data, new JsonSerializerOptions
				{
					WriteIndented = true
				});
				File.WriteAllText(FilePath(), contents);
			}
			catch (Exception ex)
			{
				ModMain.Log.Warning("Failed to save friends: " + ex.Message);
			}
		}

		private static long Now()
		{
			return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
		}

		public static Friend GetFriend(ulong id)
		{
			return _data.Friends.FirstOrDefault((Friend f) => f.PlatformId == id);
		}

		public static bool IsFriend(ulong id)
		{
			return GetFriend(id) != null;
		}

		public static bool IsBlocked(ulong id)
		{
			return _data.Blocked.Contains(id);
		}

		public static PendingRequest GetIncoming(ulong id)
		{
			return _data.Incoming.FirstOrDefault((PendingRequest r) => r.PlatformId == id);
		}

		public static PendingRequest GetOutgoing(ulong id)
		{
			return _data.Outgoing.FirstOrDefault((PendingRequest r) => r.PlatformId == id);
		}

		public static Friend AddFriend(ulong id, string name)
		{
			Friend friend = GetFriend(id);
			if (friend == null)
			{
				friend = new Friend
				{
					PlatformId = id,
					LastName = (name ?? ""),
					AddedUnix = Now()
				};
				_data.Friends.Add(friend);
			}
			else if (!string.IsNullOrEmpty(name))
			{
				friend.LastName = name;
			}
			_data.Incoming.RemoveAll((PendingRequest r) => r.PlatformId == id);
			_data.Outgoing.RemoveAll((PendingRequest r) => r.PlatformId == id);
			Save();
			return friend;
		}

		public static void RemoveFriend(ulong id)
		{
			_data.Friends.RemoveAll((Friend f) => f.PlatformId == id);
			Save();
		}

		public static void Block(ulong id)
		{
			_data.Friends.RemoveAll((Friend f) => f.PlatformId == id);
			_data.Incoming.RemoveAll((PendingRequest r) => r.PlatformId == id);
			_data.Outgoing.RemoveAll((PendingRequest r) => r.PlatformId == id);
			if (!_data.Blocked.Contains(id))
			{
				_data.Blocked.Add(id);
			}
			Save();
		}

		public static void Unblock(ulong id)
		{
			_data.Blocked.RemoveAll((ulong b) => b == id);
			Save();
		}

		public static void AddIncoming(ulong id, string name)
		{
			PendingRequest incoming = GetIncoming(id);
			if (incoming == null)
			{
				_data.Incoming.Add(new PendingRequest
				{
					PlatformId = id,
					LastName = (name ?? ""),
					Unix = Now()
				});
			}
			else if (!string.IsNullOrEmpty(name))
			{
				incoming.LastName = name;
			}
			Save();
		}

		public static void RemoveIncoming(ulong id)
		{
			_data.Incoming.RemoveAll((PendingRequest r) => r.PlatformId == id);
			Save();
		}

		public static void AddOutgoing(ulong id, string name)
		{
			if (GetOutgoing(id) == null)
			{
				_data.Outgoing.Add(new PendingRequest
				{
					PlatformId = id,
					LastName = (name ?? ""),
					Unix = Now()
				});
			}
			Save();
		}

		public static void RemoveOutgoing(ulong id)
		{
			_data.Outgoing.RemoveAll((PendingRequest r) => r.PlatformId == id);
			Save();
		}

		public static void UpdateName(ulong id, string name)
		{
			if (!string.IsNullOrEmpty(name))
			{
				Friend friend = GetFriend(id);
				if (friend != null && friend.LastName != name)
				{
					friend.LastName = name;
					Save();
				}
			}
		}
	}
}