using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using RobocraftX.Common; using RobocraftX.GUI.MyGamesScreen; using RobocraftX.StateSync; using Svelto.ECS; using GamecraftModdingAPI; using GamecraftModdingAPI.Blocks; using GamecraftModdingAPI.Tasks; using GamecraftModdingAPI.Utility; namespace GamecraftModdingAPI.App { /// /// An in-game save. /// This can be a menu item for a local save or the currently loaded save. /// Support for Steam Workshop coming soon (hopefully). /// public class Game { // extensible engines protected static GameGameEngine gameEngine = new GameGameEngine(); protected static GameMenuEngine menuEngine = new GameMenuEngine(); protected static DebugInterfaceEngine debugOverlayEngine = new DebugInterfaceEngine(); protected static GameBuildSimEventEngine buildSimEventEngine = new GameBuildSimEventEngine(); private List debugIds = new List(); private bool menuMode = true; private bool hasId = false; /// /// Initializes a new instance of the class. /// /// Menu identifier. public Game(uint id) : this(new EGID(id, MyGamesScreenExclusiveGroups.MyGames)) { } /// /// Initializes a new instance of the class. /// /// Menu identifier. public Game(EGID id) { this.Id = id.entityID; this.EGID = id; this.hasId = true; menuMode = true; 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?)"); } /// /// Initializes a new instance of the class without id. /// This is assumed to be the current game. /// public Game() { menuMode = false; 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?)"); if (menuEngine.IsInMenu) throw new GameNotFoundException("Game not found."); } /// /// Returns the currently loaded game. /// If in a menu, manipulating the returned object may not work as intended. /// /// The current game. public static Game CurrentGame() { return new Game(); } /// /// Creates a new game and adds it to the menu. /// If not in a menu, this will throw AppStateException. /// /// The new game. public static Game NewGame() { if (!menuEngine.IsInMenu) throw new AppStateException("New Game cannot be created while not in a menu."); uint nextId = menuEngine.HighestID() + 1; EGID egid = new EGID(nextId, MyGamesScreenExclusiveGroups.MyGames); menuEngine.CreateMyGame(egid); return new Game(egid); } /// /// An event that fires whenever a game is switched to simulation mode (time running mode). /// public static event EventHandler Simulate { add => buildSimEventEngine.SimulationMode += value; remove => buildSimEventEngine.SimulationMode -= value; } /// /// An event that fires whenever a game is switched to edit mode (time stopped mode). /// This does not fire when a game is loaded. /// public static event EventHandler Edit { add => buildSimEventEngine.BuildMode += value; remove => buildSimEventEngine.BuildMode -= value; } /// /// An event that fires right after a game is completely loaded. /// public static event EventHandler Enter { add => gameEngine.EnterGame += value; remove => gameEngine.EnterGame -= value; } /// /// An event that fires right before a game returns to the main menu. /// At this point, Gamecraft is transitioning state so many things are invalid/unstable here. /// public static event EventHandler Exit { add => gameEngine.ExitGame += value; remove => gameEngine.ExitGame -= value; } /// /// The game's unique menu identifier. /// /// The identifier. public uint Id { get; private set; } /// /// The game's unique menu EGID. /// /// The egid. public EGID EGID { get; private set; } /// /// Whether the game is a (valid) menu item. /// /// true if menu item; otherwise, false. public bool MenuItem { get => menuMode && hasId; } /// /// The game's name. /// /// The name. public string Name { get { if (!VerifyMode()) return null; if (menuMode) return menuEngine.GetGameInfo(EGID).GameName; return GameMode.SaveGameDetails.Name; } set { if (!VerifyMode()) return; if (menuMode) { menuEngine.SetGameName(EGID, value); } else { GameMode.SaveGameDetails.Name = value; } } } /// /// The game's description. /// /// The description. public string Description { get { if (!VerifyMode()) return null; if (menuMode) return menuEngine.GetGameInfo(EGID).GameDescription; return ""; } set { if (!VerifyMode()) return; if (menuMode) { menuEngine.SetGameDescription(EGID, value); } else { // No description exists in-game } } } /// /// The path to the game's save folder. /// /// The path. public string Path { get { if (!VerifyMode()) return null; if (menuMode) return menuEngine.GetGameInfo(EGID).SavedGamePath; return GameMode.SaveGameDetails.Folder; } set { if (!VerifyMode()) return; if (menuMode) { menuEngine.GetGameInfo(EGID).SavedGamePath.Set(value); } else { // this likely breaks things GameMode.SaveGameDetails = new SaveGameDetails(GameMode.SaveGameDetails.Name, value, GameMode.SaveGameDetails.WorkshopId); } } } /// /// The Steam Workshop Id of the game save. /// In most cases this is invalid and returns 0, so this can be ignored. /// /// The workshop identifier. public ulong WorkshopId { get { if (!VerifyMode()) return 0uL; if (menuMode) return 0uL; // MyGames don't have workshop IDs return GameMode.SaveGameDetails.WorkshopId; } set { VerifyMode(); if (menuMode) { // MyGames don't have workshop IDs // menuEngine.GetGameInfo(EGID).GameName.Set(value); } else { // this likely breaks things GameMode.SaveGameDetails = new SaveGameDetails(GameMode.SaveGameDetails.Name, GameMode.SaveGameDetails.Folder, value); } } } /// /// Whether the game is in simulation mode. /// /// true if is simulating; otherwise, false. public bool IsSimulating { get { if (!VerifyMode()) return false; return !menuMode && gameEngine.IsTimeRunningMode(); } set { if (!VerifyMode()) return; if (!menuMode && gameEngine.IsTimeRunningMode() != value) gameEngine.ToggleTimeMode(); } } /// /// Whether the game is in time-running mode. /// Alias of IsSimulating. /// /// true if is time running; otherwise, false. public bool IsTimeRunning { get => IsSimulating; set { IsSimulating = value; } } /// /// Whether the game is in time-stopped mode. /// /// true if is time stopped; otherwise, false. public bool IsTimeStopped { get { if (!VerifyMode()) return false; return !menuMode && gameEngine.IsTimeStoppedMode(); } set { if (!VerifyMode()) return; if (!menuMode && gameEngine.IsTimeStoppedMode() != value) gameEngine.ToggleTimeMode(); } } /// /// Toggles the time mode. /// public void ToggleTimeMode() { if (!VerifyMode()) return; if (menuMode || !gameEngine.IsInGame) { throw new AppStateException("Game menu item cannot toggle it's time mode"); } gameEngine.ToggleTimeMode(); } /// /// Load the game save. /// This happens asynchronously, so when this method returns the game not loaded yet. /// Use the Game.Enter event to perform operations after the game has completely loaded. /// public void EnterGame() { if (!VerifyMode()) return; if (!hasId) { throw new GameNotFoundException("Game has an invalid ID"); } ISchedulable task = new Once(() => { menuEngine.EnterGame(EGID); this.menuMode = false; }); Scheduler.Schedule(task); } /// /// Return to the menu. /// Part of this always happens asynchronously, so when this method returns the game has not exited yet. /// Use the Client.EnterMenu event to perform operations after the game has completely exited. /// /// If set to true, do this async. public void ExitGame(bool async = false) { if (!VerifyMode()) return; if (menuMode) { throw new GameNotFoundException("Cannot exit game using menu ID"); } gameEngine.ExitCurrentGame(async); this.menuMode = true; } /// /// Saves the game. /// Part of this happens asynchronously, so when this method returns the game has not been saved yet. /// public void SaveGame() { if (!VerifyMode()) return; if (menuMode) { throw new GameNotFoundException("Cannot save game using menu ID"); } gameEngine.SaveCurrentGame(); } /// /// Add information to the in-game debug display. /// When this object is garbage collected, this debug info is automatically removed. /// /// Debug info identifier. /// Content getter. public void AddDebugInfo(string id, Func contentGetter) { if (!VerifyMode()) return; if (menuMode) { throw new GameNotFoundException("Game object references a menu item but AddDebugInfo only works on the currently-loaded game"); } debugOverlayEngine.SetInfo(id, contentGetter); debugIds.Add(id); } /// /// Remove information from the in-game debug display. /// /// true, if debug info was removed, false otherwise. /// Debug info identifier. public bool RemoveDebugInfo(string id) { if (!VerifyMode()) return false; if (menuMode) { throw new GameNotFoundException("Game object references a menu item but RemoveDebugInfo only works on the currently-loaded game"); } if (!debugIds.Contains(id)) return false; debugOverlayEngine.RemoveInfo(id); return debugIds.Remove(id); } /// /// Gets the blocks in the game. /// This returns null when in a loading state, and throws AppStateException when in menu. /// /// The blocks in game. /// The block to search for. BlockIDs.Invalid will return all blocks. public Block[] GetBlocksInGame(BlockIDs filter = BlockIDs.Invalid) { if (!VerifyMode()) return null; if (menuMode) { throw new AppStateException("Game object references a menu item but GetBlocksInGame only works on the currently-loaded game"); } EGID[] blockEGIDs = gameEngine.GetAllBlocksInGame(filter); Block[] blocks = new Block[blockEGIDs.Length]; for (int b = 0; b < blockEGIDs.Length; b++) { blocks[b] = new Block(blockEGIDs[b]); } return blocks; } ~Game() { foreach (string id in debugIds) { debugOverlayEngine.RemoveInfo(id); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool VerifyMode() { if (menuMode && (!menuEngine.IsInMenu || gameEngine.IsInGame)) { // either game loading or API is broken return false; } if (!menuMode && (menuEngine.IsInMenu || !gameEngine.IsInGame)) { // either game loading or API is broken return false; } return true; } internal static void Init() { GameEngineManager.AddGameEngine(gameEngine); GameEngineManager.AddGameEngine(debugOverlayEngine); MenuEngineManager.AddMenuEngine(menuEngine); } internal static void InitDeterministic(StateSyncRegistrationHelper stateSyncReg) { stateSyncReg.AddDeterministicEngine(buildSimEventEngine); } } }