Decompiled source of FusionFriends Quest v0.4.1

Mods/FusionFavorites.dll

Decompiled 9 minutes 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;
using BoneLib.BoneMenu;
using BoneLib.Notifications;
using FavoritesMod;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.Utilities;
using LabFusion.Data;
using LabFusion.Network;
using LabFusion.Player;
using LabFusion.SDK.Metadata;
using MelonLoader;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Main), "Fusion Favorites", "0.4.0", "YourName", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("FusionFavorites")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("FusionFavorites")]
[assembly: AssemblyTitle("FusionFavorites")]
[assembly: AssemblyVersion("1.0.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 FavoritesMod
{
	public class FavoritePlayer
	{
		public string Name { get; set; } = "";

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

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

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

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

		public DateTime? LastSeen { get; set; }

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

		public string LastLobby { get; set; } = "";
	}
	public class LobbyPlayer
	{
		public string Name = "";

		public string PlayerId = "";

		public string NetworkLayer = "";
	}
	public class LobbyStatus
	{
		public string LobbyName = "";

		public string HostName = "";

		public string Level = "";

		public string Gamemode = "";

		public string Privacy = "";

		public string Version = "";

		public string LobbyKey = "";

		public bool Full;

		public int Players;

		public int MaxPlayers;

		public bool Voice;

		public bool Mortality;

		public bool FriendlyFire;

		public bool NameTags;

		public List<string> PlayerNames = new List<string>();

		public DateTime ScannedAt = DateTime.UtcNow;
	}
	public class WaitEntry
	{
		public FavoritePlayer Fav;

		public string LobbyKey = "";

		public string LobbyName = "";

		public float StartedAt;

		public int Misses;
	}
	public enum WaitState
	{
		Waiting,
		JoinNow,
		LobbyGone,
		FriendLeft
	}
	public static class FavoritesStore
	{
		private static readonly string FilePath = Path.Combine(MelonEnvironment.UserDataDirectory, "FusionFavorites.json");

		public static List<FavoritePlayer> Favorites { get; private set; } = new List<FavoritePlayer>();

		public static void Load()
		{
			try
			{
				if (File.Exists(FilePath))
				{
					Favorites = JsonSerializer.Deserialize<List<FavoritePlayer>>(File.ReadAllText(FilePath)) ?? new List<FavoritePlayer>();
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Error("Failed to load favorites: " + ex.Message);
				Favorites = new List<FavoritePlayer>();
			}
		}

		public static void Save()
		{
			try
			{
				string contents = JsonSerializer.Serialize(Favorites, new JsonSerializerOptions
				{
					WriteIndented = true
				});
				File.WriteAllText(FilePath, contents);
			}
			catch (Exception ex)
			{
				MelonLogger.Error("Failed to save favorites: " + ex.Message);
			}
		}

		public static bool Add(LobbyPlayer p)
		{
			FavoritePlayer favoritePlayer = Favorites.FirstOrDefault((FavoritePlayer f) => f.PlayerId == p.PlayerId && f.NetworkLayer == p.NetworkLayer);
			if (favoritePlayer != null)
			{
				favoritePlayer.Name = p.Name;
				Save();
				return false;
			}
			Favorites.Add(new FavoritePlayer
			{
				Name = p.Name,
				PlayerId = p.PlayerId,
				NetworkLayer = p.NetworkLayer
			});
			Save();
			return true;
		}

		public static void Remove(FavoritePlayer f)
		{
			Favorites.Remove(f);
			Save();
		}
	}
	public static class FusionBridge
	{
		private static NetworkLayer CurrentLayer => NetworkLayerManager.Layer;

		public static string CurrentLayerName
		{
			get
			{
				NetworkLayer currentLayer = CurrentLayer;
				return ((currentLayer != null) ? currentLayer.Title : null) ?? "";
			}
		}

		public static List<LobbyPlayer> GetPlayersInCurrentLobby()
		{
			List<LobbyPlayer> list = new List<LobbyPlayer>();
			NetworkLayer currentLayer = CurrentLayer;
			if (currentLayer == null)
			{
				return list;
			}
			foreach (PlayerID item in PlayerIDManager.PlayerIDs.ToList())
			{
				if (item == null || item.IsMe || string.IsNullOrEmpty(item.PlatformID))
				{
					continue;
				}
				string text = null;
				try
				{
					PlayerMetadata metadata = item.Metadata;
					object obj;
					if (metadata == null)
					{
						obj = null;
					}
					else
					{
						MetadataVariable username = metadata.Username;
						obj = ((username != null) ? username.GetValue() : null);
					}
					text = (string)obj;
				}
				catch
				{
				}
				if (string.IsNullOrWhiteSpace(text))
				{
					try
					{
						text = currentLayer.GetUsername(item.PlatformID);
					}
					catch
					{
					}
				}
				if (string.IsNullOrWhiteSpace(text))
				{
					text = $"Player {item.SmallID}";
				}
				list.Add(new LobbyPlayer
				{
					Name = text,
					PlayerId = item.PlatformID,
					NetworkLayer = currentLayer.Title
				});
			}
			return list;
		}

		public static void ScanFavorites(IEnumerable<FavoritePlayer> favs, Action<Dictionary<string, LobbyStatus>> done)
		{
			Dictionary<string, LobbyStatus> found = new Dictionary<string, LobbyStatus>();
			NetworkLayer layer = CurrentLayer;
			NetworkLayer obj = layer;
			IMatchmaker val = ((obj != null) ? obj.Matchmaker : null);
			if (val == null)
			{
				done(found);
				return;
			}
			HashSet<string> wanted = new HashSet<string>(from f in favs
				where string.Equals(f.NetworkLayer, layer.Title, StringComparison.OrdinalIgnoreCase)
				select f.PlayerId);
			val.RequestLobbies((Action<MatchmakerCallbackInfo>)delegate(MatchmakerCallbackInfo info)
			{
				//IL_0000: 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_001d: 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_0030: 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)
				try
				{
					LobbyInfo[] lobbies = info.Lobbies;
					if (lobbies != null)
					{
						LobbyInfo[] array = lobbies;
						for (int i = 0; i < array.Length; i++)
						{
							LobbyMetadataInfo metadata = array[i].Metadata;
							LobbyInfo lobbyInfo = ((LobbyMetadataInfo)(ref metadata)).LobbyInfo;
							if (lobbyInfo != null && !IsOffLimits(metadata))
							{
								LobbyStatus lobbyStatus = null;
								foreach (string item in PlatformIdsIn(lobbyInfo))
								{
									if (wanted.Contains(item) && !found.ContainsKey(item))
									{
										if (lobbyStatus == null)
										{
											lobbyStatus = BuildStatus(lobbyInfo, metadata);
										}
										found[item] = lobbyStatus;
									}
								}
							}
						}
					}
				}
				catch (Exception value)
				{
					MelonLogger.Error($"Scan failed: {value}");
				}
				done(found);
			});
		}

		public static void TryJoin(FavoritePlayer fav, Action<string> report)
		{
			NetworkLayer currentLayer = CurrentLayer;
			if (currentLayer == null)
			{
				report("Fusion isn't logged in to a network layer yet.");
				return;
			}
			if (!string.Equals(currentLayer.Title, fav.NetworkLayer, StringComparison.OrdinalIgnoreCase))
			{
				report($"{fav.Name} was saved on the {fav.NetworkLayer} layer, but you're on {currentLayer.Title}. " + "Change Target Layer in Fusion's Network Layer Manager and restart.");
				return;
			}
			IMatchmaker matchmaker = currentLayer.Matchmaker;
			if (matchmaker == null)
			{
				report("This network layer has no lobby search.");
				return;
			}
			report("Searching for " + fav.Name + "...");
			matchmaker.RequestLobbies((Action<MatchmakerCallbackInfo>)delegate(MatchmakerCallbackInfo info)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_003a: 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_0040: Unknown result type (might be due to invalid IL or missing references)
				//IL_0041: Unknown result type (might be due to invalid IL or missing references)
				//IL_0046: 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_0080: Unknown result type (might be due to invalid IL or missing references)
				//IL_0100: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					LobbyInfo[] lobbies = info.Lobbies;
					if (lobbies == null)
					{
						report(fav.Name + " isn't in a lobby you can join right now.");
					}
					else
					{
						LobbyInfo[] array = lobbies;
						foreach (LobbyInfo val in array)
						{
							LobbyMetadataInfo metadata = val.Metadata;
							LobbyInfo lobbyInfo = ((LobbyMetadataInfo)(ref metadata)).LobbyInfo;
							if (lobbyInfo != null && !IsOffLimits(metadata) && PlatformIdsIn(lobbyInfo).Contains(fav.PlayerId))
							{
								if (IsFull(metadata, lobbyInfo))
								{
									report($"{fav.Name}'s lobby is full ({lobbyInfo.PlayerCount}/{lobbyInfo.MaxPlayers}). Use Wait for a spot.");
								}
								else
								{
									Action action = ((LobbyMetadataInfo)(ref metadata)).CreateJoinDelegate(val.Lobby);
									report("Joining " + fav.Name + "'s lobby...");
									action?.Invoke();
								}
								return;
							}
						}
						report(fav.Name + " isn't in a lobby you can join right now.");
					}
				}
				catch (Exception value)
				{
					MelonLogger.Error($"Join search failed: {value}");
					report("Something went wrong searching lobbies (see the MelonLoader log).");
				}
			});
		}

		private static bool IsFull(LobbyMetadataInfo meta, LobbyInfo data)
		{
			if (!((LobbyMetadataInfo)(ref meta)).Full)
			{
				if (data.MaxPlayers > 0)
				{
					return data.PlayerCount >= data.MaxPlayers;
				}
				return false;
			}
			return true;
		}

		private static string KeyOf(LobbyInfo data)
		{
			if (!string.IsNullOrEmpty(data.LobbyID))
			{
				return data.LobbyID;
			}
			return data.LobbyCode + "|" + data.LobbyHostID;
		}

		public static bool IsHostingServer()
		{
			try
			{
				return NetworkInfo.HasServer && NetworkInfo.IsHost;
			}
			catch
			{
				return false;
			}
		}

		public static void PollWaitlist(List<WaitEntry> entries, Action<List<(WaitEntry Entry, WaitState State, Action Join)>> done)
		{
			List<(WaitEntry, WaitState, Action)> results = new List<(WaitEntry, WaitState, Action)>();
			NetworkLayer currentLayer = CurrentLayer;
			IMatchmaker val = ((currentLayer != null) ? currentLayer.Matchmaker : null);
			if (val == null || entries.Count == 0)
			{
				done(results);
				return;
			}
			val.RequestLobbies((Action<MatchmakerCallbackInfo>)delegate(MatchmakerCallbackInfo info)
			{
				//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_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_001f: 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_0035: Unknown result type (might be due to invalid IL or missing references)
				//IL_0046: 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_00e1: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					Dictionary<string, (LobbyMetadataInfo, LobbyInfo, INetworkLobby)> dictionary = new Dictionary<string, (LobbyMetadataInfo, LobbyInfo, INetworkLobby)>();
					LobbyInfo[] lobbies = info.Lobbies;
					if (lobbies != null)
					{
						LobbyInfo[] array = lobbies;
						foreach (LobbyInfo val2 in array)
						{
							LobbyMetadataInfo metadata = val2.Metadata;
							LobbyInfo lobbyInfo = ((LobbyMetadataInfo)(ref metadata)).LobbyInfo;
							if (lobbyInfo != null && !IsOffLimits(metadata))
							{
								dictionary[KeyOf(lobbyInfo)] = (metadata, lobbyInfo, val2.Lobby);
							}
						}
					}
					foreach (WaitEntry entry in entries)
					{
						if (!dictionary.TryGetValue(entry.LobbyKey, out var value))
						{
							results.Add((entry, WaitState.LobbyGone, null));
						}
						else if (!PlatformIdsIn(value.Item2).Contains(entry.Fav.PlayerId))
						{
							results.Add((entry, WaitState.FriendLeft, null));
						}
						else if (IsFull(value.Item1, value.Item2))
						{
							results.Add((entry, WaitState.Waiting, null));
						}
						else
						{
							results.Add((entry, WaitState.JoinNow, ((LobbyMetadataInfo)(ref value.Item1)).CreateJoinDelegate(value.Item3)));
						}
					}
				}
				catch (Exception value2)
				{
					MelonLogger.Error($"Waitlist check failed: {value2}");
					results.Clear();
				}
				done(results);
			});
		}

		private static bool IsOffLimits(LobbyMetadataInfo meta)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			if ((int)((LobbyMetadataInfo)(ref meta)).Privacy != 1)
			{
				return (int)((LobbyMetadataInfo)(ref meta)).Privacy == 3;
			}
			return true;
		}

		private static List<string> PlatformIdsIn(LobbyInfo data)
		{
			List<string> list = new List<string>();
			if (!string.IsNullOrEmpty(data.LobbyHostID))
			{
				list.Add(data.LobbyHostID);
			}
			PlayerList playerList = data.PlayerList;
			PlayerInfo[] array = ((playerList != null) ? playerList.Players : null);
			if (array != null)
			{
				PlayerInfo[] array2 = array;
				foreach (PlayerInfo val in array2)
				{
					if (val != null && !string.IsNullOrEmpty(val.PlatformID))
					{
						list.Add(val.PlatformID);
					}
				}
			}
			return list;
		}

		private static LobbyStatus BuildStatus(LobbyInfo data, LobbyMetadataInfo meta)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			LobbyStatus lobbyStatus = new LobbyStatus
			{
				LobbyName = (data.LobbyName ?? ""),
				HostName = (data.LobbyHostName ?? ""),
				Level = (data.LevelTitle ?? ""),
				Gamemode = (data.GamemodeTitle ?? ""),
				Privacy = ((object)((LobbyMetadataInfo)(ref meta)).Privacy/*cast due to .constrained prefix*/).ToString().ToLowerInvariant().Replace('_', ' '),
				Version = ((data.LobbyVersion != null) ? data.LobbyVersion.ToString() : ""),
				LobbyKey = KeyOf(data),
				Full = IsFull(meta, data),
				Players = data.PlayerCount,
				MaxPlayers = data.MaxPlayers,
				Voice = data.VoiceChat,
				Mortality = data.Mortality,
				FriendlyFire = data.FriendlyFire,
				NameTags = data.NameTags
			};
			PlayerList playerList = data.PlayerList;
			PlayerInfo[] array = ((playerList != null) ? playerList.Players : null);
			if (array != null)
			{
				PlayerInfo[] array2 = array;
				foreach (PlayerInfo val in array2)
				{
					if (val != null)
					{
						string text = ((!string.IsNullOrWhiteSpace(val.Username)) ? val.Username : val.Nickname);
						if (!string.IsNullOrWhiteSpace(text))
						{
							lobbyStatus.PlayerNames.Add(text);
						}
					}
				}
			}
			return lobbyStatus;
		}
	}
	public class Main : MelonMod
	{
		private Page _root;

		private Page _addPage;

		private Page _favoritesPage;

		private readonly Dictionary<string, LobbyStatus> _status = new Dictionary<string, LobbyStatus>();

		private bool _scanning;

		private float _lastScanTime = -999f;

		private PanelSettings _panelSettings;

		private WristPanel _panel;

		private bool _panelErrorLogged;

		private readonly List<WaitEntry> _waitlist = new List<WaitEntry>();

		private const float WaitPollSeconds = 6f;

		private const float WaitMaxSeconds = 1200f;

		private const int WaitMissesBeforeCancel = 2;

		private bool _waitPolling;

		private float _waitPollStartedAt;

		private float _nextWaitPoll;

		private bool _waitErrorLogged;

		private const float MinScanAge = 60f;

		public static Main Instance { get; private set; }

		internal Dictionary<string, LobbyStatus> Status => _status;

		internal bool IsScanning => _scanning;

		internal bool ScanIsStale => Time.unscaledTime - _lastScanTime > 60f;

		internal IReadOnlyList<WaitEntry> WaitEntries => _waitlist;

		public override void OnInitializeMelon()
		{
			Instance = this;
			FavoritesStore.Load();
			_panelSettings = PanelSettingsStore.Load();
			_panel = new WristPanel(_panelSettings);
			BuildMenu();
			((MelonBase)this).LoggerInstance.Msg($"Loaded {FavoritesStore.Favorites.Count} favorite(s).");
		}

		public override void OnLateUpdate()
		{
			try
			{
				_panel?.Update();
			}
			catch (Exception value)
			{
				if (!_panelErrorLogged)
				{
					_panelErrorLogged = true;
					((MelonBase)this).LoggerInstance.Error($"Wrist panel error (logged once): {value}");
				}
			}
			try
			{
				TickWaitlist();
			}
			catch (Exception value2)
			{
				if (!_waitErrorLogged)
				{
					_waitErrorLogged = true;
					((MelonBase)this).LoggerInstance.Error($"Waitlist error (logged once): {value2}");
				}
			}
		}

		internal bool IsWaiting(FavoritePlayer fav)
		{
			return _waitlist.Any((WaitEntry w) => w.Fav.PlayerId == fav.PlayerId);
		}

		internal void StartWait(FavoritePlayer fav)
		{
			if (IsWaiting(fav))
			{
				Notify("Already waiting for " + fav.Name + ".");
				return;
			}
			if (!_status.TryGetValue(fav.PlayerId, out var value) || string.IsNullOrEmpty(value.LobbyKey))
			{
				Notify("Scan first, so I know which lobby they're in.");
				return;
			}
			if (!value.Full)
			{
				FusionBridge.TryJoin(fav, Notify);
				return;
			}
			_waitlist.Add(new WaitEntry
			{
				Fav = fav,
				LobbyKey = value.LobbyKey,
				LobbyName = value.LobbyName,
				StartedAt = Time.unscaledTime
			});
			_nextWaitPoll = 0f;
			Notify("Waiting for a spot in " + fav.Name + "'s lobby. I'll join when there's room, if they're still in it.");
			RefreshPages();
		}

		internal void CancelWait(FavoritePlayer fav)
		{
			if (_waitlist.RemoveAll((WaitEntry w) => w.Fav.PlayerId == fav.PlayerId) > 0)
			{
				Notify("Stopped waiting for " + fav.Name + ".");
			}
			RefreshPages();
		}

		private void TickWaitlist()
		{
			if (_waitlist.Count == 0)
			{
				_waitPolling = false;
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (_waitPolling && unscaledTime - _waitPollStartedAt > 30f)
			{
				_waitPolling = false;
			}
			if (_waitPolling || unscaledTime < _nextWaitPoll)
			{
				return;
			}
			bool flag = false;
			for (int num = _waitlist.Count - 1; num >= 0; num--)
			{
				if (!(unscaledTime - _waitlist[num].StartedAt <= 1200f))
				{
					Notify("Stopped waiting for " + _waitlist[num].Fav.Name + " (20 minutes passed).");
					_waitlist.RemoveAt(num);
					flag = true;
				}
			}
			if (flag)
			{
				RefreshPages();
			}
			if (_waitlist.Count != 0)
			{
				_waitPolling = true;
				_waitPollStartedAt = unscaledTime;
				_nextWaitPoll = unscaledTime + 6f;
				FusionBridge.PollWaitlist(_waitlist.ToList(), delegate(List<(WaitEntry Entry, WaitState State, Action Join)> results)
				{
					_waitPolling = false;
					HandleWaitResults(results);
				});
			}
		}

		private void HandleWaitResults(List<(WaitEntry Entry, WaitState State, Action Join)> results)
		{
			bool flag = false;
			foreach (var result in results)
			{
				if (!_waitlist.Contains(result.Entry))
				{
					continue;
				}
				switch (result.State)
				{
				case WaitState.Waiting:
					result.Entry.Misses = 0;
					break;
				case WaitState.LobbyGone:
				case WaitState.FriendLeft:
					result.Entry.Misses++;
					if (result.Entry.Misses >= 2)
					{
						Notify((result.State == WaitState.FriendLeft) ? (result.Entry.Fav.Name + " left that lobby, so I stopped waiting.") : (result.Entry.Fav.Name + "'s lobby closed, so I stopped waiting."));
						_waitlist.Remove(result.Entry);
						flag = true;
					}
					break;
				case WaitState.JoinNow:
					if (FusionBridge.IsHostingServer())
					{
						Notify("A spot opened in " + result.Entry.Fav.Name + "'s lobby, but you're hosting, so I didn't move you. Join them yourself if you want.");
						_waitlist.Remove(result.Entry);
						flag = true;
						break;
					}
					Notify("A spot opened! Joining " + result.Entry.Fav.Name + "'s lobby...");
					_waitlist.Clear();
					try
					{
						result.Join?.Invoke();
					}
					catch (Exception ex)
					{
						((MelonBase)this).LoggerInstance.Error("Waitlist join failed: " + ex.Message);
					}
					RefreshPages();
					return;
				}
			}
			if (flag)
			{
				RefreshPages();
			}
		}

		private void BuildMenu()
		{
			//IL_000b: 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_004a: 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)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			_root = Page.Root.CreatePage("Fusion Favorites", Color.cyan, 0, true);
			_root.CreateFunction("Scan for friends", Color.green, (Action)Scan);
			_addPage = _root.CreatePage("Add from this lobby", Color.white, 0, true);
			_favoritesPage = _root.CreatePage("My favorites", Color.yellow, 0, true);
			_root.CreateFunction("Refresh lists", Color.gray, (Action)RefreshPages);
			BuildPanelSettingsMenu();
			RefreshPages();
		}

		private void TogglePanel()
		{
			_panel.Toggle();
		}

		private void Tweak(string what, Action<PanelSettings> change)
		{
			change(_panelSettings);
			PanelSettingsStore.Save(_panelSettings);
			Notify($"{what} (size {_panelSettings.Scale:0.0}x)");
		}

		private void BuildPanelSettingsMenu()
		{
			//IL_000b: 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_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: 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_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: 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_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: 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)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			Page obj = _root.CreatePage("Wrist panel", Color.white, 0, true);
			obj.CreateFunction("Open or close panel", Color.green, (Action)TogglePanel);
			obj.CreateFunction("Wrist tap area bigger", Color.white, (Action)delegate
			{
				Tweak("Tap area bigger", delegate(PanelSettings p)
				{
					p.TapRadius = Mathf.Clamp(p.TapRadius + 0.01f, 0.04f, 0.2f);
				});
			});
			obj.CreateFunction("Wrist tap area smaller", Color.white, (Action)delegate
			{
				Tweak("Tap area smaller", delegate(PanelSettings p)
				{
					p.TapRadius = Mathf.Clamp(p.TapRadius - 0.01f, 0.04f, 0.2f);
				});
			});
			obj.CreateFunction("Bigger", Color.white, (Action)delegate
			{
				Tweak("Bigger", delegate(PanelSettings p)
				{
					p.Scale = Mathf.Clamp(p.Scale + 0.1f, 0.4f, 2.5f);
				});
			});
			obj.CreateFunction("Smaller", Color.white, (Action)delegate
			{
				Tweak("Smaller", delegate(PanelSettings p)
				{
					p.Scale = Mathf.Clamp(p.Scale - 0.1f, 0.4f, 2.5f);
				});
			});
			obj.CreateFunction("Move away from arm", Color.white, (Action)delegate
			{
				Tweak("Moved out", delegate(PanelSettings p)
				{
					p.Up = Mathf.Clamp(p.Up + 0.01f, -0.1f, 0.3f);
				});
			});
			obj.CreateFunction("Move closer to arm", Color.white, (Action)delegate
			{
				Tweak("Moved in", delegate(PanelSettings p)
				{
					p.Up = Mathf.Clamp(p.Up - 0.01f, -0.1f, 0.3f);
				});
			});
			obj.CreateFunction("Move toward elbow", Color.white, (Action)delegate
			{
				Tweak("Toward elbow", delegate(PanelSettings p)
				{
					p.Along = Mathf.Clamp(p.Along + 0.01f, -0.2f, 0.3f);
				});
			});
			obj.CreateFunction("Move toward hand", Color.white, (Action)delegate
			{
				Tweak("Toward hand", delegate(PanelSettings p)
				{
					p.Along = Mathf.Clamp(p.Along - 0.01f, -0.2f, 0.3f);
				});
			});
			obj.CreateFunction("Nudge left", Color.white, (Action)delegate
			{
				Tweak("Nudged left", delegate(PanelSettings p)
				{
					p.Side = Mathf.Clamp(p.Side + 0.01f, -0.2f, 0.2f);
				});
			});
			obj.CreateFunction("Nudge right", Color.white, (Action)delegate
			{
				Tweak("Nudged right", delegate(PanelSettings p)
				{
					p.Side = Mathf.Clamp(p.Side - 0.01f, -0.2f, 0.2f);
				});
			});
			obj.CreateFunction("Pointer higher", Color.cyan, (Action)delegate
			{
				Tweak("Pointer higher", delegate(PanelSettings p)
				{
					p.PointerNudgeUp = Mathf.Clamp(p.PointerNudgeUp + 0.01f, -0.1f, 0.15f);
				});
			});
			obj.CreateFunction("Pointer lower", Color.cyan, (Action)delegate
			{
				Tweak("Pointer lower", delegate(PanelSettings p)
				{
					p.PointerNudgeUp = Mathf.Clamp(p.PointerNudgeUp - 0.01f, -0.1f, 0.15f);
				});
			});
			obj.CreateFunction("Pointer farther ahead", Color.cyan, (Action)delegate
			{
				Tweak("Pointer ahead", delegate(PanelSettings p)
				{
					p.PointerNudgeAhead = Mathf.Clamp(p.PointerNudgeAhead + 0.01f, -0.15f, 0.3f);
				});
			});
			obj.CreateFunction("Pointer closer to hand", Color.cyan, (Action)delegate
			{
				Tweak("Pointer back", delegate(PanelSettings p)
				{
					p.PointerNudgeAhead = Mathf.Clamp(p.PointerNudgeAhead - 0.01f, -0.15f, 0.3f);
				});
			});
			obj.CreateFunction("Reset position and size", Color.red, (Action)delegate
			{
				Tweak("Reset", delegate(PanelSettings p)
				{
					p.Scale = 1f;
					p.Up = 0f;
					p.Along = 0f;
					p.Side = 0f;
					p.PointerNudgeUp = 0.02f;
					p.PointerNudgeAhead = 0f;
					p.TapRadius = 0.08f;
				});
			});
		}

		internal void Scan()
		{
			if (_scanning)
			{
				Notify("Already scanning...");
				return;
			}
			_scanning = true;
			_lastScanTime = Time.unscaledTime;
			Notify("Scanning public lobbies...");
			FusionBridge.ScanFavorites(FavoritesStore.Favorites, delegate(Dictionary<string, LobbyStatus> found)
			{
				_scanning = false;
				_status.Clear();
				bool flag = false;
				foreach (FavoritePlayer favorite in FavoritesStore.Favorites)
				{
					if (found.TryGetValue(favorite.PlayerId, out var value))
					{
						_status[favorite.PlayerId] = value;
						favorite.LastSeen = DateTime.UtcNow;
						favorite.LastLevel = value.Level;
						favorite.LastLobby = value.LobbyName;
						flag = true;
					}
				}
				if (flag)
				{
					FavoritesStore.Save();
				}
				Notify($"{_status.Count} of {FavoritesStore.Favorites.Count} favorites online");
				RefreshPages();
			});
		}

		internal void RefreshPages()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			_addPage.RemoveAll();
			_favoritesPage.RemoveAll();
			List<LobbyPlayer> playersInCurrentLobby = FusionBridge.GetPlayersInCurrentLobby();
			if (playersInCurrentLobby.Count == 0)
			{
				_addPage.CreateFunction("(nobody else here)", Color.gray, (Action)delegate
				{
				});
			}
			foreach (LobbyPlayer item in playersInCurrentLobby)
			{
				LobbyPlayer player = item;
				_addPage.CreateFunction("Add " + player.Name, Color.white, (Action)delegate
				{
					Notify(FavoritesStore.Add(player) ? ("Added " + player.Name) : (player.Name + " was already saved (name refreshed)"));
					RefreshPages();
				});
			}
			_favoritesPage.CreateFunction("Scan now", Color.green, (Action)Scan);
			if (FavoritesStore.Favorites.Count == 0)
			{
				_favoritesPage.CreateFunction("(no favorites yet)", Color.gray, (Action)delegate
				{
				});
				return;
			}
			foreach (FavoritePlayer item2 in (from f in FavoritesStore.Favorites
				orderby _status.ContainsKey(f.PlayerId) descending, f.Name
				select f).ToList())
			{
				LobbyStatus value;
				bool flag = _status.TryGetValue(item2.PlayerId, out value);
				string text = (flag ? (item2.Name + " - " + (string.IsNullOrEmpty(value.Level) ? "online" : value.Level)) : item2.Name);
				Page page = _favoritesPage.CreatePage(text, flag ? Color.green : Color.gray, 0, true);
				BuildProfile(page, item2, flag ? value : null);
			}
		}

		private void BuildProfile(Page page, FavoritePlayer fav, LobbyStatus st)
		{
			//IL_0514: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_05d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_05f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: 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_0664: Unknown result type (might be due to invalid IL or missing references)
			//IL_0634: Unknown result type (might be due to invalid IL or missing references)
			//IL_05a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: 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_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0328: Unknown result type (might be due to invalid IL or missing references)
			//IL_0368: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_04df: Unknown result type (might be due to invalid IL or missing references)
			//IL_049f: Unknown result type (might be due to invalid IL or missing references)
			if (st != null)
			{
				if (st.Full)
				{
					page.CreateFunction($"Lobby full ({st.Players}/{st.MaxPlayers})", Color.red, (Action)delegate
					{
					});
					if (IsWaiting(fav))
					{
						page.CreateFunction("Waiting for a spot... (cancel)", Color.yellow, (Action)delegate
						{
							CancelWait(fav);
						});
					}
					else
					{
						page.CreateFunction("Wait for a spot", Color.yellow, (Action)delegate
						{
							StartWait(fav);
						});
					}
				}
				else
				{
					page.CreateFunction("Join lobby", Color.green, (Action)delegate
					{
						FusionBridge.TryJoin(fav, Notify);
					});
				}
				page.CreateFunction("Map: " + Or(st.Level, "unknown"), Color.cyan, (Action)delegate
				{
				});
				if (!string.IsNullOrEmpty(st.Gamemode))
				{
					page.CreateFunction("Gamemode: " + st.Gamemode, Color.white, (Action)delegate
					{
					});
				}
				page.CreateFunction("Lobby: " + Or(st.LobbyName, "unnamed"), Color.white, (Action)delegate
				{
				});
				page.CreateFunction("Host: " + Or(st.HostName, "unknown"), Color.white, (Action)delegate
				{
				});
				page.CreateFunction($"Players: {st.Players}/{st.MaxPlayers}", Color.white, (Action)delegate
				{
				});
				page.CreateFunction("Privacy: " + Or(st.Privacy, "unknown"), Color.white, (Action)delegate
				{
				});
				page.CreateFunction("Voice chat: " + OnOff(st.Voice), Color.white, (Action)delegate
				{
				});
				page.CreateFunction("Mortality: " + OnOff(st.Mortality), Color.white, (Action)delegate
				{
				});
				page.CreateFunction("Friendly fire: " + OnOff(st.FriendlyFire), Color.white, (Action)delegate
				{
				});
				page.CreateFunction("Nametags: " + OnOff(st.NameTags), Color.white, (Action)delegate
				{
				});
				if (!string.IsNullOrEmpty(st.Version))
				{
					page.CreateFunction("Fusion version: " + st.Version, Color.gray, (Action)delegate
					{
					});
				}
				if (st.PlayerNames.Count > 0)
				{
					string text = string.Join(", ", st.PlayerNames.Take(6));
					if (st.PlayerNames.Count > 6)
					{
						text += $" +{st.PlayerNames.Count - 6} more";
					}
					page.CreateFunction("In lobby: " + text, Color.gray, (Action)delegate
					{
					});
				}
				page.CreateFunction("Scanned " + Ago(st.ScannedAt), Color.gray, (Action)delegate
				{
				});
			}
			else
			{
				page.CreateFunction("Not in a public lobby right now", Color.gray, (Action)delegate
				{
				});
				if (fav.LastSeen.HasValue)
				{
					string text2 = (string.IsNullOrEmpty(fav.LastLevel) ? "" : (" on " + fav.LastLevel));
					page.CreateFunction("Last seen " + Ago(fav.LastSeen.Value) + text2, Color.gray, (Action)delegate
					{
					});
				}
				page.CreateFunction("Try to join anyway", Color.white, (Action)delegate
				{
					FusionBridge.TryJoin(fav, Notify);
				});
				page.CreateFunction("Scan again", Color.green, (Action)Scan);
			}
			if (!string.IsNullOrEmpty(fav.Note))
			{
				page.CreateFunction("Note: " + fav.Note, Color.yellow, (Action)delegate
				{
				});
			}
			page.CreateFunction("Remove", Color.red, (Action)delegate
			{
				FavoritesStore.Remove(fav);
				_status.Remove(fav.PlayerId);
				Notify("Removed " + fav.Name);
				RefreshPages();
			});
		}

		private static string Or(string value, string fallback)
		{
			if (!string.IsNullOrWhiteSpace(value))
			{
				return value;
			}
			return fallback;
		}

		private static string OnOff(bool v)
		{
			if (!v)
			{
				return "off";
			}
			return "on";
		}

		internal static string Ago(DateTime utc)
		{
			TimeSpan timeSpan = DateTime.UtcNow - utc;
			if (timeSpan.TotalSeconds < 60.0)
			{
				return "just now";
			}
			if (!(timeSpan.TotalMinutes < 60.0))
			{
				if (!(timeSpan.TotalHours < 24.0))
				{
					return $"{(int)timeSpan.TotalDays} d ago";
				}
				return $"{(int)timeSpan.TotalHours} h ago";
			}
			return $"{(int)timeSpan.TotalMinutes} min ago";
		}

		internal static void Notify(string message)
		{
			//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_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: 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_0022: 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_003e: Expected O, but got Unknown
			MelonLogger.Msg(message);
			try
			{
				Notifier.Send(new Notification
				{
					Title = NotificationText.op_Implicit("Fusion Favorites"),
					Message = NotificationText.op_Implicit(message),
					PopupLength = 3f,
					ShowTitleOnPopup = true
				});
			}
			catch
			{
			}
		}
	}
	public class PanelSettings
	{
		public float TapRadius { get; set; } = 0.08f;

		public float Scale { get; set; } = 1f;

		public float Up { get; set; }

		public float Along { get; set; }

		public float Side { get; set; }

		public float PointerNudgeUp { get; set; } = 0.02f;

		public float PointerNudgeAhead { get; set; }
	}
	public static class PanelSettingsStore
	{
		private static readonly string FilePath = Path.Combine(MelonEnvironment.UserDataDirectory, "FusionFavorites.panel.json");

		public static PanelSettings Load()
		{
			try
			{
				if (File.Exists(FilePath))
				{
					return JsonSerializer.Deserialize<PanelSettings>(File.ReadAllText(FilePath)) ?? new PanelSettings();
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Error("Couldn't load panel settings: " + ex.Message);
			}
			return new PanelSettings();
		}

		public static void Save(PanelSettings s)
		{
			try
			{
				File.WriteAllText(FilePath, JsonSerializer.Serialize(s, new JsonSerializerOptions
				{
					WriteIndented = true
				}));
			}
			catch (Exception ex)
			{
				MelonLogger.Error("Couldn't save panel settings: " + ex.Message);
			}
		}
	}
	internal sealed class WristPanel
	{
		private sealed class Row
		{
			public string Text;

			public Color Color;

			public Action OnSelect;

			public Row(string text, Color color, Action onSelect = null)
			{
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				Text = text;
				Color = color;
				OnSelect = onSelect;
			}
		}

		private sealed class PageData
		{
			public string Title;

			public List<Row> Rows;
		}

		private sealed class RowView
		{
			public GameObject Go;

			public Image Bg;

			public Image Outline;

			public Text Label;

			public string LastText;
		}

		private const float Width = 340f;

		private const float RowWidth = 324f;

		private const float RowHeight = 40f;

		private const float HeaderHeight = 46f;

		private const float CloseWidth = 52f;

		private const float RowsTopPad = 6f;

		private const int MaxRows = 8;

		private const float Height = 380f;

		private const float BaseScale = 0.0005f;

		private const float DoubleTapWindow = 0.8f;

		private const float TapCooldown = 1.5f;

		private const float MaxPointerDistance = 2.5f;

		private const int HoverNone = -2;

		private const int HoverBack = -1;

		private const int HoverClose = -3;

		private static readonly Color Online = new Color(0.37f, 0.85f, 0.54f);

		private static readonly Color Accent = new Color(0.37f, 0.82f, 0.91f);

		private static readonly Color Yellow = new Color(0.95f, 0.78f, 0.36f);

		private static readonly Color Gray = new Color(0.54f, 0.56f, 0.61f);

		private static readonly Color Danger = new Color(0.94f, 0.42f, 0.42f);

		private static readonly Color Plain = new Color(0.9f, 0.91f, 0.93f);

		private static readonly Color PanelBg = new Color(0.06f, 0.07f, 0.09f, 0.94f);

		private static readonly Color RowBg = new Color(0.115f, 0.125f, 0.153f, 0.95f);

		private static readonly Color RowBgInfo = new Color(0.09f, 0.1f, 0.12f, 0.7f);

		private static readonly Color RowBgHover = new Color(0.2f, 0.25f, 0.31f, 1f);

		private static readonly Color CloseBg = new Color(0.25f, 0.1f, 0.1f, 0.95f);

		private static readonly Color CloseBgHover = new Color(0.5f, 0.16f, 0.16f, 1f);

		private static readonly Color OutlineRed = new Color(0.9f, 0.16f, 0.2f, 1f);

		private static readonly Color OutlineHot = new Color(1f, 0.38f, 0.38f, 1f);

		private static Sprite _sprPanel;

		private static Sprite _sprPanelRing;

		private static Sprite _sprRow;

		private static Sprite _sprRowRing;

		private static Sprite _sprDot;

		private GameObject _root;

		private RowView _header;

		private RowView _close;

		private Image _cursor;

		private RectTransform _cursorRt;

		private readonly List<RowView> _views = new List<RowView>();

		private Font _font;

		private GameObject _laserGo;

		private LineRenderer _line;

		private bool _pointerSourceLogged;

		private bool _tapNear;

		private float _lastTap = -10f;

		private float _tapCooldownUntil;

		private int _latchTarget = -2;

		private float _latchSince;

		private float _latchSeen;

		private Func<PageData> _page;

		private readonly Stack<Func<PageData>> _history = new Stack<Func<PageData>>();

		private readonly List<Row> _shown = new List<Row>();

		private string _title = "";

		private int _scroll;

		private bool _dirty = true;

		private float _nextRefresh;

		private bool _visible;

		private bool _hasPose;

		private Vector3 _pos;

		public PanelSettings Settings { get; }

		public bool IsOpen { get; private set; }

		public WristPanel(PanelSettings settings)
		{
			Settings = settings;
			_page = HomePage;
		}

		public void Update()
		{
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: 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_007f: 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)
			//IL_0087: 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_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: 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)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: 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_00c4: 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_00d7: 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)
			//IL_00e1: 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_00fe: 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_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: 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_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: 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_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: 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)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: 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_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_024d: Unknown result type (might be due to invalid IL or missing references)
			//IL_024f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0251: Unknown result type (might be due to invalid IL or missing references)
			//IL_0253: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_0237: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				CheckOpenGesture();
			}
			catch
			{
			}
			if (IsOpen && !Player.HandsExist)
			{
				IsOpen = false;
			}
			if (!IsOpen)
			{
				SetVisible(show: false);
				return;
			}
			Hand rightHand = Player.RightHand;
			Transform head = Player.Head;
			if ((Object)(object)rightHand == (Object)null || (Object)(object)head == (Object)null)
			{
				SetVisible(show: false);
				return;
			}
			EnsureBuilt();
			if ((Object)(object)_root == (Object)null)
			{
				return;
			}
			Vector3 position = ((Component)rightHand).transform.position;
			Vector3 val = ForearmDirection(rightHand, head);
			Vector3 val2 = Vector3.ProjectOnPlane(head.position - position, val);
			if (((Vector3)(ref val2)).sqrMagnitude < 1E-06f)
			{
				val2 = head.up;
			}
			((Vector3)(ref val2)).Normalize();
			Vector3 val3 = Vector3.Cross(val, val2);
			Vector3 normalized = ((Vector3)(ref val3)).normalized;
			Vector3 val4 = position - val * (0.1f + Settings.Along) + val2 * (0.08f + Settings.Up) + normalized * Settings.Side;
			if (!_hasPose)
			{
				_pos = val4;
				_hasPose = true;
			}
			else
			{
				_pos = Vector3.Lerp(_pos, val4, 1f - Mathf.Exp(-18f * Time.unscaledDeltaTime));
			}
			Transform transform = _root.transform;
			transform.position = _pos;
			transform.rotation = Quaternion.LookRotation(_pos - head.position, head.up);
			transform.localScale = Vector3.one * (0.0005f * Settings.Scale);
			SetVisible(show: true);
			if (_dirty || Time.unscaledTime >= _nextRefresh)
			{
				Render();
				_dirty = false;
				_nextRefresh = Time.unscaledTime + 0.5f;
			}
			int num = -2;
			bool num2 = LeftHandEmpty();
			bool havePointer = false;
			bool onPanel = false;
			Vector3 origin = default(Vector3);
			Vector3 dir = default(Vector3);
			Vector3 hitWorld = default(Vector3);
			Vector2 localPos = default(Vector2);
			if (num2 && GetPointerRay(head, out origin, out dir))
			{
				havePointer = true;
				num = FindPointerTarget(origin, dir, transform, out localPos, out onPanel, out hitWorld);
			}
			UpdatePointerVisuals(havePointer, onPanel, origin, dir, hitWorld, localPos);
			ApplyHover(num);
			float unscaledTime = Time.unscaledTime;
			if (num != -2)
			{
				if (num != _latchTarget)
				{
					_latchTarget = num;
					_latchSince = unscaledTime;
				}
				_latchSeen = unscaledTime;
			}
			if (num2 && ClickPressed())
			{
				int num3 = num;
				if (num3 == -2 && _latchTarget != -2 && unscaledTime - _latchSeen < 0.25f && _latchSeen - _latchSince > 0.08f)
				{
					num3 = _latchTarget;
				}
				if (num3 != -2)
				{
					_latchTarget = -2;
					BuzzLeft();
					Activate(num3);
				}
			}
		}

		private static Vector3 ForearmDirection(Hand hand, Transform head)
		{
			//IL_0043: 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_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: 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)
			//IL_0069: 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)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: 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_0096: 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)
			Transform val = null;
			try
			{
				Rigidbody val2 = (((Object)(object)hand.physHand != (Object)null) ? hand.physHand.rbLower : null);
				if ((Object)(object)val2 != (Object)null)
				{
					val = ((Component)val2).transform;
				}
			}
			catch
			{
			}
			Vector3 val3 = (((Object)(object)val != (Object)null) ? (((Component)hand).transform.position - val.position) : ((!((Object)(object)Player.Chest != (Object)null)) ? head.forward : (((Component)hand).transform.position - Player.Chest.position)));
			if (((Vector3)(ref val3)).sqrMagnitude < 1E-06f)
			{
				val3 = head.forward;
			}
			return ((Vector3)(ref val3)).normalized;
		}

		private void SetVisible(bool show)
		{
			if ((Object)(object)_root == (Object)null)
			{
				_visible = false;
				_hasPose = false;
			}
			else if (show != _visible)
			{
				_visible = show;
				_root.SetActive(show);
				if (show)
				{
					OnShown();
					return;
				}
				_hasPose = false;
				_latchTarget = -2;
				HidePointerVisuals();
			}
		}

		private void OnShown()
		{
			_dirty = true;
			Main instance = Main.Instance;
			if (instance != null && instance.ScanIsStale && !instance.IsScanning)
			{
				instance.Scan();
			}
		}

		private bool GetPointerRay(Transform head, out Vector3 origin, out Vector3 dir)
		{
			//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_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_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_0034: 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)
			//IL_003b: 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_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: 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_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: 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_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_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: 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_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: 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_009f: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: 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_00b9: 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_00c3: 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_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			origin = default(Vector3);
			dir = default(Vector3);
			Hand leftHand = Player.LeftHand;
			if ((Object)(object)leftHand == (Object)null)
			{
				return false;
			}
			Vector3 val = ForearmDirection(leftHand, head);
			Vector3 position = ((Component)leftHand).transform.position;
			origin = position + val * 0.08f;
			dir = val;
			bool flag = false;
			try
			{
				OpenControllerRig controllerRig = Player.ControllerRig;
				Transform palmPositionTransform = leftHand.palmPositionTransform;
				if ((Object)(object)controllerRig != (Object)null && (Object)(object)palmPositionTransform != (Object)null)
				{
					SimpleTransform lPointerInPalmOffset = controllerRig.lPointerInPalmOffset;
					Vector3 val2 = palmPositionTransform.position + palmPositionTransform.rotation * lPointerInPalmOffset.position;
					Vector3 val3 = palmPositionTransform.rotation * lPointerInPalmOffset.rotation * Vector3.forward;
					if (((Vector3)(ref val3)).sqrMagnitude > 1E-06f)
					{
						((Vector3)(ref val3)).Normalize();
						if (Vector3.Dot(val3, val) > 0.3f)
						{
							Vector3 val4 = val2 - position;
							if (((Vector3)(ref val4)).sqrMagnitude < 0.16f)
							{
								origin = val2;
								dir = val3;
								flag = true;
							}
						}
					}
				}
			}
			catch
			{
			}
			if (!_pointerSourceLogged)
			{
				_pointerSourceLogged = true;
				MelonLogger.Msg(flag ? "Wrist panel pointer: using the game's palm pointer." : "Wrist panel pointer: game pointer unavailable, using the hand fallback.");
			}
			Vector3 val5 = Vector3.ProjectOnPlane(Vector3.up, dir);
			if (((Vector3)(ref val5)).sqrMagnitude < 0.0001f)
			{
				val5 = head.up;
			}
			((Vector3)(ref val5)).Normalize();
			origin += val5 * Settings.PointerNudgeUp + dir * Settings.PointerNudgeAhead;
			return true;
		}

		private int FindPointerTarget(Vector3 origin, Vector3 dir, Transform panel, out Vector2 localPos, out bool onPanel, out Vector3 hitWorld)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: 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_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: 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_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_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: 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_0059: 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_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: 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)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: 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_00d2: Unknown result type (might be due to invalid IL or missing references)
			localPos = default(Vector2);
			onPanel = false;
			hitWorld = default(Vector3);
			Vector3 forward = panel.forward;
			float num = Vector3.Dot(dir, forward);
			if (num < 0.05f)
			{
				return -2;
			}
			float num2 = Vector3.Dot(panel.position - origin, forward) / num;
			if (num2 <= 0f || num2 > 2.5f)
			{
				return -2;
			}
			hitWorld = origin + dir * num2;
			Vector3 val = panel.InverseTransformPoint(hitWorld);
			float num3 = 190f - val.y;
			if (Mathf.Abs(val.x) > 170f || num3 < 0f || num3 > 380f)
			{
				return -2;
			}
			onPanel = true;
			localPos = new Vector2(val.x, val.y);
			if (num3 < 46f)
			{
				if (!(val.x > 110f))
				{
					return -1;
				}
				return -3;
			}
			int num4 = (int)((num3 - 46f - 6f) / 40f);
			if (num4 < 0 || num4 >= _shown.Count)
			{
				return -2;
			}
			return num4;
		}

		private void UpdatePointerVisuals(bool havePointer, bool onPanel, Vector3 origin, Vector3 dir, Vector3 hit, Vector2 cursorLocal)
		{
			//IL_0024: 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_0034: 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)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_line != (Object)null)
			{
				if (havePointer)
				{
					((Renderer)_line).enabled = true;
					_line.SetPosition(0, origin);
					_line.SetPosition(1, onPanel ? hit : (origin + dir * 0.5f));
				}
				else
				{
					((Renderer)_line).enabled = false;
				}
			}
			if ((Object)(object)_cursor != (Object)null)
			{
				bool flag = havePointer && onPanel;
				if (((Component)_cursor).gameObject.activeSelf != flag)
				{
					((Component)_cursor).gameObject.SetActive(flag);
				}
				if (flag)
				{
					_cursorRt.anchoredPosition = cursorLocal;
				}
			}
		}

		private void HidePointerVisuals()
		{
			if ((Object)(object)_line != (Object)null)
			{
				((Renderer)_line).enabled = false;
			}
			if ((Object)(object)_cursor != (Object)null && ((Component)_cursor).gameObject.activeSelf)
			{
				((Component)_cursor).gameObject.SetActive(false);
			}
		}

		private static bool LeftHandEmpty()
		{
			try
			{
				Hand leftHand = Player.LeftHand;
				return (Object)(object)leftHand != (Object)null && (Object)(object)Player.GetObjectInHand(leftHand) == (Object)null;
			}
			catch
			{
				return false;
			}
		}

		private static bool ClickPressed()
		{
			try
			{
				BaseController leftController = Player.LeftController;
				if ((Object)(object)leftController == (Object)null)
				{
					return false;
				}
				return leftController.GetPrimaryInteractionButtonDown() || leftController.GetAButtonDown();
			}
			catch
			{
				return false;
			}
		}

		private static void BuzzLeft()
		{
			try
			{
				BaseController leftController = Player.LeftController;
				if ((Object)(object)leftController != (Object)null)
				{
					leftController.Haptic(0.3f);
				}
			}
			catch
			{
			}
		}

		private void Activate(int hover)
		{
			try
			{
				switch (hover)
				{
				case -3:
					Close();
					break;
				case -1:
					Pop();
					break;
				default:
					_shown[hover].OnSelect?.Invoke();
					_dirty = true;
					break;
				}
			}
			catch (Exception value)
			{
				MelonLogger.Error($"Wrist panel action failed: {value}");
			}
		}

		public void Toggle()
		{
			if (IsOpen)
			{
				Close();
			}
			else
			{
				Open();
			}
		}

		private void Open()
		{
			IsOpen = true;
			_dirty = true;
			Main.Notify("Wrist panel open. Point at the red X to close it.");
		}

		private void Close()
		{
			IsOpen = false;
			SetVisible(show: false);
			Main.Notify("Wrist panel closed. Tap your right wrist twice with your left hand to open it.");
		}

		private void CheckOpenGesture()
		{
			//IL_0052: 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_0063: 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_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			if (IsOpen || !Player.HandsExist)
			{
				_tapNear = false;
				return;
			}
			Hand leftHand = Player.LeftHand;
			Hand rightHand = Player.RightHand;
			Transform head = Player.Head;
			if ((Object)(object)leftHand == (Object)null || (Object)(object)rightHand == (Object)null || (Object)(object)head == (Object)null)
			{
				_tapNear = false;
				return;
			}
			Vector3 val = ((Component)rightHand).transform.position - ForearmDirection(rightHand, head) * 0.1f;
			bool flag = Vector3.Distance(((Component)leftHand).transform.position, val) < Settings.TapRadius;
			bool num = flag && !_tapNear;
			_tapNear = flag;
			if (!num)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (!(unscaledTime < _tapCooldownUntil))
			{
				if ((Object)(object)Player.GetObjectInHand(leftHand) != (Object)null || (Object)(object)Player.GetObjectInHand(rightHand) != (Object)null)
				{
					_lastTap = -10f;
				}
				else if (unscaledTime - _lastTap <= 0.8f)
				{
					_lastTap = -10f;
					_tapCooldownUntil = unscaledTime + 1.5f;
					Open();
				}
				else
				{
					_lastTap = unscaledTime;
				}
			}
		}

		private void Push(Func<PageData> next)
		{
			_history.Push(_page);
			_page = next;
			_scroll = 0;
			_dirty = true;
		}

		private void Pop()
		{
			if (_history.Count != 0)
			{
				_page = _history.Pop();
				_scroll = 0;
				_dirty = true;
			}
		}

		private PageData HomePage()
		{
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			Main main = Main.Instance;
			int count = FavoritesStore.Favorites.Count;
			int count2 = main.Status.Count;
			List<Row> list = new List<Row>
			{
				new Row(main.IsScanning ? "Scanning..." : $"Scan for friends ({count2}/{count} online)", Online, delegate
				{
					main.Scan();
				}),
				new Row($"My favorites ({count})", Yellow, delegate
				{
					Push(FavoritesPage);
				}),
				new Row("Add from this lobby", Plain, delegate
				{
					Push(AddPage);
				}),
				new Row("Layer: " + Or(FusionBridge.CurrentLayerName, "not connected"), Gray)
			};
			foreach (WaitEntry waitEntry in main.WaitEntries)
			{
				FavoritePlayer fav = waitEntry.Fav;
				list.Insert(1, new Row("Waiting: " + fav.Name + " (tap to cancel)", Yellow, delegate
				{
					main.CancelWait(fav);
				}));
			}
			return new PageData
			{
				Title = "Fusion Favorites",
				Rows = list
			};
		}

		private PageData FavoritesPage()
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: 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)
			Main main = Main.Instance;
			List<Row> list = new List<Row>();
			List<FavoritePlayer> list2 = (from f in FavoritesStore.Favorites
				orderby main.Status.ContainsKey(f.PlayerId) descending, f.Name
				select f).ToList();
			if (list2.Count == 0)
			{
				list.Add(new Row("No favorites yet", Gray));
			}
			foreach (FavoritePlayer item in list2)
			{
				FavoritePlayer fav = item;
				LobbyStatus value;
				bool flag = main.Status.TryGetValue(fav.PlayerId, out value);
				string text = (flag ? (fav.Name + " - " + Or(value.Level, "online")) : fav.Name);
				list.Add(new Row(text, flag ? Online : Gray, delegate
				{
					Push(() => ProfilePage(fav));
				}));
			}
			return new PageData
			{
				Title = "My favorites",
				Rows = list
			};
		}

		private PageData AddPage()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			List<Row> list = new List<Row>();
			List<LobbyPlayer> list2;
			try
			{
				list2 = FusionBridge.GetPlayersInCurrentLobby();
			}
			catch
			{
				list2 = new List<LobbyPlayer>();
			}
			if (list2.Count == 0)
			{
				list.Add(new Row("Nobody else here", Gray));
			}
			foreach (LobbyPlayer item in list2)
			{
				LobbyPlayer player = item;
				bool flag = FavoritesStore.Favorites.Any((FavoritePlayer f) => f.PlayerId == player.PlayerId && f.NetworkLayer == player.NetworkLayer);
				list.Add(new Row(flag ? (player.Name + " (saved)") : ("Add " + player.Name), flag ? Gray : Online, delegate
				{
					Main.Notify(FavoritesStore.Add(player) ? ("Added " + player.Name) : (player.Name + " was already saved"));
					Main.Instance.RefreshPages();
				}));
			}
			return new PageData
			{
				Title = "Add from lobby",
				Rows = list
			};
		}

		private PageData ProfilePage(FavoritePlayer fav)
		{
			//IL_035a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_040d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: 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_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0466: Unknown result type (might be due to invalid IL or missing references)
			//IL_0450: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_033f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0319: Unknown result type (might be due to invalid IL or missing references)
			Main main = Main.Instance;
			LobbyStatus value;
			bool num = main.Status.TryGetValue(fav.PlayerId, out value);
			List<Row> list = new List<Row>();
			if (num)
			{
				if (value.Full)
				{
					list.Add(new Row($"Lobby full ({value.Players}/{value.MaxPlayers})", Danger));
					if (main.IsWaiting(fav))
					{
						list.Add(new Row("Waiting for a spot... tap to cancel", Yellow, delegate
						{
							main.CancelWait(fav);
						}));
					}
					else
					{
						list.Add(new Row("Wait for a spot", Yellow, delegate
						{
							main.StartWait(fav);
						}));
					}
				}
				else
				{
					list.Add(new Row("Join lobby", Online, delegate
					{
						FusionBridge.TryJoin(fav, Main.Notify);
					}));
				}
				list.Add(new Row("Map: " + Or(value.Level, "unknown"), Accent));
				if (!string.IsNullOrEmpty(value.Gamemode))
				{
					list.Add(new Row("Mode: " + value.Gamemode, Plain));
				}
				list.Add(new Row("Lobby: " + Or(value.LobbyName, "unnamed"), Plain));
				list.Add(new Row($"Host: {Or(value.HostName, "?")} ({value.Players}/{value.MaxPlayers})", Plain));
				list.Add(new Row(Or(value.Privacy, "?") + " | voice " + OnOff(value.Voice), Plain));
				list.Add(new Row("mortality " + OnOff(value.Mortality) + " | fire " + OnOff(value.FriendlyFire), Plain));
				if (value.PlayerNames.Count > 0)
				{
					string text = string.Join(", ", value.PlayerNames.Take(4));
					if (value.PlayerNames.Count > 4)
					{
						text += $" +{value.PlayerNames.Count - 4}";
					}
					list.Add(new Row("In lobby: " + text, Gray));
				}
				list.Add(new Row("Scanned " + Main.Ago(value.ScannedAt), Gray));
			}
			else
			{
				list.Add(new Row("Not in a public lobby", Gray));
				if (fav.LastSeen.HasValue)
				{
					string text2 = (string.IsNullOrEmpty(fav.LastLevel) ? "" : (" on " + fav.LastLevel));
					list.Add(new Row("Last seen " + Main.Ago(fav.LastSeen.Value) + text2, Gray));
				}
				list.Add(new Row("Try to join anyway", Plain, delegate
				{
					FusionBridge.TryJoin(fav, Main.Notify);
				}));
				list.Add(new Row("Scan again", Online, delegate
				{
					main.Scan();
				}));
			}
			if (!string.IsNullOrEmpty(fav.Note))
			{
				list.Add(new Row("Note: " + fav.Note, Yellow));
			}
			list.Add(new Row("Remove", Danger, delegate
			{
				FavoritesStore.Remove(fav);
				main.Status.Remove(fav.PlayerId);
				main.RefreshPages();
				Main.Notify("Removed " + fav.Name);
				Pop();
			}));
			return new PageData
			{
				Title = fav.Name,
				Rows = list
			};
		}

		private static string Or(string v, string fallback)
		{
			if (!string.IsNullOrWhiteSpace(v))
			{
				return v;
			}
			return fallback;
		}

		private static string OnOff(bool v)
		{
			if (!v)
			{
				return "off";
			}
			return "on";
		}

		private void Render()
		{
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: 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)
			PageData pageData;
			try
			{
				pageData = _page();
			}
			catch (Exception ex)
			{
				MelonLogger.Error("Wrist panel page failed: " + ex.Message);
				_page = HomePage;
				_history.Clear();
				pageData = HomePage();
			}
			_title = ((_history.Count > 0) ? "< " : "") + pageData.Title;
			_shown.Clear();
			List<Row> rows = pageData.Rows;
			if (rows.Count <= 8)
			{
				_shown.AddRange(rows);
			}
			else
			{
				int window = 6;
				_scroll = Mathf.Clamp(_scroll, 0, rows.Count - window);
				_shown.AddRange(rows.Skip(_scroll).Take(window));
				bool flag = _scroll > 0;
				bool flag2 = _scroll + window < rows.Count;
				_shown.Add(new Row("^ more", flag ? Accent : Gray, flag ? ((Action)delegate
				{
					_scroll = Math.Max(0, _scroll - window);
				}) : null));
				_shown.Add(new Row("v more", flag2 ? Accent : Gray, flag2 ? ((Action)delegate
				{
					_scroll += window;
				}) : null));
			}
			SetLabel(_header, _title, Accent);
			SetLabel(_close, "X", Danger);
			for (int num = 0; num < _views.Count; num++)
			{
				RowView rowView = _views[num];
				if (num < _shown.Count)
				{
					rowView.Go.SetActive(true);
					SetLabel(rowView, _shown[num].Text, _shown[num].Color);
				}
				else
				{
					rowView.Go.SetActive(false);
				}
			}
		}

		private static void SetLabel(RowView v, string text, Color color)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if (v.LastText != text)
			{
				v.Label.text = text;
				v.LastText = text;
			}
			((Graphic)v.Label).color = color;
		}

		private void ApplyHover(int hover)
		{
			//IL_002b: 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_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: 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_00bb: 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)
			bool flag = _history.Count > 0;
			bool flag2 = hover == -1 && flag;
			((Graphic)_header.Bg).color = (flag2 ? RowBgHover : RowBg);
			SetOutline(_header, flag, flag2);
			bool flag3 = hover == -3;
			((Graphic)_close.Bg).color = (flag3 ? CloseBgHover : CloseBg);
			SetOutline(_close, visible: true, flag3);
			for (int i = 0; i < _views.Count && i < _shown.Count; i++)
			{
				bool flag4 = _shown[i].OnSelect != null;
				bool flag5 = flag4 && i == hover;
				((Graphic)_views[i].Bg).color = ((!flag4) ? RowBgInfo : (flag5 ? RowBgHover : RowBg));
				SetOutline(_views[i], flag4, flag5);
			}
		}

		private static void SetOutline(RowView v, bool visible, bool hot)
		{
			//IL_003c: 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)
			if (!((Object)(object)v.Outline == (Object)null))
			{
				if (((Behaviour)v.Outline).enabled != visible)
				{
					((Behaviour)v.Outline).enabled = visible;
				}
				if (visible)
				{
					((Graphic)v.Outline).color = (hot ? OutlineHot : OutlineRed);
				}
			}
		}

		private void EnsureBuilt()
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Invalid comparison between Unknown and I4
			//IL_00da: 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_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: 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_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: 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_026f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0274: Unknown result type (might be due to invalid IL or missing references)
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_root != (Object)null))
			{
				_views.Clear();
				_visible = false;
				_hasPose = false;
				_dirty = true;
				if ((Object)(object)_laserGo != (Object)null)
				{
					Object.Destroy((Object)(object)_laserGo);
				}
				_line = null;
				_font = FindFont();
				EnsureSprites();
				_root = new GameObject("FusionFavoritesWristPanel");
				Object.DontDestroyOnLoad((Object)(object)_root);
				_root.AddComponent<RectTransform>().sizeDelta = new Vector2(340f, 380f);
				_root.AddComponent<Canvas>().renderMode = (RenderMode)2;
				_root.AddComponent<CanvasScaler>().dynamicPixelsPerUnit = (((int)Application.platform == 11) ? 2f : 4f);
				_root.transform.localScale = Vector3.one * (0.0005f * Settings.Scale);
				MakeImage("Backdrop", _root.transform, PanelBg, Vector2.zero, new Vector2(340f, 380f), _sprPanel, sliced: true);
				MakeImage("PanelOutline", _root.transform, OutlineRed, Vector2.zero, new Vector2(340f, 380f), _sprPanelRing, sliced: true);
				float num = 190f;
				float y = num - 23f - 2f;
				float width = 266f;
				_header = MakeRow("Header", -29f, y, width, 42f, 26f, (TextAnchor)3, 12f, 8f);
				_close = MakeRow("Close", 136f, y, 52f, 42f, 26f, (TextAnchor)4, 0f, 0f);
				((Graphic)_close.Bg).color = CloseBg;
				for (int i = 0; i < 8; i++)
				{
					float y2 = num - 46f - 6f - ((float)i + 0.5f) * 40f;
					_views.Add(MakeRow("Row" + i, 0f, y2, 324f, 36f, 22f, (TextAnchor)3, 12f, 12f));
				}
				_cursor = MakeImage("Cursor", _root.transform, Accent, Vector2.zero, new Vector2(14f, 14f), _sprDot);
				_cursorRt = ((Component)_cursor).GetComponent<RectTransform>();
				((Component)_cursor).gameObject.SetActive(false);
				BuildLaser();
				_root.SetActive(false);
			}
		}

		private void BuildLaser()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Shader val = Shader.Find("Sprites/Default");
				if (!((Object)(object)val == (Object)null))
				{
					_laserGo = new GameObject("FusionFavoritesLaser");
					Object.DontDestroyOnLoad((Object)(object)_laserGo);
					_line = _laserGo.AddComponent<LineRenderer>();
					((Renderer)_line).material = new Material(val);
					_line.positionCount = 2;
					_line.useWorldSpace = true;
					_line.startWidth = 0.004f;
					_line.endWidth = 0.0015f;
					_line.startColor = new Color(0.37f, 0.82f, 0.91f, 0.9f);
					_line.endColor = new Color(0.37f, 0.82f, 0.91f, 0.15f);
					((Renderer)_line).enabled = false;
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Laser line unavailable (the cursor dot still works): " + ex.Message);
				_line = null;
			}
		}

		private RowView MakeRow(string name, float x, float y, float width, float height, float fontSize, TextAnchor align, float leftInset, float rightInset)
		{
			//IL_000c: 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_001c: 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_0051: 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_0067: 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_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			Image val = MakeImage(name, _root.transform, RowBg, new Vector2(x, y), new Vector2(width, height), _sprRow, sliced: true);
			GameObject val2 = new GameObject(name + "Outline");
			RectTransform obj = val2.AddComponent<RectTransform>();
			((Transform)obj).SetParent(((Component)val).transform, false);
			obj.anchorMin = Vector2.zero;
			obj.anchorMax = Vector2.one;
			obj.offsetMin = Vector2.zero;
			obj.offsetMax = Vector2.zero;
			Image val3 = val2.AddComponent<Image>();
			val3.sprite = _sprRowRing;
			val3.type = (Type)1;
			((Graphic)val3).color = OutlineRed;
			((Graphic)val3).raycastTarget = false;
			GameObject val4 = new GameObject(name + "Text");
			RectTransform obj2 = val4.AddComponent<RectTransform>();
			((Transform)obj2).SetParent(((Component)val).transform, false);
			obj2.anchorMin = Vector2.zero;
			obj2.anchorMax = Vector2.one;
			obj2.offsetMin = new Vector2(leftInset, 0f);
			obj2.offsetMax = new Vector2(0f - rightInset, 0f);
			Text val5 = val4.AddComponent<Text>();
			if ((Object)(object)_font != (Object)null)
			{
				val5.font = _font;
			}
			val5.fontSize = (int)fontSize;
			val5.alignment = align;
			val5.horizontalOverflow = (HorizontalWrapMode)0;
			val5.verticalOverflow = (VerticalWrapMode)0;
			val5.resizeTextForBestFit = true;
			val5.resizeTextMinSize = 10;
			val5.resizeTextMaxSize = (int)fontSize;
			((Graphic)val5).raycastTarget = false;
			((Graphic)val5).color = Plain;
			return new RowView
			{
				Go = ((Component)val).gameObject,
				Bg = val,
				Outline = val3,
				Label = val5,
				LastText = null
			};
		}

		private static Image MakeImage(string name, Transform parent, Color color, Vector2 pos, Vector2 size, Sprite sprite = null, bool sliced = false)
		{
			//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_0015: 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_0049: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			RectTransform obj = val.AddComponent<RectTransform>();
			((Transform)obj).SetParent(parent, false);
			obj.sizeDelta = size;
			obj.anchoredPosition = pos;
			Image val2 = val.AddComponent<Image>();
			if ((Object)(object)sprite != (Object)null)
			{
				val2.sprite = sprite;
				val2.type = (Type)(sliced ? 1 : 0);
			}
			((Graphic)val2).color = color;
			((Graphic)val2).raycastTarget = false;
			return val2;
		}

		private static void EnsureSprites()
		{
			if (!((Object)(object)_sprPanel != (Object)null) || !((Object)(object)_sprPanelRing != (Object)null) || !((Object)(object)_sprRow != (Object)null) || !((Object)(object)_sprRowRing != (Object)null) || !((Object)(object)_sprDot != (Object)null))
			{
				_sprPanel = MakeSprite(56, 24f, 0f, 26);
				_sprPanelRing = MakeSprite(56, 24f, 3f, 26);
				_sprRow = MakeSprite(28, 11f, 0f, 13);
				_sprRowRing = MakeSprite(28, 11f, 2.2f, 13);
				_sprDot = MakeSprite(20, 10f, 0f, 0);
			}
		}

		private static Sprite MakeSprite(int size, float radius, float thickness, int border)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Expected O, but got Unknown
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = new Texture2D(size, size, (TextureFormat)4, false);
			((Texture)val).filterMode = (FilterMode)1;
			((Texture)val).wrapMode = (TextureWrapMode)1;
			((Object)val).hideFlags = (HideFlags)61;
			float num = (float)size * 0.5f;
			float num2 = num - radius;
			for (int i = 0; i < size; i++)
			{
				for (int j = 0; j < size; j++)
				{
					float num3 = Mathf.Abs((float)j + 0.5f - num) - num2;
					float num4 = Mathf.Abs((float)i + 0.5f - num) - num2;
					float num5 = Mathf.Max(num3, 0f);
					float num6 = Mathf.Max(num4, 0f);
					float num7 = Mathf.Sqrt(num5 * num5 + num6 * num6) + Mathf.Min(Mathf.Max(num3, num4), 0f) - radius;
					float num8 = Mathf.Clamp01(0.5f - num7);
					if (thickness > 0f)
					{
						num8 = Mathf.Clamp01(num8 - Mathf.Clamp01(0.5f - (num7 + thickness)));
					}
					val.SetPixel(j, i, new Color(1f, 1f, 1f, num8));
				}
			}
			val.Apply();
			Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)size, (float)size), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, new Vector4((float)border, (float)border, (float)border, (float)border));
			((Object)obj).hideFlags = (HideFlags)61;
			return obj;
		}

		private static Font FindFont()
		{
			try
			{
				HashSet<string> hashSet = new HashSet<string>();
				Il2CppStringArray oSInstalledFontNames = Font.GetOSInstalledFontNames();
				if (oSInstalledFontNames != null)
				{
					foreach (string item in (Il2CppArrayBase<string>)(object)oSInstalledFontNames)
					{
						hashSet.Add(item);
					}
				}
				string[] array = new string[6] { "Arial", "Roboto", "Noto Sans", "Droid Sans", "Segoe UI", "Helvetica" };
				foreach (string text in array)
				{
					if (hashSet.Contains(text))
					{
						Font val = Font.CreateDynamicFontFromOSFont(text, 32);
						if ((Object)(object)val != (Object)null)
						{
							return val;
						}
					}
				}
				foreach (string item2 in hashSet)
				{
					Font val2 = Font.CreateDynamicFontFromOSFont(item2, 32);
					if ((Object)(object)val2 != (Object)null)
					{
						return val2;
					}
				}
			}
			catch
			{
			}
			try
			{
				Font builtinResource = Resources.GetBuiltinResource<Font>("Arial.ttf");
				if ((Object)(object)builtinResource != (Object)null)
				{
					return builtinResource;
				}
			}
			catch
			{
			}
			return null;
		}
	}
}