Decompiled source of Invite Recent Players v1.0.0

plugins/InviteRecentPlayers/InviteRecentPlayers.dll

Decompiled 5 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using MenuLib;
using MenuLib.MonoBehaviors;
using MenuLib.Structs;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Steamworks;
using Steamworks.Data;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("InviteRecentPlayers")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+0d77aba4ef99a87d6be4daeb81b0de1f2e0c0dca")]
[assembly: AssemblyProduct("InviteRecentPlayers")]
[assembly: AssemblyTitle("InviteRecentPlayers")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 InviteRecentPlayers
{
	internal sealed class InviteSentLobbyBanner
	{
		private readonly CanvasGroup _canvasGroup;

		private readonly TextMeshProUGUI _label;

		private float _visibleUntil;

		internal bool IsValid => (Object)(object)_canvasGroup != (Object)null && (Object)(object)_label != (Object)null;

		internal InviteSentLobbyBanner(CanvasGroup canvasGroup)
		{
			_canvasGroup = canvasGroup;
			_label = (((Object)(object)canvasGroup != (Object)null) ? ((Component)canvasGroup).GetComponentInChildren<TextMeshProUGUI>(true) : null);
			if ((Object)(object)_canvasGroup != (Object)null)
			{
				_canvasGroup.alpha = 0f;
				_canvasGroup.blocksRaycasts = false;
				_canvasGroup.interactable = false;
			}
		}

		internal void Show(int inviteCount, float durationSeconds = 2.5f)
		{
			if (IsValid)
			{
				((TMP_Text)_label).text = ((inviteCount == 1) ? "INVITE SENT" : $"{inviteCount} INVITES SENT");
				_visibleUntil = Time.unscaledTime + durationSeconds;
				_canvasGroup.alpha = 1f;
			}
		}

		internal void Tick()
		{
			if (IsValid)
			{
				if (_visibleUntil > Time.unscaledTime)
				{
					_canvasGroup.alpha = Mathf.Lerp(_canvasGroup.alpha, 1f, Time.unscaledDeltaTime * 10f);
				}
				else
				{
					_canvasGroup.alpha = Mathf.Lerp(_canvasGroup.alpha, 0f, Time.unscaledDeltaTime * 10f);
				}
			}
		}
	}
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInPlugin("denis.repo.invite-recent-players", "Invite Recent Players", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		internal const string CreatorSteamId = "76561198257806281";

		internal const string CreatorKey = "steam:76561198257806281";

		internal const string CreatorDisplayName = "disabro";

		private static readonly Vector2 HostLobbyButtonPosition = new Vector2(190.66f, 91.01997f);

		private static readonly Vector2 GuestLobbyButtonPosition = new Vector2(222.15f, 64.40998f);

		private static ConfigEntry<int> _recentPlayersLimit;

		private static ConfigEntry<bool> _iHaveNoFriends;

		private static ConfigEntry<bool> _saveNonFriends;

		private static readonly HashSet<string> CachedFriendSteamIds = new HashSet<string>(StringComparer.Ordinal);

		private static float _friendCacheNextRefreshAt;

		private RecentPlayersStore _store;

		private RecentPlayersTracker _tracker;

		private RecentPlayersPanel _panel;

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource SharedLogger { get; private set; }

		internal static Vector2 GetLobbyButtonPosition()
		{
			//IL_000f: 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_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			return (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) ? GuestLobbyButtonPosition : HostLobbyButtonPosition;
		}

		internal static int GetRecentPlayersLimit()
		{
			int num = 10;
			if (_recentPlayersLimit != null)
			{
				num = _recentPlayersLimit.Value;
			}
			return Mathf.Clamp(num, 10, 20);
		}

		internal static bool IsCreatorForcedVisible()
		{
			return _iHaveNoFriends != null && _iHaveNoFriends.Value;
		}

		internal static bool SaveNonFriends()
		{
			return _saveNonFriends != null && _saveNonFriends.Value;
		}

		internal static bool IsSteamFriend(string steamId)
		{
			if (string.IsNullOrWhiteSpace(steamId))
			{
				return false;
			}
			RefreshFriendCacheIfNeeded();
			return CachedFriendSteamIds.Contains(steamId);
		}

		private static void RefreshFriendCacheIfNeeded()
		{
			//IL_0051: 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_0068: 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_006e: Unknown result type (might be due to invalid IL or missing references)
			if (Time.unscaledTime < _friendCacheNextRefreshAt && CachedFriendSteamIds.Count > 0)
			{
				return;
			}
			_friendCacheNextRefreshAt = Time.unscaledTime + 5f;
			try
			{
				CachedFriendSteamIds.Clear();
				foreach (Friend friend in SteamFriends.GetFriends())
				{
					Friend current = friend;
					if (((Friend)(ref current)).IsFriend)
					{
						HashSet<string> cachedFriendSteamIds = CachedFriendSteamIds;
						SteamId id = current.Id;
						cachedFriendSteamIds.Add(((object)(SteamId)(ref id)).ToString());
					}
				}
			}
			catch
			{
				CachedFriendSteamIds.Clear();
			}
		}

		internal static bool ShouldKeepPlayerVisible(RecentPlayerRecord player)
		{
			if (player == null)
			{
				return false;
			}
			if (string.IsNullOrWhiteSpace(player.Key))
			{
				return false;
			}
			string steamId = player.SteamId;
			if (IsCreatorForcedVisible() && string.Equals(steamId, "76561198257806281", StringComparison.Ordinal))
			{
				return true;
			}
			if (SaveNonFriends())
			{
				return !string.IsNullOrWhiteSpace(steamId);
			}
			return IsSteamFriend(steamId);
		}

		internal static bool ShouldSaveObservedPlayer(string steamId)
		{
			if (string.IsNullOrWhiteSpace(steamId))
			{
				return false;
			}
			if (string.Equals(steamId, "76561198257806281", StringComparison.Ordinal))
			{
				return true;
			}
			if (SaveNonFriends())
			{
				return true;
			}
			if (IsSteamFriend(steamId))
			{
				return true;
			}
			return false;
		}

		private void Awake()
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Expected O, but got Unknown
			Instance = this;
			SharedLogger = ((BaseUnityPlugin)this).Logger;
			string storagePath = Path.Combine(Paths.ConfigPath, "denis.repo.invite-recent-players.json");
			_recentPlayersLimit = ((BaseUnityPlugin)this).Config.Bind<int>("General", "Recent Players Count", 10, new ConfigDescription("How many recent players to show.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 20), Array.Empty<object>()));
			_saveNonFriends = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Save Non-Friends", false, "When off, only Steam friends are kept in recent players. The joke helper entry is still allowed.");
			_iHaveNoFriends = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "I Have No Friends", false, "Force-show disabro in the list as a joke helper entry.");
			_store = new RecentPlayersStore(storagePath, ((BaseUnityPlugin)this).Logger);
			_store.Load();
			_store.RemoveSeededOrInvalidRecords();
			_store.RemoveNonFriendRecordsIfNeeded();
			RecentPlayersInviteService inviteService = new RecentPlayersInviteService(((BaseUnityPlugin)this).Logger);
			_tracker = new RecentPlayersTracker(_store, ((BaseUnityPlugin)this).Logger);
			_panel = new RecentPlayersPanel(_store, _tracker, inviteService, ((BaseUnityPlugin)this).Logger);
			MenuAPI.AddElementToLobbyMenu(new BuilderDelegate(_panel.BuildLobbyButton));
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Invite Recent Players loaded");
		}

		private void Update()
		{
			_panel?.Tick();
			_tracker?.Tick();
		}

		private void OnDestroy()
		{
			if (Instance == this)
			{
				Instance = null;
			}
			if (SharedLogger == ((BaseUnityPlugin)this).Logger)
			{
				SharedLogger = null;
			}
			_tracker?.FlushPendingPlayers();
		}
	}
	internal readonly struct RecentPlayerInviteCooldownState
	{
		internal static readonly RecentPlayerInviteCooldownState None = new RecentPlayerInviteCooldownState(isActive: false, string.Empty);

		internal bool IsActive { get; }

		internal string Suffix { get; }

		internal RecentPlayerInviteCooldownState(bool isActive, string suffix)
		{
			IsActive = isActive;
			Suffix = suffix ?? string.Empty;
		}
	}
	internal sealed class RecentPlayerProfileButton
	{
	}
	[Serializable]
	public sealed class RecentPlayerRecord
	{
		public string Key;

		public string UserId;

		public string DisplayName;

		public string SteamId;

		public long LastSeenUtcTicks;
	}
	[Serializable]
	public sealed class RecentPlayersSaveData
	{
		public RecentPlayerRecord[] Players;
	}
	internal sealed class RecentPlayerRow
	{
		private readonly RecentPlayerRecord _player;

		private readonly REPOButton _button;

		internal string Key => _player.Key;

		internal RectTransform Root => ((REPOElement)_button).rectTransform;

		internal REPOButton Button => _button;

		internal RecentPlayerRow(RecentPlayerRecord player, REPOButton button)
		{
			_player = player;
			_button = button;
		}

		internal static RecentPlayerRow Create(RecentPlayerRecord player, Transform parent, float rowWidth, Action onClick, Action onInviteClick, Action onProfileClick)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			REPOButton button = MenuAPI.CreateREPOButton(string.Empty, onClick, parent, default(Vector2));
			StyleButton(button);
			ApplyLayout(button, rowWidth);
			RecentPlayerRow recentPlayerRow = new RecentPlayerRow(player, button);
			recentPlayerRow.Refresh(isSelected: false, RecentPlayerInviteCooldownState.None, isAlreadyInLobby: false);
			return recentPlayerRow;
		}

		internal void Refresh(bool isSelected, RecentPlayerInviteCooldownState cooldownState, bool isAlreadyInLobby)
		{
			((TMP_Text)_button.labelTMP).text = BuildLabel(_player, isSelected, cooldownState, isAlreadyInLobby);
		}

		private static string BuildLabel(RecentPlayerRecord player, bool isSelected, RecentPlayerInviteCooldownState cooldownState, bool isAlreadyInLobby)
		{
			if (cooldownState.IsActive)
			{
				return "<color=#3F3F3F>" + player.DisplayName + " " + cooldownState.Suffix + "</color>";
			}
			if (isAlreadyInLobby)
			{
				return "<color=#FFD966>" + player.DisplayName + "</color> <color=#FFD966>✓</color>";
			}
			string text = (isSelected ? "#FFFFFF" : "#8F8F8F");
			return "<color=" + text + ">" + player.DisplayName + "</color>";
		}

		private static void ApplyLayout(REPOButton button, float rowWidth)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_0048: 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_0054: 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_007c: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Max(40f, button.GetLabelSize().y + 8f);
			button.overrideButtonSize = new Vector2(rowWidth, num);
			RectTransform rectTransform = ((TMP_Text)button.labelTMP).rectTransform;
			Vector3 localPosition = ((Transform)rectTransform).localPosition;
			((Transform)rectTransform).localPosition = new Vector3(0f, localPosition.y, localPosition.z);
			rectTransform.sizeDelta = new Vector2(Mathf.Max(120f, rowWidth - 24f), rectTransform.sizeDelta.y);
		}

		private static void StyleButton(REPOButton button)
		{
			TextMeshProUGUI labelTMP = button.labelTMP;
			((TMP_Text)labelTMP).fontStyle = (FontStyles)0;
			((TMP_Text)labelTMP).fontSize = 28f;
			((TMP_Text)labelTMP).alignment = (TextAlignmentOptions)513;
			((TMP_Text)labelTMP).enableWordWrapping = false;
			((Graphic)labelTMP).raycastTarget = false;
		}
	}
	internal sealed class RecentPlayersInviteService
	{
		private readonly ManualLogSource _logger;

		internal RecentPlayersInviteService(ManualLogSource logger)
		{
			_logger = logger;
		}

		internal List<RecentPlayerRecord> InvitePlayers(IEnumerable<RecentPlayerRecord> players)
		{
			List<RecentPlayerRecord> list = new List<RecentPlayerRecord>();
			HashSet<ulong> hashSet = (from friend in SteamFriends.GetFriends()
				select SteamId.op_Implicit(friend.Id)).ToHashSet();
			foreach (RecentPlayerRecord player in players)
			{
				ulong result;
				bool isFriend = ulong.TryParse(player.SteamId, out result) && hashSet.Contains(result);
				if (TryInvitePlayer(player, isFriend, out var message))
				{
					list.Add(player);
				}
				else
				{
					_logger.LogWarning((object)("Invite action failed for " + player.DisplayName + ": " + message));
				}
			}
			return list;
		}

		private bool TryInvitePlayer(RecentPlayerRecord player, bool isFriend, out string message)
		{
			//IL_0189: 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_00e5: 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_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			message = string.Empty;
			if (!ulong.TryParse(player.SteamId, out var result))
			{
				message = $"{player.DisplayName} has no valid Steam ID yet. steamId=[{player.SteamId}] isFriend=[{isFriend}]";
				return false;
			}
			SteamManager instance = SteamManager.instance;
			if ((Object)(object)instance == (Object)null)
			{
				message = $"SteamManager is not ready yet. isFriend=[{isFriend}]";
				return false;
			}
			FieldInfo field = typeof(SteamManager).GetField("currentLobby", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field == null)
			{
				message = $"Couldn't find current lobby field. isFriend=[{isFriend}]";
				return false;
			}
			object value = field.GetValue(instance);
			if (value == null)
			{
				message = $"No active Steam lobby found. isFriend=[{isFriend}]";
				return false;
			}
			Lobby val = (Lobby)value;
			SteamId id = ((Lobby)(ref val)).Id;
			if (!((SteamId)(ref id)).IsValid)
			{
				message = $"No active Steam lobby found. isFriend=[{isFriend}]";
				return false;
			}
			SteamId val2 = SteamId.op_Implicit(result);
			bool flag = false;
			try
			{
				flag = ((Lobby)(ref val)).InviteFriend(val2);
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)$"InviteFriend threw for {player.DisplayName}: steamId=[{player.SteamId}] isFriend=[{isFriend}] error=[{ex.Message}]");
			}
			if (!flag)
			{
				try
				{
					SteamFriends.OpenUserOverlay(val2, "steamid");
					message = $"InviteFriend returned false; opened Steam overlay for {player.DisplayName}. steamId=[{player.SteamId}] isFriend=[{isFriend}]";
					return true;
				}
				catch (Exception ex2)
				{
					_logger.LogWarning((object)$"OpenUserOverlay fallback failed for {player.DisplayName}: steamId=[{player.SteamId}] isFriend=[{isFriend}] error=[{ex2.Message}]");
					return false;
				}
			}
			message = $"Steam lobby invite sent to {player.DisplayName}. steamId=[{player.SteamId}] isFriend=[{isFriend}] inviteFriend=[true]";
			return true;
		}
	}
	internal sealed class RecentPlayersPanel
	{
		private static readonly MethodInfo OpenWebOverlayMethod = typeof(SteamFriends).GetMethod("OpenWebOverlay", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(string) }, null);

		private readonly RecentPlayersStore _store;

		private readonly RecentPlayersTracker _tracker;

		private readonly RecentPlayersInviteService _inviteService;

		private readonly ManualLogSource _logger;

		private static readonly FieldInfo JoiningPlayersCanvasGroupField = typeof(MenuPageLobby).GetField("joiningPlayersCanvasGroup", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		private REPOPopupPage _page;

		private REPOButton _lobbyButton;

		private REPOButton _selectAllButton;

		private REPOButton _inviteButton;

		private InviteSentLobbyBanner _inviteSentBanner;

		private readonly Dictionary<string, RecentPlayerRow> _playerRows = new Dictionary<string, RecentPlayerRow>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, RecentPlayerRecord> _visiblePlayersByKey = new Dictionary<string, RecentPlayerRecord>(StringComparer.OrdinalIgnoreCase);

		private readonly HashSet<string> _selectedPlayerKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, float> _inviteCooldownUntilByPlayerKey = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);

		private float _inviteStatusUntil;

		private bool _pageOpen;

		private string _pendingClickPlayerKey;

		private float _pendingClickAt;

		private int _lastLobbyStateVersion = -1;

		private int _lastCooldownVisualFrame = -1;

		private const float DoubleClickThreshold = 0.32f;

		private const float InviteCooldownSeconds = 10f;

		internal RecentPlayersPanel(RecentPlayersStore store, RecentPlayersTracker tracker, RecentPlayersInviteService inviteService, ManualLogSource logger)
		{
			_store = store;
			_tracker = tracker;
			_inviteService = inviteService;
			_logger = logger;
		}

		internal void Tick()
		{
			UpdateLobbyButtonPosition();
			_inviteSentBanner?.Tick();
			bool flag = CleanupExpiredInviteCooldowns();
			if (_pageOpen)
			{
				bool flag2 = _lastLobbyStateVersion != _tracker.CurrentLobbyStateVersion;
				int cooldownVisualFrame = GetCooldownVisualFrame();
				bool flag3 = _inviteCooldownUntilByPlayerKey.Count > 0 && cooldownVisualFrame != _lastCooldownVisualFrame;
				if (flag2 || flag || flag3)
				{
					RefreshVisibleSelectionState();
				}
				_lastLobbyStateVersion = _tracker.CurrentLobbyStateVersion;
				_lastCooldownVisualFrame = cooldownVisualFrame;
			}
			if (_inviteStatusUntil > 0f && Time.unscaledTime >= _inviteStatusUntil)
			{
				_inviteStatusUntil = 0f;
				UpdateActionButtons();
			}
		}

		internal void BuildLobbyButton(Transform parent)
		{
			//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_001a: Unknown result type (might be due to invalid IL or missing references)
			Vector2 lobbyButtonPosition = Plugin.GetLobbyButtonPosition();
			_lobbyButton = MenuAPI.CreateREPOButton("Recent Players", (Action)TogglePage, parent, lobbyButtonPosition);
		}

		private void TogglePage()
		{
			EnsurePage();
			if (_pageOpen)
			{
				_page.ClosePage(false);
				_pageOpen = false;
				ResetTransientSelectionState();
			}
			else
			{
				Rebuild();
				_page.OpenPage(true);
				((Component)_page).GetComponent<MenuPage>().PageStateSet((PageState)1);
				_pageOpen = true;
			}
		}

		private void UpdateLobbyButtonPosition()
		{
			//IL_0013: 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_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: 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_0046: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_lobbyButton == (Object)null))
			{
				Vector2 lobbyButtonPosition = Plugin.GetLobbyButtonPosition();
				if (!(((Transform)((REPOElement)_lobbyButton).rectTransform).localPosition == Vector2.op_Implicit(lobbyButtonPosition)))
				{
					((Transform)((REPOElement)_lobbyButton).rectTransform).localPosition = Vector2.op_Implicit(lobbyButtonPosition);
				}
			}
		}

		private void EnsurePage()
		{
			//IL_002a: 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_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			if (!((Object)(object)_page != (Object)null))
			{
				_page = MenuAPI.CreateREPOPopupPage("Recent Players", true, false, 0f, (Vector2?)new Vector2(40f, 0f));
				_page.maskPadding = new Padding(0f, 0f, 0f, 20f);
				_page.onEscapePressed = (ShouldCloseMenuDelegate)delegate
				{
					_pageOpen = false;
					ResetTransientSelectionState();
					return true;
				};
				EnsureActionButtons();
			}
		}

		private void Rebuild()
		{
			//IL_00da: 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_00b7: Expected O, but got Unknown
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Expected O, but got Unknown
			if ((Object)(object)_page == (Object)null)
			{
				return;
			}
			Transform scroller = (Transform)(object)_page.menuScrollBox.scroller;
			for (int num = scroller.childCount - 1; num >= 2; num--)
			{
				Object.Destroy((Object)(object)((Component)scroller.GetChild(num)).gameObject);
			}
			_playerRows.Clear();
			_visiblePlayersByKey.Clear();
			List<RecentPlayerRecord> list = BuildDisplayPlayers();
			if (list.Count == 0)
			{
				_page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
				{
					//IL_0028: Unknown result type (might be due to invalid IL or missing references)
					//IL_002e: 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_0050: 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_006f: 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)
					REPOButton val = MenuAPI.CreateREPOButton("Nobody here yet :(", (Action)delegate
					{
					}, scrollView, default(Vector2));
					RectTransform rectTransform = ((REPOElement)val).rectTransform;
					Rect rect = _page.maskRectTransform.rect;
					((Transform)rectTransform).localPosition = Vector2.op_Implicit(new Vector2((((Rect)(ref rect)).width - val.GetLabelSize().x) * 0.5f, 0f));
					return ((REPOElement)val).rectTransform;
				}, 0f, 12f);
				_page.scrollView.UpdateElements();
				return;
			}
			float rowWidth = _page.maskRectTransform.sizeDelta.x - 16f;
			foreach (RecentPlayerRecord player in list)
			{
				_page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform scrollView)
				{
					RecentPlayerRow recentPlayerRow = RecentPlayerRow.Create(player, scrollView, rowWidth, delegate
					{
						HandleRowClick(player);
					}, delegate
					{
						InviteSinglePlayer(player);
					}, delegate
					{
						OpenPlayerProfile(player);
					});
					recentPlayerRow.Refresh(_selectedPlayerKeys.Contains(player.Key), GetInviteCooldownState(player.Key), IsPlayerAlreadyInLobby(player.Key));
					_playerRows[player.Key] = recentPlayerRow;
					_visiblePlayersByKey[player.Key] = player;
					return recentPlayerRow.Root;
				}, 0f, 8f);
			}
			_page.scrollView.UpdateElements();
			UpdateActionButtons();
		}

		private List<RecentPlayerRecord> BuildDisplayPlayers()
		{
			List<RecentPlayerRecord> list = new List<RecentPlayerRecord>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			List<RecentPlayerRecord> currentLobbyVisiblePlayers = _tracker.GetCurrentLobbyVisiblePlayers();
			foreach (RecentPlayerRecord item in currentLobbyVisiblePlayers)
			{
				if (item != null && !string.IsNullOrWhiteSpace(item.Key) && hashSet.Add(item.Key))
				{
					list.Add(item);
				}
			}
			List<RecentPlayerRecord> visiblePlayers = _store.GetVisiblePlayers(Plugin.GetRecentPlayersLimit());
			foreach (RecentPlayerRecord item2 in visiblePlayers)
			{
				if (item2 != null && !string.IsNullOrWhiteSpace(item2.Key) && hashSet.Add(item2.Key))
				{
					list.Add(item2);
				}
			}
			return list;
		}

		private void OpenPlayerProfile(RecentPlayerRecord player)
		{
			//IL_0089: 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_008f: Unknown result type (might be due to invalid IL or missing references)
			if (player == null || string.IsNullOrWhiteSpace(player.SteamId))
			{
				return;
			}
			if (!ulong.TryParse(player.SteamId, out var result) || result == 0)
			{
				_logger.LogWarning((object)("RecentPlayers profile open skipped: invalid steamId=[" + player?.SteamId + "] name=[" + player?.DisplayName + "]"));
				return;
			}
			try
			{
				SteamId val = SteamId.op_Implicit(result);
				SteamFriends.OpenUserOverlay(val, "steamid");
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("RecentPlayers profile overlay failed for [" + player.DisplayName + "] steamId=[" + player.SteamId + "] error=[" + ex.Message + "]"));
			}
		}

		private void HandleRowClick(RecentPlayerRecord player)
		{
			if (player != null && !string.IsNullOrWhiteSpace(player.Key) && IsPlayerInvitable(player.Key))
			{
				if (_pendingClickPlayerKey != null && string.Equals(_pendingClickPlayerKey, player.Key, StringComparison.OrdinalIgnoreCase) && Time.unscaledTime - _pendingClickAt <= 0.32f)
				{
					ClearPendingClick();
					InviteSinglePlayer(player);
				}
				else
				{
					_pendingClickPlayerKey = player.Key;
					_pendingClickAt = Time.unscaledTime;
					TogglePlayerSelection(player);
				}
			}
		}

		private void TogglePlayerSelection(RecentPlayerRecord player)
		{
			if (!IsPlayerInvitable(player.Key))
			{
				_selectedPlayerKeys.Remove(player.Key);
				RefreshVisibleSelectionState();
				return;
			}
			if (_selectedPlayerKeys.Contains(player.Key))
			{
				_selectedPlayerKeys.Remove(player.Key);
			}
			else
			{
				_selectedPlayerKeys.Add(player.Key);
			}
			RefreshVisibleSelectionState();
		}

		private void SelectAllPlayers()
		{
			List<RecentPlayerRecord> list = _visiblePlayersByKey.Values.Where((RecentPlayerRecord player) => IsPlayerInvitable(player.Key)).ToList();
			if (list.Count > 0 && list.All((RecentPlayerRecord player) => _selectedPlayerKeys.Contains(player.Key)))
			{
				foreach (RecentPlayerRecord item in list)
				{
					_selectedPlayerKeys.Remove(item.Key);
				}
			}
			else
			{
				foreach (RecentPlayerRecord item2 in list)
				{
					_selectedPlayerKeys.Add(item2.Key);
				}
			}
			RefreshVisibleSelectionState();
		}

		private void InviteSelectedPlayers()
		{
			ClearPendingClick();
			List<RecentPlayerRecord> list = _visiblePlayersByKey.Values.Where((RecentPlayerRecord player) => _selectedPlayerKeys.Contains(player.Key) && IsPlayerInvitable(player.Key)).OrderBy<RecentPlayerRecord, string>((RecentPlayerRecord player) => player.DisplayName, StringComparer.OrdinalIgnoreCase).ToList();
			if (list.Count != 0)
			{
				List<RecentPlayerRecord> list2 = _inviteService.InvitePlayers(list);
				if (list2.Count > 0)
				{
					ApplyInviteCooldown(list2);
					ShowInviteSentBanner(list2.Count);
					_inviteStatusUntil = Time.unscaledTime + 3f;
					UpdateActionButtons();
				}
			}
		}

		private void InviteSinglePlayer(RecentPlayerRecord player)
		{
			if (player != null && IsPlayerInvitable(player.Key))
			{
				List<RecentPlayerRecord> list = new List<RecentPlayerRecord>(1);
				list.Add(player);
				List<RecentPlayerRecord> list2 = _inviteService.InvitePlayers(list);
				if (list2 != null && list2.Count > 0)
				{
					ApplyInviteCooldown(list2);
					ShowInviteSentBanner(list2.Count);
					_inviteStatusUntil = Time.unscaledTime + 3f;
					UpdateActionButtons();
				}
			}
		}

		private void EnsureActionButtons()
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Expected O, but got Unknown
			if ((Object)(object)_page == (Object)null)
			{
				return;
			}
			if ((Object)(object)_inviteButton == (Object)null)
			{
				_page.AddElement((BuilderDelegate)delegate(Transform parent)
				{
					//IL_001e: Unknown result type (might be due to invalid IL or missing references)
					_inviteButton = MenuAPI.CreateREPOButton("Invite", (Action)InviteSelectedPlayers, parent, new Vector2(370f, 18f));
				});
			}
			if ((Object)(object)_selectAllButton == (Object)null)
			{
				_page.AddElement((BuilderDelegate)delegate(Transform parent)
				{
					//IL_001e: Unknown result type (might be due to invalid IL or missing references)
					_selectAllButton = MenuAPI.CreateREPOButton("Select All", (Action)SelectAllPlayers, parent, new Vector2(570f, 18f));
				});
			}
		}

		private void RefreshVisibleSelectionState()
		{
			CleanupNonInvitableSelections();
			foreach (KeyValuePair<string, RecentPlayerRow> playerRow in _playerRows)
			{
				playerRow.Deconstruct(out var key, out var value);
				string text = key;
				RecentPlayerRow recentPlayerRow = value;
				bool isSelected = _selectedPlayerKeys.Contains(text);
				recentPlayerRow.Refresh(isSelected, GetInviteCooldownState(text), IsPlayerAlreadyInLobby(text));
			}
			UpdateActionButtons();
		}

		private void ClearPendingClick()
		{
			_pendingClickPlayerKey = null;
			_pendingClickAt = 0f;
		}

		private void ResetTransientSelectionState()
		{
			ClearPendingClick();
			if (_selectedPlayerKeys.Count != 0)
			{
				_selectedPlayerKeys.Clear();
				RefreshVisibleSelectionState();
			}
		}

		private void EnsureInviteSentBanner()
		{
			if (_inviteSentBanner != null && _inviteSentBanner.IsValid)
			{
				return;
			}
			MenuPageLobby instance = MenuPageLobby.instance;
			if ((Object)(object)instance == (Object)null || JoiningPlayersCanvasGroupField == null)
			{
				return;
			}
			object value = JoiningPlayersCanvasGroupField.GetValue(instance);
			CanvasGroup val = (CanvasGroup)((value is CanvasGroup) ? value : null);
			if (val == null || (Object)(object)val == (Object)null)
			{
				return;
			}
			GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)val).transform.parent);
			((Object)val2).name = "Invite Sent Banner";
			CanvasGroup component = val2.GetComponent<CanvasGroup>();
			if (!((Object)(object)component == (Object)null))
			{
				TextMeshProUGUI componentInChildren = ((Component)component).GetComponentInChildren<TextMeshProUGUI>(true);
				if (!((Object)(object)componentInChildren == (Object)null))
				{
					((TMP_Text)componentInChildren).text = "INVITE SENT";
					_inviteSentBanner = new InviteSentLobbyBanner(component);
				}
			}
		}

		private void ShowInviteSentBanner(int inviteCount)
		{
			EnsureInviteSentBanner();
			if (inviteCount > 0 && _inviteSentBanner != null && _inviteSentBanner.IsValid)
			{
				_inviteSentBanner.Show(inviteCount);
			}
		}

		private void UpdateActionButtons()
		{
			if ((Object)(object)_selectAllButton?.labelTMP != (Object)null)
			{
				((TMP_Text)_selectAllButton.labelTMP).text = "SELECT ALL";
			}
			if ((Object)(object)_inviteButton?.labelTMP != (Object)null)
			{
				int num = _selectedPlayerKeys.Count(IsPlayerInvitable);
				if (_inviteStatusUntil > Time.unscaledTime)
				{
					((TMP_Text)_inviteButton.labelTMP).text = "INVITED";
				}
				else if (num > 0)
				{
					((TMP_Text)_inviteButton.labelTMP).text = $"<color=#FFFFFF>INVITE [{num}]</color>";
				}
				else
				{
					((TMP_Text)_inviteButton.labelTMP).text = "Invite";
				}
			}
		}

		private void ApplyInviteCooldown(IEnumerable<RecentPlayerRecord> invitedPlayers)
		{
			float value = Time.unscaledTime + 10f;
			foreach (RecentPlayerRecord invitedPlayer in invitedPlayers)
			{
				if (invitedPlayer != null && !string.IsNullOrWhiteSpace(invitedPlayer.Key))
				{
					_inviteCooldownUntilByPlayerKey[invitedPlayer.Key] = value;
					_selectedPlayerKeys.Remove(invitedPlayer.Key);
				}
			}
			RefreshVisibleSelectionState();
		}

		private bool IsInviteCooldownActive(string playerKey)
		{
			float value;
			return !string.IsNullOrWhiteSpace(playerKey) && _inviteCooldownUntilByPlayerKey.TryGetValue(playerKey, out value) && value > Time.unscaledTime;
		}

		private bool IsPlayerAlreadyInLobby(string playerKey)
		{
			return _tracker != null && _tracker.IsPlayerInCurrentLobby(playerKey);
		}

		private bool IsPlayerInvitable(string playerKey)
		{
			return !string.IsNullOrWhiteSpace(playerKey) && !IsInviteCooldownActive(playerKey) && !IsPlayerAlreadyInLobby(playerKey);
		}

		private void CleanupNonInvitableSelections()
		{
			string[] array = _selectedPlayerKeys.Where((string key) => !IsPlayerInvitable(key)).ToArray();
			string[] array2 = array;
			foreach (string item in array2)
			{
				_selectedPlayerKeys.Remove(item);
			}
		}

		private bool CleanupExpiredInviteCooldowns()
		{
			if (_inviteCooldownUntilByPlayerKey.Count == 0)
			{
				return false;
			}
			string[] array = (from entry in _inviteCooldownUntilByPlayerKey
				where entry.Value <= Time.unscaledTime
				select entry.Key).ToArray();
			string[] array2 = array;
			foreach (string key in array2)
			{
				_inviteCooldownUntilByPlayerKey.Remove(key);
			}
			return array.Length != 0;
		}

		private RecentPlayerInviteCooldownState GetInviteCooldownState(string playerKey)
		{
			if (!IsInviteCooldownActive(playerKey))
			{
				return RecentPlayerInviteCooldownState.None;
			}
			float num = _inviteCooldownUntilByPlayerKey[playerKey] - Time.unscaledTime;
			int num2 = Mathf.Abs(Mathf.FloorToInt(num * 9f)) % 5;
			if (1 == 0)
			{
			}
			RecentPlayerInviteCooldownState result = num2 switch
			{
				0 => new RecentPlayerInviteCooldownState(isActive: true, "•··"), 
				1 => new RecentPlayerInviteCooldownState(isActive: true, "·•·"), 
				2 => new RecentPlayerInviteCooldownState(isActive: true, "··•"), 
				3 => new RecentPlayerInviteCooldownState(isActive: true, "·•·"), 
				_ => new RecentPlayerInviteCooldownState(isActive: true, "•··"), 
			};
			if (1 == 0)
			{
			}
			return result;
		}

		private int GetCooldownVisualFrame()
		{
			if (_inviteCooldownUntilByPlayerKey.Count == 0)
			{
				return -1;
			}
			return Mathf.FloorToInt(Time.unscaledTime * 9f);
		}
	}
	internal sealed class RecentPlayersStore
	{
		private readonly string _storagePath;

		private readonly ManualLogSource _logger;

		internal Dictionary<string, RecentPlayerRecord> Records { get; } = new Dictionary<string, RecentPlayerRecord>(StringComparer.OrdinalIgnoreCase);


		internal RecentPlayersStore(string storagePath, ManualLogSource logger)
		{
			_storagePath = storagePath;
			_logger = logger;
		}

		internal void Load()
		{
			try
			{
				if (!File.Exists(_storagePath))
				{
					return;
				}
				string text = File.ReadAllText(_storagePath);
				if (string.IsNullOrWhiteSpace(text) || text.Trim() == "{}")
				{
					return;
				}
				if (text.TrimStart().StartsWith("{"))
				{
					RecentPlayersSaveData recentPlayersSaveData = JsonUtility.FromJson<RecentPlayersSaveData>(text);
					if (recentPlayersSaveData?.Players == null)
					{
						return;
					}
					RecentPlayerRecord[] players = recentPlayersSaveData.Players;
					foreach (RecentPlayerRecord recentPlayerRecord in players)
					{
						if (recentPlayerRecord != null && !string.IsNullOrWhiteSpace(recentPlayerRecord.Key))
						{
							Records[recentPlayerRecord.Key] = recentPlayerRecord;
						}
					}
					return;
				}
				string[] array = text.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
				foreach (string text2 in array)
				{
					string[] array2 = text2.Split('\t');
					if (array2.Length >= 5)
					{
						long result;
						RecentPlayerRecord recentPlayerRecord2 = new RecentPlayerRecord
						{
							Key = Unescape(array2[0]),
							UserId = Unescape(array2[1]),
							DisplayName = Unescape(array2[2]),
							SteamId = Unescape(array2[3]),
							LastSeenUtcTicks = (long.TryParse(array2[4], out result) ? result : 0)
						};
						if (!string.IsNullOrWhiteSpace(recentPlayerRecord2.Key))
						{
							Records[recentPlayerRecord2.Key] = recentPlayerRecord2;
						}
					}
				}
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("Failed to load recent players: " + ex.Message));
			}
		}

		internal void Save()
		{
			try
			{
				RecentPlayerRecord[] array = Records.Values.OrderByDescending((RecentPlayerRecord p) => p.LastSeenUtcTicks).Take(64).ToArray();
				StringBuilder stringBuilder = new StringBuilder();
				RecentPlayerRecord[] array2 = array;
				foreach (RecentPlayerRecord recentPlayerRecord in array2)
				{
					stringBuilder.Append(Escape(recentPlayerRecord.Key)).Append('\t').Append(Escape(recentPlayerRecord.UserId))
						.Append('\t')
						.Append(Escape(recentPlayerRecord.DisplayName))
						.Append('\t')
						.Append(Escape(recentPlayerRecord.SteamId))
						.Append('\t')
						.Append(recentPlayerRecord.LastSeenUtcTicks)
						.Append('\n');
				}
				File.WriteAllText(_storagePath, stringBuilder.ToString());
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("Failed to save recent players: " + ex.Message));
			}
		}

		internal void RemoveSeededOrInvalidRecords()
		{
			string[] array = (from player in Records.Values
				where player == null || string.IsNullOrWhiteSpace(player.Key) || player.LastSeenUtcTicks <= 0
				select player?.Key into key
				where !string.IsNullOrWhiteSpace(key)
				select key).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray();
			if (array.Length != 0)
			{
				string[] array2 = array;
				foreach (string key2 in array2)
				{
					Records.Remove(key2);
				}
				Save();
			}
		}

		internal bool UpsertObservedPlayer(string key, string userId, string displayName, string steamId, long nowTicks)
		{
			RecentPlayerRecord recentPlayerRecord = FindExistingRecord(key, userId, steamId);
			bool result = false;
			if (recentPlayerRecord != null)
			{
				if (!string.Equals(recentPlayerRecord.Key, key, StringComparison.OrdinalIgnoreCase))
				{
					Records.Remove(recentPlayerRecord.Key);
					recentPlayerRecord.Key = key;
					result = true;
				}
				if (!string.Equals(recentPlayerRecord.UserId, userId ?? string.Empty, StringComparison.Ordinal))
				{
					recentPlayerRecord.UserId = userId ?? string.Empty;
					result = true;
				}
				if (!string.Equals(recentPlayerRecord.DisplayName, displayName, StringComparison.Ordinal))
				{
					recentPlayerRecord.DisplayName = displayName;
					result = true;
				}
				if (recentPlayerRecord.LastSeenUtcTicks != nowTicks)
				{
					recentPlayerRecord.LastSeenUtcTicks = nowTicks;
					result = true;
				}
				if (!string.IsNullOrWhiteSpace(steamId) && !string.Equals(recentPlayerRecord.SteamId, steamId, StringComparison.Ordinal))
				{
					recentPlayerRecord.SteamId = steamId;
					result = true;
				}
				Records[key] = recentPlayerRecord;
				return result;
			}
			Records[key] = new RecentPlayerRecord
			{
				Key = key,
				UserId = (userId ?? string.Empty),
				DisplayName = displayName,
				SteamId = (steamId ?? string.Empty),
				LastSeenUtcTicks = nowTicks
			};
			return true;
		}

		internal List<RecentPlayerRecord> GetVisiblePlayers(int limit = 24)
		{
			List<RecentPlayerRecord> list = (from p in Records.Values.Where(Plugin.ShouldKeepPlayerVisible)
				orderby p.LastSeenUtcTicks descending
				select p).ThenBy<RecentPlayerRecord, string>((RecentPlayerRecord p) => p.DisplayName, StringComparer.OrdinalIgnoreCase).ToList();
			if (Plugin.IsCreatorForcedVisible())
			{
				list.RemoveAll((RecentPlayerRecord player) => string.Equals(player.Key, "steam:76561198257806281", StringComparison.OrdinalIgnoreCase));
				RecentPlayerRecord recentPlayerRecord = new RecentPlayerRecord
				{
					Key = "steam:76561198257806281",
					UserId = "76561198257806281",
					DisplayName = "disabro",
					SteamId = "76561198257806281",
					LastSeenUtcTicks = 0L
				};
				if (limit <= 1)
				{
					return new List<RecentPlayerRecord> { recentPlayerRecord };
				}
				return list.Take(Math.Max(0, limit - 1)).Append(recentPlayerRecord).ToList();
			}
			return list.Take(limit).ToList();
		}

		internal void RemoveNonFriendRecordsIfNeeded()
		{
			if (Plugin.SaveNonFriends())
			{
				return;
			}
			string[] array = (from player in Records.Values
				where player != null && !string.IsNullOrWhiteSpace(player.Key) && !string.Equals(player.SteamId, "76561198257806281", StringComparison.Ordinal) && !Plugin.IsSteamFriend(player.SteamId)
				select player.Key).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray();
			if (array.Length != 0)
			{
				string[] array2 = array;
				foreach (string key in array2)
				{
					Records.Remove(key);
				}
				Save();
			}
		}

		private RecentPlayerRecord FindExistingRecord(string key, string userId, string steamId)
		{
			if (!string.IsNullOrWhiteSpace(key) && Records.TryGetValue(key, out var value))
			{
				return value;
			}
			if (!string.IsNullOrWhiteSpace(steamId))
			{
				RecentPlayerRecord recentPlayerRecord = Records.Values.FirstOrDefault((RecentPlayerRecord player) => string.Equals(player?.SteamId, steamId, StringComparison.Ordinal));
				if (recentPlayerRecord != null)
				{
					return recentPlayerRecord;
				}
			}
			if (!string.IsNullOrWhiteSpace(userId))
			{
				RecentPlayerRecord recentPlayerRecord2 = Records.Values.FirstOrDefault((RecentPlayerRecord player) => string.Equals(player?.UserId, userId, StringComparison.Ordinal));
				if (recentPlayerRecord2 != null)
				{
					return recentPlayerRecord2;
				}
			}
			return null;
		}

		private static string Escape(string value)
		{
			return Uri.EscapeDataString(value ?? string.Empty);
		}

		private static string Unescape(string value)
		{
			return Uri.UnescapeDataString(value ?? string.Empty);
		}
	}
	internal sealed class RecentPlayersTracker
	{
		private const float RefreshObservedPlayersSeconds = 1f;

		private readonly RecentPlayersStore _store;

		private readonly ManualLogSource _logger;

		private readonly FieldInfo _currentLobbyField;

		private readonly HashSet<string> _currentLobbyPlayerKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, RecentPlayerRecord> _pendingLobbyPlayers = new Dictionary<string, RecentPlayerRecord>(StringComparer.OrdinalIgnoreCase);

		private string _activeLobbyId;

		private float _nextRefreshAt;

		private bool _loggedMissingLobbyOnce;

		private int _currentLobbyStateVersion;

		internal IReadOnlyCollection<string> CurrentLobbyPlayerKeys => _currentLobbyPlayerKeys;

		internal int CurrentLobbyStateVersion => _currentLobbyStateVersion;

		internal RecentPlayersTracker(RecentPlayersStore store, ManualLogSource logger)
		{
			_store = store;
			_logger = logger;
			_currentLobbyField = typeof(SteamManager).GetField("currentLobby", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		}

		internal List<RecentPlayerRecord> GetCurrentLobbyVisiblePlayers()
		{
			return _pendingLobbyPlayers.Values.Where((RecentPlayerRecord player) => player != null && Plugin.ShouldKeepPlayerVisible(player)).OrderBy<RecentPlayerRecord, string>((RecentPlayerRecord player) => player.DisplayName, StringComparer.OrdinalIgnoreCase).ToList();
		}

		internal bool IsPlayerInCurrentLobby(string playerKey)
		{
			return !string.IsNullOrWhiteSpace(playerKey) && _currentLobbyPlayerKeys.Contains(playerKey);
		}

		internal void Tick()
		{
			if (!(Time.unscaledTime < _nextRefreshAt))
			{
				_nextRefreshAt = Time.unscaledTime + 1f;
				ObserveCurrentLobbyMembers();
			}
		}

		internal void FlushPendingPlayers()
		{
			CommitPendingLobbyPlayers();
		}

		private void ObserveCurrentLobbyMembers()
		{
			//IL_00a9: 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_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: 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_0135: 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_0164: 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_0170: 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_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			SteamManager instance = SteamManager.instance;
			if ((Object)(object)instance == (Object)null)
			{
				if (!_loggedMissingLobbyOnce)
				{
					_logger.LogWarning((object)"RecentPlayers: SteamManager.instance is null.");
					_loggedMissingLobbyOnce = true;
				}
				return;
			}
			if (_currentLobbyField == null)
			{
				if (!_loggedMissingLobbyOnce)
				{
					_logger.LogWarning((object)"RecentPlayers: could not resolve SteamManager.currentLobby field.");
					_loggedMissingLobbyOnce = true;
				}
				return;
			}
			object value = _currentLobbyField.GetValue(instance);
			if (value == null)
			{
				HandleLobbyEnded();
				return;
			}
			Lobby val = (Lobby)value;
			SteamId id = ((Lobby)(ref val)).Id;
			if (!((SteamId)(ref id)).IsValid)
			{
				HandleLobbyEnded();
				_loggedMissingLobbyOnce = true;
				return;
			}
			_loggedMissingLobbyOnce = false;
			id = ((Lobby)(ref val)).Id;
			string text = ((object)(SteamId)(ref id)).ToString();
			if (!string.Equals(_activeLobbyId, text, StringComparison.Ordinal))
			{
				CommitPendingLobbyPlayers();
				_activeLobbyId = text;
			}
			long ticks = DateTime.UtcNow.Ticks;
			SteamId steamId = SteamClient.SteamId;
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			bool flag = false;
			foreach (Friend member in ((Lobby)(ref val)).Members)
			{
				Friend current = member;
				if (SteamId.op_Implicit(current.Id) == SteamId.op_Implicit(steamId))
				{
					continue;
				}
				id = current.Id;
				string text2 = ((object)(SteamId)(ref id)).ToString();
				if (string.IsNullOrWhiteSpace(text2))
				{
					continue;
				}
				string displayName = (string.IsNullOrWhiteSpace(((Friend)(ref current)).Name) ? text2 : ((Friend)(ref current)).Name.Trim());
				string playerKey = GetPlayerKey(text2);
				hashSet.Add(playerKey);
				if (Plugin.ShouldSaveObservedPlayer(text2))
				{
					_pendingLobbyPlayers[playerKey] = new RecentPlayerRecord
					{
						Key = playerKey,
						UserId = text2,
						DisplayName = displayName,
						SteamId = text2,
						LastSeenUtcTicks = ticks
					};
					if (_store.UpsertObservedPlayer(playerKey, text2, displayName, text2, ticks))
					{
						flag = true;
					}
				}
			}
			if (flag)
			{
				_store.RemoveNonFriendRecordsIfNeeded();
				_store.Save();
			}
			CommitPlayersWhoLeftCurrentLobby(hashSet);
			SetCurrentLobbyKeys(hashSet);
		}

		private void HandleLobbyEnded()
		{
			SetCurrentLobbyKeys(Array.Empty<string>());
			CommitPendingLobbyPlayers();
			_activeLobbyId = null;
		}

		private void CommitPendingLobbyPlayers()
		{
			if (_pendingLobbyPlayers.Count == 0)
			{
				return;
			}
			bool flag = false;
			foreach (RecentPlayerRecord value in _pendingLobbyPlayers.Values)
			{
				if (value != null && Plugin.ShouldSaveObservedPlayer(value.SteamId) && _store.UpsertObservedPlayer(value.Key, value.UserId, value.DisplayName, value.SteamId, value.LastSeenUtcTicks))
				{
					flag = true;
				}
			}
			_pendingLobbyPlayers.Clear();
			_store.RemoveNonFriendRecordsIfNeeded();
			if (flag)
			{
				_store.Save();
			}
		}

		private void CommitPlayersWhoLeftCurrentLobby(HashSet<string> currentKeys)
		{
			if (_pendingLobbyPlayers.Count == 0)
			{
				return;
			}
			string[] array = _pendingLobbyPlayers.Keys.Where((string key) => !currentKeys.Contains(key)).ToArray();
			if (array.Length == 0)
			{
				return;
			}
			bool flag = false;
			string[] array2 = array;
			foreach (string key2 in array2)
			{
				if (_pendingLobbyPlayers.TryGetValue(key2, out var value))
				{
					if (value != null && Plugin.ShouldSaveObservedPlayer(value.SteamId) && _store.UpsertObservedPlayer(value.Key, value.UserId, value.DisplayName, value.SteamId, value.LastSeenUtcTicks))
					{
						flag = true;
					}
					_pendingLobbyPlayers.Remove(key2);
				}
			}
			_store.RemoveNonFriendRecordsIfNeeded();
			if (flag)
			{
				_store.Save();
			}
		}

		private void SetCurrentLobbyKeys(IEnumerable<string> keys)
		{
			string[] array = keys?.Where((string key) => !string.IsNullOrWhiteSpace(key)).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray() ?? Array.Empty<string>();
			if (_currentLobbyPlayerKeys.Count != array.Length || !_currentLobbyPlayerKeys.SetEquals(array))
			{
				_currentLobbyPlayerKeys.Clear();
				string[] array2 = array;
				foreach (string item in array2)
				{
					_currentLobbyPlayerKeys.Add(item);
				}
				_currentLobbyStateVersion++;
			}
		}

		private static string GetPlayerKey(string steamId)
		{
			return "steam:" + steamId;
		}
	}
}