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 { // 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. /// public static event EventHandler EnterMenu { add => appEngine.EnterMenu += value; remove => appEngine.EnterMenu -= value; } /// /// An event that fire whenever the main menu is exited. /// public static event EventHandler ExitMenu { add => appEngine.ExitMenu += value; remove => appEngine.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 (!appEngine.IsInMenu) return new Game[0]; return appEngine.GetMyGames(); } } /// /// Whether Techblox is in the Main Menu /// /// true if in menu; false when loading or in a game. public bool InMenu { get => appEngine.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); } // 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("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]); /*HandleErrorClosed = (Action) AccessTools.Method("TechbloxModdingAPI.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; } } }