NorbiPeti
f30dcd251f
Added support for getting the player's current building mode (build, color, config, blueprint) Added support for getting the current game's mode (building, playing, prefab etc.)
489 lines
14 KiB
C#
489 lines
14 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
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<string> debugIds = new List<string>();
|
|
|
|
private bool menuMode = true;
|
|
private bool hasId = false;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:GamecraftModdingAPI.App.Game"/> class.
|
|
/// </summary>
|
|
/// <param name="id">Menu identifier.</param>
|
|
public Game(uint id) : this(new EGID(id, MyGamesScreenExclusiveGroups.MyGames))
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:GamecraftModdingAPI.App.Game"/> class.
|
|
/// </summary>
|
|
/// <param name="id">Menu identifier.</param>
|
|
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?)");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:GamecraftModdingAPI.App.Game"/> class without id.
|
|
/// This is assumed to be the current game.
|
|
/// </summary>
|
|
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.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the currently loaded game.
|
|
/// If in a menu, manipulating the returned object may not work as intended.
|
|
/// </summary>
|
|
/// <returns>The current game.</returns>
|
|
public static Game CurrentGame()
|
|
{
|
|
return new Game();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new game and adds it to the menu.
|
|
/// If not in a menu, this will throw AppStateException.
|
|
/// </summary>
|
|
/// <returns>The new game.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// An event that fires whenever a game is switched to simulation mode (time running mode).
|
|
/// </summary>
|
|
public static event EventHandler<GameEventArgs> Simulate
|
|
{
|
|
add => buildSimEventEngine.SimulationMode += value;
|
|
remove => buildSimEventEngine.SimulationMode -= value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// An event that fires whenever a game is switched to edit mode (time stopped mode).
|
|
/// This does not fire when a game is loaded.
|
|
/// </summary>
|
|
public static event EventHandler<GameEventArgs> Edit
|
|
{
|
|
add => buildSimEventEngine.BuildMode += value;
|
|
remove => buildSimEventEngine.BuildMode -= value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// An event that fires right after a game is completely loaded.
|
|
/// </summary>
|
|
public static event EventHandler<GameEventArgs> Enter
|
|
{
|
|
add => gameEngine.EnterGame += value;
|
|
remove => gameEngine.EnterGame -= value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public static event EventHandler<GameEventArgs> Exit
|
|
{
|
|
add => gameEngine.ExitGame += value;
|
|
remove => gameEngine.ExitGame -= value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The game's unique menu identifier.
|
|
/// </summary>
|
|
/// <value>The identifier.</value>
|
|
public uint Id
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The game's unique menu EGID.
|
|
/// </summary>
|
|
/// <value>The egid.</value>
|
|
public EGID EGID
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the game is a (valid) menu item.
|
|
/// </summary>
|
|
/// <value><c>true</c> if menu item; otherwise, <c>false</c>.</value>
|
|
public bool MenuItem
|
|
{
|
|
get => menuMode && hasId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The game's name.
|
|
/// </summary>
|
|
/// <value>The name.</value>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The game's description.
|
|
/// </summary>
|
|
/// <value>The description.</value>
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The path to the game's save folder.
|
|
/// </summary>
|
|
/// <value>The path.</value>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Steam Workshop Id of the game save.
|
|
/// In most cases this is invalid and returns 0, so this can be ignored.
|
|
/// </summary>
|
|
/// <value>The workshop identifier.</value>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the game is in simulation mode.
|
|
/// </summary>
|
|
/// <value><c>true</c> if is simulating; otherwise, <c>false</c>.</value>
|
|
public bool IsSimulating
|
|
{
|
|
get
|
|
{
|
|
if (!VerifyMode()) return false;
|
|
return !menuMode && gameEngine.IsTimeRunningMode();
|
|
}
|
|
|
|
set
|
|
{
|
|
if (!VerifyMode()) return;
|
|
if (!menuMode && gameEngine.IsTimeRunningMode() != value)
|
|
gameEngine.ToggleTimeMode();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the game is in time-running mode.
|
|
/// Alias of IsSimulating.
|
|
/// </summary>
|
|
/// <value><c>true</c> if is time running; otherwise, <c>false</c>.</value>
|
|
public bool IsTimeRunning
|
|
{
|
|
get => IsSimulating;
|
|
|
|
set
|
|
{
|
|
IsSimulating = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the game is in time-stopped mode.
|
|
/// </summary>
|
|
/// <value><c>true</c> if is time stopped; otherwise, <c>false</c>.</value>
|
|
public bool IsTimeStopped
|
|
{
|
|
get
|
|
{
|
|
if (!VerifyMode()) return false;
|
|
return !menuMode && gameEngine.IsTimeStoppedMode();
|
|
}
|
|
|
|
set
|
|
{
|
|
if (!VerifyMode()) return;
|
|
if (!menuMode && gameEngine.IsTimeStoppedMode() != value)
|
|
gameEngine.ToggleTimeMode();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Toggles the time mode.
|
|
/// </summary>
|
|
public void ToggleTimeMode()
|
|
{
|
|
if (!VerifyMode()) return;
|
|
if (menuMode || !gameEngine.IsInGame)
|
|
{
|
|
throw new AppStateException("Game menu item cannot toggle it's time mode");
|
|
}
|
|
gameEngine.ToggleTimeMode();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The mode of the game.
|
|
/// </summary>
|
|
public CurrentGameMode Mode
|
|
{
|
|
get
|
|
{
|
|
if (menuMode || !VerifyMode()) return CurrentGameMode.None;
|
|
return (CurrentGameMode) GameMode.CurrentMode;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="async">If set to <c>true</c>, do this async.</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves the game.
|
|
/// Part of this happens asynchronously, so when this method returns the game has not been saved yet.
|
|
/// </summary>
|
|
public void SaveGame()
|
|
{
|
|
if (!VerifyMode()) return;
|
|
if (menuMode)
|
|
{
|
|
throw new GameNotFoundException("Cannot save game using menu ID");
|
|
}
|
|
gameEngine.SaveCurrentGame();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add information to the in-game debug display.
|
|
/// When this object is garbage collected, this debug info is automatically removed.
|
|
/// </summary>
|
|
/// <param name="id">Debug info identifier.</param>
|
|
/// <param name="contentGetter">Content getter.</param>
|
|
public void AddDebugInfo(string id, Func<string> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove information from the in-game debug display.
|
|
/// </summary>
|
|
/// <returns><c>true</c>, if debug info was removed, <c>false</c> otherwise.</returns>
|
|
/// <param name="id">Debug info identifier.</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the blocks in the game.
|
|
/// This returns null when in a loading state, and throws AppStateException when in menu.
|
|
/// </summary>
|
|
/// <returns>The blocks in game.</returns>
|
|
/// <param name="filter">The block to search for. BlockIDs.Invalid will return all blocks.</param>
|
|
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);
|
|
}
|
|
}
|
|
}
|