Decompiled source of LabLink v1.2.1

Plugins\LabLink.dll

Decompiled a day 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 BoneLib.BoneMenu;
using LabFusion.Data;
using LabFusion.Entities;
using LabFusion.Network;
using LabFusion.Player;
using LabFusion.SDK.Metadata;
using LabLink;
using LabLink.Friends;
using MelonLoader;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(LabLinkMain), "LabLink", "1.0.0", "LabLink Team", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace LabLink
{
	public class LabLinkMain : MelonMod
	{
		public static LabLinkMain? Instance;

		public static FriendManager? FriendManager;

		public static PlayerTargeter? PlayerTargeter;

		public static string ModDirectory = string.Empty;

		private static Page? _rootPage;

		private static Page? _friendsPage;

		public override void OnInitializeMelon()
		{
			Instance = this;
			ModDirectory = Path.Combine((string)(typeof(MelonUtils).GetProperty("GameDirectory", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) ?? ""), "LabLink");
			Directory.CreateDirectory(ModDirectory);
			FriendManager = new FriendManager();
			FriendManager.Load();
			PlayerTargeter = new PlayerTargeter();
			SetupBoneMenu();
			((MelonBase)this).LoggerInstance.Msg("LabLink initialized!");
		}

		private static void SetupBoneMenu()
		{
			//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_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			_rootPage = Page.Root.CreatePage("LabLink", Color.cyan, 0, true);
			_rootPage.CreateFunction("Refresh Friends", Color.white, (Action)delegate
			{
				FriendManager?.RefreshNow();
				RebuildFriendsPage();
			});
			_rootPage.CreateFunction("Add Friend (Point + Press A)", Color.green, (Action)delegate
			{
				if (PlayerTargeter != null && PlayerTargeter.IsTargeting)
				{
					PlayerTargeter.AddTargetedAsFriend();
				}
				else
				{
					FriendManager?.Notify("Point at a player and try again.");
				}
			});
			_friendsPage = _rootPage.CreatePage("Friends List", Color.yellow, 0, true);
			RebuildFriendsPage();
		}

		public static void RebuildFriendsPage()
		{
			//IL_0023: 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_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: 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_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_0341: Unknown result type (might be due to invalid IL or missing references)
			//IL_0310: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
			if (_friendsPage == null || FriendManager == null)
			{
				return;
			}
			_friendsPage.RemoveAll();
			_friendsPage.CreateFunction("Refresh", Color.white, (Action)delegate
			{
				FriendManager.RefreshNow();
				RebuildFriendsPage();
			});
			if (!NetworkLayerManager.LoggedIn)
			{
				_friendsPage.CreateFunction("Not connected to Fusion", Color.red, (Action)delegate
				{
				});
				return;
			}
			IReadOnlyList<FriendStatus> friendStatuses = FriendManager.FriendStatuses;
			if (friendStatuses.Count == 0)
			{
				_friendsPage.CreateFunction("No friends added yet", Color.gray, (Action)delegate
				{
				});
				_friendsPage.CreateFunction("Point at a player + press A", Color.gray, (Action)delegate
				{
				});
				return;
			}
			foreach (FriendStatus status in friendStatuses)
			{
				Color val = (status.IsOnline ? Color.green : Color.red);
				string text = (status.IsOnline ? "ONLINE" : "OFFLINE");
				Page val2 = _friendsPage.CreatePage(status.Entry.Username + " [" + text + "]", val, 0, true);
				val2.CreateFunction("Status: " + text, val, (Action)delegate
				{
				});
				if (status.IsOnline && !string.IsNullOrEmpty(status.CurrentLobbyCode))
				{
					val2.CreateFunction("Lobby: " + status.CurrentLobbyName, Color.white, (Action)delegate
					{
					});
					val2.CreateFunction("Level: " + status.CurrentLevel, Color.white, (Action)delegate
					{
					});
					val2.CreateFunction($"Players: {status.LobbyPlayerCount}/{status.LobbyMaxPlayers}", Color.white, (Action)delegate
					{
					});
					val2.CreateFunction("JOIN", Color.green, (Action)delegate
					{
						FriendManager.JoinFriend(status);
						RebuildFriendsPage();
					});
				}
				else if (status.IsOnline)
				{
					val2.CreateFunction("In main menu (not in lobby)", Color.yellow, (Action)delegate
					{
					});
				}
				val2.CreateFunction("Remove Friend", Color.red, (Action)delegate
				{
					FriendManager.RemoveFriend(status.Entry.PlatformID);
					RebuildFriendsPage();
				});
			}
		}

		public override void OnUpdate()
		{
			if (FriendManager != null)
			{
				if (Input.GetKeyDown((KeyCode)284))
				{
					RebuildFriendsPage();
					Menu.OpenPage(_rootPage);
				}
				FriendManager.Update();
				PlayerTargeter?.Update();
			}
		}
	}
	public class PlayerTargeter
	{
		private float _checkTimer;

		private const float CHECK_INTERVAL = 0.15f;

		private const float MAX_DISTANCE = 10f;

		public PlayerID? TargetedPlayer { get; private set; }

		public bool IsTargeting => TargetedPlayer != null;

		public void Update()
		{
			//IL_0052: 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_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: 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)
			//IL_00e8: 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_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			_checkTimer += Time.unscaledDeltaTime;
			if (_checkTimer < 0.15f)
			{
				return;
			}
			_checkTimer = 0f;
			TargetedPlayer = null;
			if (!NetworkInfo.HasServer)
			{
				return;
			}
			Camera main = Camera.main;
			if ((Object)(object)main == (Object)null)
			{
				return;
			}
			Ray val = default(Ray);
			((Ray)(ref val))..ctor(((Component)main).transform.position, ((Component)main).transform.forward);
			float num = 10f;
			PlayerID targetedPlayer = null;
			NetworkPlayer val2 = default(NetworkPlayer);
			foreach (PlayerID playerID in PlayerIDManager.PlayerIDs)
			{
				if (!playerID.IsMe && NetworkPlayerManager.TryGetPlayer(playerID.SmallID, ref val2) && val2 != null && val2.RigRefs != null && !((Object)(object)val2.RigRefs.Head == (Object)null))
				{
					Vector3 position = val2.RigRefs.Head.position;
					Vector3 val3 = Vector3.Project(position - ((Ray)(ref val)).origin, ((Ray)(ref val)).direction);
					float magnitude = ((Vector3)(ref val3)).magnitude;
					float num2 = Vector3.Distance(((Ray)(ref val)).origin + val3, position);
					if (magnitude < num && num2 < 0.5f)
					{
						num = magnitude;
						targetedPlayer = playerID;
					}
				}
			}
			TargetedPlayer = targetedPlayer;
			if (TargetedPlayer != null && Input.GetKeyDown((KeyCode)331))
			{
				AddTargetedAsFriend();
			}
		}

		public void AddTargetedAsFriend()
		{
			if (TargetedPlayer == null)
			{
				return;
			}
			string platformId = TargetedPlayer.PlatformID.ToString();
			PlayerMetadata metadata = TargetedPlayer.Metadata;
			object obj;
			if (metadata == null)
			{
				obj = null;
			}
			else
			{
				MetadataVariable username = metadata.Username;
				obj = ((username != null) ? username.GetValueOrEmpty() : null);
			}
			if (obj == null)
			{
				obj = "Unknown";
			}
			string text = (string)obj;
			if (LabLinkMain.FriendManager != null)
			{
				if (LabLinkMain.FriendManager.IsFriend(platformId))
				{
					LabLinkMain.FriendManager.Notify(text + " is already your friend!");
				}
				else
				{
					LabLinkMain.FriendManager.AddFriend(platformId, text);
				}
			}
		}
	}
}
namespace LabLink.UI
{
	public static class FriendListUI
	{
	}
}
namespace LabLink.Friends
{
	public class FriendEntry
	{
		public string Username { get; set; } = string.Empty;

