A stable modding interface between Techblox and mods https://mod.exmods.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

177 lines
6.4KB

  1. using System;
  2. using System.Reflection;
  3. using HarmonyLib;
  4. using RobocraftX.Services;
  5. using UnityEngine;
  6. using RobocraftX.Common;
  7. using TechbloxModdingAPI.Utility;
  8. namespace TechbloxModdingAPI.App
  9. {
  10. /// <summary>
  11. /// The Techblox application that is running this code right now.
  12. /// </summary>
  13. public class Client
  14. {
  15. public static Client Instance { get; } = new Client();
  16. protected static Func<object> ErrorHandlerInstanceGetter;
  17. protected static Action<object, Error> EnqueueError;
  18. /// <summary>
  19. /// An event that fires whenever the main menu is loaded.
  20. /// </summary>
  21. public static event EventHandler<MenuEventArgs> EnterMenu
  22. {
  23. add => Game.menuEngine.EnterMenu += value;
  24. remove => Game.menuEngine.EnterMenu -= value;
  25. }
  26. /// <summary>
  27. /// An event that fire whenever the main menu is exited.
  28. /// </summary>
  29. public static event EventHandler<MenuEventArgs> ExitMenu
  30. {
  31. add => Game.menuEngine.ExitMenu += value;
  32. remove => Game.menuEngine.ExitMenu -= value;
  33. }
  34. /// <summary>
  35. /// Techblox build version string.
  36. /// Usually this is in the form YYYY.mm.DD.HH.MM.SS
  37. /// </summary>
  38. /// <value>The version.</value>
  39. public string Version
  40. {
  41. get => Application.version;
  42. }
  43. /// <summary>
  44. /// Unity version string.
  45. /// </summary>
  46. /// <value>The unity version.</value>
  47. public string UnityVersion
  48. {
  49. get => Application.unityVersion;
  50. }
  51. /// <summary>
  52. /// Game saves currently visible in the menu.
  53. /// These take a second to completely populate after the EnterMenu event fires.
  54. /// </summary>
  55. /// <value>My games.</value>
  56. public Game[] MyGames
  57. {
  58. get
  59. {
  60. if (!Game.menuEngine.IsInMenu) return Array.Empty<Game>();
  61. return Game.menuEngine.GetMyGames();
  62. }
  63. }
  64. /// <summary>
  65. /// Whether Techblox is in the Main Menu
  66. /// </summary>
  67. /// <value><c>true</c> if in menu; <c>false</c> when loading or in a game.</value>
  68. public bool InMenu
  69. {
  70. get => Game.menuEngine.IsInMenu;
  71. }
  72. /// <summary>
  73. /// Open a popup which prompts the user to click a button.
  74. /// This reuses Techblox's error dialog popup
  75. /// </summary>
  76. /// <param name="popup">The popup to display. Use an instance of SingleChoicePrompt or DualChoicePrompt.</param>
  77. public void PromptUser(Error popup)
  78. {
  79. // if the stuff wasn't mostly set to internal, this would be written as:
  80. // RobocraftX.Services.ErrorHandler.Instance.EqueueError(error);
  81. object errorHandlerInstance = ErrorHandlerInstanceGetter();
  82. EnqueueError(errorHandlerInstance, popup);
  83. }
  84. public void CloseCurrentPrompt()
  85. {
  86. object errorHandlerInstance = ErrorHandlerInstanceGetter();
  87. var popup = GetPopupCloseMethods(errorHandlerInstance);
  88. popup.Close();
  89. }
  90. public void SelectFirstPromptButton()
  91. {
  92. object errorHandlerInstance = ErrorHandlerInstanceGetter();
  93. var popup = GetPopupCloseMethods(errorHandlerInstance);
  94. popup.FirstButton();
  95. }
  96. public void SelectSecondPromptButton()
  97. {
  98. object errorHandlerInstance = ErrorHandlerInstanceGetter();
  99. var popup = GetPopupCloseMethods(errorHandlerInstance);
  100. popup.SecondButton();
  101. }
  102. internal void CloseBetaPopup()
  103. {
  104. Game.menuEngine.CloseBetaPopup();
  105. }
  106. internal static void Init()
  107. {
  108. // this would have been so much simpler if this didn't involve a bunch of internal fields & classes
  109. Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
  110. Type errorHandle = AccessTools.TypeByName("RobocraftX.Services.ErrorHandle");
  111. ErrorHandlerInstanceGetter = (Func<object>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenInstanceGetter")
  112. .MakeGenericMethod(errorHandler)
  113. .Invoke(null, new object[0]);
  114. EnqueueError = (Action<object, Error>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError")
  115. .MakeGenericMethod(errorHandler, errorHandle)
  116. .Invoke(null, new object[0]);
  117. }
  118. // Creating delegates once is faster than reflection every time
  119. // Admittedly, this way is more difficult to code and less readable
  120. private static Func<object> GenInstanceGetter<T>()
  121. {
  122. Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
  123. MethodInfo instance = AccessTools.PropertyGetter(errorHandler, "Instance");
  124. Func<T> getterSimple = (Func<T>) Delegate.CreateDelegate(typeof(Func<T>), null, instance);
  125. Func<object> getterCasted = () => (object) getterSimple();
  126. return getterCasted;
  127. }
  128. private static Action<object, Error> GenEnqueueError<T, TRes>()
  129. {
  130. Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
  131. MethodInfo enqueueError = AccessTools.Method(errorHandler, "EnqueueError");
  132. Func<T, Error, TRes> enqueueSimple =
  133. (Func<T, Error, TRes>) Delegate.CreateDelegate(typeof(Func<T, Error, TRes>), enqueueError);
  134. Action<object, Error> enqueueCasted =
  135. (object instance, Error error) => { enqueueSimple((T) instance, error); };
  136. return enqueueCasted;
  137. }
  138. private static (Action Close, Action FirstButton, Action SecondButton) _errorPopup;
  139. private static (Action Close, Action FirstButton, Action SecondButton) GetPopupCloseMethods(object handler)
  140. {
  141. if (_errorPopup.Close != null)
  142. return _errorPopup;
  143. Type errorHandler = handler.GetType();
  144. FieldInfo field = AccessTools.Field(errorHandler, "errorPopup");
  145. var errorPopup = (ErrorPopup)field.GetValue(handler);
  146. MethodInfo info = AccessTools.Method(errorPopup.GetType(), "ClosePopup");
  147. var close = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
  148. info = AccessTools.Method(errorPopup.GetType(), "HandleFirstOption");
  149. var first = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
  150. info = AccessTools.Method(errorPopup.GetType(), "HandleSecondOption");
  151. var second = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
  152. _errorPopup = (close, first, second);
  153. return _errorPopup;
  154. }
  155. }
  156. }