diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index cbc11aa..e8ad1a2 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -1,6 +1,10 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using AmongUs.GameOptions; +using BepInEx.Unity.IL2CPP.Utils.Collections; using HarmonyLib; using Il2CppInterop.Runtime.Attributes; using MiraAPI.GameOptions; @@ -178,7 +182,8 @@ internal static class GameOptionsMenuPatch private static void ModifiersUpdate(ref float num) { var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ParentMenu is MenuCategory.Modifiers || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; + .Where(x => x.ParentMenu is MenuCategory.Modifiers || + x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; foreach (var modGroup in groups) { @@ -189,7 +194,8 @@ internal static class GameOptionsMenuPatch private static void ModifiersCreate(GameOptionsMenu menu) { var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ParentMenu is MenuCategory.Modifiers || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; + .Where(x => x.ParentMenu is MenuCategory.Modifiers || + x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; foreach (var group in groups) { CreateGroup(menu, group); @@ -208,20 +214,17 @@ internal static class GameOptionsMenuPatch __instance.MapPicker.gameObject.SetActive(false); - if (__instance.name == "MODIFIERS TAB") + switch (__instance.name) { - ModifiersCreate(__instance); - return false; - } - else if (__instance.name == "CUSTOM TAB 1") - { - CustomMenuOneCreate(__instance); - return false; - } - else if (__instance.name == "CUSTOM TAB 2") - { - CustomMenuTwoCreate(__instance); - return false; + case "MODIFIERS TAB": + ModifiersCreate(__instance); + return false; + case "CUSTOM TAB 1": + CustomMenuOneCreate(__instance); + return false; + case "CUSTOM TAB 2": + CustomMenuTwoCreate(__instance); + return false; } var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups @@ -318,117 +321,120 @@ internal static class GameOptionsMenuPatch defaultPreset = preset; } - foreach (var newOpt in options) - { - newOpt.SetClickMask(menu.ButtonClickMask); - - SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); - foreach (var renderer in componentsInChildren) + menu.StartCoroutine( + options.CoLoopWithBudget((newOpt) => { - if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) + newOpt.SetClickMask(menu.ButtonClickMask); + + SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); + foreach (var renderer in componentsInChildren) { - renderer.color = group.GroupColor.FindAlternateColor(); - if (renderer.transform.parent.TryGetComponent(out var btn)) + if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) { - btn.interactableColor = group.GroupColor.FindAlternateColor(); - btn.interactableHoveredColor = Color.white; + renderer.color = group.GroupColor.FindAlternateColor(); + if (renderer.transform.parent.TryGetComponent(out var btn)) + { + btn.interactableColor = group.GroupColor.FindAlternateColor(); + btn.interactableHoveredColor = Color.white; + } } + + renderer.material.SetInt(PlayerMaterial.MaskLayer, 20); } - renderer.material.SetInt(PlayerMaterial.MaskLayer, 20); - } - - foreach (var textMeshPro in newOpt.GetComponentsInChildren(true)) - { - if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) + foreach (var textMeshPro in newOpt.GetComponentsInChildren(true)) { - textMeshPro.color = group.GroupColor; + if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) + { + textMeshPro.color = group.GroupColor; + } + + textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f); + textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20); } - textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f); - textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20); - } - - if (newOpt is ToggleOption toggle) - { - toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset(); - toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor - ? group.GroupColor - : MiraAssets.AcceptedTeal; - var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite") - .GetComponent(); - rend.sprite = MiraAssets.CheckmarkBox.LoadAsset(); - rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor - ? group.GroupColor - : MiraAssets.AcceptedTeal; - } - - menu.Children.Add(newOpt); - var resetBtn = new GameObject("ResetOption"); - resetBtn.transform.parent = newOpt.transform; - resetBtn.transform.localScale = new(.5f, .5f, 1); - resetBtn.layer = LayerMask.NameToLayer("UI"); - resetBtn.transform.localPosition = new Vector3(-3.1f, 0f, -2f); - var resetRend = resetBtn.AddComponent(); - resetRend.sprite = MiraAssets.ResetButton.LoadAsset(); - resetRend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; - resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) - ? Color.white - : group.GroupColor.FindAlternateColor(); - var resetBoxCol = resetBtn.gameObject.AddComponent(); - resetBoxCol.size = new Vector2(1f, 1f); - resetBoxCol.offset = new Vector2(0, 0); - var passiveButton = resetBtn.AddComponent(); - passiveButton.OnClick = new Button.ButtonClickedEvent(); - passiveButton.ClickSound = menu.BackButton.GetComponent().ClickSound; - passiveButton.OnMouseOver = new UnityEvent(); - passiveButton.OnMouseOver.AddListener( - (UnityAction)(() => + if (newOpt is ToggleOption toggle) { - resetRend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor + toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset(); + toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? group.GroupColor : MiraAssets.AcceptedTeal; - })); - passiveButton.OnMouseOut = new UnityEvent(); - passiveButton.OnMouseOut.AddListener( - (UnityAction)(() => - { - resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) - ? Color.white - : group.GroupColor.FindAlternateColor(); - })); - if (newOpt is ToggleOption toggleOpt) - { - passiveButton.OnClick.AddListener( - (UnityAction)(() => - { - defaultPreset!.ResetOption(toggleOpt); - })); - } - else if (newOpt is NumberOption numOpt) - { - passiveButton.OnClick.AddListener( - (UnityAction)(() => - { - defaultPreset!.ResetOption(numOpt); - })); - } - else if (newOpt is StringOption strOpt) - { - passiveButton.OnClick.AddListener( - (UnityAction)(() => - { - defaultPreset!.ResetOption(strOpt); - })); - } - if (!defaultPreset!.IsOptionInPreset(newOpt)) - { - resetBtn.Destroy(); - } + var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite") + .GetComponent(); + rend.sprite = MiraAssets.CheckmarkBox.LoadAsset(); + rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor + ? group.GroupColor + : MiraAssets.AcceptedTeal; + } - newOpt.Initialize(); - newOpt.gameObject.SetActive(false); - } + menu.Children.Add(newOpt); + var resetBtn = new GameObject("ResetOption"); + resetBtn.transform.parent = newOpt.transform; + resetBtn.transform.localScale = new(.5f, .5f, 1); + resetBtn.layer = LayerMask.NameToLayer("UI"); + resetBtn.transform.localPosition = new Vector3(-3.1f, 0f, -2f); + var resetRend = resetBtn.AddComponent(); + resetRend.sprite = MiraAssets.ResetButton.LoadAsset(); + resetRend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) + ? Color.white + : group.GroupColor.FindAlternateColor(); + var resetBoxCol = resetBtn.gameObject.AddComponent(); + resetBoxCol.size = new Vector2(1f, 1f); + resetBoxCol.offset = new Vector2(0, 0); + var passiveButton = resetBtn.AddComponent(); + passiveButton.OnClick = new Button.ButtonClickedEvent(); + passiveButton.ClickSound = menu.BackButton.GetComponent().ClickSound; + passiveButton.OnMouseOver = new UnityEvent(); + passiveButton.OnMouseOver.AddListener( + (UnityAction)(() => + { + resetRend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor + ? group.GroupColor + : MiraAssets.AcceptedTeal; + })); + passiveButton.OnMouseOut = new UnityEvent(); + passiveButton.OnMouseOut.AddListener( + (UnityAction)(() => + { + resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) + ? Color.white + : group.GroupColor.FindAlternateColor(); + })); + if (newOpt is ToggleOption toggleOpt) + { + passiveButton.OnClick.AddListener( + (UnityAction)(() => + { + defaultPreset!.ResetOption(toggleOpt); + })); + } + else if (newOpt is NumberOption numOpt) + { + passiveButton.OnClick.AddListener( + (UnityAction)(() => + { + defaultPreset!.ResetOption(numOpt); + })); + } + else if (newOpt is StringOption strOpt) + { + passiveButton.OnClick.AddListener( + (UnityAction)(() => + { + defaultPreset!.ResetOption(strOpt); + })); + } + + if (!defaultPreset!.IsOptionInPreset(newOpt)) + { + resetBtn.Destroy(); + } + + newOpt.Initialize(); + newOpt.gameObject.SetActive(false); + }).WrapToIl2Cpp() + ); var boxCol = categoryHeaderMasked.gameObject.AddComponent(); boxCol.size = new Vector2(7, 0.7f); diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 55947c8..18daa7d 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -1,9 +1,10 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; +using BepInEx.Unity.IL2CPP.Utils.Collections; using HarmonyLib; -using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.GameOptions; using MiraAPI.Networking; using MiraAPI.Roles; @@ -11,7 +12,6 @@ using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; @@ -49,9 +49,7 @@ public static class RoleSettingMenuPatches __instance.scrollBar.SetScrollBounds(__instance); } - [HarmonyPrefix] - [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] - public static bool SetQuotaTabPatch(RolesSettingsMenu __instance) + private static IEnumerator CoQuotaTabPatch(RolesSettingsMenu roleMenu) { foreach (var obj in Headers) { @@ -59,8 +57,10 @@ public static class RoleSettingMenuPatches { continue; } + obj.DeepDestroy(false); } + Headers.Clear(); foreach (var obj in RoleOptionSettings) { @@ -68,24 +68,26 @@ public static class RoleSettingMenuPatches { continue; } + obj.gameObject.DeepDestroy(false); } + RoleOptionSettings.Clear(); CurrentRole = null; CurrentRoleOptions = null; - __instance.roleChances = new Il2CppSystem.Collections.Generic.List(); - __instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List(); + roleMenu.roleChances = new Il2CppSystem.Collections.Generic.List(); + roleMenu.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List(); - var maskBg = __instance.scrollBar.transform.FindChild("MaskBg"); - var hitbox = __instance.scrollBar.transform.FindChild("Hitbox"); - var dividerImage = __instance.transform.FindChild("HeaderButtons/DividerImage"); + var maskBg = roleMenu.scrollBar.transform.FindChild("MaskBg"); + var hitbox = roleMenu.scrollBar.transform.FindChild("Hitbox"); + var dividerImage = roleMenu.transform.FindChild("HeaderButtons/DividerImage"); if (GameSettingMenuPatches.SelectedModIdx == 0) { - __instance.AllButton.transform.parent.gameObject.SetActive(true); - __instance.AllButton.gameObject.SetActive(true); - __instance.scrollBar.transform.localPosition = new Vector3(-1.4957f, 0.657f, -4); + roleMenu.AllButton.transform.parent.gameObject.SetActive(true); + roleMenu.AllButton.gameObject.SetActive(true); + roleMenu.scrollBar.transform.localPosition = new Vector3(-1.4957f, 0.657f, -4); maskBg.localPosition = new Vector3(1.5353f, -.5734f, -.1f); maskBg.localScale = new Vector3(6.6811f, 3.3563f, 0.5598f); hitbox.localPosition = new Vector3(0.3297f, -.2333f, 4f); @@ -94,67 +96,70 @@ public static class RoleSettingMenuPatches var num = 0.662f; - var list = CustomRoleManager.AllRoles.Where( - r => !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Crewmate && r.Role != RoleTypes.Crewmate && - r.Role != RoleTypes.CrewmateGhost).ToList(); - var list2 = CustomRoleManager.AllRoles.Where( - r => !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Impostor && r.Role != RoleTypes.Impostor && - r.Role != RoleTypes.ImpostorGhost).ToList(); + var list = CustomRoleManager.AllRoles.Where(r => + !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Crewmate && + r.Role != RoleTypes.Crewmate && + r.Role != RoleTypes.CrewmateGhost).ToList(); + var list2 = CustomRoleManager.AllRoles.Where(r => + !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Impostor && + r.Role != RoleTypes.Impostor && + r.Role != RoleTypes.ImpostorGhost).ToList(); - if (__instance.roleTabs == null || __instance.roleTabs.Count == 0) + if (roleMenu.roleTabs == null || roleMenu.roleTabs.Count == 0) { var num2 = -1.928f; - __instance.roleTabs = new(); - __instance.roleTabs.Add(__instance.AllButton); + roleMenu.roleTabs = new(); + roleMenu.roleTabs.Add(roleMenu.AllButton); foreach (var t in list) { - __instance.AddRoleTab(t, ref num2); + roleMenu.AddRoleTab(t, ref num2); } foreach (var t in list2) { - __instance.AddRoleTab(t, ref num2); + roleMenu.AddRoleTab(t, ref num2); } } var categoryHeaderEditRole = Object.Instantiate( - __instance.categoryHeaderEditRoleOrigin, + roleMenu.categoryHeaderEditRoleOrigin, Vector3.zero, Quaternion.identity, - __instance.RoleChancesSettings.transform); + roleMenu.RoleChancesSettings.transform); categoryHeaderEditRole.SetHeader(StringNames.CrewmateRolesHeader, 20); categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f); num -= 0.522f; var num3 = 0; foreach (var t in list) { - __instance.CreateQuotaOption(t, ref num, num3); + roleMenu.CreateQuotaOption(t, ref num, num3); num3++; } num -= 0.22f; var categoryHeaderEditRole2 = Object.Instantiate( - __instance.categoryHeaderEditRoleOrigin, + roleMenu.categoryHeaderEditRoleOrigin, Vector3.zero, Quaternion.identity, - __instance.RoleChancesSettings.transform); + roleMenu.RoleChancesSettings.transform); categoryHeaderEditRole2.SetHeader(StringNames.ImpostorRolesHeader, 20); categoryHeaderEditRole2.transform.localPosition = new Vector3(4.986f, num, -2f); num -= 0.522f; foreach (var t in list2) { - __instance.CreateQuotaOption(t, ref num, num3); + roleMenu.CreateQuotaOption(t, ref num, num3); num3++; } - return false; + Info($"Created {num3} role options for the default game."); + yield return null; } ScrollerNum = 0.522f; - __instance.AllButton.transform.parent.gameObject.SetActive(false); - __instance.AllButton.gameObject.SetActive(false); - __instance.scrollBar.transform.localPosition = new Vector3(-1.4957f, 1.5261f, -4); + roleMenu.AllButton.transform.parent.gameObject.SetActive(false); + roleMenu.AllButton.gameObject.SetActive(false); + roleMenu.scrollBar.transform.localPosition = new Vector3(-1.4957f, 1.5261f, -4); maskBg.localPosition = new Vector3(1.5353f, -1.0607f, -.1f); maskBg.localScale = new Vector3(6.6811f, 4.1563f, 0.5598f); hitbox.localPosition = new Vector3(0.3297f, -.6333f, 4f); @@ -168,7 +173,8 @@ public static class RoleSettingMenuPatches if (roleGroups is null) { - return false; + Error("No role groups found for the selected mod."); + yield break; } // sort the groups by priority @@ -176,13 +182,14 @@ public static class RoleSettingMenuPatches .OrderBy(x => x.Key.Priority) .ThenBy(x => x.Key.Name); - var quotaThing = __instance.categoryHeaderEditRoleOrigin.transform.FindChild("QuotaHeader"); + var quotaThing = roleMenu.categoryHeaderEditRoleOrigin.transform.FindChild("QuotaHeader"); var usingNewQuota = false; - var template = __instance.transform.parent.parent.GetComponent().GameSettingsTab.categoryHeaderOrigin; + var template = roleMenu.transform.parent.parent.GetComponent().GameSettingsTab + .categoryHeaderOrigin; foreach (var grouping in sortedRoleGroups) { - if (!grouping.Any() || grouping.All(x=> x.Configuration.HideSettings || !x.VisibleInSettings())) + if (!grouping.Any() || grouping.All(x => x.Configuration.HideSettings || !x.VisibleInSettings())) { continue; } @@ -202,7 +209,7 @@ public static class RoleSettingMenuPatches template, Vector3.zero, Quaternion.identity, - __instance.RoleChancesSettings.transform); + roleMenu.RoleChancesSettings.transform); categoryHeaderMasked.SetHeader(name, 20); @@ -245,19 +252,23 @@ public static class RoleSettingMenuPatches categoryHeaderMasked.Background.color = Palette.ImpostorRoleHeaderRed; break; default: - categoryHeaderMasked.Title.color = group.Color.Equals(MiraApiPlugin.DefaultHeaderColor) ? Color.white : group.Color.FindAlternateColor(); + categoryHeaderMasked.Title.color = group.Color.Equals(MiraApiPlugin.DefaultHeaderColor) + ? Color.white + : group.Color.FindAlternateColor(); categoryHeaderMasked.Divider.color = group.Color; categoryHeaderMasked.Background.color = group.Color; break; } - categoryHeaderMasked.Title.fontStyle = __instance.categoryHeaderEditRoleOrigin.Title.fontStyle; - categoryHeaderMasked.Title.font = __instance.categoryHeaderEditRoleOrigin.Title.font; - categoryHeaderMasked.Title.fontMaterial = __instance.categoryHeaderEditRoleOrigin.Title.fontMaterial; + categoryHeaderMasked.Title.fontStyle = roleMenu.categoryHeaderEditRoleOrigin.Title.fontStyle; + categoryHeaderMasked.Title.font = roleMenu.categoryHeaderEditRoleOrigin.Title.font; + categoryHeaderMasked.Title.fontMaterial = roleMenu.categoryHeaderEditRoleOrigin.Title.fontMaterial; categoryHeaderMasked.Divider.color = categoryHeaderMasked.Background.color; categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.55f, -0.1833f, 0); - categoryHeaderMasked.Background.size = new Vector2(categoryHeaderMasked.Background.size.x + 1.5f, categoryHeaderMasked.Background.size.y); + categoryHeaderMasked.Background.size = new Vector2( + categoryHeaderMasked.Background.size.x + 1.5f, + categoryHeaderMasked.Background.size.y); categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f; categoryHeaderMasked.transform.localPosition = new Vector3(-0.44f, ScrollerNum, -2f); @@ -277,20 +288,21 @@ public static class RoleSettingMenuPatches if (!RoleGroupHidden[group]) { - foreach (var role in grouping) - { - if (role is not RoleBehaviour roleBehaviour) + yield return + grouping.CoLoopWithBudget(role => { - continue; - } + if (role is not RoleBehaviour roleBehaviour) + { + return; + } - var option = CreateQuotaOption(__instance, roleBehaviour, num4); - if (option is not null) - { - RoleOptionSettings.Add(option); - num4++; - } - } + var option = CreateQuotaOption(roleMenu, roleBehaviour, num4); + if (option is not null) + { + RoleOptionSettings.Add(option); + num4++; + } + }); } var boxCol = categoryHeaderMasked.gameObject.AddComponent(); @@ -298,25 +310,28 @@ public static class RoleSettingMenuPatches boxCol.offset = new Vector2(1.5f, -0.3f); var headerBtn = categoryHeaderMasked.gameObject.AddComponent(); - headerBtn.ClickSound = __instance.BackButton.GetComponent().ClickSound; + headerBtn.ClickSound = roleMenu.BackButton.GetComponent().ClickSound; headerBtn.OnMouseOver = new UnityEvent(); headerBtn.OnMouseOut = new UnityEvent(); headerBtn.OnClick.AddListener( (UnityAction)(() => { - RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition; + RolePositions[GameSettingMenuPatches.SelectedModIdx] = roleMenu.scrollBar.Inner.localPosition; if (RoleGroupHidden.TryGetValue(group, out var groupHidden)) { RoleGroupHidden[group] = !groupHidden; } + foreach (var obj in Headers) { if (!obj) { continue; } + obj.DeepDestroy(false); } + Headers.Clear(); foreach (var obj in RoleOptionSettings) { @@ -324,10 +339,12 @@ public static class RoleSettingMenuPatches { continue; } + obj.gameObject.DeepDestroy(false); } + RoleOptionSettings.Clear(); - __instance.SetQuotaTab(); + roleMenu.SetQuotaTab(); })); headerBtn.SetButtonEnableState(true); @@ -337,7 +354,14 @@ public static class RoleSettingMenuPatches } } - __instance.scrollBar.SetScrollBounds(__instance); + roleMenu.scrollBar.SetScrollBounds(roleMenu); + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] + public static bool SetQuotaTabPatch(RolesSettingsMenu __instance) + { + __instance.StartCoroutine(CoQuotaTabPatch(__instance).WrapToIl2Cpp()); return false; } @@ -362,6 +386,7 @@ public static class RoleSettingMenuPatches } [HarmonyPrefix] + // TODO: turn this into a fixed update [HarmonyPatch(nameof(RolesSettingsMenu.Update))] public static bool UpdatePatch(RolesSettingsMenu __instance) { diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 717f0b9..4590ad6 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -1,10 +1,12 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using System.Timers; using HarmonyLib; using MiraAPI.GameOptions; using MiraAPI.Networking; @@ -239,6 +241,44 @@ public static class Extensions } } + /// + /// Runs a given action in a loop with a frame budget based on the current target frame rate. Once the budget is hit, the loop continues on the next frame. + /// + /// The collection to iterate over. + /// The action to perform on each item. + /// The type of the collection. + /// Coroutine. + public static IEnumerator CoLoopWithBudget(this IEnumerable collection, Action action) + { + var fps = Application.targetFrameRate > 0 ? Application.targetFrameRate : 60; + long budget = 1 / (fps * 2); // default is half of current frame time. + yield return collection.CoLoopWithBudget(budget, action); + } + + /// + /// Runs a given action in a loop with a given frame budget. Once the budget is hit, the loop continues on the next frame. + /// + /// The collection to iterate over. + /// The # of milliseconds to spend in the loop. + /// The action to perform on each item. + /// The type of the collection. + /// Coroutine. + public static IEnumerator CoLoopWithBudget(this IEnumerable collection, long frameBudget, Action action) + { + var timer = new Stopwatch(); + timer.Start(); + foreach (var item in collection) + { + action(item); + + if (timer.ElapsedMilliseconds > frameBudget) + { + timer.Restart(); + yield return null; + } + } + } + private static IEnumerator Nuke(GameObject? go, bool clearGc) { if (go == null)