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.

205 lines
6.0KB

  1. using System;
  2. using System.Reflection;
  3. using HarmonyLib;
  4. using RobocraftX;
  5. using RobocraftX.Common;
  6. using RobocraftX.FrontEnd;
  7. using RobocraftX.GUI;
  8. using RobocraftX.GUI.MyGamesScreen;
  9. using Svelto.ECS;
  10. using Svelto.ECS.Experimental;
  11. using Techblox.GameSelection;
  12. using TechbloxModdingAPI.Engines;
  13. using TechbloxModdingAPI.Utility;
  14. using GameMode = RobocraftX.Common.GameMode;
  15. namespace TechbloxModdingAPI.App
  16. {
  17. public class GameMenuEngine : IFactoryEngine
  18. {
  19. public WrappedHandler<MenuEventArgs> EnterMenu;
  20. public WrappedHandler<MenuEventArgs> ExitMenu;
  21. public IEntityFactory Factory { set; private get; }
  22. public string Name => "TechbloxModdingAPIGameInfoGameEngine";
  23. public bool isRemovable => false;
  24. public EntitiesDB entitiesDB { set; private get; }
  25. public GameMenuEngine()
  26. {
  27. MenuEnteredEnginePatch.EnteredExitedMenu = () =>
  28. {
  29. if (IsInMenu)
  30. EnterMenu.Invoke(this, new MenuEventArgs { });
  31. else
  32. ExitMenu.Invoke(this, new MenuEventArgs { });
  33. };
  34. }
  35. public void Dispose()
  36. {
  37. }
  38. public void Ready()
  39. {
  40. MenuEnteredEnginePatch.IsInMenu = true; // At first it uses ActivateMenu(), then GoToMenu() which is patched
  41. MenuEnteredEnginePatch.EnteredExitedMenu();
  42. }
  43. // game functionality
  44. public bool IsInMenu => MenuEnteredEnginePatch.IsInMenu;
  45. public Game[] GetMyGames()
  46. {
  47. EntityCollection<MyGameDataEntityStruct> mgsevs = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
  48. var mgsevsB = mgsevs.ToBuffer().buffer;
  49. Game[] games = new Game[mgsevs.count];
  50. for (int i = 0; i < mgsevs.count; i++)
  51. {
  52. Utility.Logging.MetaDebugLog($"Found game named {mgsevsB[i].GameName}");
  53. games[i] = new Game(mgsevsB[i].ID);
  54. }
  55. return games;
  56. }
  57. public bool CreateMyGame(EGID id, string path = "", uint thumbnailId = 0, string gameName = "", string creatorName = "", string description = "", long createdDate = 0L)
  58. {
  59. EntityInitializer eci = Factory.BuildEntity<MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal>(id);
  60. eci.Init(new MyGameDataEntityStruct
  61. {
  62. SavedGamePath = new ECSString(path),
  63. ThumbnailId = thumbnailId,
  64. GameName = new ECSString(gameName),
  65. CreatorName = new ECSString(creatorName),
  66. GameDescription = new ECSString(description),
  67. CreatedDate = createdDate,
  68. });
  69. // entitiesDB.PublishEntityChange<MyGameDataEntityStruct>(id); // this will always fail
  70. return true;
  71. }
  72. public uint HighestID()
  73. {
  74. EntityCollection<MyGameDataEntityStruct> games = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
  75. var gamesB = games.ToBuffer().buffer;
  76. uint max = 0;
  77. for (int i = 0; i < games.count; i++)
  78. {
  79. if (gamesB[i].ID.entityID > max)
  80. {
  81. max = gamesB[i].ID.entityID;
  82. }
  83. }
  84. return max;
  85. }
  86. public bool EnterGame(EGID id)
  87. {
  88. if (!ExistsGameInfo(id)) return false;
  89. ref MyGameDataEntityStruct mgdes = ref GetGameInfo(id);
  90. return EnterGame(mgdes.GameName, mgdes.FileId);
  91. }
  92. public bool EnterGame(string gameName, string fileId, bool autoEnterSim = false)
  93. {
  94. GameMode.CurrentMode = autoEnterSim ? RCXMode.Play : RCXMode.Build;
  95. var data = new GameSelectionData
  96. {
  97. gameMode = Techblox.GameSelection.GameMode.PlayGame,
  98. gameType = GameType.MachineEditor,
  99. saveName = gameName,
  100. saveType = SaveType.ExistingSave,
  101. gameID = "GAMEID_Road_Track", //TODO: Expose to the API
  102. userContentID = fileId
  103. };
  104. // the private FullGameCompositionRoot.SwitchToGame() method gets passed to menu items for this reason
  105. AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame").Invoke(FullGameFields.Instance, new object[]{data});
  106. return true;
  107. }
  108. public bool SetGameName(EGID id, string name)
  109. {
  110. if (!ExistsGameInfo(id)) return false;
  111. GetGameInfo(id).GameName.Set(name);
  112. GetGameViewInfo(id).MyGamesSlotComponent.GameName = StringUtil.SanitiseString(name);
  113. return true;
  114. }
  115. public bool SetGameDescription(EGID id, string name)
  116. {
  117. if (!ExistsGameInfo(id)) return false;
  118. GetGameInfo(id).GameDescription.Set(name);
  119. GetGameViewInfo(id).MyGamesSlotComponent.GameDescription = StringUtil.SanitiseString(name);
  120. return true;
  121. }
  122. public bool ExistsGameInfo(EGID id)
  123. {
  124. return entitiesDB.Exists<MyGameDataEntityStruct>(id);
  125. }
  126. public ref MyGameDataEntityStruct GetGameInfo(EGID id)
  127. {
  128. return ref GetComponent<MyGameDataEntityStruct>(id);
  129. }
  130. public dynamic GetGameViewInfo(EGID id)
  131. {
  132. dynamic structOptional = AccessTools.Method("TechbloxModdingAPI.Utility.NativeApiExtensions:QueryEntityOptional", new []{typeof(EntitiesDB), typeof(EGID)})
  133. .MakeGenericMethod(AccessTools.TypeByName("RobocraftX.GUI.MyGamesScreen.MyGamesSlotEntityViewStruct"))
  134. .Invoke(null, new object[] {entitiesDB, new EGID(id.entityID, MyGamesScreenExclusiveGroups.GameSlotGuiEntities)});
  135. if (structOptional == null) throw new Exception("Could not get game slot entity");
  136. return structOptional ? structOptional : null;
  137. }
  138. public ref T GetComponent<T>(EGID id) where T: unmanaged, IEntityComponent
  139. {
  140. return ref entitiesDB.QueryEntity<T>(id);
  141. }
  142. }
  143. internal class MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal : GenericEntityDescriptor<MyGameDataEntityStruct> { }
  144. [HarmonyPatch]
  145. static class MenuEnteredEnginePatch
  146. {
  147. internal static bool IsInMenu;
  148. internal static Action EnteredExitedMenu;
  149. public static void Postfix()
  150. {
  151. IsInMenu = true;
  152. EnteredExitedMenu();
  153. }
  154. public static MethodBase TargetMethod()
  155. {
  156. return AccessTools.Method(typeof(FullGameCompositionRoot), "GoToMenu");
  157. }
  158. }
  159. [HarmonyPatch]
  160. static class MenuExitedEnginePatch
  161. {
  162. public static void Prefix()
  163. {
  164. MenuEnteredEnginePatch.IsInMenu = false;
  165. MenuEnteredEnginePatch.EnteredExitedMenu();
  166. }
  167. public static MethodBase TargetMethod()
  168. {
  169. return AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame");
  170. }
  171. }
  172. public struct MenuEventArgs
  173. {
  174. }
  175. }