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();
}
///
/// The mode of the game.
///
public CurrentGameMode Mode
{
get
{
if (menuMode || !VerifyMode()) return CurrentGameMode.None;
return (CurrentGameMode) GameMode.CurrentMode;
}
}
///
/// 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);
}
}
}