using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using HG; using JetBrains.Annotations; using RoR2.UI; using TMPro; using UnityEngine; using UnityEngine.UI; namespace RoR2.RemoteGameBrowser; public class RemoteGameBrowserController : MonoBehaviour { private class FilterInfo { public RemoteGameFilterValue value; public string token; public Func getUIValue; public Action setUIValue; public GameObject controlGameObject; public Component controlMainComponent; public LanguageTextMeshController labelController; } private class FilterManager { [MeansImplicitUse(ImplicitUseKindFlags.Assign)] private class SetupAttribute : Attribute { public RemoteGameFilterValue defaultValue; public int minValue; public int maxValue; public SetupAttribute(bool defaultValue) { this.defaultValue = defaultValue; } public SetupAttribute(int defaultValue, int minValue = int.MinValue, int maxValue = int.MaxValue) { this.defaultValue = defaultValue; this.minValue = minValue; this.maxValue = maxValue; } public SetupAttribute(string defaultValue) { this.defaultValue = defaultValue; } } public readonly RemoteGameBrowserController owner; private RectTransform currentContainer; public List allFilters = new List(); [Setup(true)] public FilterInfo showDedicatedServers; [Setup(true)] public FilterInfo showLobbies; [Setup(true)] public FilterInfo showDifficultyEasyGames; [Setup(true)] public FilterInfo showDifficultyNormalGames; [Setup(true)] public FilterInfo showDifficultyHardGames; [Setup(true)] public FilterInfo showGamesWithRuleVoting; [Setup(true)] public FilterInfo showGamesWithoutRuleVoting; [Setup(true)] public FilterInfo showPasswordedGames; [Setup(0, int.MinValue, int.MaxValue, minValue = 0, maxValue = 999)] public FilterInfo maxPing; [Setup(false)] public FilterInfo mustHavePlayers; [Setup(true)] public FilterInfo mustHaveEnoughSlots; [Setup(1, int.MinValue, int.MaxValue, minValue = 1)] public FilterInfo minMaxPlayers; [Setup(16, int.MinValue, int.MaxValue, minValue = 1)] public FilterInfo maxMaxPlayers; [Setup("")] public FilterInfo requiredTags; [Setup("")] public FilterInfo forbiddenTags; [Setup(false)] public FilterInfo showStartedGames; [Setup(true)] public FilterInfo hideIncompatibleGames; public FilterManager(RemoteGameBrowserController owner) { this.owner = owner; currentContainer = owner.filterControlContainer; GenerateFilters(); } private FilterInfo AddFilter(string token, RemoteGameFilterValue defaultValue, GameObject controlPrefab, Func getUIValue, Action setUIValue) where T : Component { FilterInfo filterInfo = new FilterInfo { token = token, value = defaultValue, controlGameObject = UnityEngine.Object.Instantiate(controlPrefab, currentContainer), getUIValue = getUIValue, setUIValue = setUIValue }; filterInfo.controlMainComponent = filterInfo.controlGameObject.transform.Find("MainControl").GetComponent(); filterInfo.labelController = filterInfo.controlGameObject.transform.Find("NameLabel").GetComponent(); filterInfo.controlGameObject.SetActive(value: true); filterInfo.setUIValue(filterInfo.controlMainComponent, defaultValue); allFilters.Add(filterInfo); return filterInfo; } private FilterInfo AddBoolFilter(string token, bool defaultValue) { return AddFilter(token, defaultValue, owner.togglePrefab, GetUIValue, SetUIValue); static RemoteGameFilterValue? GetUIValue(Component c) { return ((MPToggle)c).isOn; } static void SetUIValue(Component c, RemoteGameFilterValue value) { ((MPToggle)c).isOn = value.boolValue; } } private FilterInfo AddIntFilter(string token, int defaultValue, int minValue = int.MinValue, int maxValue = int.MaxValue, uint minDigits = 1u, uint maxDigits = uint.MaxValue) { FilterInfo filterInfo = AddFilter(token, defaultValue, owner.textFieldPrefab, GetUIValue, SetUIValue); ((TMP_InputField)filterInfo.controlMainComponent).characterValidation = TMP_InputField.CharacterValidation.Integer; return filterInfo; RemoteGameFilterValue? GetUIValue(Component c) { if (int.TryParse(((TMP_InputField)c).text, out var result)) { return Mathf.Clamp(result, minValue, maxValue); } return null; } void SetUIValue(Component c, RemoteGameFilterValue v) { StringBuilder stringBuilder = HG.StringBuilderPool.RentStringBuilder(); stringBuilder.AppendInt(v.intValue, minDigits, maxDigits); ((TMP_InputField)c).SetTextWithoutNotify(stringBuilder.ToString()); HG.StringBuilderPool.ReturnStringBuilder(stringBuilder); } } private FilterInfo AddStringFilter(string token, string defaultValue) { return AddFilter(token, defaultValue, owner.textFieldPrefab, GetUIValue, SetUIValue); static RemoteGameFilterValue? GetUIValue(Component c) { return ((TMP_InputField)c).text; } static void SetUIValue(Component c, RemoteGameFilterValue v) { ((TMP_InputField)c).SetTextWithoutNotify(v.stringValue); } } private void GenerateFilters() { Regex regex = new Regex("([A-Z]?[a-z]+)"); FieldInfo[] fields = typeof(FilterManager).GetFields(); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType != typeof(FilterInfo)) { continue; } SetupAttribute customAttribute = CustomAttributeExtensions.GetCustomAttribute(fieldInfo); if (customAttribute == null) { return; } string name = fieldInfo.Name; StringBuilder stringBuilder = HG.StringBuilderPool.RentStringBuilder(); stringBuilder.Append("GAME_BROWSER_FILTER"); foreach (Match item in regex.Matches(name)) { stringBuilder.Append("_"); for (int j = 0; j < item.Length; j++) { stringBuilder.Append(char.ToUpperInvariant(name[item.Index + j])); } } string token = stringBuilder.ToString(); HG.StringBuilderPool.ReturnStringBuilder(stringBuilder); FilterInfo filterInfo = null; filterInfo = customAttribute.defaultValue.valueType switch { RemoteGameFilterValue.ValueType.Bool => AddBoolFilter(token, customAttribute.defaultValue.boolValue), RemoteGameFilterValue.ValueType.Int => AddIntFilter(token, customAttribute.defaultValue.intValue, customAttribute.minValue, customAttribute.maxValue), RemoteGameFilterValue.ValueType.String => AddStringFilter(token, customAttribute.defaultValue.stringValue), _ => throw new ArgumentOutOfRangeException(), }; fieldInfo.SetValue(this, filterInfo); filterInfo.labelController.token = filterInfo.token; } CopyInternalValuesToUI(); } public void CopyInternalValuesToUI() { foreach (FilterInfo allFilter in allFilters) { allFilter.setUIValue(allFilter.controlMainComponent, allFilter.value); } } public void CopyUIValuesToInternal() { foreach (FilterInfo allFilter in allFilters) { allFilter.value = allFilter.getUIValue(allFilter.controlMainComponent) ?? allFilter.value; } } } public GameObject cardPrefab; public RectTransform cardContainer; public MPButton previousPageButton; public MPButton nextPageButton; public HGTextMeshProUGUI pageNumberLabel; public GameObject togglePrefab; public GameObject textFieldPrefab; public RectTransform filterControlContainer; public MPDropdown sortTypeDropdown; public MPToggle sortAscendToggle; public Graphic busyIcon; private UIElementAllocator cardAllocator; private bool displayDataDirty; private float cardPrefabHeight = 1f; private float initialRequestTime = float.PositiveInfinity; private IRemoteGameProvider primaryRemoteGameProvider; private PageRemoteGameProvider pageRemoteGameProvider; private SortRemoteGameProvider sortRemoteGameProvider; private AdvancedFilterRemoteGameProvider advancedFilterRemoteGameProvider; private AggregateRemoteGameProvider aggregateRemoteGameProvider; private SteamworksServerRemoteGameProvider serverRemoteGameProvider; private BaseAsyncRemoteGameProvider lobbyRemoteGameProvider; private FilterManager filters; private void Awake() { cardAllocator = new UIElementAllocator(cardContainer, cardPrefab); serverRemoteGameProvider = new SteamworksServerRemoteGameProvider(SteamworksServerRemoteGameProvider.Mode.Internet) { refreshOnFiltersChanged = true }; if (PlatformSystems.ShouldUseEpicOnlineSystems) { lobbyRemoteGameProvider = new EOSLobbyRemoteGameProvider(); } else { lobbyRemoteGameProvider = new SteamworksLobbyRemoteGameProvider(); } aggregateRemoteGameProvider = new AggregateRemoteGameProvider(); advancedFilterRemoteGameProvider = new AdvancedFilterRemoteGameProvider(aggregateRemoteGameProvider); sortRemoteGameProvider = new SortRemoteGameProvider(advancedFilterRemoteGameProvider); pageRemoteGameProvider = new PageRemoteGameProvider(sortRemoteGameProvider); primaryRemoteGameProvider = pageRemoteGameProvider; primaryRemoteGameProvider.onNewInfoAvailable += OnNewInfoAvailable; previousPageButton.onClick.AddListener(OnPreviousPageButtonClick); nextPageButton.onClick.AddListener(OnNextPageButtonClick); cardPrefabHeight = cardPrefab.GetComponent().preferredHeight; } private void Start() { filters = new FilterManager(this); initialRequestTime = Time.unscaledTime + 0.2f; } private void Update() { if (Input.GetKeyDown(KeyCode.F5) || initialRequestTime <= Time.unscaledTime) { initialRequestTime = float.PositiveInfinity; RequestRefresh(); } UpdateSearchFiltersInternal(); UpdateSorting(); float height = cardContainer.rect.height; float num = cardPrefabHeight; int gamesPerPage = Mathf.FloorToInt(height / num); pageRemoteGameProvider.SetGamesPerPage(gamesPerPage); previousPageButton.interactable = pageRemoteGameProvider.CanGoToPreviousPage(); nextPageButton.interactable = pageRemoteGameProvider.CanGoToNextPage(); if (displayDataDirty) { SetDisplayData(primaryRemoteGameProvider.GetKnownGames()); } UpdateBusyIcon(); } private void OnDestroy() { pageRemoteGameProvider.Dispose(); sortRemoteGameProvider.Dispose(); advancedFilterRemoteGameProvider.Dispose(); aggregateRemoteGameProvider.Dispose(); lobbyRemoteGameProvider.Dispose(); serverRemoteGameProvider.Dispose(); } private void OnEnable() { primaryRemoteGameProvider.RequestRefresh(); RebuildSortTypeDropdown(); } private void OnPreviousPageButtonClick() { pageRemoteGameProvider.GoToPreviousPage(); } private void OnNextPageButtonClick() { pageRemoteGameProvider.GoToNextPage(); } public void RequestRefresh() { primaryRemoteGameProvider.RequestRefresh(); } private void OnNewInfoAvailable() { displayDataDirty = true; } private void SetDisplayData(IList remoteGameInfos) { displayDataDirty = false; cardAllocator.AllocateElements(remoteGameInfos.Count); ReadOnlyCollection elements = cardAllocator.elements; for (int i = 0; i < elements.Count; i++) { elements[i].SetDisplayData(remoteGameInfos[i]); } if ((bool)pageNumberLabel) { StringBuilder stringBuilder = HG.StringBuilderPool.RentStringBuilder(); pageRemoteGameProvider.GetCurrentPageInfo(out var pageIndex, out var maxPages); stringBuilder.AppendInt(pageIndex + 1).Append("/").AppendInt(Math.Max(maxPages, 1)); pageNumberLabel.SetText(stringBuilder); HG.StringBuilderPool.ReturnStringBuilder(stringBuilder); } } private void UpdateSearchFiltersInternal() { filters.CopyUIValuesToInternal(); if (!PlatformSystems.ShouldUseEpicOnlineSystems) { aggregateRemoteGameProvider.SetProviderAdded(serverRemoteGameProvider, filters.showDedicatedServers.value.boolValue); SteamworksServerRemoteGameProvider.SearchFilters searchFilters = serverRemoteGameProvider.GetSearchFilters(); searchFilters.allowDedicatedServers = filters.showDedicatedServers.value.boolValue; searchFilters.allowListenServers = true; searchFilters.mustNotBeFull = filters.mustHaveEnoughSlots.value.boolValue; searchFilters.mustHavePlayers = filters.mustHavePlayers.value.boolValue; searchFilters.requiredTags = filters.requiredTags.value.stringValue; searchFilters.forbiddenTags = filters.forbiddenTags.value.stringValue; searchFilters.allowInProgressGames = filters.showStartedGames.value.boolValue; searchFilters.allowMismatchedMods = !filters.hideIncompatibleGames.value.boolValue; serverRemoteGameProvider.SetSearchFilters(searchFilters); } aggregateRemoteGameProvider.SetProviderAdded(lobbyRemoteGameProvider, filters.showLobbies.value.boolValue); BaseAsyncRemoteGameProvider.SearchFilters searchFilters2 = lobbyRemoteGameProvider.GetSearchFilters(); searchFilters2.allowMismatchedMods = !filters.hideIncompatibleGames.value.boolValue; lobbyRemoteGameProvider.SetSearchFilters(searchFilters2); int requiredSlots = 0; if (!filters.mustHaveEnoughSlots.value.boolValue && PlatformSystems.lobbyManager.calculatedTotalPlayerCount > 0) { requiredSlots = PlatformSystems.lobbyManager.calculatedTotalPlayerCount; } AdvancedFilterRemoteGameProvider.SearchFilters searchFilters3 = advancedFilterRemoteGameProvider.GetSearchFilters(); searchFilters3.allowPassword = filters.showPasswordedGames.value.boolValue; searchFilters3.minMaxPlayers = filters.minMaxPlayers.value.intValue; searchFilters3.maxMaxPlayers = filters.maxMaxPlayers.value.intValue; searchFilters3.maxPing = filters.maxPing.value.intValue; searchFilters3.requiredSlots = requiredSlots; searchFilters3.allowDifficultyEasy = filters.showDifficultyEasyGames.value.boolValue; searchFilters3.allowDifficultyNormal = filters.showDifficultyNormalGames.value.boolValue; searchFilters3.allowDifficultyHard = filters.showDifficultyHardGames.value.boolValue; searchFilters3.showGamesWithRuleVoting = filters.showGamesWithRuleVoting.value.boolValue; searchFilters3.showGamesWithoutRuleVoting = filters.showGamesWithoutRuleVoting.value.boolValue; searchFilters3.allowInProgressGames = filters.showStartedGames.value.boolValue; advancedFilterRemoteGameProvider.SetSearchFilters(searchFilters3); } private void RebuildSortTypeDropdown() { List list = CollectionPool>.RentCollection(); sortTypeDropdown.ClearOptions(); for (int i = 0; i < SortRemoteGameProvider.sorters.Length; i++) { list.Add(Language.GetString(SortRemoteGameProvider.sorters[i].nameToken)); } sortTypeDropdown.AddOptions(list); SortRemoteGameProvider.Parameters parameters = sortRemoteGameProvider.GetParameters(); sortTypeDropdown.SetValueWithoutNotify(parameters.sorterIndex); CollectionPool>.ReturnCollection(list); } private void UpdateSorting() { SortRemoteGameProvider.Parameters parameters = sortRemoteGameProvider.GetParameters(); parameters.ascending = sortAscendToggle.isOn; parameters.sorterIndex = sortTypeDropdown.value; sortRemoteGameProvider.SetParameters(parameters); } private void UpdateBusyIcon() { if ((bool)busyIcon) { Color color = busyIcon.color; float num = 1f; if (!primaryRemoteGameProvider.IsBusy()) { num = color.a - Time.unscaledDeltaTime; } if (num != color.a) { color.a = num; busyIcon.color = color; } } } [ContextMenu("Copy Filter Tokens")] private void CopyFilterTokens() { StringBuilder stringBuilder = new StringBuilder(); foreach (FilterInfo allFilter in filters.allFilters) { stringBuilder.Append('"').Append(allFilter.token).Append('"') .Append(": \"\",") .AppendLine(); } GUIUtility.systemCopyBuffer = stringBuilder.ToString(); } }