		public string PlatformID { get; set; } = string.Empty;

		public DateTime AddedDate { get; set; } = DateTime.UtcNow;

		public string Notes { get; set; } = string.Empty;
	}
	public class FriendListData
	{
		public List<FriendEntry> Friends { get; set; } = new List<FriendEntry>();
	}
	public class FriendStatus
	{
		public FriendEntry Entry;

		public bool IsOnline;

		public string CurrentLobbyName = string.Empty;

		public string CurrentLobbyCode = string.Empty;

		public int LobbyPlayerCount;

		public int LobbyMaxPlayers;

		public string CurrentLevel = string.Empty;

		public FriendStatus(FriendEntry entry)
		{
			Entry = entry;
		}
	}
	public struct LobbyScanResult
	{
		public LobbyMetadataInfo Metadata;

		public INetworkLobby Lobby;
	}
	public class FriendManager
	{
		private FriendListData _data = new FriendListData();

		private List<FriendStatus> _statuses = new List<FriendStatus>();

		private float _checkTimer;

		private const float CHECK_INTERVAL = 3f;

		private float _lobbyScanTimer;

		private const float LOBBY_SCAN_INTERVAL = 10f;

		private List<LobbyScanResult> _cachedLobbies = new List<LobbyScanResult>();

		private Action<string>? _onNotification;

