From 189c3ca2a5a545d6cf48f43e3aee791b063cbca0 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Tue, 23 Jun 2020 13:49:42 -0400 Subject: [PATCH] Document App and Test additions (+ minor tweaks) --- GamecraftModdingAPI/App/AppCallbacksTest.cs | 4 + GamecraftModdingAPI/App/Client.cs | 24 ++++ GamecraftModdingAPI/App/Game.cs | 130 +++++++++++++++++- GamecraftModdingAPI/App/GameGameEngine.cs | 21 ++- GamecraftModdingAPI/Player.cs | 9 ++ GamecraftModdingAPI/Players/PlayerEngine.cs | 24 +++- .../Tests/APITestAttributes.cs | 20 +++ GamecraftModdingAPI/Tests/Assert.cs | 18 +++ GamecraftModdingAPI/Tests/TestRoot.cs | 7 + 9 files changed, 249 insertions(+), 8 deletions(-) diff --git a/GamecraftModdingAPI/App/AppCallbacksTest.cs b/GamecraftModdingAPI/App/AppCallbacksTest.cs index 0c2987b..041ac50 100644 --- a/GamecraftModdingAPI/App/AppCallbacksTest.cs +++ b/GamecraftModdingAPI/App/AppCallbacksTest.cs @@ -5,6 +5,10 @@ using GamecraftModdingAPI.Tests; namespace GamecraftModdingAPI.App { #if TEST + /// + /// App callbacks tests. + /// Only available in TEST builds. + /// [APITestClass] public static class AppCallbacksTest { diff --git a/GamecraftModdingAPI/App/Client.cs b/GamecraftModdingAPI/App/Client.cs index 2f1f005..e95d9f3 100644 --- a/GamecraftModdingAPI/App/Client.cs +++ b/GamecraftModdingAPI/App/Client.cs @@ -6,32 +6,56 @@ using GamecraftModdingAPI.Utility; namespace GamecraftModdingAPI.App { + /// + /// The Gamecraft application that is running this code right now. + /// public class Client { + // extensible engine protected static AppEngine appEngine = new AppEngine(); + /// + /// An event that fires whenever the main menu is loaded. + /// public static event EventHandler EnterMenu { add => appEngine.EnterMenu += value; remove => appEngine.EnterMenu -= value; } + /// + /// An event that fire whenever the main menu is exited. + /// public static event EventHandler ExitMenu { add => appEngine.ExitMenu += value; remove => appEngine.ExitMenu -= value; } + /// + /// Gamecraft build version string. + /// Usually this is in the form YYYY.mm.DD.HH.MM.SS + /// + /// The version. public string Version { get => Application.version; } + /// + /// Unity version string. + /// + /// The unity version. public string UnityVersion { get => Application.unityVersion; } + /// + /// Game saves currently visible in the menu. + /// These take a second to completely populate after the EnterMenu event fires. + /// + /// My games. public Game[] MyGames { get diff --git a/GamecraftModdingAPI/App/Game.cs b/GamecraftModdingAPI/App/Game.cs index 0d31abf..3fd4900 100644 --- a/GamecraftModdingAPI/App/Game.cs +++ b/GamecraftModdingAPI/App/Game.cs @@ -9,12 +9,17 @@ using Svelto.ECS; using GamecraftModdingAPI.Tasks; using GamecraftModdingAPI.Utility; -// TODO: exceptions 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(); @@ -25,10 +30,18 @@ namespace GamecraftModdingAPI.App 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; @@ -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?)"); } + /// + /// Initializes a new instance of the class without id. + /// This is assumed to be the current game. + /// public Game() { menuMode = false; @@ -45,11 +62,21 @@ namespace GamecraftModdingAPI.App 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."); @@ -59,47 +86,77 @@ namespace GamecraftModdingAPI.App 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 @@ -123,6 +180,10 @@ namespace GamecraftModdingAPI.App } } + /// + /// The game's description. + /// + /// The description. public string Description { get @@ -146,6 +207,10 @@ namespace GamecraftModdingAPI.App } } + /// + /// The path to the game's save folder. + /// + /// The path. public string Path { get @@ -170,6 +235,11 @@ namespace GamecraftModdingAPI.App } } + /// + /// 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 @@ -195,6 +265,10 @@ namespace GamecraftModdingAPI.App } } + /// + /// Whether the game is in simulation mode. + /// + /// true if is simulating; otherwise, false. public bool IsSimulating { get @@ -211,6 +285,11 @@ namespace GamecraftModdingAPI.App } } + /// + /// Whether the game is in time-running mode. + /// Alias of IsSimulating. + /// + /// true if is time running; otherwise, false. public bool IsTimeRunning { get => IsSimulating; @@ -221,6 +300,10 @@ namespace GamecraftModdingAPI.App } } + /// + /// Whether the game is in time-stopped mode. + /// + /// true if is time stopped; otherwise, false. public bool IsTimeStopped { get @@ -237,6 +320,9 @@ namespace GamecraftModdingAPI.App } } + /// + /// Toggles the time mode. + /// public void ToggleTimeMode() { if (!VerifyMode()) return; @@ -247,6 +333,11 @@ namespace GamecraftModdingAPI.App 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; @@ -258,17 +349,43 @@ namespace GamecraftModdingAPI.App Scheduler.Schedule(task); } - public void ExitGame() + /// + /// 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"); } - ISchedulable task = new Once(() => { gameEngine.ExitCurrentGame(); this.menuMode = true; }); - Scheduler.Schedule(task); + 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; @@ -280,6 +397,11 @@ namespace GamecraftModdingAPI.App 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; diff --git a/GamecraftModdingAPI/App/GameGameEngine.cs b/GamecraftModdingAPI/App/GameGameEngine.cs index 9253af2..d46eb32 100644 --- a/GamecraftModdingAPI/App/GameGameEngine.cs +++ b/GamecraftModdingAPI/App/GameGameEngine.cs @@ -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(CommonExclusiveGroups.GameSceneEGID).WantsToQuit = true; + entitiesDB.PublishEntityChange(CommonExclusiveGroups.GameSceneEGID); + } + } public IEnumerator ExitCurrentGameAsync() @@ -62,6 +71,14 @@ namespace GamecraftModdingAPI.App entitiesDB.PublishEntityChange(CommonExclusiveGroups.GameSceneEGID); } + public void SaveCurrentGame() + { + ref GameSceneEntityStruct gses = ref entitiesDB.QueryEntity(CommonExclusiveGroups.GameSceneEGID); + gses.LoadAfterSaving = false; + gses.SaveNow = true; + entitiesDB.PublishEntityChange(CommonExclusiveGroups.GameSceneEGID); + } + public bool IsTimeRunningMode() { return TimeRunningModeUtil.IsTimeRunningMode(entitiesDB); diff --git a/GamecraftModdingAPI/Player.cs b/GamecraftModdingAPI/Player.cs index efb381e..3d80e7b 100644 --- a/GamecraftModdingAPI/Player.cs +++ b/GamecraftModdingAPI/Player.cs @@ -45,6 +45,15 @@ namespace GamecraftModdingAPI return playerEngine.ExistsById(player); } + /// + /// The amount of Players in the current game. + /// + /// The count. + public static uint Count() + { + return playerEngine.GetAllPlayerCount(); + } + /// /// Initializes a new instance of the class. /// diff --git a/GamecraftModdingAPI/Players/PlayerEngine.cs b/GamecraftModdingAPI/Players/PlayerEngine.cs index 2bb29a7..1bcce50 100644 --- a/GamecraftModdingAPI/Players/PlayerEngine.cs +++ b/GamecraftModdingAPI/Players/PlayerEngine.cs @@ -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(eg); + } + return count; + } + + public uint GetLocalPlayerCount() + { + return entitiesDB.Count(PlayersExclusiveGroups.LocalPlayers); + } + + public uint GetRemotePlayerCount() + { + return entitiesDB.Count(PlayersExclusiveGroups.RemotePlayers); + } + public bool ExistsById(uint playerId) { return entitiesDB.Exists(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) diff --git a/GamecraftModdingAPI/Tests/APITestAttributes.cs b/GamecraftModdingAPI/Tests/APITestAttributes.cs index 8506a56..cf9da49 100644 --- a/GamecraftModdingAPI/Tests/APITestAttributes.cs +++ b/GamecraftModdingAPI/Tests/APITestAttributes.cs @@ -1,6 +1,10 @@ using System; namespace GamecraftModdingAPI.Tests { + /// + /// Test type. + /// When provided to APITestCaseAttribute, this dictates when the test case is called. + /// public enum TestType { Menu, @@ -9,6 +13,10 @@ namespace GamecraftModdingAPI.Tests EditMode, } + /// + /// API Test Class attribute. + /// Classes without this attribute will be ignored when searching for test cases. + /// [AttributeUsage(AttributeTargets.Class)] public class APITestClassAttribute : Attribute { @@ -20,6 +28,10 @@ namespace GamecraftModdingAPI.Tests } } + /// + /// API Test Case attribute. + /// Static methods with this attribute will be called when the API test system is running. + /// [AttributeUsage(AttributeTargets.Method)] public class APITestCaseAttribute : Attribute { @@ -31,12 +43,20 @@ namespace GamecraftModdingAPI.Tests } } + /// + /// API Test StartUp attribute. + /// Static methods with this attribute will be called before any test case is run by the API test system. + /// [AttributeUsage(AttributeTargets.Method)] public class APITestStartUpAttribute : Attribute { } + /// + /// API Test TearDown attribute. + /// Static methods with this attribute will be called after all API test system test cases have completed (failed or succeeded). + /// [AttributeUsage(AttributeTargets.Method)] public class APITestTearDownAttribute : Attribute { diff --git a/GamecraftModdingAPI/Tests/Assert.cs b/GamecraftModdingAPI/Tests/Assert.cs index fb03013..d888f45 100644 --- a/GamecraftModdingAPI/Tests/Assert.cs +++ b/GamecraftModdingAPI/Tests/Assert.cs @@ -5,6 +5,9 @@ using System.Runtime.CompilerServices; namespace GamecraftModdingAPI.Tests { + /// + /// API test system assertion utilities. + /// public static class Assert { private static StreamWriter logFile = null; @@ -19,6 +22,11 @@ namespace GamecraftModdingAPI.Tests private const string INFO = "DEBUG: "; + /// + /// Log a message to the test log. + /// + /// Message. + /// Message ending. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Log(string msg, string end = "\n") { @@ -27,6 +35,16 @@ namespace GamecraftModdingAPI.Tests logFile.Flush(); } + /// + /// 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. + /// + /// The callback event handler. + /// Event name. + /// Event error message. + /// The event handler callback argument object. public static EventHandler CallsBack(string eventName, string eventMsg = null) { if (eventMsg == null) eventMsg = $"expected callback to {eventName} but it never occurred..."; diff --git a/GamecraftModdingAPI/Tests/TestRoot.cs b/GamecraftModdingAPI/Tests/TestRoot.cs index 167173f..99f77b7 100644 --- a/GamecraftModdingAPI/Tests/TestRoot.cs +++ b/GamecraftModdingAPI/Tests/TestRoot.cs @@ -15,6 +15,9 @@ using GamecraftModdingAPI.Utility; namespace GamecraftModdingAPI.Tests { + /// + /// API test system root class. + /// public static class TestRoot { public static bool AutoShutdown = true; @@ -229,6 +232,10 @@ namespace GamecraftModdingAPI.Tests } } + /// + /// Runs the tests. + /// + /// Assembly to search for tests. When set to null, this uses the GamecraftModdingAPI assembly. public static void RunTests(Assembly asm = null) { if (asm == null) asm = Assembly.GetExecutingAssembly();