diff --git a/GamecraftModdingAPI/App/Client.cs b/GamecraftModdingAPI/App/Client.cs index fbe960e..ca5dcfa 100644 --- a/GamecraftModdingAPI/App/Client.cs +++ b/GamecraftModdingAPI/App/Client.cs @@ -1,8 +1,12 @@ using System; +using System.Reflection; +using HarmonyLib; +using RobocraftX.Services; using UnityEngine; using GamecraftModdingAPI.Utility; +using RobocraftX.Common; namespace GamecraftModdingAPI.App { @@ -14,6 +18,12 @@ namespace GamecraftModdingAPI.App // extensible engine protected static AppEngine appEngine = new AppEngine(); + protected static Func ErrorHandlerInstanceGetter; + + protected static Action EnqueueError; + + protected static Action HandleErrorClosed; + /// /// An event that fires whenever the main menu is loaded. /// @@ -74,9 +84,76 @@ namespace GamecraftModdingAPI.App get => appEngine.IsInMenu; } + /// + /// Open a popup which prompts the user to click a button. + /// This reuses Gamecraft's error dialog popup + /// + /// The popup to display. Use an instance of SingleChoicePrompt or DualChoicePrompt. + public void PromptUser(Error popup) + { + // if the stuff wasn't mostly set to internal, this would be written as: + // RobocraftX.Services.ErrorHandler.Instance.EqueueError(error); + object errorHandlerInstance = ErrorHandlerInstanceGetter(); + EnqueueError(errorHandlerInstance, popup); + } + + // TODO + /*public void CloseCurrentPrompt() + { + // RobocraftX.Services.ErrorHandler.Instance.HandlePopupClosed(); + // FIXME: this is a call that is also called when closing, not the actual closing action itself (so it doesn't work) + object errorHandlerInstance = ErrorHandlerInstanceGetter(); + HandleErrorClosed(errorHandlerInstance); + }*/ + internal static void Init() { + // this would have been so much simpler if this didn't involve a bunch of internal fields & classes + Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler"); + Type errorHandle = AccessTools.TypeByName("RobocraftX.Services.ErrorHandle"); + ErrorHandlerInstanceGetter = (Func) AccessTools.Method("GamecraftModdingAPI.App.Client:GenInstanceGetter") + .MakeGenericMethod(errorHandler) + .Invoke(null, new object[0]); + EnqueueError = (Action) AccessTools.Method("GamecraftModdingAPI.App.Client:GenEnqueueError") + .MakeGenericMethod(errorHandler, errorHandle) + .Invoke(null, new object[0]); + /*HandleErrorClosed = (Action) AccessTools.Method("GamecraftModdingAPI.App.Client:GenHandlePopupClosed") + .MakeGenericMethod(errorHandler) + .Invoke(null, new object[0]);*/ + // register engines MenuEngineManager.AddMenuEngine(appEngine); } + + // Creating delegates once is faster than reflection every time + // Admittedly, this way is more difficult to code and less readable + private static Func GenInstanceGetter() + { + Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler"); + MethodInfo instance = AccessTools.PropertyGetter(errorHandler, "Instance"); + Func getterSimple = (Func) Delegate.CreateDelegate(typeof(Func), null, instance); + Func getterCasted = () => (object) getterSimple(); + return getterCasted; + } + + private static Action GenEnqueueError() + { + Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler"); + MethodInfo enqueueError = AccessTools.Method(errorHandler, "EnqueueError"); + Func enqueueSimple = + (Func) Delegate.CreateDelegate(typeof(Func), enqueueError); + Action enqueueCasted = + (object instance, Error error) => { enqueueSimple((T) instance, error); }; + return enqueueCasted; + } + + private static Action GenHandlePopupClosed() + { + Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler"); + MethodInfo handlePopupClosed = AccessTools.Method(errorHandler, "HandlePopupClosed"); + Action handleSimple = + (Action) Delegate.CreateDelegate(typeof(Action), handlePopupClosed); + Action handleCasted = (object instance) => handleSimple((T) instance); + return handleCasted; + } } } diff --git a/GamecraftModdingAPI/App/ClientAlertTest.cs b/GamecraftModdingAPI/App/ClientAlertTest.cs new file mode 100644 index 0000000..23ced76 --- /dev/null +++ b/GamecraftModdingAPI/App/ClientAlertTest.cs @@ -0,0 +1,59 @@ +using System; +using HarmonyLib; + +using RobocraftX.Services; + +using GamecraftModdingAPI.Tests; + +namespace GamecraftModdingAPI.App +{ +#if TEST + /// + /// Client popups tests. + /// Only available in TEST builds. + /// + [APITestClass] + public static class ClientAlertTest + { + private static DualChoicePrompt popup2 = null; + + private static SingleChoicePrompt popup1 = null; + + [APITestStartUp] + public static void StartUp2() + { + popup2 = new DualChoicePrompt("This is a test double-button popup", + "The cake is a lie", + "lmao", + () => { }, + "kek", + () => { }); + } + + [APITestStartUp] + public static void StartUp1() + { + popup1 = new SingleChoicePrompt("The cake is a lie", + "This is a test single-button popup", + "qwertyuiop", + () => { }); + } + + [APITestCase(TestType.Menu)] + public static void TestPopUp2() + { + Client c = new Client(); + c.PromptUser(popup2); + //c.CloseCurrentPrompt(); + } + + [APITestCase(TestType.Menu)] + public static void TestPopUp1() + { + Client c = new Client(); + c.PromptUser(popup1); + //c.CloseCurrentPrompt(); + } + } +#endif +} \ No newline at end of file diff --git a/GamecraftModdingAPI/App/UserPrompts.cs b/GamecraftModdingAPI/App/UserPrompts.cs new file mode 100644 index 0000000..26a9395 --- /dev/null +++ b/GamecraftModdingAPI/App/UserPrompts.cs @@ -0,0 +1,27 @@ +using System; +using HarmonyLib; + +using RobocraftX.Services; + +namespace GamecraftModdingAPI.App +{ + public class DualChoicePrompt : MultiChoiceError + { + public DualChoicePrompt(string errorMessage, string title, string firstButtonText, Action firstButtonAction, string secondButtonText, Action secondButtonAction) : base(errorMessage, firstButtonText, firstButtonAction, secondButtonText, secondButtonAction) + { + // internal readonly field smh + new Traverse(this).Field("Title").Value = title; + } + } + + public class SingleChoicePrompt : SingleChoiceError + { + public SingleChoicePrompt(string errorMessage, string buttonText, Action buttonClickAction) : base(errorMessage, buttonText, buttonClickAction) + { + } + + public SingleChoicePrompt(string titleText, string errorMessage, string buttonText, Action buttonClickAction) : base(titleText, errorMessage, buttonText, buttonClickAction) + { + } + } +} \ No newline at end of file