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.

210 lines
6.2KB

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