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
{
#if TEST
/// <summary>
/// App callbacks tests.
/// Only available in TEST builds.
/// </summary>
[APITestClass]
public static class AppCallbacksTest
{

View file

@ -6,32 +6,56 @@ using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.App
{
/// <summary>
/// The Gamecraft application that is running this code right now.
/// </summary>
public class Client
{
// extensible engine
protected static AppEngine appEngine = new AppEngine();
/// <summary>
/// An event that fires whenever the main menu is loaded.
/// </summary>
public static event EventHandler<MenuEventArgs> EnterMenu
{
add => 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
{
add => 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
{
get => Application.version;
}
/// <summary>
/// Unity version string.
/// </summary>
/// <value>The unity version.</value>
public string 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
{
get

View file

@ -9,12 +9,17 @@ using Svelto.ECS;
using GamecraftModdingAPI.Tasks;
using GamecraftModdingAPI.Utility;
// TODO: exceptions
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();
@ -25,10 +30,18 @@ namespace GamecraftModdingAPI.App
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;
@ -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?)");
}
/// <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;
@ -45,11 +62,21 @@ namespace GamecraftModdingAPI.App
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.");
@ -59,47 +86,77 @@ namespace GamecraftModdingAPI.App
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
@ -123,6 +180,10 @@ namespace GamecraftModdingAPI.App
}
}
/// <summary>
/// The game's description.
/// </summary>
/// <value>The description.</value>
public string Description
{
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
{
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
{
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
{
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
{
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
{
get
@ -237,6 +320,9 @@ namespace GamecraftModdingAPI.App
}
}
/// <summary>
/// Toggles the time mode.
/// </summary>
public void ToggleTimeMode()
{
if (!VerifyMode()) return;
@ -247,6 +333,11 @@ namespace GamecraftModdingAPI.App
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()
{
if (!VerifyMode()) return;
@ -258,17 +349,43 @@ namespace GamecraftModdingAPI.App
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 (menuMode)
{
throw new GameNotFoundException("Cannot exit game using menu ID");
}
ISchedulable task = new Once(() => { gameEngine.ExitCurrentGame(); this.menuMode = true; });
Scheduler.Schedule(task);
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;
@ -280,6 +397,11 @@ namespace GamecraftModdingAPI.App
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;

View file

@ -47,9 +47,18 @@ namespace GamecraftModdingAPI.App
private set;
} = 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()
@ -62,6 +71,14 @@ namespace GamecraftModdingAPI.App
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()
{
return TimeRunningModeUtil.IsTimeRunningMode(entitiesDB);

View file

@ -45,6 +45,15 @@ namespace GamecraftModdingAPI
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>
/// Initializes a new instance of the <see cref="T:GamecraftModdingAPI.Player"/> class.
/// </summary>

View file

@ -65,6 +65,26 @@ namespace GamecraftModdingAPI.Players
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)
{
return entitiesDB.Exists<PlayerIDStruct>(playerId, PlayersExclusiveGroups.LocalPlayers)
@ -109,7 +129,7 @@ namespace GamecraftModdingAPI.Players
{
return ((Quaternion) rbes.rotation).eulerAngles;
}
return default;
return default(float3);
}
public bool SetRotation(uint playerId, float3 value)
@ -174,7 +194,7 @@ namespace GamecraftModdingAPI.Players
{
return rbes.physicsMass;
}
return default;
return default(PhysicsMass);
}
public bool SetInverseMass(uint playerId, float inverseMass)

View file

@ -1,6 +1,10 @@
using System;
namespace GamecraftModdingAPI.Tests
{
/// <summary>
/// Test type.
/// When provided to APITestCaseAttribute, this dictates when the test case is called.
/// </summary>
public enum TestType
{
Menu,
@ -9,6 +13,10 @@ namespace GamecraftModdingAPI.Tests
EditMode,
}
/// <summary>
/// API Test Class attribute.
/// Classes without this attribute will be ignored when searching for test cases.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
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)]
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)]
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)]
public class APITestTearDownAttribute : Attribute
{

View file

@ -5,6 +5,9 @@ using System.Runtime.CompilerServices;
namespace GamecraftModdingAPI.Tests
{
/// <summary>
/// API test system assertion utilities.
/// </summary>
public static class Assert
{
private static StreamWriter logFile = null;
@ -19,6 +22,11 @@ namespace GamecraftModdingAPI.Tests
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)]
public static void Log(string msg, string end = "\n")
{
@ -27,6 +35,16 @@ namespace GamecraftModdingAPI.Tests
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)
{
if (eventMsg == null) eventMsg = $"expected callback to {eventName} but it never occurred...";

View file

@ -15,6 +15,9 @@ using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Tests
{
/// <summary>
/// API test system root class.
/// </summary>
public static class TestRoot
{
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)
{
if (asm == null) asm = Assembly.GetExecutingAssembly();