		private Action? _onFriendsListChanged;

		private Dictionary<ulong, bool> _previousOnlineState = new Dictionary<ulong, bool>();

		private string SavePath => Path.Combine(LabLinkMain.ModDirectory, "friends.json");

		public IReadOnlyList<FriendStatus> FriendStatuses => _statuses;

		public IReadOnlyList<FriendEntry> Friends => _data.Friends;

		public event Action<string>? OnNotification
		{
			add
			{
				_onNotification = (Action<string>)Delegate.Combine(_onNotification, value);
			}
			remove
			{
				_onNotification = (Action<string>)Delegate.Remove(_onNotification, value);
			}
		}

		public event Action? OnFriendsListChanged
		{
			add
			{
				_onFriendsListChanged = (Action)Delegate.Combine(_onFriendsListChanged, value);
			}
			remove
			{
				_onFriendsListChanged = (Action)Delegate.Remove(_onFriendsListChanged, value);
			}
		}

		public void Notify(string message)
		{
			_onNotification?.Invoke(message);
		}

		public void Load()
		{
			try
			{
				if (File.Exists(SavePath))
				{
					string text = File.ReadAllText(SavePath);
					_data = JsonConvert.DeserializeObject<FriendListData>(text) ?? new FriendListData();
				}
				else
				{
					_data = new FriendListData();
				}
				RebuildStatuses();
				MelonLogger.Msg($"[LabLink] Loaded {_data.Friends.Count} friends.");
			}
			catch (Exception ex)
			{
				MelonLogger.Error("[LabLink] Failed to load friends: " + ex.Message);
				_data = new FriendListData();
			}
		}

		public void Save()
		{
			try
			{
				string contents = JsonConvert.SerializeObject((object)_data, (Formatting)1);
				File.WriteAllText(SavePath, contents);
			}
			catch (Exception ex)
			{
				MelonLogger.Error("[LabLink] Failed to save friends: " + ex.Message);
			}
		}

		public bool AddFriend(string platformId, string username)
		{
			if (_data.Friends.Any((FriendEntry f) => f.PlatformID == platformId))
			{
				return false;
			}
			FriendEntry item = new FriendEntry
			{
				PlatformID = platformId,
				Username = username,
				AddedDate = DateTime.UtcNow
			};
			_data.Friends.Add(item);
			Save();
			RebuildStatuses();
			_onFriendsListChanged?.Invoke();
			_onNotification?.Invoke("Added " + username + " as a friend!");
			return true;
		}

