diff --git a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs index 658a096..ac2007c 100644 --- a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs +++ b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs @@ -6,6 +6,7 @@ using HarmonyLib; using MiraAPI.Hud; using MiraAPI.PluginLoading; using MiraAPI.Presets; +using MiraAPI.Utilities.Assets; using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; @@ -21,7 +22,7 @@ internal static class GamePresetsTabPatches private static GameObject _saveButton = null!; private static GameObject _refreshButton = null!; private static GameObject _folderButton = null!; - + private static GameObject _newDivider = null!; private static GameObject _presetHolder = null!; private static GridArrange _arrange = null!; @@ -39,11 +40,19 @@ internal static class GamePresetsTabPatches return; } + __instance.PresetDescriptionText.gameObject.GetComponent().Destroy(); + + var oldDiv = __instance.transform.FindChild("DividerImage"); + _newDivider = Object.Instantiate(oldDiv.gameObject, oldDiv.transform.parent); + _newDivider.transform.localPosition = new Vector3(1.85f, 0.13f, 0f); + _newDivider.transform.localScale = new Vector3(1.13f, 1, 1); + _newDivider.transform.localRotation = Quaternion.Euler(new Vector3(0, 0, 90)); + // create the save button var saveButton = Object.Instantiate(prefab, __instance.transform); _saveButton = saveButton.gameObject; _saveButton.gameObject.name = "SaveButton"; - _saveButton.transform.localPosition = new Vector3(3.4f, 1.7f, -2); + _saveButton.transform.localPosition = new Vector3(3.2f, 1.7f, -2); // set the button text and alignment var saveText = saveButton.buttonText; @@ -55,36 +64,34 @@ internal static class GamePresetsTabPatches saveText.transform.parent.localPosition.y, saveText.transform.parent.localPosition.z); - // adjust the button size and colliders - foreach (var collider in saveButton.Colliders) - { - if (collider.TryCast() is { } col) - { - col.size = new Vector2(col.size.x / 2, col.size.y); - } - } - - foreach (var rend in saveButton.GetComponentsInChildren(true)) - { - rend.size = new Vector2(rend.size.x / 2, rend.size.y); - } + DivideSize(saveButton.gameObject, 2f); + // Create refresh button var refreshButton = Object.Instantiate(saveButton, __instance.transform); _refreshButton = refreshButton.gameObject; _refreshButton.name = "RefreshButton"; - _refreshButton.transform.localPosition = new Vector3(3.4f, 1.1f, -2); + _refreshButton.transform.localPosition = new Vector3(2.85f, 1.1f, -2); - var refreshText = refreshButton.buttonText; - refreshText.text = "Refresh"; + var icon = new GameObject("Sprite"); + icon.transform.SetParent(refreshButton.transform); + icon.transform.localPosition = new Vector3(-0.47f, -0.08f, -5f); + icon.transform.localScale = new Vector3(0.5f, 0.5f, 1); + icon.layer = refreshButton.gameObject.layer; - var openFolderButton = Object.Instantiate(saveButton, __instance.transform); + var iconSpriteRend = icon.AddComponent(); + iconSpriteRend.sprite = MiraAssets.RefreshIcon.LoadAsset(); + + DivideSize(refreshButton.gameObject, 2f); + refreshButton.buttonText.gameObject.Destroy(); + + var openFolderButton = Object.Instantiate(refreshButton, __instance.transform); _folderButton = openFolderButton.gameObject; _folderButton.name = "OpenFolderButton"; - _folderButton.transform.localPosition = new Vector3(3.4f, 0.5f, -2); + _folderButton.transform.localPosition = new Vector3(3.55f, 1.1f, -2); - var openFolderText = openFolderButton.buttonText; - openFolderText.text = "Presets Folder"; - openFolderText.fontSizeMax = 4; + var folderRend = openFolderButton.transform.FindChild("Sprite").gameObject.GetComponent(); + folderRend.sprite = MiraAssets.FolderIcon.LoadAsset(); + folderRend.transform.localScale = new Vector3(0.4f, 0.4f, 1); openFolderButton.OnClick = new Button.ButtonClickedEvent(); openFolderButton.OnClick.AddListener( @@ -103,7 +110,6 @@ internal static class GamePresetsTabPatches // add the click event to save the preset saveButton.OnClick = new Button.ButtonClickedEvent(); - var input = saveButton.gameObject.AddComponent(); saveButton.OnClick.AddListener( (UnityAction)(() => { @@ -112,31 +118,33 @@ internal static class GamePresetsTabPatches return; } - input.CreateDialog( - "Preset Name", - "Enter preset name...", - "Save Preset", - name => + SavePresetPopup.CreatePopup(name => + { + if (GameSettingMenuPatches.SelectedMod == null) { - if (GameSettingMenuPatches.SelectedMod == null) - { - return; - } + return; + } - var presetFile = new ConfigFile( - Path.Join( - PresetManager.PresetDirectory, - GameSettingMenuPatches.SelectedMod.PluginId, - $"{name}.cfg"), - true); - foreach (var option in GameSettingMenuPatches.SelectedMod.InternalOptions.Where( - x => x.IncludeInPreset)) - { - option.SaveToPreset(presetFile, true); - } + if (name == string.Empty) + { + return; + } - presetFile.Save(); - }); + var presetFile = new ConfigFile( + Path.Join( + PresetManager.PresetDirectory, + GameSettingMenuPatches.SelectedMod.PluginId, + $"{name}.cfg"), + true); + foreach (var option in GameSettingMenuPatches.SelectedMod.InternalOptions.Where( + x => x.IncludeInPreset)) + { + option.SaveToPreset(presetFile, true); + } + + presetFile.Save(); + Refresh(); + }); })); // add the click event to refresh the presets @@ -146,13 +154,15 @@ internal static class GamePresetsTabPatches _saveButton.SetActive(false); _refreshButton.SetActive(false); _folderButton.SetActive(false); + _newDivider.SetActive(false); _presetHolder = new GameObject("PresetHolder"); _presetHolder.transform.SetParent(__instance.transform, false); - _presetHolder.transform.localPosition = new Vector3(-1.7f, 1.7f, 0); + _presetHolder.transform.localScale = new Vector3(0.9f, 0.9f, 1); + _presetHolder.transform.localPosition = new Vector3(-1.9f, 1.7f, 0); _arrange = _presetHolder.AddComponent(); _arrange.Alignment = GridArrange.StartAlign.Right; - _arrange.CellSize = new Vector2(2.5f, -.55f); + _arrange.CellSize = new Vector2(2.15f, -.55f); _arrange.MaxColumns = 2; var watcher = new FileSystemWatcher @@ -177,6 +187,22 @@ internal static class GamePresetsTabPatches GameSettingMenu.Instance.ChangeTab(0, false); // refresh the tab } } + + public static void DivideSize(GameObject obj, float amount) + { + foreach (var collider in obj.GetComponentsInChildren(true)) + { + if (collider.TryCast() is { } col) + { + col.size = new Vector2(col.size.x / amount, col.size.y); + } + } + + foreach (var rend in obj.GetComponentsInChildren(true)) + { + rend.size = new Vector2(rend.size.x / amount, rend.size.y); + } + } [HarmonyPatch(typeof(GamePresetsTab), nameof(GamePresetsTab.OnEnable))] public static class GamePresetsOnEnablePatch @@ -188,6 +214,15 @@ internal static class GamePresetsTabPatches __instance.StandardPresetButton.gameObject.SetActive(GameSettingMenuPatches.SelectedModIdx == 0); __instance.SecondPresetButton.gameObject.SetActive(GameSettingMenuPatches.SelectedModIdx == 0); + if (GameSettingMenuPatches.SelectedModIdx != 0) + { + __instance.PresetDescriptionText.text = "Select or create an options preset for this mod"; + } + else + { + __instance.SetSelectedText(); + } + // Show the save and refresh buttons if (_saveButton) { @@ -202,6 +237,11 @@ internal static class GamePresetsTabPatches _folderButton.SetActive(GameSettingMenuPatches.SelectedModIdx != 0); } + if (_newDivider) + { + _newDivider.SetActive(GameSettingMenuPatches.SelectedModIdx != 0); + } + var prefab = GameSettingMenu.Instance.GameSettingsButton; foreach (var preset in MiraPluginManager.Instance.RegisteredPlugins.SelectMany(x => x.Presets)) @@ -213,6 +253,8 @@ internal static class GamePresetsTabPatches tmp.DestroyImmediate(); } + button.buttonText.alignment = TextAlignmentOptions.Center; + button.OnClick = new Button.ButtonClickedEvent(); button.OnClick.AddListener( (UnityAction)(() => diff --git a/MiraAPI/Presets/SavePresetPopup.cs b/MiraAPI/Presets/SavePresetPopup.cs new file mode 100644 index 0000000..5b4549d --- /dev/null +++ b/MiraAPI/Presets/SavePresetPopup.cs @@ -0,0 +1,91 @@ +using System; +using Il2CppInterop.Runtime.InteropTypes.Fields; +using MiraAPI.Modifiers.ModifierDisplay; +using MiraAPI.Utilities; +using MiraAPI.Utilities.Assets; +using Reactor.Utilities.Attributes; +using Reactor.Utilities.Extensions; +using TMPro; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.UI; + +[RegisterInIl2Cpp] +public class SavePresetPopup(nint ptr) : Minigame(ptr) +{ + private PassiveButton saveButton; + private PassiveButton closeButton; + private TextMeshPro textBoxText; + private TextBoxTMP textBox; + + private Action onSave; + + // Cleanup holder object + public void OnDestroy() + { + transform.parent.gameObject.Destroy(); + } + + public override void Close() + { + } + + private void Awake() + { + var textboxHolder = transform.GetChild(1).GetChild(1); + saveButton = transform.FindChild("SaveButton").GetComponent(); + closeButton = transform.FindChild("CloseButton").GetComponent(); + textBox = textboxHolder.GetChild(0).GetComponent(); + textBoxText = textBox.transform.GetChild(0).GetComponent(); + + saveButton.OnClick = new Button.ButtonClickedEvent(); + saveButton.OnClick.AddListener((UnityAction)(() => + { + onSave.Invoke(textBoxText.text); + this.BaseClose(); + })); + + closeButton.OnClick = new Button.ButtonClickedEvent(); + closeButton.OnClick.AddListener((UnityAction)(() => + { + this.BaseClose(); + })); + + textBox.OnChange = new Button.ButtonClickedEvent(); + textBox.OnChange.AddListener((UnityAction)(() => + { + textBoxText.text = textBoxText.text.Replace(" ", string.Empty); + })); + + textBox.GetComponent().OnClick.AddListener((UnityAction)(() => + { + textBox.GiveFocus(); + })); + + foreach (var tmp in gameObject.GetComponentsInChildren()) + { + if (tmp.gameObject.transform.parent.name == "Textbox") continue; + var text = tmp.text; + tmp.text = $"{text}"; + } + } + + public static void CreatePopup(Action saveAction) + { + // Because innerscuff doesnt account for Z value in Close animation + var holder = new GameObject("PopupHolder") + { + transform = + { + parent = Camera.main!.transform, + }, + }; + holder.transform.localPosition = new Vector3(0, -1f, -500); + + var gameObject = Instantiate(MiraAssets.PresetSavePopup.LoadAsset(), holder.transform); + var popup = gameObject.GetComponent(); + popup.onSave = saveAction; + popup.Begin(null); + } +} + diff --git a/MiraAPI/Resources/mirabundle-win-x86.bundle b/MiraAPI/Resources/mirabundle-win-x86.bundle index 59afd15..d73dcbe 100644 Binary files a/MiraAPI/Resources/mirabundle-win-x86.bundle and b/MiraAPI/Resources/mirabundle-win-x86.bundle differ diff --git a/MiraAPI/Utilities/Assets/MiraAssets.cs b/MiraAPI/Utilities/Assets/MiraAssets.cs index 06d57cd..5e9e484 100644 --- a/MiraAPI/Utilities/Assets/MiraAssets.cs +++ b/MiraAPI/Utilities/Assets/MiraAssets.cs @@ -21,14 +21,16 @@ public static class MiraAssets 0U, SpriteMeshType.Tight, new Vector4(20, 20, 20, 20)); - public static readonly AssetBundle MiraAssetBundle = AssetBundleManager.Load("mirabundle"); - public static readonly LoadableAsset ModifierDisplay = new LoadableBundleAsset("Modifiers", MiraAssetBundle); - - public static readonly LoadableResourceAsset Empty = new("MiraAPI.Resources.Empty.png"); RoundedBox = new LoadableAssetWrapper(boxSprite); } + internal static readonly AssetBundle MiraAssetBundle = AssetBundleManager.Load("mirabundle"); + internal static readonly LoadableAsset ModifierDisplay = new LoadableBundleAsset("Modifiers", MiraAssetBundle); + internal static readonly LoadableAsset PresetSavePopup = new LoadableBundleAsset("PresetSavePopup", MiraAssetBundle); + internal static readonly LoadableAsset RefreshIcon = new LoadableBundleAsset("refresh", MiraAssetBundle); + internal static readonly LoadableAsset FolderIcon = new LoadableBundleAsset("freePlay_folderTaskRoom", MiraAssetBundle); + /// /// Gets the accepted teal color used for various selections in the UI. /// diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 7a0d9a7..2669d92 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -41,6 +41,49 @@ public static class Extensions BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value))); } + /// + /// Used if you override Minigame.Close. + /// + /// The minigame. + public static void BaseClose(this Minigame self) + { + bool isComplete; + if (self.amClosing == Minigame.CloseState.Closing) + { + UnityEngine.Object.Destroy(self.gameObject); + return; + } + if (self.CloseSound && Constants.ShouldPlaySfx()) + { + SoundManager.Instance.PlaySound(self.CloseSound, false, 1f, null); + } + if (PlayerControl.LocalPlayer.Data.Role.TeamType == RoleTeamTypes.Crewmate) + { + GameManager.Instance.LogicMinigame.OnMinigameClose(); + } + if (PlayerControl.LocalPlayer) + { + PlayerControl.HideCursorTemporarily(); + } + self.amClosing = Minigame.CloseState.Closing; + self.logger.Info(string.Concat("Closing minigame ", self.GetType().Name)); + IAnalyticsReporter analytics = DestroyableSingleton.Instance.Analytics; + NetworkedPlayerInfo data = PlayerControl.LocalPlayer.Data; + TaskTypes taskType = self.TaskType; + float realtimeSinceStartup = Time.realtimeSinceStartup - self.timeOpened; + PlayerTask myTask = self.MyTask; + if (myTask != null) + { + isComplete = myTask.IsComplete; + } + else + { + isComplete = false; + } + analytics.MinigameClosed(data, taskType, realtimeSinceStartup, isComplete); + self.StartCoroutine(self.CoDestroySelf()); + } + /// /// Gets a PlayerControl from their PlayerVoteArea in a meeting. ///