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.

522 lines
16KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.CompilerServices;
  4. using RobocraftX.Common;
  5. using RobocraftX.GUI.MyGamesScreen;
  6. using RobocraftX.StateSync;
  7. using Svelto.ECS;
  8. using TechbloxModdingAPI;
  9. using TechbloxModdingAPI.Blocks;
  10. using TechbloxModdingAPI.Tasks;
  11. using TechbloxModdingAPI.Utility;
  12. namespace TechbloxModdingAPI.App
  13. {
  14. /// <summary>
  15. /// An in-game save.
  16. /// This can be a menu item for a local save or the currently loaded save.
  17. /// Support for Steam Workshop coming soon (hopefully).
  18. /// </summary>
  19. public class Game
  20. {
  21. // extensible engines
  22. protected static GameGameEngine gameEngine = new GameGameEngine();
  23. protected static GameMenuEngine menuEngine = new GameMenuEngine();
  24. protected static DebugInterfaceEngine debugOverlayEngine = new DebugInterfaceEngine();
  25. protected static GameBuildSimEventEngine buildSimEventEngine = new GameBuildSimEventEngine();
  26. private List<string> debugIds = new List<string>();
  27. private bool menuMode = true;
  28. private bool hasId = false;
  29. /// <summary>
  30. /// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.App.Game"/> class.
  31. /// </summary>
  32. /// <param name="id">Menu identifier.</param>
  33. public Game(uint id) : this(new EGID(id, MyGamesScreenExclusiveGroups.MyGames))
  34. {
  35. }
  36. /// <summary>
  37. /// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.App.Game"/> class.
  38. /// </summary>
  39. /// <param name="id">Menu identifier.</param>
  40. public Game(EGID id)
  41. {
  42. this.Id = id.entityID;
  43. this.EGID = id;
  44. this.hasId = true;
  45. menuMode = true;
  46. if (!VerifyMode()) throw new AppStateException("Game cannot be created while not in a game nor in a menu (is the game in a loading screen?)");
  47. }
  48. /// <summary>
  49. /// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.App.Game"/> class without id.
  50. /// This is assumed to be the current game.
  51. /// </summary>
  52. public Game()
  53. {
  54. menuMode = false;
  55. if (!VerifyMode()) throw new AppStateException("Game cannot be created while not in a game nor in a menu (is the game in a loading screen?)");
  56. if (menuEngine.IsInMenu) throw new GameNotFoundException("Game not found.");
  57. }
  58. /// <summary>
  59. /// Returns the currently loaded game.
  60. /// If in a menu, manipulating the returned object may not work as intended.
  61. /// </summary>
  62. /// <returns>The current game.</returns>
  63. public static Game CurrentGame()
  64. {
  65. return new Game();
  66. }
  67. /// <summary>
  68. /// Creates a new game and adds it to the menu.
  69. /// If not in a menu, this will throw AppStateException.
  70. /// </summary>
  71. /// <returns>The new game.</returns>
  72. public static Game NewGame()
  73. {
  74. if (!menuEngine.IsInMenu) throw new AppStateException("New Game cannot be created while not in a menu.");
  75. uint nextId = menuEngine.HighestID() + 1;
  76. EGID egid = new EGID(nextId, MyGamesScreenExclusiveGroups.MyGames);
  77. menuEngine.CreateMyGame(egid);
  78. return new Game(egid);
  79. }
  80. /// <summary>
  81. /// An event that fires whenever a game is switched to simulation mode (time running mode).
  82. /// </summary>
  83. public static event EventHandler<GameEventArgs> Simulate
  84. {
  85. add => buildSimEventEngine.SimulationMode += ExceptionUtil.WrapHandler(value);
  86. remove => buildSimEventEngine.SimulationMode -= value;
  87. }
  88. /// <summary>
  89. /// An event that fires whenever a game is switched to edit mode (time stopped mode).
  90. /// This does not fire when a game is loaded.
  91. /// </summary>
  92. public static event EventHandler<GameEventArgs> Edit
  93. {
  94. add => buildSimEventEngine.BuildMode += ExceptionUtil.WrapHandler(value);
  95. remove => buildSimEventEngine.BuildMode -= value;
  96. }
  97. /// <summary>
  98. /// An event that fires right after a game is completely loaded.
  99. /// </summary>
  100. public static event EventHandler<GameEventArgs> Enter
  101. {
  102. add => gameEngine.EnterGame += ExceptionUtil.WrapHandler(value);
  103. remove => gameEngine.EnterGame -= value;
  104. }
  105. /// <summary>
  106. /// An event that fires right before a game returns to the main menu.
  107. /// At this point, Techblox is transitioning state so many things are invalid/unstable here.
  108. /// </summary>
  109. public static event EventHandler<GameEventArgs> Exit
  110. {
  111. add => gameEngine.ExitGame += ExceptionUtil.WrapHandler(value);
  112. remove => gameEngine.ExitGame -= value;
  113. }
  114. /// <summary>
  115. /// The game's unique menu identifier.
  116. /// </summary>
  117. /// <value>The identifier.</value>
  118. public uint Id
  119. {
  120. get;
  121. private set;
  122. }
  123. /// <summary>
  124. /// The game's unique menu EGID.
  125. /// </summary>
  126. /// <value>The egid.</value>
  127. public EGID EGID
  128. {
  129. get;
  130. private set;
  131. }
  132. /// <summary>
  133. /// Whether the game is a (valid) menu item.
  134. /// </summary>
  135. /// <value><c>true</c> if menu item; otherwise, <c>false</c>.</value>
  136. public bool MenuItem
  137. {
  138. get => menuMode && hasId;
  139. }
  140. /// <summary>
  141. /// The game's name.
  142. /// </summary>
  143. /// <value>The name.</value>
  144. public string Name
  145. {
  146. get
  147. {
  148. if (!VerifyMode()) return null;
  149. if (menuMode) return menuEngine.GetGameInfo(EGID).GameName;
  150. return GameMode.SaveGameDetails.Name;
  151. }
  152. set
  153. {
  154. if (!VerifyMode()) return;
  155. if (menuMode)
  156. {
  157. menuEngine.SetGameName(EGID, value);
  158. }
  159. else
  160. {
  161. GameMode.SaveGameDetails.Name = value;
  162. }
  163. }
  164. }
  165. /// <summary>
  166. /// The game's description.
  167. /// </summary>
  168. /// <value>The description.</value>
  169. public string Description
  170. {
  171. get
  172. {
  173. if (!VerifyMode()) return null;
  174. if (menuMode) return menuEngine.GetGameInfo(EGID).GameDescription;
  175. return "";
  176. }
  177. set
  178. {
  179. if (!VerifyMode()) return;
  180. if (menuMode)
  181. {
  182. menuEngine.SetGameDescription(EGID, value);
  183. }
  184. else
  185. {
  186. // No description exists in-game
  187. }
  188. }
  189. }
  190. /// <summary>
  191. /// The path to the game's save folder.
  192. /// </summary>
  193. /// <value>The path.</value>
  194. public string Path
  195. {
  196. get
  197. {
  198. if (!VerifyMode()) return null;
  199. if (menuMode) return menuEngine.GetGameInfo(EGID).SavedGamePath;
  200. return GameMode.SaveGameDetails.Folder;
  201. }
  202. set
  203. {
  204. if (!VerifyMode()) return;
  205. if (menuMode)
  206. {
  207. menuEngine.GetGameInfo(EGID).SavedGamePath.Set(value);
  208. }
  209. else
  210. {
  211. // this likely breaks things
  212. GameMode.SaveGameDetails = new SaveGameDetails(GameMode.SaveGameDetails.Id,
  213. GameMode.SaveGameDetails.SaveMode, GameMode.SaveGameDetails.Name, value,
  214. GameMode.SaveGameDetails.WorkshopId);
  215. }
  216. }
  217. }
  218. /// <summary>
  219. /// The Steam Workshop Id of the game save.
  220. /// In most cases this is invalid and returns 0, so this can be ignored.
  221. /// </summary>
  222. /// <value>The workshop identifier.</value>
  223. public ulong WorkshopId
  224. {
  225. get
  226. {
  227. if (!VerifyMode()) return 0uL;
  228. if (menuMode) return 0uL; // MyGames don't have workshop IDs
  229. return GameMode.SaveGameDetails.WorkshopId;
  230. }
  231. set
  232. {
  233. VerifyMode();
  234. if (menuMode)
  235. {
  236. // MyGames don't have workshop IDs
  237. // menuEngine.GetGameInfo(EGID).GameName.Set(value);
  238. }
  239. else
  240. {
  241. // this likely breaks things
  242. GameMode.SaveGameDetails = new SaveGameDetails(GameMode.SaveGameDetails.Id,
  243. GameMode.SaveGameDetails.SaveMode, GameMode.SaveGameDetails.Name,
  244. GameMode.SaveGameDetails.Folder, value);
  245. }
  246. }
  247. }
  248. /// <summary>
  249. /// Whether the game is in simulation mode.
  250. /// </summary>
  251. /// <value><c>true</c> if is simulating; otherwise, <c>false</c>.</value>
  252. public bool IsSimulating
  253. {
  254. get
  255. {
  256. if (!VerifyMode()) return false;
  257. return !menuMode && gameEngine.IsTimeRunningMode();
  258. }
  259. set
  260. {
  261. if (!VerifyMode()) return;
  262. if (!menuMode && gameEngine.IsTimeRunningMode() != value)
  263. gameEngine.ToggleTimeMode();
  264. }
  265. }
  266. /// <summary>
  267. /// Whether the game is in time-running mode.
  268. /// Alias of IsSimulating.
  269. /// </summary>
  270. /// <value><c>true</c> if is time running; otherwise, <c>false</c>.</value>
  271. public bool IsTimeRunning
  272. {
  273. get => IsSimulating;
  274. set
  275. {
  276. IsSimulating = value;
  277. }
  278. }
  279. /// <summary>
  280. /// Whether the game is in time-stopped mode.
  281. /// </summary>
  282. /// <value><c>true</c> if is time stopped; otherwise, <c>false</c>.</value>
  283. public bool IsTimeStopped
  284. {
  285. get
  286. {
  287. if (!VerifyMode()) return false;
  288. return !menuMode && gameEngine.IsTimeStoppedMode();
  289. }
  290. set
  291. {
  292. if (!VerifyMode()) return;
  293. if (!menuMode && gameEngine.IsTimeStoppedMode() != value)
  294. gameEngine.ToggleTimeMode();
  295. }
  296. }
  297. /// <summary>
  298. /// Toggles the time mode.
  299. /// </summary>
  300. public void ToggleTimeMode()
  301. {
  302. if (!VerifyMode()) return;
  303. if (menuMode || !gameEngine.IsInGame)
  304. {
  305. throw new AppStateException("Game menu item cannot toggle it's time mode");
  306. }
  307. gameEngine.ToggleTimeMode();
  308. }
  309. /// <summary>
  310. /// The mode of the game.
  311. /// </summary>
  312. public CurrentGameMode Mode
  313. {
  314. get
  315. {
  316. if (menuMode || !VerifyMode()) return CurrentGameMode.None;
  317. return (CurrentGameMode) GameMode.CurrentMode;
  318. }
  319. }
  320. /// <summary>
  321. /// Load the game save.
  322. /// This happens asynchronously, so when this method returns the game not loaded yet.
  323. /// Use the Game.Enter event to perform operations after the game has completely loaded.
  324. /// </summary>
  325. public void EnterGame()
  326. {
  327. if (!VerifyMode()) return;
  328. if (!hasId)
  329. {
  330. throw new GameNotFoundException("Game has an invalid ID");
  331. }
  332. ISchedulable task = new Once(() => { menuEngine.EnterGame(EGID); this.menuMode = false; });
  333. Scheduler.Schedule(task);
  334. }
  335. /// <summary>
  336. /// Return to the menu.
  337. /// Part of this always happens asynchronously, so when this method returns the game has not exited yet.
  338. /// Use the Client.EnterMenu event to perform operations after the game has completely exited.
  339. /// </summary>
  340. /// <param name="async">If set to <c>true</c>, do this async.</param>
  341. public void ExitGame(bool async = false)
  342. {
  343. if (!VerifyMode()) return;
  344. if (menuMode)
  345. {
  346. throw new GameNotFoundException("Cannot exit game using menu ID");
  347. }
  348. gameEngine.ExitCurrentGame(async);
  349. this.menuMode = true;
  350. }
  351. /// <summary>
  352. /// Saves the game.
  353. /// Part of this happens asynchronously, so when this method returns the game has not been saved yet.
  354. /// </summary>
  355. public void SaveGame()
  356. {
  357. if (!VerifyMode()) return;
  358. if (menuMode)
  359. {
  360. throw new GameNotFoundException("Cannot save game using menu ID");
  361. }
  362. gameEngine.SaveCurrentGame();
  363. }
  364. /// <summary>
  365. /// Add information to the in-game debug display.
  366. /// When this object is garbage collected, this debug info is automatically removed.
  367. /// The provided getter function is called each frame so make sure it returns quickly.
  368. /// </summary>
  369. /// <param name="id">Debug info identifier.</param>
  370. /// <param name="contentGetter">A function that returns the current information.</param>
  371. public void AddDebugInfo(string id, Func<string> contentGetter)
  372. {
  373. if (!VerifyMode()) return;
  374. if (menuMode)
  375. {
  376. throw new GameNotFoundException("Game object references a menu item but AddDebugInfo only works on the currently-loaded game");
  377. }
  378. debugOverlayEngine.SetInfo("game_" + id, contentGetter);
  379. debugIds.Add(id);
  380. }
  381. /// <summary>
  382. /// Remove information from the in-game debug display.
  383. /// </summary>
  384. /// <returns><c>true</c>, if debug info was removed, <c>false</c> otherwise.</returns>
  385. /// <param name="id">Debug info identifier.</param>
  386. public bool RemoveDebugInfo(string id)
  387. {
  388. if (!VerifyMode()) return false;
  389. if (menuMode)
  390. {
  391. throw new GameNotFoundException("Game object references a menu item but RemoveDebugInfo only works on the currently-loaded game");
  392. }
  393. if (!debugIds.Contains(id)) return false;
  394. debugOverlayEngine.RemoveInfo("game_" + id);
  395. return debugIds.Remove(id);
  396. }
  397. /// <summary>
  398. /// Add information to the in-game debug display.
  399. /// This debug info will be present for all games until it is manually removed.
  400. /// The provided getter function is called each frame so make sure it returns quickly.
  401. /// </summary>
  402. /// <param name="id">Debug info identifier.</param>
  403. /// <param name="contentGetter">A function that returns the current information.</param>
  404. public static void AddPersistentDebugInfo(string id, Func<string> contentGetter)
  405. {
  406. debugOverlayEngine.SetInfo("persistent_" + id, contentGetter);
  407. }
  408. /// <summary>
  409. /// Remove persistent information from the in-game debug display.
  410. /// </summary>
  411. /// <returns><c>true</c>, if debug info was removed, <c>false</c> otherwise.</returns>
  412. /// <param name="id">Debug info identifier.</param>
  413. public static bool RemovePersistentDebugInfo(string id)
  414. {
  415. return debugOverlayEngine.RemoveInfo("persistent_" + id);
  416. }
  417. /// <summary>
  418. /// Gets the blocks in the game.
  419. /// This returns null when in a loading state, and throws AppStateException when in menu.
  420. /// </summary>
  421. /// <returns>The blocks in game.</returns>
  422. /// <param name="filter">The block to search for. BlockIDs.Invalid will return all blocks.</param>
  423. public Block[] GetBlocksInGame(BlockIDs filter = BlockIDs.Invalid)
  424. {
  425. if (!VerifyMode()) return null;
  426. if (menuMode)
  427. {
  428. throw new AppStateException("Game object references a menu item but GetBlocksInGame only works on the currently-loaded game");
  429. }
  430. EGID[] blockEGIDs = gameEngine.GetAllBlocksInGame(filter);
  431. Block[] blocks = new Block[blockEGIDs.Length];
  432. for (int b = 0; b < blockEGIDs.Length; b++)
  433. {
  434. blocks[b] = Block.New(blockEGIDs[b]);
  435. }
  436. return blocks;
  437. }
  438. /// <summary>
  439. /// Enable the screenshot taker for updating the game's screenshot. Breaks the pause menu in a new save.
  440. /// </summary>
  441. public void EnableScreenshotTaker()
  442. {
  443. if (!VerifyMode()) return;
  444. gameEngine.EnableScreenshotTaker();
  445. }
  446. ~Game()
  447. {
  448. foreach (string id in debugIds)
  449. {
  450. debugOverlayEngine.RemoveInfo(id);
  451. }
  452. }
  453. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  454. private bool VerifyMode()
  455. {
  456. if (menuMode && (!menuEngine.IsInMenu || gameEngine.IsInGame))
  457. {
  458. // either game loading or API is broken
  459. return false;
  460. }
  461. if (!menuMode && (menuEngine.IsInMenu || !gameEngine.IsInGame))
  462. {
  463. // either game loading or API is broken
  464. return false;
  465. }
  466. return true;
  467. }
  468. internal static void Init()
  469. {
  470. GameEngineManager.AddGameEngine(gameEngine);
  471. GameEngineManager.AddGameEngine(debugOverlayEngine);
  472. GameEngineManager.AddGameEngine(buildSimEventEngine);
  473. MenuEngineManager.AddMenuEngine(menuEngine);
  474. }
  475. }
  476. }