		public bool AddFriendByUsername(string username)
		{
			string platformId = string.Empty;
			string username2 = string.Empty;
			bool flag = false;
			foreach (PlayerID playerID in PlayerIDManager.PlayerIDs)
			{
				string valueOrEmpty = playerID.Metadata.Username.GetValueOrEmpty();
				if (string.Equals(valueOrEmpty, username, StringComparison.OrdinalIgnoreCase))
				{
					platformId = playerID.PlatformID.ToString();
					username2 = valueOrEmpty;
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				foreach (LobbyScanResult cachedLobby in _cachedLobbies)
				{
					LobbyScanResult current2 = cachedLobby;
					LobbyInfo lobbyInfo = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo;
					object obj;
					if (lobbyInfo == null)
					{
						obj = null;
					}
					else
					{
						PlayerList playerList = lobbyInfo.PlayerList;
						obj = ((playerList != null) ? playerList.Players : null);
					}
					if (obj == null)
					{
						continue;
					}
					PlayerInfo[] players = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.PlayerList.Players;
					foreach (PlayerInfo val in players)
					{
						if (string.Equals(val.Username, username, StringComparison.OrdinalIgnoreCase))
						{
							platformId = val.PlatformID;
							username2 = val.Username;
							flag = true;
							break;
						}
					}
					if (flag)
					{
						break;
					}
				}
			}
			if (!flag)
			{
				return false;
			}
			return AddFriend(platformId, username2);
		}

		public bool RemoveFriend(string platformId)
		{
			if (_data.Friends.RemoveAll((FriendEntry f) => f.PlatformID == platformId) > 0)
			{
				Save();
				RebuildStatuses();
				_onFriendsListChanged?.Invoke();
				return true;
			}
			return false;
		}

		public bool IsFriend(string platformId)
		{
			return _data.Friends.Any((FriendEntry f) => f.PlatformID == platformId);
		}

		private void RebuildStatuses()
		{
			_statuses.Clear();
			foreach (FriendEntry friend in _data.Friends)
			{
				_statuses.Add(new FriendStatus(friend));
			}
			UpdateStatuses();
		}

		public void Update()
		{
			_checkTimer += Time.unscaledDeltaTime;
			if (_checkTimer >= 3f)
			{
				_checkTimer = 0f;
				UpdateStatuses();
			}
			_lobbyScanTimer += Time.unscaledDeltaTime;
			if (_lobbyScanTimer >= 10f)
			{
				_lobbyScanTimer = 0f;
				ScanLobbies();
			}
		}

		public void RefreshNow()
		{
			UpdateStatuses();
			ScanLobbies();
			_onNotification?.Invoke("Friend status refreshed!");
		}

		private void UpdateStatuses()
		{
			if (!NetworkLayerManager.LoggedIn)
			{
				return;
			}
			foreach (FriendStatus status in _statuses)
			{
				bool isOnline = status.IsOnline;
				status.IsOnline = false;
				status.CurrentLobbyName = string.Empty;
				status.CurrentLobbyCode = string.Empty;
				status.LobbyPlayerCount = 0;
				status.CurrentLevel = string.Empty;
				foreach (PlayerID playerID in PlayerIDManager.PlayerIDs)
				{
					if (!(playerID.PlatformID.ToString() == status.Entry.PlatformID))
					{
						continue;
					}
					status.IsOnline = true;
					if (!NetworkInfo.HasServer || LobbyInfoManager.LobbyInfo == null)
					{
						break;
					}
					LobbyInfo lobbyInfo = LobbyInfoManager.LobbyInfo;
					PlayerList playerList = lobbyInfo.PlayerList;
					PlayerInfo[] array = ((playerList != null) ? playerList.Players : null);
					if (array == null)
					{
						break;
					}
					PlayerInfo[] array2 = array;
					for (int i = 0; i < array2.Length; i++)
					{
						if (array2[i].PlatformID == status.Entry.PlatformID)
						{
							status.CurrentLobbyName = lobbyInfo.LobbyName ?? "Unknown";
							status.CurrentLobbyCode = lobbyInfo.LobbyCode ?? string.Empty;
							status.LobbyPlayerCount = lobbyInfo.PlayerCount;
							status.LobbyMaxPlayers = lobbyInfo.MaxPlayers;
							status.CurrentLevel = lobbyInfo.LevelTitle ?? "Unknown";
							break;
						}
					}
					break;
				}
				if (!status.IsOnline)
				{
					foreach (LobbyScanResult cachedLobby in _cachedLobbies)
					{
						LobbyScanResult current2 = cachedLobby;
						LobbyInfo lobbyInfo2 = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo;
						object obj;
						if (lobbyInfo2 == null)
						{
							obj = null;
						}
						else
						{
							PlayerList playerList2 = lobbyInfo2.PlayerList;
							obj = ((playerList2 != null) ? playerList2.Players : null);
						}
						if (obj == null)
						{
							continue;
						}
						PlayerInfo[] array2 = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.PlayerList.Players;
						for (int i = 0; i < array2.Length; i++)
						{
							if (array2[i].PlatformID == status.Entry.PlatformID)
							{
								status.IsOnline = true;
								status.CurrentLobbyName = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.LobbyName ?? "Unknown";
								status.CurrentLobbyCode = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.LobbyCode ?? string.Empty;
								status.LobbyPlayerCount = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.PlayerCount;
								status.LobbyMaxPlayers = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.MaxPlayers;
								status.CurrentLevel = ((LobbyMetadataInfo)(ref current2.Metadata)).LobbyInfo.LevelTitle ?? "Unknown";
								break;
							}
						}
						if (status.IsOnline)
						{
							break;
						}
					}
				}
				if (isOnline != status.IsOnline)
				{
					if (status.IsOnline)
					{
						_onNotification?.Invoke(status.Entry.Username + " is now online!");
					}
					else
					{
						_onNotification?.Invoke(status.Entry.Username + " went offline.");
					}
				}
			}
		}

		private void ScanLobbies()
		{
			if (!NetworkLayerManager.LoggedIn)
			{
				return;
			}
			try
			{
				IMatchmaker matchmaker = NetworkLayerManager.Layer.Matchmaker;
				if (matchmaker == null)
				{
					return;
				}
				matchmaker.RequestLobbies((Action<MatchmakerCallbackInfo>)delegate(MatchmakerCallbackInfo callback)
				{
					//IL_000b: 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_0026: 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)
					//IL_0038: 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_0044: Unknown result type (might be due to invalid IL or missing references)
					_cachedLobbies.Clear();
					if (callback.Lobbies != null)
					{
						LobbyInfo[] lobbies = callback.Lobbies;
						foreach (LobbyInfo val in lobbies)
						{
							_cachedLobbies.Add(new LobbyScanResult
							{
								Metadata = val.Metadata,
								Lobby = val.Lobby
							});
						}
						UpdateStatuses();
					}
				});
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[LabLink] Lobby scan failed: " + ex.Message);
			}
		}

		public void JoinFriend(FriendStatus friend)
		{
			if (!friend.IsOnline || string.IsNullOrEmpty(friend.CurrentLobbyCode))
			{
				_onNotification?.Invoke(friend.Entry.Username + " is not in a joinable lobby.");
				return;
			}
			try
			{
				NetworkHelper.JoinServerByCode(friend.CurrentLobbyCode);
				_onNotification?.Invoke("Joining " + friend.Entry.Username + "'s lobby...");
			}
			catch (Exception ex)
			{
				MelonLogger.Error("[LabLink] Failed to join lobby: " + ex.Message);
				_onNotification?.Invoke("Failed to join lobby.");
			}
		}

		public List<LobbyScanResult> GetCachedLobbies()
		{
			return _cachedLobbies;
		}
	}
}