using System; using System.Reflection; using HarmonyLib; using RobocraftX.Services; using UnityEngine; using RobocraftX.Common; using TechbloxModdingAPI.Utility; namespace TechbloxModdingAPI.App { /// /// The Techblox application that is running this code right now. /// public class Client { public static Client Instance { get; } = new Client(); protected static Func ErrorHandlerInstanceGetter; protected static Action EnqueueError; /// /// An event that fires whenever the main menu is loaded. /// public static event EventHandler EnterMenu { add => Game.menuEngine.EnterMenu += value; remove => Game.menuEngine.EnterMenu -= value; } /// /// An event that fire whenever the main menu is exited. /// public static event EventHandler ExitMenu { add => Game.menuEngine.ExitMenu += value; remove => Game.menuEngine.ExitMenu -= value; } /// /// Techblox build version string. /// Usually this is in the form YYYY.mm.DD.HH.MM.SS /// /// The version. public string Version { get => Application.version; } /// /// Unity version string. /// /// The unity version. public string UnityVersion { get => Application.unityVersion; } /// /// Game saves currently visible in the menu. /// These take a second to completely populate after the EnterMenu event fires. /// /// My games. public Game[] MyGames { get { if (!Game.menuEngine.IsInMenu) return Array.Empty(); return Game.menuEngine.GetMyGames(); } } /// /// Whether Techblox is in the Main Menu /// /// true if in menu; false when loading or in a game. public bool InMenu { get => Game.menuEngine.IsInMenu; } /// /// Open a popup which prompts the user to click a button. /// This reuses Techblox'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); } public void CloseCurrentPrompt() { object errorHandlerInstance = ErrorHandlerInstanceGetter(); var popup = GetPopupCloseMethods(errorHandlerInstance); popup.Close(); } public void SelectFirstPromptButton() { object errorHandlerInstance = ErrorHandlerInstanceGetter(); var popup = GetPopupCloseMethods(errorHandlerInstance); popup.FirstButton(); } public void SelectSecondPromptButton() { object errorHandlerInstance = ErrorHandlerInstanceGetter(); var popup = GetPopupCloseMethods(errorHandlerInstance); popup.SecondButton(); } internal void CloseBetaPopup() { Game.menuEngine.CloseBetaPopup(); } 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("TechbloxModdingAPI.App.Client:GenInstanceGetter") .MakeGenericMethod(errorHandler) .Invoke(null, new object[0]); EnqueueError = (Action) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError") .MakeGenericMethod(errorHandler, errorHandle) .Invoke(null, new object[0]); } // 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 Close, Action FirstButton, Action SecondButton) _errorPopup; private static (Action Close, Action FirstButton, Action SecondButton) GetPopupCloseMethods(object handler) { if (_errorPopup.Close != null) return _errorPopup; Type errorHandler = handler.GetType(); FieldInfo field = AccessTools.Field(errorHandler, "errorPopup"); var errorPopup = (ErrorPopup)field.GetValue(handler); MethodInfo info = AccessTools.Method(errorPopup.GetType(), "ClosePopup"); var close = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info); info = AccessTools.Method(errorPopup.GetType(), "HandleFirstOption"); var first = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info); info = AccessTools.Method(errorPopup.GetType(), "HandleSecondOption"); var second = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info); _errorPopup = (close, first, second); return _errorPopup; } } }