Document App and Test additions (+ minor tweaks)

This commit is contained in:
NGnius (Graham) 2020-06-23 13:49:42 -04:00
parent 0019b7c073
commit 189c3ca2a5
9 changed files with 249 additions and 8 deletions

View file

@ -5,6 +5,10 @@ using GamecraftModdingAPI.Tests;
namespace GamecraftModdingAPI.App namespace GamecraftModdingAPI.App
{ {
#if TEST #if TEST
/// <summary>
/// App callbacks tests.
/// Only available in TEST builds.
/// </summary>
[APITestClass] [APITestClass]
public static class AppCallbacksTest public static class AppCallbacksTest
{ {

View file

@ -6,32 +6,56 @@ using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.App namespace GamecraftModdingAPI.App
{ {
/// <summary>
/// The Gamecraft application that is running this code right now.
/// </summary>
public class Client public class Client
{ {
// extensible engine
protected static AppEngine appEngine = new AppEngine(); protected static AppEngine appEngine = new AppEngine();
/// <summary>
/// An event that fires whenever the main menu is loaded.
/// </summary>
public static event EventHandler<MenuEventArgs> EnterMenu public static event EventHandler<MenuEventArgs> EnterMenu
{ {
add => appEngine.EnterMenu += value; add => appEngine.EnterMenu += value;
remove => appEngine.EnterMenu -= value; remove => appEngine.EnterMenu -= value;
} }
/// <summary>
/// An event that fire whenever the main menu is exited.
/// </summary>
public static event EventHandler<MenuEventArgs> ExitMenu public static event EventHandler<MenuEventArgs> ExitMenu
{ {
add => appEngine.ExitMenu += value; add => appEngine.ExitMenu += value;
remove => appEngine.ExitMenu -= value; remove => appEngine.ExitMenu -= value;
} }
/// <summary>
/// Gamecraft build version string.
/// Usually this is in the form YYYY.mm.DD.HH.MM.SS
/// </summary>
/// <value>The version.</value>
public string Version public string Version
{ {
get => Application.version; get => Application.version;
} }
/// <summary>
/// Unity version string.
/// </summary>
/// <value>The unity version.</value>
public string UnityVersion public string UnityVersion
{ {
get => Application.unityVersion; get => Application.unityVersion;
} }
/// <summary>
/// Game saves currently visible in the menu.
/// These take a second to completely populate after the EnterMenu event fires.
/// </summary>
/// <value>My games.</value>
public Game[] MyGames public Game[] MyGames
{ {
get get

View file

@ -9,12 +9,17 @@ using Svelto.ECS;
using GamecraftModdingAPI.Tasks; using GamecraftModdingAPI.Tasks;
using GamecraftModdingAPI.Utility; using GamecraftModdingAPI.Utility;
// TODO: exceptions
namespace GamecraftModdingAPI.App 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 public class Game
{ {
// extensible engines
protected static GameGameEngine gameEngine = new GameGameEngine(); protected static GameGameEngine gameEngine = new GameGameEngine();
protected static GameMenuEngine menuEngine = new GameMenuEngine(); protected static GameMenuEngine menuEngine = new GameMenuEngine();
protected static DebugInterfaceEngine debugOverlayEngine = new DebugInterfaceEngine(); protected static DebugInterfaceEngine debugOverlayEngine = new DebugInterfaceEngine();
@ -25,10 +30,18 @@ namespace GamecraftModdingAPI.App
private bool menuMode = true; private bool menuMode = true;
private bool hasId = false; 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)) 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) public Game(EGID id)
{ {
this.Id = id.entityID; this.Id = id.entityID;
@ -38,6 +51,10 @@ namespace GamecraftModdingAPI.App
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 (!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() public Game()
{ {
menuMode = false; menuMode = false;
@ -45,11 +62,21 @@ namespace GamecraftModdingAPI.App
if (menuEngine.IsInMenu) throw new GameNotFoundException("Game not found."); 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() public static Game CurrentGame()
{ {
return new Game(); 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() public static Game NewGame()
{ {
if (!menuEngine.IsInMenu) throw new AppStateException("New Game cannot be created while not in a menu."); if (!menuEngine.IsInMenu) throw new AppStateException("New Game cannot be created while not in a menu.");
@ -59,47 +86,77 @@ namespace GamecraftModdingAPI.App
return new Game(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 public static event EventHandler<GameEventArgs> Simulate
{ {
add => buildSimEventEngine.SimulationMode += value; add => buildSimEventEngine.SimulationMode += value;
remove => 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 public static event EventHandler<GameEventArgs> Edit
{ {
add => buildSimEventEngine.BuildMode += value; add => buildSimEventEngine.BuildMode += value;
remove => 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 public static event EventHandler<GameEventArgs> Enter
{ {
add => gameEngine.EnterGame += value; add => gameEngine.EnterGame += value;
remove => 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 public static event EventHandler<GameEventArgs> Exit
{ {
add => gameEngine.ExitGame += value; add => gameEngine.ExitGame += value;
remove => gameEngine.ExitGame -= value; remove => gameEngine.ExitGame -= value;
} }
/// <summary>
/// The game's unique menu identifier.
/// </summary>
/// <value>The identifier.</value>
public uint Id public uint Id
{ {
get; get;
private set; private set;
} }
/// <summary>
/// The game's unique menu EGID.
/// </summary>
/// <value>The egid.</value>
public EGID EGID public EGID EGID
{ {
get; get;
private set; 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 public bool MenuItem
{ {
get => menuMode && hasId; get => menuMode && hasId;
} }
/// <summary>
/// The game's name.
/// </summary>
/// <value>The name.</value>
public string Name public string Name
{ {
get get
@ -123,6 +180,10 @@ namespace GamecraftModdingAPI.App
} }
} }
/// <summary>
/// The game's description.
/// </summary>
/// <value>The description.</value>
public string Description public string Description
{ {
get get
@ -146,6 +207,10 @@ namespace GamecraftModdingAPI.App
} }
} }
/// <summary>
/// The path to the game's save folder.
/// </summary>
/// <value>The path.</value>
public string Path public string Path
{ {
get get
@ -170,6 +235,11 @@ namespace GamecraftModdingAPI.App
} }
} }
/// <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 public ulong WorkshopId
{ {
get get
@ -195,6 +265,10 @@ namespace GamecraftModdingAPI.App
} }
} }
/// <summary>
/// Whether the game is in simulation mode.
/// </summary>
/// <value><c>true</c> if is simulating; otherwise, <c>false</c>.</value>
public bool IsSimulating public bool IsSimulating
{ {
get get
@ -211,6 +285,11 @@ namespace GamecraftModdingAPI.App
} }
} }
/// <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 public bool IsTimeRunning
{ {
get => IsSimulating; get => IsSimulating;
@ -221,6 +300,10 @@ namespace GamecraftModdingAPI.App
} }
} }
/// <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 public bool IsTimeStopped
{ {
get get
@ -237,6 +320,9 @@ namespace GamecraftModdingAPI.App
} }
} }
/// <summary>
/// Toggles the time mode.
/// </summary>
public void ToggleTimeMode() public void ToggleTimeMode()
{ {
if (!VerifyMode()) return; if (!VerifyMode()) return;
@ -247,6 +333,11 @@ namespace GamecraftModdingAPI.App
gameEngine.ToggleTimeMode(); gameEngine.ToggleTimeMode();
} }
/// <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() public void EnterGame()
{ {
if (!VerifyMode()) return; if (!VerifyMode()) return;
@ -258,17 +349,43 @@ namespace GamecraftModdingAPI.App
Scheduler.Schedule(task); Scheduler.Schedule(task);
} }
public void ExitGame() /// <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 (!VerifyMode()) return;
if (menuMode) if (menuMode)
{ {
throw new GameNotFoundException("Cannot exit game using menu ID"); throw new GameNotFoundException("Cannot exit game using menu ID");
} }
ISchedulable task = new Once(() => { gameEngine.ExitCurrentGame(); this.menuMode = true; }); gameEngine.ExitCurrentGame(async);
Scheduler.Schedule(task); 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) public void AddDebugInfo(string id, Func<string> contentGetter)
{ {
if (!VerifyMode()) return; if (!VerifyMode()) return;
@ -280,6 +397,11 @@ namespace GamecraftModdingAPI.App
debugIds.Add(id); 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) public bool RemoveDebugInfo(string id)
{ {
if (!VerifyMode()) return false; if (!VerifyMode()) return false;

View file

@ -47,9 +47,18 @@ namespace GamecraftModdingAPI.App
private set; private set;
} = false; } = false;
public void ExitCurrentGame() public void ExitCurrentGame(bool async = false)
{ {
ExitCurrentGameAsync().RunOn(Lean.EveryFrameStepRunner_RUNS_IN_TIME_STOPPED_AND_RUNNING); if (async)
{
ExitCurrentGameAsync().RunOn(Lean.EveryFrameStepRunner_RUNS_IN_TIME_STOPPED_AND_RUNNING);
}
else
{
entitiesDB.QueryEntity<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID).WantsToQuit = true;
entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID);
}
} }
public IEnumerator<TaskContract> ExitCurrentGameAsync() public IEnumerator<TaskContract> ExitCurrentGameAsync()
@ -62,6 +71,14 @@ namespace GamecraftModdingAPI.App
entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID); entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID);
} }
public void SaveCurrentGame()
{
ref GameSceneEntityStruct gses = ref entitiesDB.QueryEntity<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID);
gses.LoadAfterSaving = false;
gses.SaveNow = true;
entitiesDB.PublishEntityChange<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID);
}
public bool IsTimeRunningMode() public bool IsTimeRunningMode()
{ {
return TimeRunningModeUtil.IsTimeRunningMode(entitiesDB); return TimeRunningModeUtil.IsTimeRunningMode(entitiesDB);

View file

@ -45,6 +45,15 @@ namespace GamecraftModdingAPI
return playerEngine.ExistsById(player); return playerEngine.ExistsById(player);
} }
/// <summary>
/// The amount of Players in the current game.
/// </summary>
/// <returns>The count.</returns>
public static uint Count()
{
return playerEngine.GetAllPlayerCount();
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="T:GamecraftModdingAPI.Player"/> class. /// Initializes a new instance of the <see cref="T:GamecraftModdingAPI.Player"/> class.
/// </summary> /// </summary>

View file

@ -65,6 +65,26 @@ namespace GamecraftModdingAPI.Players
return uint.MaxValue; return uint.MaxValue;
} }
public uint GetAllPlayerCount()
{
uint count = 0;
foreach (ExclusiveGroupStruct eg in PlayersExclusiveGroups.AllPlayers)
{
count += entitiesDB.Count<PlayerIDStruct>(eg);
}
return count;
}
public uint GetLocalPlayerCount()
{
return entitiesDB.Count<PlayerIDStruct>(PlayersExclusiveGroups.LocalPlayers);
}
public uint GetRemotePlayerCount()
{
return entitiesDB.Count<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers);
}
public bool ExistsById(uint playerId) public bool ExistsById(uint playerId)
{ {
return entitiesDB.Exists<PlayerIDStruct>(playerId, PlayersExclusiveGroups.LocalPlayers) return entitiesDB.Exists<PlayerIDStruct>(playerId, PlayersExclusiveGroups.LocalPlayers)
@ -109,7 +129,7 @@ namespace GamecraftModdingAPI.Players
{ {
return ((Quaternion) rbes.rotation).eulerAngles; return ((Quaternion) rbes.rotation).eulerAngles;
} }
return default; return default(float3);
} }
public bool SetRotation(uint playerId, float3 value) public bool SetRotation(uint playerId, float3 value)
@ -174,7 +194,7 @@ namespace GamecraftModdingAPI.Players
{ {
return rbes.physicsMass; return rbes.physicsMass;
} }
return default; return default(PhysicsMass);
} }
public bool SetInverseMass(uint playerId, float inverseMass) public bool SetInverseMass(uint playerId, float inverseMass)

View file

@ -1,6 +1,10 @@
using System; using System;
namespace GamecraftModdingAPI.Tests namespace GamecraftModdingAPI.Tests
{ {
/// <summary>
/// Test type.
/// When provided to APITestCaseAttribute, this dictates when the test case is called.
/// </summary>
public enum TestType public enum TestType
{ {
Menu, Menu,
@ -9,6 +13,10 @@ namespace GamecraftModdingAPI.Tests
EditMode, EditMode,
} }
/// <summary>
/// API Test Class attribute.
/// Classes without this attribute will be ignored when searching for test cases.
/// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class APITestClassAttribute : Attribute public class APITestClassAttribute : Attribute
{ {
@ -20,6 +28,10 @@ namespace GamecraftModdingAPI.Tests
} }
} }
/// <summary>
/// API Test Case attribute.
/// Static methods with this attribute will be called when the API test system is running.
/// </summary>
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public class APITestCaseAttribute : Attribute public class APITestCaseAttribute : Attribute
{ {
@ -31,12 +43,20 @@ namespace GamecraftModdingAPI.Tests
} }
} }
/// <summary>
/// API Test StartUp attribute.
/// Static methods with this attribute will be called before any test case is run by the API test system.
/// </summary>
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public class APITestStartUpAttribute : Attribute public class APITestStartUpAttribute : Attribute
{ {
} }
/// <summary>
/// API Test TearDown attribute.
/// Static methods with this attribute will be called after all API test system test cases have completed (failed or succeeded).
/// </summary>
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public class APITestTearDownAttribute : Attribute public class APITestTearDownAttribute : Attribute
{ {

View file

@ -5,6 +5,9 @@ using System.Runtime.CompilerServices;
namespace GamecraftModdingAPI.Tests namespace GamecraftModdingAPI.Tests
{ {
/// <summary>
/// API test system assertion utilities.
/// </summary>
public static class Assert public static class Assert
{ {
private static StreamWriter logFile = null; private static StreamWriter logFile = null;
@ -19,6 +22,11 @@ namespace GamecraftModdingAPI.Tests
private const string INFO = "DEBUG: "; private const string INFO = "DEBUG: ";
/// <summary>
/// Log a message to the test log.
/// </summary>
/// <param name="msg">Message.</param>
/// <param name="end">Message ending.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Log(string msg, string end = "\n") public static void Log(string msg, string end = "\n")
{ {
@ -27,6 +35,16 @@ namespace GamecraftModdingAPI.Tests
logFile.Flush(); logFile.Flush();
} }
/// <summary>
/// Asserts that the event receives a callback... eventually.
/// Add the eventhandler returned by this method to the relevant event.
/// This does not assert that the callback happens under that event's intended circumstances.
/// Add another event handler to assert specific circumstance requirements.
/// </summary>
/// <returns>The callback event handler.</returns>
/// <param name="eventName">Event name.</param>
/// <param name="eventMsg">Event error message.</param>
/// <typeparam name="T">The event handler callback argument object.</typeparam>
public static EventHandler<T> CallsBack<T>(string eventName, string eventMsg = null) public static EventHandler<T> CallsBack<T>(string eventName, string eventMsg = null)
{ {
if (eventMsg == null) eventMsg = $"expected callback to {eventName} but it never occurred..."; if (eventMsg == null) eventMsg = $"expected callback to {eventName} but it never occurred...";

View file

@ -15,6 +15,9 @@ using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Tests namespace GamecraftModdingAPI.Tests
{ {
/// <summary>
/// API test system root class.
/// </summary>
public static class TestRoot public static class TestRoot
{ {
public static bool AutoShutdown = true; public static bool AutoShutdown = true;
@ -229,6 +232,10 @@ namespace GamecraftModdingAPI.Tests
} }
} }
/// <summary>
/// Runs the tests.
/// </summary>
/// <param name="asm">Assembly to search for tests. When set to null, this uses the GamecraftModdingAPI assembly. </param>
public static void RunTests(Assembly asm = null) public static void RunTests(Assembly asm = null)
{ {
if (asm == null) asm = Assembly.GetExecutingAssembly(); if (asm == null) asm = Assembly.GetExecutingAssembly();