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.

154 lines
5.7KB

  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. protected static Func<object> ErrorHandlerInstanceGetter;
  16. protected static Action<object, Error> EnqueueError;
  17. protected static Action<object> HandleErrorClosed;
  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. // TODO
  85. /*public void CloseCurrentPrompt()
  86. {
  87. // RobocraftX.Services.ErrorHandler.Instance.HandlePopupClosed();
  88. // FIXME: this is a call that is also called when closing, not the actual closing action itself (so it doesn't work)
  89. object errorHandlerInstance = ErrorHandlerInstanceGetter();
  90. HandleErrorClosed(errorHandlerInstance);
  91. }*/
  92. internal static void Init()
  93. {
  94. // this would have been so much simpler if this didn't involve a bunch of internal fields & classes
  95. Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
  96. Type errorHandle = AccessTools.TypeByName("RobocraftX.Services.ErrorHandle");
  97. ErrorHandlerInstanceGetter = (Func<object>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenInstanceGetter")
  98. .MakeGenericMethod(errorHandler)
  99. .Invoke(null, new object[0]);
  100. EnqueueError = (Action<object, Error>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError")
  101. .MakeGenericMethod(errorHandler, errorHandle)
  102. .Invoke(null, new object[0]);
  103. /*HandleErrorClosed = (Action<object>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenHandlePopupClosed")
  104. .MakeGenericMethod(errorHandler)
  105. .Invoke(null, new object[0]);*/
  106. }
  107. // Creating delegates once is faster than reflection every time
  108. // Admittedly, this way is more difficult to code and less readable
  109. private static Func<object> GenInstanceGetter<T>()
  110. {
  111. Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
  112. MethodInfo instance = AccessTools.PropertyGetter(errorHandler, "Instance");
  113. Func<T> getterSimple = (Func<T>) Delegate.CreateDelegate(typeof(Func<T>), null, instance);
  114. Func<object> getterCasted = () => (object) getterSimple();
  115. return getterCasted;
  116. }
  117. private static Action<object, Error> GenEnqueueError<T, TRes>()
  118. {
  119. Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
  120. MethodInfo enqueueError = AccessTools.Method(errorHandler, "EnqueueError");
  121. Func<T, Error, TRes> enqueueSimple =
  122. (Func<T, Error, TRes>) Delegate.CreateDelegate(typeof(Func<T, Error, TRes>), enqueueError);
  123. Action<object, Error> enqueueCasted =
  124. (object instance, Error error) => { enqueueSimple((T) instance, error); };
  125. return enqueueCasted;
  126. }
  127. private static Action<object> GenHandlePopupClosed<T>()
  128. {
  129. Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
  130. MethodInfo handlePopupClosed = AccessTools.Method(errorHandler, "HandlePopupClosed");
  131. Action<T> handleSimple =
  132. (Action<T>) Delegate.CreateDelegate(typeof(Action<T>), handlePopupClosed);
  133. Action<object> handleCasted = (object instance) => handleSimple((T) instance);
  134. return handleCasted;
  135. }
  136. }
  137. }