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.

202 lines
5.8KB

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