Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Invite Recent Players v1.0.0
plugins/InviteRecentPlayers/InviteRecentPlayers.dll
Decompiled 5 days agousing 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; } } }