@@ -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 | |||
{ | |||
@@ -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 | |||
@@ -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; | |||
@@ -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); | |||
@@ -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> | |||
@@ -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) | |||
@@ -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 | |||
{ | |||
@@ -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..."; | |||
@@ -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(); | |||