diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index e65f4c3..ecf574c 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -38,6 +38,7 @@ internal static class GameOptionsMenuPatch [HarmonyPatch(nameof(GameOptionsMenu.Initialize))] public static bool InitPatch(GameOptionsMenu __instance) { + Helpers.CacheAdvancedSettingsRowPrefab(__instance.numberOptionOrigin); __instance.Children ??= new(); __instance.Children.Clear(); diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index e6ae2d5..6c1ede7 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -1,4 +1,5 @@ using HarmonyLib; +using MiraAPI.Utilities; namespace MiraAPI.Patches.Options; @@ -13,6 +14,11 @@ internal static class GameSettingMenuPatches [HarmonyPatch(nameof(GameSettingMenu.OnEnable))] public static void OnEnablePrefix(GameSettingMenu __instance) { + if (__instance.GameSettingsTab && __instance.GameSettingsTab.numberOptionOrigin) + { + Helpers.CacheAdvancedSettingsRowPrefab(__instance.GameSettingsTab.numberOptionOrigin); + } + // Add Menu State object if (!__instance.gameObject.GetComponent()) { diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 26d68df..29a57a4 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using HarmonyLib; +using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.GameOptions; using MiraAPI.GameOptions.OptionTypes; @@ -32,6 +33,7 @@ public static class Helpers { internal const float MatchInfoGuideBaseScale = 1.3f; private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100)); + private static NumberOption? _advancedSettingsRowPrefab; public static ReadOnlyCollection? GetModdedOptionsForType(Type classType) { @@ -845,8 +847,60 @@ public static class Helpers } /// - /// Creates a page of vanilla-looking settings rows (label box on the left, value box on the right, two columns) - /// inside a roles-style scroller. Use this for lists of "Name: Value" settings that are not roles or modifiers. + /// Caches a persistent clone of the vanilla settings row for use in the Match Info Guide. + /// + /// The vanilla settings row prefab. + internal static void CacheAdvancedSettingsRowPrefab(NumberOption? rowPrefab) + { + if (!rowPrefab) + { + foreach (var candidate in Resources.FindObjectsOfTypeAll(Il2CppType.Of())) + { + var menu = candidate.TryCast(); + var settingsTab = menu ? menu!.GameSettingsTab : null; + if (settingsTab && settingsTab!.numberOptionOrigin) + { + rowPrefab = settingsTab.numberOptionOrigin; + break; + } + } + } + + if (!rowPrefab) + { + foreach (var candidate in Resources.FindObjectsOfTypeAll(Il2CppType.Of())) + { + var option = candidate.TryCast(); + if (!option) + { + continue; + } + + if (option!.TitleText && option.ValueText && option.PlusBtn && option.MinusBtn) + { + rowPrefab = option; + break; + } + } + } + + if (_advancedSettingsRowPrefab || !rowPrefab) + { + return; + } + + var cachedPrefab = Object.Instantiate(rowPrefab!.gameObject); + cachedPrefab.name = "MiraAPIAdvancedSettingsRowPrefab"; + cachedPrefab.transform.SetParent(null, false); + cachedPrefab.transform.localPosition = Vector3.zero; + cachedPrefab.transform.localRotation = Quaternion.identity; + cachedPrefab.SetActive(false); + Object.DontDestroyOnLoad(cachedPrefab); + _advancedSettingsRowPrefab = cachedPrefab.GetComponent(); + } + + /// + /// Creates a page of vanilla settings rows inside a roles-style scroller. /// /// The guide object. /// The the page is parented to. @@ -857,10 +911,9 @@ public static class Helpers Scroller parent, IReadOnlyList> settings) { - const float rowHeight = 0.4f; - const float rowPitch = 0.52f; + const float rowPitch = 0.57f; const float contentTop = 0.95f; - const float visibleHeight = 1.5f; + const float visibleHeight = 1.8f; const int columns = 2; var inner = parent.Inner; @@ -872,88 +925,102 @@ public static class Helpers var columnWidth = layout ? Mathf.Clamp(layout.cellSize.x, 2f, 3.2f) : 2.7f; var columnGap = layout ? Mathf.Clamp(layout.spacing.x, 0.1f, 0.6f) : 0.3f; - var labelWidth = columnWidth * 0.62f; - var valueWidth = columnWidth * 0.30f; - var boxGap = columnWidth - labelWidth - valueWidth; - var left = -((columns * columnWidth) + ((columns - 1) * columnGap)) / 2f; - - var prefab = guide.MatchInfoRolePanelPrefab; - var vanillaFont = HudManager.Instance.TaskPanel.taskText; - - var boxMaterial = new Material(prefab.roleIcon.sharedMaterial); - boxMaterial.SetInt(PlayerMaterial.MaskLayer, 50); - var boxSprite = MiraAssets.BlankSprite.LoadAsset(); var layer = inner.gameObject.layer; - var page = new GameObject("AdvancedSettingsPage") { layer = layer }; page.transform.SetParent(inner, false); page.transform.localPosition = Vector3.zero; + page.SetActive(false); - void MakeBox(Vector3 pos, Vector2 size, Color color) + var gameSettingsMenu = GameSettingMenu.Instance; + CacheAdvancedSettingsRowPrefab(gameSettingsMenu ? gameSettingsMenu.GameSettingsTab.numberOptionOrigin : null); + var rowPrefab = _advancedSettingsRowPrefab; + if (!rowPrefab) { - var box = new GameObject("Box") { layer = layer }; - box.transform.SetParent(page.transform, false); - box.transform.localPosition = pos; - var sr = box.AddComponent(); - sr.sprite = boxSprite; - sr.drawMode = SpriteDrawMode.Sliced; - sr.size = size; - sr.color = color; - sr.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; - sr.sharedMaterial = boxMaterial; + Error("Could not render MiraAPI settings rows because the vanilla NumberOption prefab was not cached."); + return page; } - void MakeText(string text, Vector3 pos, Vector2 size) + var spriteBounds = rowPrefab!.GetComponentsInChildren(true) + .Where(renderer => + renderer.enabled && + renderer.sprite && + renderer.transform != rowPrefab.PlusBtn.transform && + !renderer.transform.IsChildOf(rowPrefab.PlusBtn.transform) && + renderer.transform != rowPrefab.MinusBtn.transform && + !renderer.transform.IsChildOf(rowPrefab.MinusBtn.transform)) + .Select(renderer => renderer.bounds) + .ToArray(); + if (spriteBounds.Length == 0) { - var tmp = Object.Instantiate(prefab.roleName, page.transform); - tmp.richText = true; - tmp.font = vanillaFont.font; - tmp.fontMaterial = vanillaFont.fontMaterial; - tmp.text = text; - tmp.enableAutoSizing = true; - tmp.fontSizeMin = 0.9f; - tmp.fontSizeMax = 2.2f; - tmp.fontStyle = FontStyles.Normal; - tmp.enableWordWrapping = true; - tmp.alignment = TextAlignmentOptions.Center; - tmp.color = Color.white; - tmp.rectTransform.sizeDelta = size; - tmp.transform.localPosition = pos; - tmp.EnableStencilMasking(); + Error("Could not render MiraAPI settings rows because the vanilla NumberOption prefab has no sprites."); + return page; } + var prefabBounds = spriteBounds.Skip(1).Aggregate( + spriteBounds[0], + (bounds, next) => + { + bounds.Encapsulate(next); + return bounds; + }); + var rowScale = (columnWidth / prefabBounds.size.x) * 1.1f; + var rowHeight = prefabBounds.size.y * rowScale; + for (var i = 0; i < settings.Count; i++) { - var col = i % columns; - var row = i / columns; - var originX = left + (col * (columnWidth + columnGap)); - var centerY = contentTop - (rowHeight / 2f) - (row * rowPitch); + var column = i % columns; + var rowIndex = i / columns; + var originX = -((columns * columnWidth) + ((columns - 1) * columnGap)) / 2f + + (column * (columnWidth + columnGap)); + var centerY = contentTop - (rowHeight / 2f) - (rowIndex * rowPitch); + var row = Object.Instantiate(rowPrefab, page.transform); + row.enabled = false; + row.PlusBtn.gameObject.SetActive(false); + row.MinusBtn.gameObject.SetActive(false); - var labelX = originX + (labelWidth / 2f); - var valueX = originX + labelWidth + boxGap + (valueWidth / 2f); + foreach (var button in row.GetComponentsInChildren(true)) + { + button.enabled = false; + } - MakeBox( - new Vector3(labelX, centerY, 0f), - new Vector2(labelWidth, rowHeight), - new Color(0.16f, 0.16f, 0.16f, 1f)); - MakeBox(new Vector3(valueX, centerY, -0.01f), new Vector2(valueWidth, rowHeight), Color.white); - MakeBox( - new Vector3(valueX, centerY, -0.02f), - new Vector2(valueWidth - 0.04f, rowHeight - 0.04f), - Color.black); + foreach (var collider in row.GetComponentsInChildren(true)) + { + collider.enabled = false; + } - MakeText( - $"{settings[i].Key}", - new Vector3(labelX, centerY, -0.1f), - new Vector2(labelWidth - 0.12f, rowHeight - 0.04f)); - MakeText( - settings[i].Value, - new Vector3(valueX, centerY, -0.1f), - new Vector2(valueWidth - 0.1f, rowHeight - 0.04f)); + row.transform.localScale = rowPrefab.transform.localScale * rowScale; + row.transform.localPosition = new Vector3( + originX + (columnWidth / 2f) - (prefabBounds.center.x * rowScale), + centerY - (prefabBounds.center.y * rowScale), + 0f); + row.TitleText.text = settings[i].Key; + row.TitleText.fontStyle = FontStyles.Bold; + row.ValueText.text = settings[i].Value; + + foreach (var text in row.GetComponentsInChildren(true)) + { + if (text.TryGetComponent(out var translator)) + { + translator.enabled = false; + Object.Destroy(translator); + } + + text.fontMaterial.SetFloat(guide.MatchInfoRolePanelPrefab.STENCIL_NAME, 50f); + } + + foreach (var renderer in row.GetComponentsInChildren(true)) + { + renderer.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + renderer.material.SetInt(PlayerMaterial.MaskLayer, 50); + } + + row.gameObject.SetActive(true); } var rows = Mathf.CeilToInt(settings.Count / (float)columns); - parent.SetYBoundsMax(Mathf.Clamp((rows * rowPitch) - visibleHeight, 0f, 999f)); + var contentHeight = rows == 0 ? 0f : rowHeight + ((rows - 1) * rowPitch); + parent.SetYBoundsMax(Mathf.Clamp(contentHeight - visibleHeight, 0f, 999f)); + page.SetActive(true); return page; }