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(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 _recentPlayersLimit; private static ConfigEntry _iHaveNoFriends; private static ConfigEntry _saveNonFriends; private static readonly HashSet CachedFriendSteamIds = new HashSet(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 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("General", "Recent Players Count", 10, new ConfigDescription("How many recent players to show.", (AcceptableValueBase)(object)new AcceptableValueRange(10, 20), Array.Empty())); _saveNonFriends = ((BaseUnityPlugin)this).Config.Bind("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("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 "" + player.DisplayName + " " + cooldownState.Suffix + ""; } if (isAlreadyInLobby) { return "" + player.DisplayName + " ✓"; } string text = (isSelected ? "#FFFFFF" : "#8F8F8F"); return "" + player.DisplayName + ""; } 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 InvitePlayers(IEnumerable players) { List list = new List(); HashSet 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 _playerRows = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _visiblePlayersByKey = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly HashSet _selectedPlayerKeys = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _inviteCooldownUntilByPlayerKey = new Dictionary(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().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 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 BuildDisplayPlayers() { List list = new List(); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); List currentLobbyVisiblePlayers = _tracker.GetCurrentLobbyVisiblePlayers(); foreach (RecentPlayerRecord item in currentLobbyVisiblePlayers) { if (item != null && !string.IsNullOrWhiteSpace(item.Key) && hashSet.Add(item.Key)) { list.Add(item); } } List 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 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 list = _visiblePlayersByKey.Values.Where((RecentPlayerRecord player) => _selectedPlayerKeys.Contains(player.Key) && IsPlayerInvitable(player.Key)).OrderBy((RecentPlayerRecord player) => player.DisplayName, StringComparer.OrdinalIgnoreCase).ToList(); if (list.Count != 0) { List 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 list = new List(1); list.Add(player); List 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 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(((Component)val).gameObject, ((Component)val).transform.parent); ((Object)val2).name = "Invite Sent Banner"; CanvasGroup component = val2.GetComponent(); if (!((Object)(object)component == (Object)null)) { TextMeshProUGUI componentInChildren = ((Component)component).GetComponentInChildren(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 = $"INVITE [{num}]"; } else { ((TMP_Text)_inviteButton.labelTMP).text = "Invite"; } } } private void ApplyInviteCooldown(IEnumerable 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 Records { get; } = new Dictionary(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(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(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 GetVisiblePlayers(int limit = 24) { List list = (from p in Records.Values.Where(Plugin.ShouldKeepPlayerVisible) orderby p.LastSeenUtcTicks descending select p).ThenBy((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 }; } 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(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 _currentLobbyPlayerKeys = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _pendingLobbyPlayers = new Dictionary(StringComparer.OrdinalIgnoreCase); private string _activeLobbyId; private float _nextRefreshAt; private bool _loggedMissingLobbyOnce; private int _currentLobbyStateVersion; internal IReadOnlyCollection 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 GetCurrentLobbyVisiblePlayers() { return _pendingLobbyPlayers.Values.Where((RecentPlayerRecord player) => player != null && Plugin.ShouldKeepPlayerVisible(player)).OrderBy((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 hashSet = new HashSet(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()); 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 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 keys) { string[] array = keys?.Where((string key) => !string.IsNullOrWhiteSpace(key)).Distinct(StringComparer.OrdinalIgnoreCase).ToArray() ?? Array.Empty(); 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; } } }