Merge branch 'master' into customblocks
# Conflicts: # GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
This commit is contained in:
commit
f295f712b6
74 changed files with 4550 additions and 1275 deletions
67
Automation/bump_version.py
Executable file
67
Automation/bump_version.py
Executable file
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import re
|
||||
# this assumes a mostly semver-complient version number
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Increment GamecraftModdingAPI version")
|
||||
parser.add_argument('version', metavar="VN", type=str, help="The version number to increment, or the index of the number (zero-indexed).")
|
||||
args = parser.parse_args()
|
||||
|
||||
version_index = -1
|
||||
try:
|
||||
version_index = int(args.version)
|
||||
except Exception:
|
||||
if args.version.lower() == "major":
|
||||
version_index = 0
|
||||
elif args.version.lower() == "minor":
|
||||
version_index = 1
|
||||
elif args.version.lower() == "patch":
|
||||
version_index = 2
|
||||
|
||||
if version_index < 0:
|
||||
print("Could not parse version argument.")
|
||||
exit(version_index)
|
||||
|
||||
print(version_index)
|
||||
old_version = ""
|
||||
new_version = ""
|
||||
|
||||
with open("../GamecraftModdingAPI/GamecraftModdingAPI.csproj", "r") as xmlFile:
|
||||
print("Parsing GamecraftModdingAPI.csproj")
|
||||
fileStr = xmlFile.read()
|
||||
versionMatch = re.search(r"<Version>(.+)</Version>", fileStr)
|
||||
if versionMatch is None:
|
||||
print("Unable to find version number in GamecraftModdingAPI.csproj")
|
||||
exit(1)
|
||||
old_version = versionMatch.group(1)
|
||||
versionList = old_version.split(".")
|
||||
if len(versionList) <= version_index:
|
||||
print("Invalid version string")
|
||||
exit(1)
|
||||
versionList[version_index] = str(int(versionList[version_index]) + 1)
|
||||
for i in range(version_index + 1, len(versionList)):
|
||||
try:
|
||||
int(versionList[i])
|
||||
versionList[i] = "0"
|
||||
except Exception:
|
||||
tmp = versionList[i].split("-")
|
||||
tmp[0] = "0"
|
||||
versionList[i] = "-".join(tmp)
|
||||
new_version = ".".join(versionList)
|
||||
print(new_version)
|
||||
newFileContents = fileStr.replace("<Version>"+old_version+"</Version>", "<Version>"+new_version+"</Version>")
|
||||
|
||||
with open("../GamecraftModdingAPI/GamecraftModdingAPI.csproj", "w") as xmlFile:
|
||||
print("Writing new version to project file")
|
||||
xmlFile.write(newFileContents)
|
||||
|
||||
with open("../doxygen.conf", "r") as doxFile:
|
||||
print("Parsing doxygen.conf")
|
||||
doxStr = doxFile.read()
|
||||
newFileContents = doxStr.replace("= \"v" + old_version + "\"", "= \"v" + new_version + "\"")
|
||||
|
||||
with open("../doxygen.conf", "w") as doxFile:
|
||||
print("Writing new version to doxygen config")
|
||||
doxFile.write(newFileContents)
|
|
@ -9,12 +9,15 @@ Global
|
|||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Test|Any CPU = Test|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Test|Any CPU.ActiveCfg = Test|Any CPU
|
||||
{7FD5A7D8-4F3E-426A-B07D-7DC70442A4DF}.Test|Any CPU.Build.0 = Test|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
35
GamecraftModdingAPI/App/AppCallbacksTest.cs
Normal file
35
GamecraftModdingAPI/App/AppCallbacksTest.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
|
||||
using GamecraftModdingAPI.Tests;
|
||||
|
||||
namespace GamecraftModdingAPI.App
|
||||
{
|
||||
#if TEST
|
||||
/// <summary>
|
||||
/// App callbacks tests.
|
||||
/// Only available in TEST builds.
|
||||
/// </summary>
|
||||
[APITestClass]
|
||||
public static class AppCallbacksTest
|
||||
{
|
||||
[APITestStartUp]
|
||||
public static void StartUp()
|
||||
{
|
||||
// this could be split into 6 separate test cases
|
||||
Game.Enter += Assert.CallsBack<GameEventArgs>("GameEnter");
|
||||
Game.Exit += Assert.CallsBack<GameEventArgs>("GameExit");
|
||||
Game.Simulate += Assert.CallsBack<GameEventArgs>("GameSimulate");
|
||||
Game.Edit += Assert.CallsBack<GameEventArgs>("GameEdit");
|
||||
Client.EnterMenu += Assert.CallsBack<MenuEventArgs>("MenuEnter");
|
||||
Client.ExitMenu += Assert.CallsBack<MenuEventArgs>("MenuExit");
|
||||
}
|
||||
|
||||
[APITestCase(TestType.Game)]
|
||||
public static void Test()
|
||||
{
|
||||
// the test is actually completely implemented in StartUp()
|
||||
// this is here just so it looks less weird (not required)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
63
GamecraftModdingAPI/App/AppEngine.cs
Normal file
63
GamecraftModdingAPI/App/AppEngine.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
|
||||
using RobocraftX.GUI.MyGamesScreen;
|
||||
using RobocraftX.GUI;
|
||||
using Svelto.ECS;
|
||||
|
||||
using GamecraftModdingAPI.Engines;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
namespace GamecraftModdingAPI.App
|
||||
{
|
||||
public class AppEngine : IFactoryEngine
|
||||
{
|
||||
public event EventHandler<MenuEventArgs> EnterMenu;
|
||||
|
||||
public event EventHandler<MenuEventArgs> ExitMenu;
|
||||
|
||||
public IEntityFactory Factory { set; private get; }
|
||||
|
||||
public string Name => "GamecraftModdingAPIAppEngine";
|
||||
|
||||
public bool isRemovable => false;
|
||||
|
||||
public EntitiesDB entitiesDB { set; private get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IsInMenu = false;
|
||||
ExceptionUtil.InvokeEvent(ExitMenu, this, new MenuEventArgs { });
|
||||
}
|
||||
|
||||
public void Ready()
|
||||
{
|
||||
IsInMenu = true;
|
||||
ExceptionUtil.InvokeEvent(EnterMenu, this, new MenuEventArgs { });
|
||||
}
|
||||
|
||||
// app functionality
|
||||
|
||||
public bool IsInMenu
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = false;
|
||||
|
||||
public Game[] GetMyGames()
|
||||
{
|
||||
EntityCollection<MyGameDataEntityStruct> mgsevs = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
|
||||
Game[] games = new Game[mgsevs.count];
|
||||
for (int i = 0; i < mgsevs.count; i++)
|
||||
{
|
||||
Utility.Logging.MetaDebugLog($"Found game named {mgsevs[i].GameName}");
|
||||
games[i] = new Game(mgsevs[i].ID);
|
||||
}
|
||||
return games;
|
||||
}
|
||||
}
|
||||
|
||||
public struct MenuEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
}
|
42
GamecraftModdingAPI/App/AppExceptions.cs
Normal file
42
GamecraftModdingAPI/App/AppExceptions.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace GamecraftModdingAPI.App
|
||||
{
|
||||
public class AppException : GamecraftModdingAPIException
|
||||
{
|
||||
public AppException()
|
||||
{
|
||||
}
|
||||
|
||||
public AppException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AppException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class AppStateException : AppException
|
||||
{
|
||||
public AppStateException()
|
||||
{
|
||||
}
|
||||
|
||||
public AppStateException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class GameNotFoundException : AppException
|
||||
{
|
||||
public GameNotFoundException()
|
||||
{
|
||||
}
|
||||
|
||||
public GameNotFoundException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
159
GamecraftModdingAPI/App/Client.cs
Normal file
159
GamecraftModdingAPI/App/Client.cs
Normal file
|
@ -0,0 +1,159 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
|
||||
using RobocraftX.Services;
|
||||
using UnityEngine;
|
||||
|
||||
using GamecraftModdingAPI.Utility;
|
||||
using RobocraftX.Common;
|
||||
|
||||
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();
|
||||
|
||||
protected static Func<object> ErrorHandlerInstanceGetter;
|
||||
|
||||
protected static Action<object, Error> EnqueueError;
|
||||
|
||||
protected static Action<object> HandleErrorClosed;
|
||||
|
||||
/// <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
|
||||
{
|
||||
if (!appEngine.IsInMenu) return new Game[0];
|
||||
return appEngine.GetMyGames();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether Gamecraft is in the Main Menu
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if in menu; <c>false</c> when loading or in a game.</value>
|
||||
public bool InMenu
|
||||
{
|
||||
get => appEngine.IsInMenu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open a popup which prompts the user to click a button.
|
||||
/// This reuses Gamecraft's error dialog popup
|
||||
/// </summary>
|
||||
/// <param name="popup">The popup to display. Use an instance of SingleChoicePrompt or DualChoicePrompt.</param>
|
||||
public void PromptUser(Error popup)
|
||||
{
|
||||
// if the stuff wasn't mostly set to internal, this would be written as:
|
||||
// RobocraftX.Services.ErrorHandler.Instance.EqueueError(error);
|
||||
object errorHandlerInstance = ErrorHandlerInstanceGetter();
|
||||
EnqueueError(errorHandlerInstance, popup);
|
||||
}
|
||||
|
||||
// TODO
|
||||
/*public void CloseCurrentPrompt()
|
||||
{
|
||||
// RobocraftX.Services.ErrorHandler.Instance.HandlePopupClosed();
|
||||
// FIXME: this is a call that is also called when closing, not the actual closing action itself (so it doesn't work)
|
||||
object errorHandlerInstance = ErrorHandlerInstanceGetter();
|
||||
HandleErrorClosed(errorHandlerInstance);
|
||||
}*/
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
// this would have been so much simpler if this didn't involve a bunch of internal fields & classes
|
||||
Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
|
||||
Type errorHandle = AccessTools.TypeByName("RobocraftX.Services.ErrorHandle");
|
||||
ErrorHandlerInstanceGetter = (Func<object>) AccessTools.Method("GamecraftModdingAPI.App.Client:GenInstanceGetter")
|
||||
.MakeGenericMethod(errorHandler)
|
||||
.Invoke(null, new object[0]);
|
||||
EnqueueError = (Action<object, Error>) AccessTools.Method("GamecraftModdingAPI.App.Client:GenEnqueueError")
|
||||
.MakeGenericMethod(errorHandler, errorHandle)
|
||||
.Invoke(null, new object[0]);
|
||||
/*HandleErrorClosed = (Action<object>) AccessTools.Method("GamecraftModdingAPI.App.Client:GenHandlePopupClosed")
|
||||
.MakeGenericMethod(errorHandler)
|
||||
.Invoke(null, new object[0]);*/
|
||||
// register engines
|
||||
MenuEngineManager.AddMenuEngine(appEngine);
|
||||
}
|
||||
|
||||
// Creating delegates once is faster than reflection every time
|
||||
// Admittedly, this way is more difficult to code and less readable
|
||||
private static Func<object> GenInstanceGetter<T>()
|
||||
{
|
||||
Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
|
||||
MethodInfo instance = AccessTools.PropertyGetter(errorHandler, "Instance");
|
||||
Func<T> getterSimple = (Func<T>) Delegate.CreateDelegate(typeof(Func<T>), null, instance);
|
||||
Func<object> getterCasted = () => (object) getterSimple();
|
||||
return getterCasted;
|
||||
}
|
||||
|
||||
private static Action<object, Error> GenEnqueueError<T, TRes>()
|
||||
{
|
||||
Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
|
||||
MethodInfo enqueueError = AccessTools.Method(errorHandler, "EnqueueError");
|
||||
Func<T, Error, TRes> enqueueSimple =
|
||||
(Func<T, Error, TRes>) Delegate.CreateDelegate(typeof(Func<T, Error, TRes>), enqueueError);
|
||||
Action<object, Error> enqueueCasted =
|
||||
(object instance, Error error) => { enqueueSimple((T) instance, error); };
|
||||
return enqueueCasted;
|
||||
}
|
||||
|
||||
private static Action<object> GenHandlePopupClosed<T>()
|
||||
{
|
||||
Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
|
||||
MethodInfo handlePopupClosed = AccessTools.Method(errorHandler, "HandlePopupClosed");
|
||||
Action<T> handleSimple =
|
||||
(Action<T>) Delegate.CreateDelegate(typeof(Action<T>), handlePopupClosed);
|
||||
Action<object> handleCasted = (object instance) => handleSimple((T) instance);
|
||||
return handleCasted;
|
||||
}
|
||||
}
|
||||
}
|
59
GamecraftModdingAPI/App/ClientAlertTest.cs
Normal file
59
GamecraftModdingAPI/App/ClientAlertTest.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using HarmonyLib;
|
||||
|
||||
using RobocraftX.Services;
|
||||
|
||||
using GamecraftModdingAPI.Tests;
|
||||
|
||||
namespace GamecraftModdingAPI.App
|
||||
{
|
||||
#if TEST
|
||||
/// <summary>
|
||||
/// Client popups tests.
|
||||
/// Only available in TEST builds.
|
||||
/// </summary>
|
||||
[APITestClass]
|
||||
public static class ClientAlertTest
|
||||
{
|
||||
private static DualChoicePrompt popup2 = null;
|
||||
|
||||
private static SingleChoicePrompt popup1 = null;
|
||||
|
||||
[APITestStartUp]
|
||||
public static void StartUp2()
|
||||
{
|
||||
popup2 = new DualChoicePrompt("This is a test double-button popup",
|
||||
"The cake is a lie",
|
||||
"lmao",
|
||||
() => { },
|
||||
"kek",
|
||||
() => { });
|
||||
}
|
||||
|
||||
[APITestStartUp]
|
||||
public static void StartUp1()
|
||||
{
|
||||
popup1 = new SingleChoicePrompt("The cake is a lie",
|
||||
"This is a test single-button popup",
|
||||
"qwertyuiop",
|
||||
() => { });
|
||||
}
|
||||
|
||||
[APITestCase(TestType.Menu)]
|
||||
public static void TestPopUp2()
|
||||
{
|
||||
Client c = new Client();
|
||||
c.PromptUser(popup2);
|
||||
//c.CloseCurrentPrompt();
|
||||
}
|
||||
|
||||
[APITestCase(TestType.Menu)]
|
||||
public static void TestPopUp1()
|
||||
{
|
||||
Client c = new Client();
|
||||
c.PromptUser(popup1);
|
||||
//c.CloseCurrentPrompt();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
477
GamecraftModdingAPI/App/Game.cs
Normal file
477
GamecraftModdingAPI/App/Game.cs
Normal file
|
@ -0,0 +1,477 @@
|
|||
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>
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
}
|
48
GamecraftModdingAPI/App/GameBuildSimEventEngine.cs
Normal file
48
GamecraftModdingAPI/App/GameBuildSimEventEngine.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
|
||||
using RobocraftX.Common;
|
||||
using RobocraftX.StateSync;
|
||||
using Svelto.ECS;
|
||||
using Unity.Jobs;
|
||||
|
||||
using GamecraftModdingAPI.Engines;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
namespace GamecraftModdingAPI.App
|
||||
{
|
||||
public class GameBuildSimEventEngine : IApiEngine, IUnorderedInitializeOnTimeRunningModeEntered, IUnorderedInitializeOnTimeStoppedModeEntered
|
||||
{
|
||||
public event EventHandler<GameEventArgs> SimulationMode;
|
||||
|
||||
public event EventHandler<GameEventArgs> BuildMode;
|
||||
|
||||
public string Name => "GamecraftModdingAPIBuildSimEventGameEngine";
|
||||
|
||||
public bool isRemovable => false;
|
||||
|
||||
public EntitiesDB entitiesDB { set; private get; }
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public void Ready() { }
|
||||
|
||||
public JobHandle OnInitializeTimeRunningMode(JobHandle inputDeps)
|
||||
{
|
||||
ExceptionUtil.InvokeEvent(SimulationMode, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder });
|
||||
return inputDeps;
|
||||
}
|
||||
|
||||
public JobHandle OnInitializeTimeStoppedMode(JobHandle inputDeps)
|
||||
{
|
||||
ExceptionUtil.InvokeEvent(BuildMode, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder });
|
||||
return inputDeps;
|
||||
}
|
||||
}
|
||||
|
||||
public struct GameEventArgs
|
||||
{
|
||||
public string GameName;
|
||||
|
||||
public string GamePath;
|
||||
}
|
||||
}
|
119
GamecraftModdingAPI/App/GameGameEngine.cs
Normal file
119
GamecraftModdingAPI/App/GameGameEngine.cs
Normal file
|
@ -0,0 +1,119 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
|
||||
using RobocraftX;
|
||||
using RobocraftX.Common;
|
||||
using RobocraftX.Schedulers;
|
||||
using RobocraftX.SimulationModeState;
|
||||
using Svelto.ECS;
|
||||
using Svelto.Tasks;
|
||||
using Svelto.Tasks.Lean;
|
||||
|
||||
using GamecraftModdingAPI.Blocks;
|
||||
using GamecraftModdingAPI.Engines;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
namespace GamecraftModdingAPI.App
|
||||
{
|
||||
public class GameGameEngine : IApiEngine
|
||||
{
|
||||
public event EventHandler<GameEventArgs> EnterGame;
|
||||
|
||||
public event EventHandler<GameEventArgs> ExitGame;
|
||||
|
||||
public string Name => "GamecraftModdingAPIGameInfoMenuEngine";
|
||||
|
||||
public bool isRemovable => false;
|
||||
|
||||
public EntitiesDB entitiesDB { set; private get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ExceptionUtil.InvokeEvent(ExitGame, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder });
|
||||
IsInGame = false;
|
||||
}
|
||||
|
||||
public void Ready()
|
||||
{
|
||||
ExceptionUtil.InvokeEvent(EnterGame, this, new GameEventArgs { GameName = GameMode.SaveGameDetails.Name, GamePath = GameMode.SaveGameDetails.Folder });
|
||||
IsInGame = true;
|
||||
}
|
||||
|
||||
// game functionality
|
||||
|
||||
public bool IsInGame
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = false;
|
||||
|
||||
public void ExitCurrentGame(bool async = false)
|
||||
{
|
||||
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()
|
||||
{
|
||||
/*
|
||||
while (Lean.EveryFrameStepRunner_RUNS_IN_TIME_STOPPED_AND_RUNNING.isStopping) { yield return Yield.It; }
|
||||
AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToMenu").Invoke(FullGameFields.Instance, new object[0]);*/
|
||||
yield return Yield.It;
|
||||
entitiesDB.QueryEntity<GameSceneEntityStruct>(CommonExclusiveGroups.GameSceneEGID).WantsToQuit = true;
|
||||
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);
|
||||
}
|
||||
|
||||
public bool IsTimeStoppedMode()
|
||||
{
|
||||
return TimeRunningModeUtil.IsTimeStoppedMode(entitiesDB);
|
||||
}
|
||||
|
||||
public void ToggleTimeMode()
|
||||
{
|
||||
TimeRunningModeUtil.ToggleTimeRunningState(entitiesDB);
|
||||
}
|
||||
|
||||
public EGID[] GetAllBlocksInGame(BlockIDs filter = BlockIDs.Invalid)
|
||||
{
|
||||
var allBlocks = entitiesDB.QueryEntities<DBEntityStruct>();
|
||||
List<EGID> blockEGIDs = new List<EGID>();
|
||||
if (filter == BlockIDs.Invalid)
|
||||
{
|
||||
foreach (var (blocks, _) in allBlocks)
|
||||
foreach (var block in blocks)
|
||||
blockEGIDs.Add(block.ID);
|
||||
return blockEGIDs.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var (blocks, _) in allBlocks)
|
||||
foreach (var block in blocks)
|
||||
if (block.DBID == (ulong) filter)
|
||||
blockEGIDs.Add(block.ID);
|
||||
return blockEGIDs.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
139
GamecraftModdingAPI/App/GameMenuEngine.cs
Normal file
139
GamecraftModdingAPI/App/GameMenuEngine.cs
Normal file
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
using HarmonyLib;
|
||||
|
||||
using RobocraftX;
|
||||
using RobocraftX.Common;
|
||||
using RobocraftX.GUI;
|
||||
using RobocraftX.GUI.MyGamesScreen;
|
||||
using Svelto.ECS;
|
||||
using Svelto.ECS.Experimental;
|
||||
|
||||
using GamecraftModdingAPI.Engines;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
using Svelto.DataStructures;
|
||||
|
||||
namespace GamecraftModdingAPI.App
|
||||
{
|
||||
public class GameMenuEngine : IFactoryEngine
|
||||
{
|
||||
public IEntityFactory Factory { set; private get; }
|
||||
|
||||
public string Name => "GamecraftModdingAPIGameInfoGameEngine";
|
||||
|
||||
public bool isRemovable => false;
|
||||
|
||||
public EntitiesDB entitiesDB { set; private get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IsInMenu = false;
|
||||
}
|
||||
|
||||
public void Ready()
|
||||
{
|
||||
IsInMenu = true;
|
||||
}
|
||||
|
||||
// game functionality
|
||||
|
||||
public bool IsInMenu
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = false;
|
||||
|
||||
public bool CreateMyGame(EGID id, string path = "", uint thumbnailId = 0, string gameName = "", string creatorName = "", string description = "", long createdDate = 0L)
|
||||
{
|
||||
EntityComponentInitializer eci = Factory.BuildEntity<MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal>(id);
|
||||
eci.Init(new MyGameDataEntityStruct
|
||||
{
|
||||
SavedGamePath = new ECSString(path),
|
||||
ThumbnailId = thumbnailId,
|
||||
GameName = new ECSString(gameName),
|
||||
CreatorName = new ECSString(creatorName),
|
||||
GameDescription = new ECSString(description),
|
||||
CreatedDate = createdDate,
|
||||
});
|
||||
// entitiesDB.PublishEntityChange<MyGameDataEntityStruct>(id); // this will always fail
|
||||
return true;
|
||||
}
|
||||
|
||||
public uint HighestID()
|
||||
{
|
||||
EntityCollection<MyGameDataEntityStruct> games = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
|
||||
uint max = 0;
|
||||
for (int i = 0; i < games.count; i++)
|
||||
{
|
||||
if (games[i].ID.entityID > max)
|
||||
{
|
||||
max = games[i].ID.entityID;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
public bool EnterGame(EGID id)
|
||||
{
|
||||
if (!ExistsGameInfo(id)) return false;
|
||||
ref MyGameDataEntityStruct mgdes = ref GetGameInfo(id);
|
||||
return EnterGame(mgdes.GameName, mgdes.SavedGamePath);
|
||||
}
|
||||
|
||||
public bool EnterGame(string gameName, string path, ulong workshopId = 0uL, bool autoEnterSim = false)
|
||||
{
|
||||
GameMode.CurrentMode = autoEnterSim ? RCXMode.Play : RCXMode.Build;
|
||||
GameMode.SaveGameDetails = new SaveGameDetails(gameName, path, workshopId);
|
||||
// the private FullGameCompositionRoot.SwitchToGame() method gets passed to menu items for this reason
|
||||
AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame").Invoke(FullGameFields.Instance, new object[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetGameName(EGID id, string name)
|
||||
{
|
||||
if (!ExistsGameInfo(id)) return false;
|
||||
GetGameInfo(id).GameName.Set(name);
|
||||
GetGameViewInfo(id).MyGamesSlotComponent.GameName = StringUtil.SanitiseString(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetGameDescription(EGID id, string name)
|
||||
{
|
||||
if (!ExistsGameInfo(id)) return false;
|
||||
GetGameInfo(id).GameDescription.Set(name);
|
||||
GetGameViewInfo(id).MyGamesSlotComponent.GameDescription = StringUtil.SanitiseString(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ExistsGameInfo(EGID id)
|
||||
{
|
||||
return entitiesDB.Exists<MyGameDataEntityStruct>(id);
|
||||
}
|
||||
|
||||
public ref MyGameDataEntityStruct GetGameInfo(EGID id)
|
||||
{
|
||||
return ref GetComponent<MyGameDataEntityStruct>(id);
|
||||
}
|
||||
|
||||
public ref MyGamesSlotEntityViewStruct GetGameViewInfo(EGID id)
|
||||
{
|
||||
EntityCollection<MyGamesSlotEntityViewStruct> entities =
|
||||
entitiesDB.QueryEntities<MyGamesSlotEntityViewStruct>(MyGamesScreenExclusiveGroups.GameSlotGuiEntities);
|
||||
for (int i = 0; i < entities.count; i++)
|
||||
{
|
||||
if (entities[i].ID.entityID == id.entityID)
|
||||
{
|
||||
return ref entities[i];
|
||||
}
|
||||
}
|
||||
MyGamesSlotEntityViewStruct[] defRef = new MyGamesSlotEntityViewStruct[1];
|
||||
return ref defRef[0];
|
||||
}
|
||||
|
||||
public ref T GetComponent<T>(EGID id) where T: unmanaged, IEntityComponent
|
||||
{
|
||||
return ref entitiesDB.QueryEntity<T>(id);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MyGameDataEntityDescriptor_DamnItFJWhyDidYouMakeThisInternal : GenericEntityDescriptor<MyGameDataEntityStruct> { }
|
||||
}
|
26
GamecraftModdingAPI/App/StateSyncRegPatch.cs
Normal file
26
GamecraftModdingAPI/App/StateSyncRegPatch.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
using RobocraftX.CR.MainGame;
|
||||
using RobocraftX.StateSync;
|
||||
|
||||
using HarmonyLib;
|
||||
|
||||
namespace GamecraftModdingAPI.App
|
||||
{
|
||||
[HarmonyPatch]
|
||||
class StateSyncRegPatch
|
||||
{
|
||||
public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
|
||||
{
|
||||
// register sim/build events engines
|
||||
Game.InitDeterministic(stateSyncReg);
|
||||
}
|
||||
|
||||
[HarmonyTargetMethod]
|
||||
public static MethodBase Target()
|
||||
{
|
||||
return AccessTools.Method(typeof(MainGameCompositionRoot), "DeterministicCompose").MakeGenericMethod(typeof(object));
|
||||
}
|
||||
}
|
||||
}
|
27
GamecraftModdingAPI/App/UserPrompts.cs
Normal file
27
GamecraftModdingAPI/App/UserPrompts.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using HarmonyLib;
|
||||
|
||||
using RobocraftX.Services;
|
||||
|
||||
namespace GamecraftModdingAPI.App
|
||||
{
|
||||
public class DualChoicePrompt : MultiChoiceError
|
||||
{
|
||||
public DualChoicePrompt(string errorMessage, string title, string firstButtonText, Action firstButtonAction, string secondButtonText, Action secondButtonAction) : base(errorMessage, firstButtonText, firstButtonAction, secondButtonText, secondButtonAction)
|
||||
{
|
||||
// internal readonly field smh
|
||||
new Traverse(this).Field<string>("Title").Value = title;
|
||||
}
|
||||
}
|
||||
|
||||
public class SingleChoicePrompt : SingleChoiceError
|
||||
{
|
||||
public SingleChoicePrompt(string errorMessage, string buttonText, Action buttonClickAction) : base(errorMessage, buttonText, buttonClickAction)
|
||||
{
|
||||
}
|
||||
|
||||
public SingleChoicePrompt(string titleText, string errorMessage, string buttonText, Action buttonClickAction) : base(titleText, errorMessage, buttonText, buttonClickAction)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
using Svelto.ECS;
|
||||
using Svelto.ECS.EntityStructs;
|
||||
|
@ -28,7 +29,7 @@ namespace GamecraftModdingAPI
|
|||
protected static readonly SignalEngine SignalEngine = new SignalEngine();
|
||||
protected static readonly BlockEventsEngine BlockEventsEngine = new BlockEventsEngine();
|
||||
protected static readonly ScalingEngine ScalingEngine = new ScalingEngine();
|
||||
|
||||
|
||||
protected internal static readonly BlockEngine BlockEngine = new BlockEngine();
|
||||
|
||||
/// <summary>
|
||||
|
@ -54,23 +55,13 @@ namespace GamecraftModdingAPI
|
|||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
|
||||
int uscale = 1, float3 scale = default, Player player = null)
|
||||
{
|
||||
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
|
||||
{
|
||||
return new Block(PlacementEngine.PlaceBlock(block, color, darkness,
|
||||
position, uscale, scale, player, rotation));
|
||||
}
|
||||
|
||||
return null;
|
||||
return PlaceNew<Block>(block, position, rotation, color, darkness, uscale, scale, player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Place a new block at the given position. If scaled, position means the center of the block. The default block size is 0.2 in terms of position.
|
||||
/// Place blocks next to each other to connect them.
|
||||
/// The placed block will be a complete block with a placement grid and collision which will be saved along with the game.
|
||||
/// <para></para>
|
||||
/// <para>This method waits for the block to be constructed in the game which may take a significant amount of time.
|
||||
/// Only use this to place a single block.
|
||||
/// For placing multiple blocks, use PlaceNew() then AsyncUtils.WaitForSubmission() when done with placing blocks.</para>
|
||||
/// </summary>
|
||||
/// <param name="block">The block's type</param>
|
||||
/// <param name="color">The block's color</param>
|
||||
|
@ -81,23 +72,18 @@ namespace GamecraftModdingAPI
|
|||
/// <param name="scale">The block's non-uniform scale - 0 means <paramref name="uscale"/> is used</param>
|
||||
/// <param name="player">The player who placed the block</param>
|
||||
/// <returns>The placed block or null if failed</returns>
|
||||
public static async Task<Block> PlaceNewAsync(BlockIDs block, float3 position,
|
||||
public static T PlaceNew<T>(BlockIDs block, float3 position,
|
||||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
|
||||
int uscale = 1, float3 scale = default, Player player = null)
|
||||
int uscale = 1, float3 scale = default, Player player = null) where T : Block
|
||||
{
|
||||
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
|
||||
{
|
||||
try
|
||||
{
|
||||
var ret = new Block(PlacementEngine.PlaceBlock(block, color, darkness,
|
||||
position, uscale, scale, player, rotation));
|
||||
await AsyncUtils.WaitForSubmission();
|
||||
return ret;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.MetaDebugLog(e);
|
||||
}
|
||||
var egid = PlacementEngine.PlaceBlock(block, color, darkness,
|
||||
position, uscale, scale, player, rotation, out var initializer);
|
||||
var bl = New<T>(egid.entityID, egid.groupID);
|
||||
bl.InitData.Group = BlockEngine.InitGroup(initializer);
|
||||
Placed += bl.OnPlacedInit;
|
||||
return bl;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -109,7 +95,7 @@ namespace GamecraftModdingAPI
|
|||
/// <returns>The block object</returns>
|
||||
public static Block GetLastPlacedBlock()
|
||||
{
|
||||
return new Block(BlockIdentifiers.LatestBlockID);
|
||||
return New<Block>(BlockIdentifiers.LatestBlockID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -130,27 +116,125 @@ namespace GamecraftModdingAPI
|
|||
remove => BlockEventsEngine.Removed -= value;
|
||||
}
|
||||
|
||||
private static Dictionary<Type, Func<EGID, Block>> initializers = new Dictionary<Type, Func<EGID, Block>>();
|
||||
|
||||
private static Dictionary<Type, ExclusiveGroupStruct[]> typeToGroup =
|
||||
new Dictionary<Type, ExclusiveGroupStruct[]>
|
||||
{
|
||||
{typeof(ConsoleBlock), new[] {CommonExclusiveGroups.BUILD_CONSOLE_BLOCK_GROUP}},
|
||||
{typeof(LogicGate), new [] {CommonExclusiveGroups.BUILD_LOGIC_BLOCK_GROUP}},
|
||||
{typeof(Motor), new[] {CommonExclusiveGroups.BUILD_MOTOR_BLOCK_GROUP}},
|
||||
{typeof(MusicBlock), new[] {CommonExclusiveGroups.BUILD_MUSIC_BLOCK_GROUP}},
|
||||
{typeof(Piston), new[] {CommonExclusiveGroups.BUILD_PISTON_BLOCK_GROUP}},
|
||||
{typeof(Servo), new[] {CommonExclusiveGroups.BUILD_SERVO_BLOCK_GROUP}},
|
||||
{
|
||||
typeof(SpawnPoint),
|
||||
new[]
|
||||
{
|
||||
CommonExclusiveGroups.BUILD_SPAWNPOINT_BLOCK_GROUP,
|
||||
CommonExclusiveGroups.BUILD_BUILDINGSPAWN_BLOCK_GROUP
|
||||
}
|
||||
},
|
||||
{typeof(TextBlock), new[] {CommonExclusiveGroups.BUILD_TEXT_BLOCK_GROUP}},
|
||||
{typeof(Timer), new[] {CommonExclusiveGroups.BUILD_TIMER_BLOCK_GROUP}}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of T with the given ID and group using dynamically created delegates.
|
||||
/// It's equivalent to new T(EGID) with a minimal overhead thanks to caching the created delegates.
|
||||
/// </summary>
|
||||
/// <param name="id">The block ID</param>
|
||||
/// <param name="group">The block group</param>
|
||||
/// <typeparam name="T">The block's type or Block itself</typeparam>
|
||||
/// <returns>An instance of the provided type</returns>
|
||||
/// <exception cref="BlockTypeException">The block group doesn't match or cannot be found</exception>
|
||||
/// <exception cref="MissingMethodException">The block class doesn't have the needed constructor</exception>
|
||||
private static T New<T>(uint id, ExclusiveGroupStruct? group = null) where T : Block
|
||||
{
|
||||
var type = typeof(T);
|
||||
EGID egid;
|
||||
if (!group.HasValue)
|
||||
{
|
||||
if (typeToGroup.TryGetValue(type, out var gr) && gr.Length == 1)
|
||||
egid = new EGID(id, gr[0]);
|
||||
else
|
||||
egid = BlockEngine.FindBlockEGID(id) ?? throw new BlockTypeException("Could not find block group!");
|
||||
}
|
||||
else
|
||||
{
|
||||
egid = new EGID(id, group.Value);
|
||||
if (typeToGroup.TryGetValue(type, out var gr)
|
||||
&& gr.All(egs => egs != group.Value)) //If this subclass has a specific group, then use that - so Block should still work
|
||||
throw new BlockTypeException($"Incompatible block type! Type {type.Name} belongs to group {gr.Select(g => g.ToString()).Aggregate((a, b) => a + ", " + b)} instead of {group.Value}");
|
||||
}
|
||||
|
||||
if (initializers.TryGetValue(type, out var func))
|
||||
{
|
||||
var bl = (T) func(egid);
|
||||
return bl;
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/a/10593806/2703239
|
||||
var ctor = type.GetConstructor(new[] {typeof(EGID)});
|
||||
if (ctor == null)
|
||||
throw new MissingMethodException("There is no constructor with an EGID parameter for this object");
|
||||
DynamicMethod dynamic = new DynamicMethod(string.Empty,
|
||||
type,
|
||||
new[] {typeof(EGID)},
|
||||
type);
|
||||
ILGenerator il = dynamic.GetILGenerator();
|
||||
|
||||
il.DeclareLocal(type);
|
||||
il.Emit(OpCodes.Ldarg_0); //Load EGID and pass to constructor
|
||||
il.Emit(OpCodes.Newobj, ctor); //Call constructor
|
||||
//il.Emit(OpCodes.Stloc_0); - doesn't seem like we need these
|
||||
//il.Emit(OpCodes.Ldloc_0);
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
func = (Func<EGID, T>) dynamic.CreateDelegate(typeof(Func<EGID, T>));
|
||||
initializers.Add(type, func);
|
||||
var block = (T) func(egid);
|
||||
return block;
|
||||
}
|
||||
|
||||
public Block(EGID id)
|
||||
{
|
||||
Id = id;
|
||||
var type = GetType();
|
||||
if (typeToGroup.TryGetValue(type, out var groups))
|
||||
{
|
||||
if (groups.All(gr => gr != id.groupID))
|
||||
throw new BlockTypeException("The block has the wrong group! The type is " + GetType() +
|
||||
" while the group is " + id.groupID);
|
||||
}
|
||||
else if (type != typeof(Block))
|
||||
Logging.LogWarning($"Unknown block type! Add {type} to the dictionary.");
|
||||
}
|
||||
|
||||
public Block(uint id) : this(new EGID(id, CommonExclusiveGroups.OWNED_BLOCKS_GROUP))
|
||||
/// <summary>
|
||||
/// This overload searches for the correct group the block is in.
|
||||
/// It will throw an exception if the block doesn't exist.
|
||||
/// Use the EGID constructor where possible or subclasses of Block as those specify the group.
|
||||
/// </summary>
|
||||
public Block(uint id)
|
||||
{
|
||||
Id = BlockEngine.FindBlockEGID(id) ?? throw new BlockTypeException("Could not find the appropriate group for the block. The block probably doesn't exist or hasn't been submitted.");
|
||||
}
|
||||
|
||||
public EGID Id { get; }
|
||||
|
||||
internal BlockEngine.BlockInitData InitData;
|
||||
|
||||
/// <summary>
|
||||
/// The block's current position or zero if the block no longer exists.
|
||||
/// A block is 0.2 wide by default in terms of position.
|
||||
/// </summary>
|
||||
public float3 Position
|
||||
{
|
||||
get => Exists ? MovementEngine.GetPosition(Id.entityID) : float3.zero;
|
||||
get => MovementEngine.GetPosition(Id, InitData);
|
||||
set
|
||||
{
|
||||
if (Exists) MovementEngine.MoveBlock(Id.entityID, value);
|
||||
MovementEngine.MoveBlock(Id, InitData, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,10 +243,10 @@ namespace GamecraftModdingAPI
|
|||
/// </summary>
|
||||
public float3 Rotation
|
||||
{
|
||||
get => Exists ? RotationEngine.GetRotation(Id.entityID) : float3.zero;
|
||||
get => RotationEngine.GetRotation(Id, InitData);
|
||||
set
|
||||
{
|
||||
if (Exists) RotationEngine.RotateBlock(Id.entityID, value);
|
||||
RotationEngine.RotateBlock(Id, InitData, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,12 +256,11 @@ namespace GamecraftModdingAPI
|
|||
/// </summary>
|
||||
public float3 Scale
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<ScalingEntityStruct>(Id).scale;
|
||||
get => BlockEngine.GetBlockInfo(this, (ScalingEntityStruct st) => st.scale);
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref ScalingEntityStruct st, float3 val) => st.scale = val, value);
|
||||
if (!Exists) return; //UpdateCollision needs the block to exist
|
||||
ref var scaling = ref BlockEngine.GetBlockInfo<ScalingEntityStruct>(Id);
|
||||
scaling.scale = value;
|
||||
ScalingEngine.UpdateCollision(Id);
|
||||
}
|
||||
}
|
||||
|
@ -188,11 +271,11 @@ namespace GamecraftModdingAPI
|
|||
/// </summary>
|
||||
public int UniformScale
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(Id).scaleFactor;
|
||||
get => BlockEngine.GetBlockInfo(this, (UniformBlockScaleEntityStruct st) => st.scaleFactor);
|
||||
set
|
||||
{
|
||||
ref var scaleStruct = ref BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(Id);
|
||||
scaleStruct.scaleFactor = value;
|
||||
BlockEngine.SetBlockInfo(this, (ref UniformBlockScaleEntityStruct st, int val) => st.scaleFactor = val,
|
||||
value);
|
||||
Scale = new float3(value, value, value);
|
||||
}
|
||||
}
|
||||
|
@ -204,8 +287,7 @@ namespace GamecraftModdingAPI
|
|||
{
|
||||
get
|
||||
{
|
||||
var id = (BlockIDs) BlockEngine.GetBlockInfo<DBEntityStruct>(Id, out var exists).DBID;
|
||||
return exists ? id : BlockIDs.Invalid;
|
||||
return BlockEngine.GetBlockInfo(this, (DBEntityStruct st) => (BlockIDs) st.DBID, BlockIDs.Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,17 +298,19 @@ namespace GamecraftModdingAPI
|
|||
{
|
||||
get
|
||||
{
|
||||
byte index = BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id, out var exists).indexInPalette;
|
||||
if (!exists) index = byte.MaxValue;
|
||||
byte index = BlockEngine.GetBlockInfo(this, (ColourParameterEntityStruct st) => st.indexInPalette,
|
||||
byte.MaxValue);
|
||||
return new BlockColor(index);
|
||||
}
|
||||
set
|
||||
{
|
||||
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id);
|
||||
color.indexInPalette = (byte)(value.Color + value.Darkness * 10);
|
||||
color.overridePaletteColour = false;
|
||||
color.needsUpdate = true;
|
||||
BlockEngine.SetBlockColorFromPalette(ref color);
|
||||
BlockEngine.SetBlockInfo(this, (ref ColourParameterEntityStruct color, BlockColor val) =>
|
||||
{
|
||||
color.indexInPalette = (byte) (val.Color + val.Darkness * 10);
|
||||
color.overridePaletteColour = false;
|
||||
color.needsUpdate = true;
|
||||
BlockEngine.SetBlockColorFromPalette(ref color);
|
||||
}, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,32 +319,37 @@ namespace GamecraftModdingAPI
|
|||
/// </summary>
|
||||
public float4 CustomColor
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id).overriddenColour;
|
||||
get => BlockEngine.GetBlockInfo(this, (ColourParameterEntityStruct st) => st.overriddenColour);
|
||||
set
|
||||
{
|
||||
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id);
|
||||
color.overriddenColour = value;
|
||||
color.overridePaletteColour = true;
|
||||
color.needsUpdate = true;
|
||||
BlockEngine.SetBlockInfo(this, (ref ColourParameterEntityStruct color, float4 val) =>
|
||||
{
|
||||
color.overriddenColour = val;
|
||||
color.overridePaletteColour = true;
|
||||
color.needsUpdate = true;
|
||||
}, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The short text displayed on the block if applicable, or null.
|
||||
/// The text displayed on the block if applicable, or null.
|
||||
/// Setting it is temporary to the session, it won't be saved.
|
||||
/// </summary>
|
||||
public string Label
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<TextLabelEntityViewStruct>(Id).textLabelComponent?.text;
|
||||
get => BlockEngine.GetBlockInfoViewStruct(this, (TextLabelEntityViewStruct st) => st.textLabelComponent?.text);
|
||||
set
|
||||
{
|
||||
ref var text = ref BlockEngine.GetBlockInfo<TextLabelEntityViewStruct>(Id);
|
||||
if (text.textLabelComponent != null) text.textLabelComponent.text = value;
|
||||
BlockEngine.SetBlockInfoViewStruct(this, (ref TextLabelEntityViewStruct text, string val) =>
|
||||
{
|
||||
if (text.textLabelComponent != null) text.textLabelComponent.text = val;
|
||||
}, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the block exists. The other properties will return a default value if the block doesn't exist.
|
||||
/// If the block was just placed, then this will also return false but the properties will work correctly.
|
||||
/// </summary>
|
||||
public bool Exists => BlockEngine.BlockExists(Id);
|
||||
|
||||
|
@ -276,14 +365,21 @@ namespace GamecraftModdingAPI
|
|||
public bool Remove() => RemovalEngine.RemoveBlock(Id);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the rigid body of the cluster of blocks this one belongs to during simulation.
|
||||
/// Returns the rigid body of the chunk of blocks this one belongs to during simulation.
|
||||
/// Can be used to apply forces or move the block around while the simulation is running.
|
||||
/// </summary>
|
||||
/// <returns>The SimBody of the cluster or null if the block doesn't exist.</returns>
|
||||
/// <returns>The SimBody of the chunk or null if the block doesn't exist.</returns>
|
||||
public SimBody GetSimBody()
|
||||
{
|
||||
uint id = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(Id, out var exists).machineRigidBodyId;
|
||||
return exists ? new SimBody(id) : null;
|
||||
return BlockEngine.GetBlockInfo(this,
|
||||
(GridConnectionsEntityStruct st) => new SimBody(st.machineRigidBodyId, st.clusterId));
|
||||
}
|
||||
|
||||
private void OnPlacedInit(object sender, BlockPlacedRemovedEventArgs e)
|
||||
{ //Member method instead of lambda to avoid constantly creating delegates
|
||||
if (e.ID != Id) return;
|
||||
Placed -= OnPlacedInit; //And we can reference it
|
||||
InitData = default; //Remove initializer as it's no longer valid - if the block gets removed it shouldn't be used again
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
@ -325,6 +421,8 @@ namespace GamecraftModdingAPI
|
|||
GameEngineManager.AddGameEngine(BlockEngine);
|
||||
GameEngineManager.AddGameEngine(BlockEventsEngine);
|
||||
GameEngineManager.AddGameEngine(ScalingEngine);
|
||||
GameEngineManager.AddGameEngine(SignalEngine);
|
||||
Wire.signalEngine = SignalEngine; // requires same functionality, no need to duplicate the engine
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -338,12 +436,11 @@ namespace GamecraftModdingAPI
|
|||
// C# can't cast to a child of Block unless the object was originally that child type
|
||||
// And C# doesn't let me make implicit cast operators for child types
|
||||
// So thanks to Microsoft, we've got this horrible implementation using reflection
|
||||
ConstructorInfo ctor = typeof(T).GetConstructor(types: new System.Type[] { typeof(EGID) });
|
||||
if (ctor == null)
|
||||
{
|
||||
throw new BlockSpecializationException("Specialized block constructor does not accept an EGID");
|
||||
}
|
||||
return (T)ctor.Invoke(new object[] { Id });
|
||||
|
||||
//Lets improve that using delegates
|
||||
var block = New<T>(Id.entityID, Id.groupID);
|
||||
block.InitData = this.InitData;
|
||||
return block;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Gamecraft.Wires;
|
||||
using RobocraftX.Blocks;
|
||||
|
@ -8,16 +10,16 @@ using RobocraftX.Physics;
|
|||
using RobocraftX.Scene.Simulation;
|
||||
using Svelto.DataStructures;
|
||||
using Svelto.ECS;
|
||||
using Svelto.ECS.Hybrid;
|
||||
|
||||
using GamecraftModdingAPI.Engines;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
/// <summary>
|
||||
/// Engine for executing general block actions
|
||||
/// </summary>
|
||||
public class BlockEngine : IApiEngine
|
||||
public partial class BlockEngine : IApiEngine
|
||||
{
|
||||
public string Name { get; } = "GamecraftModdingAPIBlockGameEngine";
|
||||
|
||||
|
@ -25,8 +27,6 @@ namespace GamecraftModdingAPI.Blocks
|
|||
|
||||
public bool isRemovable => false;
|
||||
|
||||
internal bool Synced = true;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
@ -38,14 +38,15 @@ namespace GamecraftModdingAPI.Blocks
|
|||
public Block[] GetConnectedBlocks(EGID blockID)
|
||||
{
|
||||
if (!BlockExists(blockID)) return new Block[0];
|
||||
Stack<uint> cubeStack = new Stack<uint>();
|
||||
FasterList<uint> cubes = new FasterList<uint>(10);
|
||||
var coll = entitiesDB.QueryEntities<GridConnectionsEntityStruct>(CommonExclusiveGroups
|
||||
.OWNED_BLOCKS_GROUP);
|
||||
for (int i = 0; i < coll.count; i++)
|
||||
coll[i].isProcessed = false;
|
||||
Stack<EGID> cubeStack = new Stack<EGID>();
|
||||
FasterList<EGID> cubes = new FasterList<EGID>(10);
|
||||
var coll = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
|
||||
foreach (var (ecoll, _) in coll)
|
||||
foreach (ref var conn in ecoll)
|
||||
conn.isProcessed = false;
|
||||
|
||||
ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID.entityID, cubeStack, cubes, (in GridConnectionsEntityStruct g) => { return false; });
|
||||
ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubes,
|
||||
(in GridConnectionsEntityStruct g) => { return false; });
|
||||
|
||||
var ret = new Block[cubes.count];
|
||||
for (int i = 0; i < cubes.count; i++)
|
||||
|
@ -60,75 +61,110 @@ namespace GamecraftModdingAPI.Blocks
|
|||
color.paletteColour = paletteEntry.Colour;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a struct of a block. Can be used to set properties.
|
||||
/// Returns a default value if not found.
|
||||
/// </summary>
|
||||
/// <param name="blockID">The block's ID</param>
|
||||
/// <typeparam name="T">The struct to query</typeparam>
|
||||
/// <returns>An editable reference to the struct</returns>
|
||||
public ref T GetBlockInfo<T>(EGID blockID) where T : struct, IEntityComponent
|
||||
public ref T GetBlockInfo<T>(EGID blockID) where T : unmanaged, IEntityComponent
|
||||
{
|
||||
if (!Synced)
|
||||
{
|
||||
Sync();
|
||||
Synced = true;
|
||||
}
|
||||
if (entitiesDB.Exists<T>(blockID))
|
||||
return ref entitiesDB.QueryEntity<T>(blockID);
|
||||
T[] structHolder = new T[1]; //Create something that can be referenced
|
||||
return ref structHolder[0]; //Gets a default value automatically
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a struct of a block. Can be used to set properties.
|
||||
/// Returns a default value if not found.
|
||||
/// </summary>
|
||||
/// <param name="blockID">The block's ID</param>
|
||||
/// <param name="exists">Whether the specified struct exists for the block</param>
|
||||
/// <typeparam name="T">The struct to query</typeparam>
|
||||
/// <returns>An editable reference to the struct</returns>
|
||||
public ref T GetBlockInfo<T>(EGID blockID, out bool exists) where T : struct, IEntityComponent
|
||||
|
||||
public ref T GetBlockInfoViewStruct<T>(EGID blockID) where T : struct, INeedEGID, IEntityComponent
|
||||
{
|
||||
if (!Synced)
|
||||
if (entitiesDB.Exists<T>(blockID))
|
||||
{
|
||||
Sync();
|
||||
Synced = true;
|
||||
// TODO: optimize by using EntitiesDB internal calls instead of iterating over everything
|
||||
EntityCollection<T> entities = entitiesDB.QueryEntities<T>(blockID.groupID);
|
||||
for (int i = 0; i < entities.count; i++)
|
||||
{
|
||||
if (entities[i].ID == blockID)
|
||||
{
|
||||
return ref entities[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
exists = entitiesDB.Exists<T>(blockID);
|
||||
if (exists)
|
||||
return ref entitiesDB.QueryEntity<T>(blockID);
|
||||
T[] structHolder = new T[1];
|
||||
return ref structHolder[0];
|
||||
T[] structHolder = new T[1]; //Create something that can be referenced
|
||||
return ref structHolder[0]; //Gets a default value automatically
|
||||
}
|
||||
|
||||
public bool BlockExists(EGID id)
|
||||
public U GetBlockInfo<T, U>(Block block, Func<T, U> getter,
|
||||
U def = default) where T : unmanaged, IEntityComponent
|
||||
{
|
||||
if (!Synced)
|
||||
{
|
||||
Sync();
|
||||
Synced = true;
|
||||
}
|
||||
return entitiesDB.Exists<DBEntityStruct>(id);
|
||||
if (entitiesDB.Exists<T>(block.Id))
|
||||
return getter(entitiesDB.QueryEntity<T>(block.Id));
|
||||
return GetBlockInitInfo(block, getter, def);
|
||||
}
|
||||
|
||||
public U GetBlockInfoViewStruct<T, U>(Block block, Func<T, U> getter,
|
||||
U def = default) where T : struct, IEntityViewComponent
|
||||
{
|
||||
if (entitiesDB.Exists<T>(block.Id))
|
||||
return getter(entitiesDB.QueryEntity<T>(block.Id));
|
||||
return GetBlockInitInfo(block, getter, def);
|
||||
}
|
||||
|
||||
public bool GetBlockInfoExists<T>(EGID blockID) where T : struct, IEntityComponent
|
||||
private U GetBlockInitInfo<T, U>(Block block, Func<T, U> getter, U def) where T : struct, IEntityComponent
|
||||
{
|
||||
if (!Synced)
|
||||
{
|
||||
Sync();
|
||||
Synced = true;
|
||||
}
|
||||
return entitiesDB.Exists<T>(blockID);
|
||||
if (block.InitData.Group == null) return def;
|
||||
var initializer = new EntityComponentInitializer(block.Id, block.InitData.Group);
|
||||
if (initializer.Has<T>())
|
||||
return getter(initializer.Get<T>());
|
||||
return def;
|
||||
}
|
||||
|
||||
public delegate void Setter<T, U>(ref T component, U value) where T : struct, IEntityComponent;
|
||||
|
||||
public void SetBlockInfoViewStruct<T, U>(Block block, Setter<T, U> setter, U value) where T : struct, IEntityViewComponent
|
||||
{
|
||||
if (entitiesDB.Exists<T>(block.Id))
|
||||
setter(ref entitiesDB.QueryEntity<T>(block.Id), value);
|
||||
else
|
||||
SetBlockInitInfo(block, setter, value);
|
||||
}
|
||||
|
||||
public void SetBlockInfo<T, U>(Block block, Setter<T, U> setter, U value) where T : unmanaged, IEntityComponent
|
||||
{
|
||||
if (entitiesDB.Exists<T>(block.Id))
|
||||
setter(ref entitiesDB.QueryEntity<T>(block.Id), value);
|
||||
else
|
||||
SetBlockInitInfo(block, setter, value);
|
||||
}
|
||||
|
||||
private void SetBlockInitInfo<T, U>(Block block, Setter<T, U> setter, U value)
|
||||
where T : struct, IEntityComponent
|
||||
{
|
||||
if (block.InitData.Group != null)
|
||||
{
|
||||
var initializer = new EntityComponentInitializer(block.Id, block.InitData.Group);
|
||||
T component = initializer.Has<T>() ? initializer.Get<T>() : default;
|
||||
ref T structRef = ref component;
|
||||
setter(ref structRef, value);
|
||||
initializer.Init(structRef);
|
||||
}
|
||||
}
|
||||
|
||||
public bool BlockExists(EGID blockID)
|
||||
{
|
||||
return entitiesDB.Exists<DBEntityStruct>(blockID);
|
||||
}
|
||||
|
||||
public bool GetBlockInfoExists<T>(Block block) where T : struct, IEntityComponent
|
||||
{
|
||||
if (entitiesDB.Exists<T>(block.Id))
|
||||
return true;
|
||||
if (block.InitData.Group == null)
|
||||
return false;
|
||||
var init = new EntityComponentInitializer(block.Id, block.InitData.Group);
|
||||
return init.Has<T>();
|
||||
}
|
||||
|
||||
public SimBody[] GetSimBodiesFromID(byte id)
|
||||
{
|
||||
var ret = new FasterList<SimBody>(4);
|
||||
if (!entitiesDB.HasAny<ObjectIdEntityStruct>(CommonExclusiveGroups.OWNED_BLOCKS_GROUP))
|
||||
if (!entitiesDB.HasAny<ObjectIdEntityStruct>(CommonExclusiveGroups.BUILD_OBJID_BLOCK_GROUP))
|
||||
return new SimBody[0];
|
||||
var oids = entitiesDB.QueryEntities<ObjectIdEntityStruct>(CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
var connections = entitiesDB.QueryMappedEntities<GridConnectionsEntityStruct>(CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
var oids = entitiesDB.QueryEntities<ObjectIdEntityStruct>(CommonExclusiveGroups.BUILD_OBJID_BLOCK_GROUP);
|
||||
var connections = entitiesDB.QueryMappedEntities<GridConnectionsEntityStruct>(CommonExclusiveGroups.BUILD_OBJID_BLOCK_GROUP);
|
||||
foreach (ref ObjectIdEntityStruct oid in oids)
|
||||
{
|
||||
if (oid.objectId != id) continue;
|
||||
|
@ -147,9 +183,9 @@ namespace GamecraftModdingAPI.Blocks
|
|||
public ObjectIdentifier[] GetObjectIDsFromID(byte id, bool sim)
|
||||
{
|
||||
var ret = new FasterList<ObjectIdentifier>(4);
|
||||
if (!entitiesDB.HasAny<ObjectIdEntityStruct>(CommonExclusiveGroups.OWNED_BLOCKS_GROUP))
|
||||
if (!entitiesDB.HasAny<ObjectIdEntityStruct>(CommonExclusiveGroups.BUILD_OBJID_BLOCK_GROUP))
|
||||
return new ObjectIdentifier[0];
|
||||
var oids = entitiesDB.QueryEntities<ObjectIdEntityStruct>(CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
var oids = entitiesDB.QueryEntities<ObjectIdEntityStruct>(CommonExclusiveGroups.BUILD_OBJID_BLOCK_GROUP);
|
||||
foreach (ref ObjectIdEntityStruct oid in oids)
|
||||
if (sim ? oid.simObjectId == id : oid.objectId == id)
|
||||
ret.Add(new ObjectIdentifier(oid.ID));
|
||||
|
@ -170,15 +206,47 @@ namespace GamecraftModdingAPI.Blocks
|
|||
return list.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize newly created entity components with entities DB.
|
||||
/// This forces a partial game tick, so it may be slow.
|
||||
/// This also has the potential to make Gamecraft unstable.
|
||||
/// Use this sparingly.
|
||||
/// </summary>
|
||||
private static void Sync()
|
||||
public SimBody[] GetClusterBodies(uint cid)
|
||||
{
|
||||
DeterministicStepCompositionRootPatch.SubmitEntitiesNow();
|
||||
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
|
||||
var bodies = new HashSet<uint>();
|
||||
foreach (var (coll, _) in groups)
|
||||
{
|
||||
foreach (var conn in coll)
|
||||
{
|
||||
if (conn.clusterId == cid)
|
||||
bodies.Add(conn.machineRigidBodyId);
|
||||
}
|
||||
}
|
||||
|
||||
return bodies.Select(id => new SimBody(id)).ToArray();
|
||||
}
|
||||
|
||||
public EGID? FindBlockEGID(uint id)
|
||||
{
|
||||
var groups = entitiesDB.FindGroups<DBEntityStruct>();
|
||||
foreach (ExclusiveGroupStruct group in groups)
|
||||
{
|
||||
if (entitiesDB.Exists<DBEntityStruct>(id, group))
|
||||
return new EGID(id, group);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cluster GetCluster(uint sbid)
|
||||
{
|
||||
var groups = entitiesDB.QueryEntities<GridConnectionsEntityStruct>();
|
||||
foreach (var (coll, _) in groups)
|
||||
{
|
||||
foreach (var conn in coll)
|
||||
{
|
||||
if (conn.machineRigidBodyId == sbid)
|
||||
return new Cluster(conn.clusterId);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
|
52
GamecraftModdingAPI/Blocks/BlockEngineInit.cs
Normal file
52
GamecraftModdingAPI/Blocks/BlockEngineInit.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
using Svelto.DataStructures;
|
||||
using Svelto.ECS;
|
||||
using Svelto.ECS.Internal;
|
||||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
public partial class BlockEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds information needed to construct a component initializer
|
||||
/// </summary>
|
||||
internal struct BlockInitData
|
||||
{
|
||||
public FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> Group;
|
||||
}
|
||||
|
||||
internal delegate FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> GetInitGroup(
|
||||
EntityComponentInitializer initializer);
|
||||
|
||||
/// <summary>
|
||||
/// Accesses the group field of the initializer
|
||||
/// </summary>
|
||||
internal GetInitGroup InitGroup = CreateAccessor<GetInitGroup>("_group");
|
||||
|
||||
//https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection
|
||||
internal static TDelegate CreateAccessor<TDelegate>(string memberName) where TDelegate : Delegate
|
||||
{
|
||||
var invokeMethod = typeof(TDelegate).GetMethod("Invoke");
|
||||
if (invokeMethod == null)
|
||||
throw new InvalidOperationException($"{typeof(TDelegate)} signature could not be determined.");
|
||||
|
||||
var delegateParameters = invokeMethod.GetParameters();
|
||||
if (delegateParameters.Length != 1)
|
||||
throw new InvalidOperationException("Delegate must have a single parameter.");
|
||||
|
||||
var paramType = delegateParameters[0].ParameterType;
|
||||
|
||||
var objParam = Expression.Parameter(paramType, "obj");
|
||||
var memberExpr = Expression.PropertyOrField(objParam, memberName);
|
||||
Expression returnExpr = memberExpr;
|
||||
if (invokeMethod.ReturnType != memberExpr.Type)
|
||||
returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType);
|
||||
|
||||
var lambda =
|
||||
Expression.Lambda<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] {objParam});
|
||||
return lambda.Compile();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using GamecraftModdingAPI.Engines;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
using RobocraftX.Common;
|
||||
using Svelto.ECS;
|
||||
|
||||
using GamecraftModdingAPI.Engines;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
public class BlockEventsEngine : IReactionaryEngine<DBEntityStruct>
|
||||
|
@ -16,6 +18,7 @@ namespace GamecraftModdingAPI.Blocks
|
|||
}
|
||||
|
||||
public EntitiesDB entitiesDB { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
@ -23,14 +26,21 @@ namespace GamecraftModdingAPI.Blocks
|
|||
public string Name { get; } = "GamecraftModdingAPIBlockEventsEngine";
|
||||
public bool isRemovable { get; } = false;
|
||||
|
||||
private bool shouldAddRemove;
|
||||
public void Add(ref DBEntityStruct entityComponent, EGID egid)
|
||||
{
|
||||
ExceptionUtil.InvokeEvent(Placed, this, new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID});
|
||||
if (!(shouldAddRemove = !shouldAddRemove))
|
||||
return;
|
||||
ExceptionUtil.InvokeEvent(Placed, this,
|
||||
new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID});
|
||||
}
|
||||
|
||||
public void Remove(ref DBEntityStruct entityComponent, EGID egid)
|
||||
{
|
||||
ExceptionUtil.InvokeEvent(Removed, this, new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID});
|
||||
if (!(shouldAddRemove = !shouldAddRemove))
|
||||
return;
|
||||
ExceptionUtil.InvokeEvent(Removed, this,
|
||||
new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,5 +48,8 @@ namespace GamecraftModdingAPI.Blocks
|
|||
{
|
||||
public EGID ID;
|
||||
public BlockIDs Type;
|
||||
private Block block;
|
||||
|
||||
public Block Block => block ?? (block = new Block(ID));
|
||||
}
|
||||
}
|
|
@ -40,4 +40,26 @@ namespace GamecraftModdingAPI.Blocks
|
|||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class WiringException : BlockException
|
||||
{
|
||||
public WiringException()
|
||||
{
|
||||
}
|
||||
|
||||
public WiringException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class WireInvalidException : WiringException
|
||||
{
|
||||
public WireInvalidException()
|
||||
{
|
||||
}
|
||||
|
||||
public WireInvalidException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace GamecraftModdingAPI.Blocks
|
|||
public enum BlockIDs : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom value for the API. Doesn't exist for Gamecraft.
|
||||
/// Called "nothing" in Gamecraft. (DBID.NOTHING)
|
||||
/// </summary>
|
||||
Invalid = ushort.MaxValue,
|
||||
AluminiumCube = 0,
|
||||
|
@ -192,6 +192,9 @@ namespace GamecraftModdingAPI.Blocks
|
|||
PlayerFilter,
|
||||
TeamFilter,
|
||||
Number2Text, //193
|
||||
DestructionManager = 260,
|
||||
ChunkHealthModifier,
|
||||
ClusterHealthModifier, //262
|
||||
BeachTree1 = 200,
|
||||
BeachTree2,
|
||||
BeachTree3,
|
||||
|
@ -243,6 +246,8 @@ namespace GamecraftModdingAPI.Blocks
|
|||
AdvancedRotator,
|
||||
MusicBlock, //256
|
||||
PlasmaCannonBlock,
|
||||
QuantumRiflePickup = 300,
|
||||
QuantumRifleAmmoPickup,
|
||||
MagmaRockCube=777,
|
||||
MagmaRockCubeSliced,
|
||||
MagmaRockSlope,
|
||||
|
|
|
@ -12,8 +12,8 @@ namespace GamecraftModdingAPI.Blocks
|
|||
{
|
||||
/// <summary>
|
||||
/// Blocks placed by the player
|
||||
/// </summary>
|
||||
public static ExclusiveGroup OWNED_BLOCKS { get { return CommonExclusiveGroups.OWNED_BLOCKS_GROUP; } }
|
||||
/// </summary> - TODO
|
||||
//public static ExclusiveGroup OWNED_BLOCKS { get { return CommonExclusiveGroups.REAL_BLOCKS_GROUPS_DON_T_USE_IN_NEW_CODE; } }
|
||||
|
||||
/// <summary>
|
||||
/// Extra parts used in functional blocks
|
||||
|
@ -23,7 +23,7 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// <summary>
|
||||
/// Blocks which are disabled in Simulation mode
|
||||
/// </summary>
|
||||
public static ExclusiveGroup SIM_BLOCKS_DISABLED { get { return CommonExclusiveGroups.BLOCKS_DISABLED_IN_SIM_GROUP; } }
|
||||
public static ExclusiveGroup SIM_BLOCKS_DISABLED { get { return CommonExclusiveGroups.DISABLED_JOINTS_IN_SIM_GROUP; } }
|
||||
|
||||
//public static ExclusiveGroup SPAWN_POINTS { get { return CommonExclusiveGroups.SPAWN_POINTS_GROUP; } }
|
||||
|
||||
|
@ -34,7 +34,7 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public static uint LatestBlockID {
|
||||
get
|
||||
{
|
||||
{ //Need the private field as the property increments itself
|
||||
return ((uint) AccessTools.Field(typeof(CommonExclusiveGroups), "_nextBlockEntityID").GetValue(null)) - 1;
|
||||
}
|
||||
}
|
||||
|
|
124
GamecraftModdingAPI/Blocks/BlockTests.cs
Normal file
124
GamecraftModdingAPI/Blocks/BlockTests.cs
Normal file
|
@ -0,0 +1,124 @@
|
|||
using System;
|
||||
|
||||
using Gamecraft.Wires;
|
||||
|
||||
using GamecraftModdingAPI;
|
||||
using GamecraftModdingAPI.Tests;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
#if TEST
|
||||
/// <summary>
|
||||
/// Block test cases. Not accessible in release versions.
|
||||
/// </summary>
|
||||
[APITestClass]
|
||||
public static class BlockTests
|
||||
{
|
||||
[APITestCase(TestType.EditMode)]
|
||||
public static void TestPlaceNew()
|
||||
{
|
||||
Block newBlock = Block.PlaceNew(BlockIDs.AluminiumCube, Unity.Mathematics.float3.zero);
|
||||
Assert.NotNull(newBlock.Id, "Newly placed block is missing Id. This should be populated when the block is placed.", "Newly placed block Id is not null, block successfully placed.");
|
||||
}
|
||||
|
||||
[APITestCase(TestType.EditMode)]
|
||||
public static void TestInitProperty()
|
||||
{
|
||||
Block newBlock = Block.PlaceNew(BlockIDs.AluminiumCube, Unity.Mathematics.float3.zero + 2);
|
||||
if (!Assert.CloseTo(newBlock.Position, (Unity.Mathematics.float3.zero + 2), $"Newly placed block at {newBlock.Position} is expected at {Unity.Mathematics.float3.zero + 2}.", "Newly placed block position matches.")) return;
|
||||
//Assert.Equal(newBlock.Exists, true, "Newly placed block does not exist, possibly because Sync() skipped/missed/failed.", "Newly placed block exists, Sync() successful.");
|
||||
}
|
||||
|
||||
[APITestCase(TestType.EditMode)]
|
||||
public static void TestTextBlock()
|
||||
{
|
||||
TextBlock textBlock = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
|
||||
Assert.Errorless(() => { textBlock = Block.PlaceNew<TextBlock>(BlockIDs.TextBlock, Unity.Mathematics.float3.zero + 1); }, "Block.PlaceNew<TextBlock>() raised an exception: ", "Block.PlaceNew<TextBlock>() completed without issue.");
|
||||
if (!Assert.NotNull(textBlock, "Block.PlaceNew<TextBlock>() returned null, possibly because it failed silently.", "Specialized TextBlock is not null.")) return;
|
||||
if (!Assert.NotNull(textBlock.Text, "TextBlock.Text is null, possibly because it failed silently.", "TextBlock.Text is not null.")) return;
|
||||
if (!Assert.NotNull(textBlock.TextBlockId, "TextBlock.TextBlockId is null, possibly because it failed silently.", "TextBlock.TextBlockId is not null.")) return;
|
||||
}
|
||||
|
||||
[APITestCase(TestType.EditMode)]
|
||||
public static void TestMotor()
|
||||
{
|
||||
Block newBlock = Block.PlaceNew(BlockIDs.MotorS, Unity.Mathematics.float3.zero + 1);
|
||||
Motor b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
|
||||
Assert.Errorless(() => { b = newBlock.Specialise<Motor>(); }, "Block.Specialize<Motor>() raised an exception: ", "Block.Specialize<Motor>() completed without issue.");
|
||||
if (!Assert.NotNull(b, "Block.Specialize<Motor>() returned null, possibly because it failed silently.", "Specialized Motor is not null.")) return;
|
||||
if (!Assert.CloseTo(b.Torque, 75f, $"Motor.Torque {b.Torque} does not equal default value, possibly because it failed silently.", "Motor.Torque close enough to default.")) return;
|
||||
if (!Assert.CloseTo(b.TopSpeed, 30f, $"Motor.TopSpeed {b.TopSpeed} does not equal default value, possibly because it failed silently.", "Motor.Torque is close enough to default.")) return;
|
||||
if (!Assert.Equal(b.Reverse, false, $"Motor.Reverse {b.Reverse} does not equal default value, possibly because it failed silently.", "Motor.Reverse is default.")) return;
|
||||
}
|
||||
|
||||
[APITestCase(TestType.EditMode)]
|
||||
public static void TestPiston()
|
||||
{
|
||||
Block newBlock = Block.PlaceNew(BlockIDs.PneumaticPiston, Unity.Mathematics.float3.zero + 1);
|
||||
Piston b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
|
||||
Assert.Errorless(() => { b = newBlock.Specialise<Piston>(); }, "Block.Specialize<Piston>() raised an exception: ", "Block.Specialize<Piston>() completed without issue.");
|
||||
if (!Assert.NotNull(b, "Block.Specialize<Piston>() returned null, possibly because it failed silently.", "Specialized Piston is not null.")) return;
|
||||
if (!Assert.CloseTo(b.MaximumExtension, 1.01f, $"Piston.MaximumExtension {b.MaximumExtension} does not equal default value, possibly because it failed silently.", "Piston.MaximumExtension is close enough to default.")) return;
|
||||
if (!Assert.CloseTo(b.MaximumForce, 750f, $"Piston.MaximumForce {b.MaximumForce} does not equal default value, possibly because it failed silently.", "Piston.MaximumForce is close enough to default.")) return;
|
||||
}
|
||||
|
||||
[APITestCase(TestType.EditMode)]
|
||||
public static void TestServo()
|
||||
{
|
||||
Block newBlock = Block.PlaceNew(BlockIDs.ServoAxle, Unity.Mathematics.float3.zero + 1);
|
||||
Servo b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
|
||||
Assert.Errorless(() => { b = newBlock.Specialise<Servo>(); }, "Block.Specialize<Servo>() raised an exception: ", "Block.Specialize<Servo>() completed without issue.");
|
||||
if (!Assert.NotNull(b, "Block.Specialize<Servo>() returned null, possibly because it failed silently.", "Specialized Servo is not null.")) return;
|
||||
if (!Assert.CloseTo(b.MaximumAngle, 180f, $"Servo.MaximumAngle {b.MaximumAngle} does not equal default value, possibly because it failed silently.", "Servo.MaximumAngle is close enough to default.")) return;
|
||||
if (!Assert.CloseTo(b.MinimumAngle, -180f, $"Servo.MinimumAngle {b.MinimumAngle} does not equal default value, possibly because it failed silently.", "Servo.MinimumAngle is close enough to default.")) return;
|
||||
if (!Assert.CloseTo(b.MaximumForce, 750f, $"Servo.MaximumForce {b.MaximumForce} does not equal default value, possibly because it failed silently.", "Servo.MaximumForce is close enough to default.")) return;
|
||||
}
|
||||
|
||||
[APITestCase(TestType.Game)]
|
||||
public static void TestMusicBlock1()
|
||||
{
|
||||
Block newBlock = Block.PlaceNew(BlockIDs.MusicBlock, Unity.Mathematics.float3.zero + 2);
|
||||
MusicBlock b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
|
||||
Assert.Errorless(() => { b = newBlock.Specialise<MusicBlock>(); }, "Block.Specialize<MusicBlock>() raised an exception: ", "Block.Specialize<MusicBlock>() completed without issue.");
|
||||
if (!Assert.NotNull(b, "Block.Specialize<MusicBlock>() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return;
|
||||
if (!Assert.CloseTo(b.Volume, 100f, $"MusicBlock.Volume {b.Volume} does not equal default value, possibly because it failed silently.", "MusicBlock.Volume is close enough to default.")) return;
|
||||
if (!Assert.Equal(b.TrackIndex, 0, $"MusicBlock.TrackIndex {b.TrackIndex} does not equal default value, possibly because it failed silently.", "MusicBlock.TrackIndex is equal to default.")) return;
|
||||
_musicBlock = b;
|
||||
}
|
||||
|
||||
private static MusicBlock _musicBlock;
|
||||
|
||||
[APITestCase(TestType.EditMode)]
|
||||
public static void TestMusicBlock2()
|
||||
{
|
||||
//Block newBlock = Block.GetLastPlacedBlock();
|
||||
var b = _musicBlock;
|
||||
if (!Assert.NotNull(b, "Block.Specialize<MusicBlock>() returned null, possibly because it failed silently.", "Specialized MusicBlock is not null.")) return;
|
||||
b.IsPlaying = true; // play sfx
|
||||
if (!Assert.Equal(b.IsPlaying, true, $"MusicBlock.IsPlaying {b.IsPlaying} does not equal true, possibly because it failed silently.", "MusicBlock.IsPlaying is set properly.")) return;
|
||||
if (!Assert.Equal(b.ChannelType, ChannelType.None, $"MusicBlock.ChannelType {b.ChannelType} does not equal default value, possibly because it failed silently.", "MusicBlock.ChannelType is equal to default.")) return;
|
||||
//Assert.Log(b.Track.ToString());
|
||||
if (!Assert.Equal(b.Track.ToString(), new Guid("3237ff8f-f5f2-4f84-8144-496ca280f8c0").ToString(), $"MusicBlock.Track {b.Track} does not equal default value, possibly because it failed silently.", "MusicBlock.Track is equal to default.")) return;
|
||||
}
|
||||
|
||||
[APITestCase(TestType.EditMode)]
|
||||
public static void TestLogicGate()
|
||||
{
|
||||
Block newBlock = Block.PlaceNew(BlockIDs.NOTLogicBlock, Unity.Mathematics.float3.zero + 1);
|
||||
LogicGate b = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
|
||||
Assert.Errorless(() => { b = newBlock.Specialise<LogicGate>(); }, "Block.Specialize<LogicGate>() raised an exception: ", "Block.Specialize<LogicGate>() completed without issue.");
|
||||
if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return;
|
||||
if (!Assert.Equal(b.InputCount, 1u, $"LogicGate.InputCount {b.InputCount} does not equal default value, possibly because it failed silently.", "LogicGate.InputCount is default.")) return;
|
||||
if (!Assert.Equal(b.OutputCount, 1u, $"LogicGate.OutputCount {b.OutputCount} does not equal default value, possibly because it failed silently.", "LogicGate.OutputCount is default.")) return;
|
||||
if (!Assert.NotNull(b, "Block.Specialize<LogicGate>() returned null, possibly because it failed silently.", "Specialized LogicGate is not null.")) return;
|
||||
//if (!Assert.Equal(b.PortName(0, true), "Input", $"LogicGate.PortName(0, input:true) {b.PortName(0, true)} does not equal default value, possibly because it failed silently.", "LogicGate.PortName(0, input:true) is close enough to default.")) return;
|
||||
LogicGate target = null;
|
||||
if (!Assert.Errorless(() => { target = Block.PlaceNew<LogicGate>(BlockIDs.ANDLogicBlock, Unity.Mathematics.float3.zero + 2); })) return;
|
||||
Wire newWire = null;
|
||||
if (!Assert.Errorless(() => { newWire = b.Connect(0, target, 0);})) return;
|
||||
if (!Assert.NotNull(newWire, "SignalingBlock.Connect(...) returned null, possible because it failed silently.", "SignalingBlock.Connect(...) returned a non-null value.")) return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
|
||||
using RobocraftX.Blocks;
|
||||
using RobocraftX.Common;
|
||||
using Svelto.ECS;
|
||||
using Unity.Mathematics;
|
||||
|
||||
|
@ -9,81 +10,66 @@ using GamecraftModdingAPI.Utility;
|
|||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
public class ConsoleBlock : Block
|
||||
public class ConsoleBlock : SignalingBlock
|
||||
{
|
||||
public static ConsoleBlock PlaceNew(float3 position,
|
||||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
|
||||
int uscale = 1, float3 scale = default, Player player = null)
|
||||
public ConsoleBlock(EGID id): base(id)
|
||||
{
|
||||
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
|
||||
{
|
||||
EGID id = PlacementEngine.PlaceBlock(BlockIDs.ConsoleBlock, color, darkness,
|
||||
position, uscale, scale, player, rotation);
|
||||
return new ConsoleBlock(id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ConsoleBlock(EGID id): base(id)
|
||||
public ConsoleBlock(uint id): base(new EGID(id, CommonExclusiveGroups.BUILD_CONSOLE_BLOCK_GROUP))
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<ConsoleBlockEntityStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
public ConsoleBlock(uint id): base(id)
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<ConsoleBlockEntityStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
// custom console block properties
|
||||
|
||||
/// <summary>
|
||||
/// Setting a nonexistent command will crash the game when switching to simulation
|
||||
/// </summary>
|
||||
public string Command
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).commandName;
|
||||
return BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.commandName);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).commandName.Set(value);
|
||||
BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.commandName.Set(val),
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
public string Arg1
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg1;
|
||||
get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg1);
|
||||
|
||||
set
|
||||
{
|
||||
BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg1.Set(value);
|
||||
BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg1.Set(val),
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
public string Arg2
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg2;
|
||||
get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg2);
|
||||
|
||||
set
|
||||
{
|
||||
BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg2.Set(value);
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg2.Set(val),
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
public string Arg3
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg3;
|
||||
get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg3);
|
||||
|
||||
set
|
||||
{
|
||||
BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg3.Set(value);
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg3.Set(val),
|
||||
value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
16
GamecraftModdingAPI/Blocks/LogicGate.cs
Normal file
16
GamecraftModdingAPI/Blocks/LogicGate.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using RobocraftX.Common;
|
||||
using Svelto.ECS;
|
||||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
public class LogicGate : SignalingBlock
|
||||
{
|
||||
public LogicGate(EGID id) : base(id)
|
||||
{
|
||||
}
|
||||
|
||||
public LogicGate(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_LOGIC_BLOCK_GROUP))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
|
||||
using RobocraftX.Blocks;
|
||||
using RobocraftX.Common;
|
||||
using Svelto.ECS;
|
||||
using Unity.Mathematics;
|
||||
|
||||
|
@ -8,45 +9,14 @@ using GamecraftModdingAPI.Utility;
|
|||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
public class Motor : Block
|
||||
public class Motor : SignalingBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Places a new motor.
|
||||
/// Any valid motor type is accepted.
|
||||
/// This re-implements Block.PlaceNew(...)
|
||||
/// </summary>
|
||||
public static new Motor PlaceNew(BlockIDs block, float3 position,
|
||||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
|
||||
int uscale = 1, float3 scale = default, Player player = null)
|
||||
{
|
||||
if (!(block == BlockIDs.MotorS || block == BlockIDs.MotorM))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {typeof(Motor).Name} block");
|
||||
}
|
||||
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
|
||||
{
|
||||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness,
|
||||
position, uscale, scale, player, rotation);
|
||||
return new Motor(id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Motor(EGID id) : base(id)
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<MotorReadOnlyStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
public Motor(uint id) : base(id)
|
||||
public Motor(uint id): base(new EGID(id, CommonExclusiveGroups.BUILD_MOTOR_BLOCK_GROUP))
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<MotorReadOnlyStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
// custom motor properties
|
||||
|
@ -58,13 +28,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id).maxVelocity;
|
||||
return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.maxVelocity);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
ref MotorReadOnlyStruct motor = ref BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id);
|
||||
motor.maxVelocity = value;
|
||||
BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, float val) => st.maxVelocity = val, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,13 +44,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id).maxForce;
|
||||
return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.maxForce);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
ref MotorReadOnlyStruct motor = ref BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id);
|
||||
motor.maxForce = value;
|
||||
BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, float val) => st.maxForce = val, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,13 +60,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id).reverse;
|
||||
return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.reverse);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
ref MotorReadOnlyStruct motor = ref BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id);
|
||||
motor.reverse = value;
|
||||
BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, bool val) => st.reverse = val, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,12 +35,21 @@ namespace GamecraftModdingAPI.Blocks
|
|||
|
||||
// implementations for Movement static class
|
||||
|
||||
public float3 MoveBlock(uint blockID, float3 vector)
|
||||
internal float3 MoveBlock(EGID blockID, BlockEngine.BlockInitData data, float3 vector)
|
||||
{
|
||||
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
if (!entitiesDB.Exists<PositionEntityStruct>(blockID))
|
||||
{
|
||||
if (data.Group == null) return float3.zero;
|
||||
var init = new EntityComponentInitializer(blockID, data.Group);
|
||||
init.Init(new PositionEntityStruct {position = vector});
|
||||
init.Init(new GridRotationStruct {position = vector});
|
||||
init.Init(new LocalTransformEntityStruct {position = vector});
|
||||
return vector;
|
||||
}
|
||||
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID);
|
||||
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID);
|
||||
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID);
|
||||
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID);
|
||||
// main (persistent) position
|
||||
posStruct.position = vector;
|
||||
// placement grid position
|
||||
|
@ -52,13 +61,19 @@ namespace GamecraftModdingAPI.Blocks
|
|||
{
|
||||
Value = posStruct.position
|
||||
});
|
||||
entitiesDB.QueryEntity<GridConnectionsEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP).isProcessed = false;
|
||||
entitiesDB.QueryEntity<GridConnectionsEntityStruct>(blockID).isProcessed = false;
|
||||
return posStruct.position;
|
||||
}
|
||||
|
||||
public float3 GetPosition(uint blockID)
|
||||
internal float3 GetPosition(EGID blockID, BlockEngine.BlockInitData data)
|
||||
{
|
||||
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
if (!entitiesDB.Exists<PositionEntityStruct>(blockID))
|
||||
{
|
||||
if (data.Group == null) return float3.zero;
|
||||
var init = new EntityComponentInitializer(blockID, data.Group);
|
||||
return init.Has<PositionEntityStruct>() ? init.Get<PositionEntityStruct>().position : float3.zero;
|
||||
}
|
||||
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID);
|
||||
return posStruct.position;
|
||||
}
|
||||
}
|
||||
|
|
146
GamecraftModdingAPI/Blocks/MusicBlock.cs
Normal file
146
GamecraftModdingAPI/Blocks/MusicBlock.cs
Normal file
|
@ -0,0 +1,146 @@
|
|||
using System;
|
||||
|
||||
using FMOD.Studio;
|
||||
using FMODUnity;
|
||||
using Gamecraft.Wires;
|
||||
using RobocraftX.Common;
|
||||
using RobocraftX.Blocks;
|
||||
using Svelto.ECS;
|
||||
using Unity.Mathematics;
|
||||
|
||||
using GamecraftModdingAPI;
|
||||
using GamecraftModdingAPI.Tests;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
public class MusicBlock : SignalingBlock
|
||||
{
|
||||
public MusicBlock(EGID id) : base(id)
|
||||
{
|
||||
}
|
||||
|
||||
public MusicBlock(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_MUSIC_BLOCK_GROUP))
|
||||
{
|
||||
}
|
||||
|
||||
public byte TrackIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo(this, (MusicBlockDataEntityStruct st) => st.trackIndx);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this,
|
||||
(ref MusicBlockDataEntityStruct msdes, byte val) => msdes.trackIndx = val, value);
|
||||
}
|
||||
}
|
||||
|
||||
public Guid Track
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo(this,
|
||||
(MusicBlockDataEntityStruct msdes) => msdes.fmod2DEventPaths.Get<Guid>(msdes.trackIndx));
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref MusicBlockDataEntityStruct msdes, Guid val) =>
|
||||
{
|
||||
for (byte i = 0; i < msdes.fmod2DEventPaths.Count<Guid>(); i++)
|
||||
{
|
||||
Guid track = msdes.fmod2DEventPaths.Get<Guid>(i);
|
||||
if (track == val)
|
||||
{
|
||||
msdes.trackIndx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, value);
|
||||
}
|
||||
}
|
||||
|
||||
public Guid[] Tracks
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo(this, (MusicBlockDataEntityStruct msdes) =>
|
||||
{
|
||||
Guid[] tracks = new Guid[msdes.fmod2DEventPaths.Count<Guid>()];
|
||||
for (byte i = 0; i < tracks.Length; i++)
|
||||
{
|
||||
tracks[i] = msdes.fmod2DEventPaths.Get<Guid>(i);
|
||||
}
|
||||
return tracks;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo(this, (MusicBlockDataEntityStruct msdes) => msdes.tweakableVolume);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this,
|
||||
(ref MusicBlockDataEntityStruct msdes, float val) => msdes.tweakableVolume = val, value);
|
||||
}
|
||||
}
|
||||
|
||||
public ChannelType ChannelType
|
||||
{
|
||||
get
|
||||
{
|
||||
Assert.Log("Block exists: " + Exists);
|
||||
return BlockEngine.GetBlockInfo(this,
|
||||
(MusicBlockDataEntityStruct msdes) => (ChannelType) msdes.channelType);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this,
|
||||
(ref MusicBlockDataEntityStruct msdes, ChannelType val) => msdes.channelType = (byte) val, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPlaying
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo(this,
|
||||
(MusicBlockDataEntityStruct msdes) => msdes.isPlaying);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref MusicBlockDataEntityStruct msdes, bool val) =>
|
||||
{
|
||||
if (msdes.isPlaying == val) return;
|
||||
if (val)
|
||||
{
|
||||
// start playing
|
||||
EventInstance inst = RuntimeManager.CreateInstance(msdes.fmod2DEventPaths.Get<Guid>(msdes.trackIndx));
|
||||
inst.setVolume(msdes.tweakableVolume / 100f);
|
||||
inst.start();
|
||||
msdes.eventHandle = inst.handle;
|
||||
}
|
||||
else
|
||||
{
|
||||
// stop playing
|
||||
EventInstance inst = default(EventInstance);
|
||||
inst.handle = msdes.eventHandle;
|
||||
inst.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT);
|
||||
inst.release();
|
||||
}
|
||||
msdes.isPlaying = val;
|
||||
}, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Gamecraft.Wires;
|
||||
using RobocraftX.Common;
|
||||
using Svelto.ECS;
|
||||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
|
@ -7,27 +8,22 @@ namespace GamecraftModdingAPI.Blocks
|
|||
{
|
||||
public ObjectIdentifier(EGID id) : base(id)
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<ObjectIdEntityStruct>(Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectIdentifier(uint id) : base(id)
|
||||
public ObjectIdentifier(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_OBJID_BLOCK_GROUP))
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<ObjectIdEntityStruct>(Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
public char Identifier
|
||||
{
|
||||
get => (char) (BlockEngine.GetBlockInfo<ObjectIdEntityStruct>(Id).objectId + 'A');
|
||||
get => (char) BlockEngine.GetBlockInfo(this, (ObjectIdEntityStruct st) => st.objectId + 'A');
|
||||
set
|
||||
{
|
||||
BlockEngine.GetBlockInfo<ObjectIdEntityStruct>(Id).objectId = (byte) (value - 'A');
|
||||
Label = value + ""; //The label isn't updated automatically
|
||||
BlockEngine.SetBlockInfo(this, (ref ObjectIdEntityStruct st, char val) =>
|
||||
{
|
||||
st.objectId = (byte) (val - 'A');
|
||||
Label = val + ""; //The label isn't updated automatically
|
||||
}, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +32,7 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public byte SimID
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<ObjectIdEntityStruct>(Id).simObjectId;
|
||||
get => BlockEngine.GetBlockInfo(this, (ObjectIdEntityStruct st) => st.simObjectId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -5,48 +5,18 @@ using Svelto.ECS;
|
|||
using Unity.Mathematics;
|
||||
|
||||
using GamecraftModdingAPI.Utility;
|
||||
using RobocraftX.Common;
|
||||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
public class Piston : Block
|
||||
public class Piston : SignalingBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Places a new piston.
|
||||
/// Any valid piston type is accepted.
|
||||
/// This re-implements Block.PlaceNew(...)
|
||||
/// </summary>
|
||||
public static new Piston PlaceNew(BlockIDs block, float3 position,
|
||||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
|
||||
int uscale = 1, float3 scale = default, Player player = null)
|
||||
public Piston(EGID id) : base(id)
|
||||
{
|
||||
if (!(block == BlockIDs.ServoPiston || block == BlockIDs.StepperPiston || block == BlockIDs.PneumaticPiston))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {typeof(Piston).Name} block");
|
||||
}
|
||||
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
|
||||
{
|
||||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness,
|
||||
position, uscale, scale, player, rotation);
|
||||
return new Piston(id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Piston(EGID id) : base(id)
|
||||
public Piston(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_PISTON_BLOCK_GROUP))
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<PistonReadOnlyStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
public Piston(uint id) : base(id)
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<PistonReadOnlyStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
// custom piston properties
|
||||
|
@ -55,13 +25,13 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// The piston's max extension distance.
|
||||
/// </summary>
|
||||
public float MaximumExtension
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<PistonReadOnlyStruct>(Id).maxDeviation;
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.maxDeviation);
|
||||
|
||||
set
|
||||
{
|
||||
ref PistonReadOnlyStruct piston = ref BlockEngine.GetBlockInfo<PistonReadOnlyStruct>(Id);
|
||||
piston.maxDeviation = value;
|
||||
BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.maxDeviation = val,
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,13 +39,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// The piston's max extension force.
|
||||
/// </summary>
|
||||
public float MaximumForce
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<PistonReadOnlyStruct>(Id).maxForce;
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.maxForce);
|
||||
|
||||
set
|
||||
{
|
||||
ref PistonReadOnlyStruct piston = ref BlockEngine.GetBlockInfo<PistonReadOnlyStruct>(Id);
|
||||
piston.maxForce = value;
|
||||
BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.maxForce = val, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,15 +40,16 @@ namespace GamecraftModdingAPI.Blocks
|
|||
private static BlockEntityFactory _blockEntityFactory; //Injected from PlaceBlockEngine
|
||||
|
||||
public EGID PlaceBlock(BlockIDs block, BlockColors color, byte darkness, float3 position, int uscale,
|
||||
float3 scale, Player player, float3 rotation)
|
||||
float3 scale, Player player, float3 rotation, out EntityComponentInitializer initializer)
|
||||
{ //It appears that only the non-uniform scale has any visible effect, but if that's not given here it will be set to the uniform one
|
||||
if (darkness > 9)
|
||||
throw new Exception("That is too dark. Make sure to use 0-9 as darkness. (0 is default.)");
|
||||
return BuildBlock((ushort) block, (byte) (color + darkness * 10), position, uscale, scale, rotation,
|
||||
initializer = BuildBlock((ushort) block, (byte) (color + darkness * 10), position, uscale, scale, rotation,
|
||||
(player ?? new Player(PlayerType.Local)).Id);
|
||||
return initializer.EGID;
|
||||
}
|
||||
|
||||
private EGID BuildBlock(ushort block, byte color, float3 position, int uscale, float3 scale, float3 rot, uint playerId)
|
||||
private EntityComponentInitializer BuildBlock(ushort block, byte color, float3 position, int uscale, float3 scale, float3 rot, uint playerId)
|
||||
{
|
||||
if (_blockEntityFactory == null)
|
||||
throw new Exception("The factory is null.");
|
||||
|
@ -66,8 +67,6 @@ namespace GamecraftModdingAPI.Blocks
|
|||
RotationEntityStruct rotation = new RotationEntityStruct {rotation = rotQ};
|
||||
GridRotationStruct gridRotation = new GridRotationStruct
|
||||
{position = position, rotation = rotQ};
|
||||
CubeCategoryStruct category = new CubeCategoryStruct
|
||||
{category = CubeCategory.General, type = CubeType.Block};
|
||||
DBEntityStruct dbEntity = new DBEntityStruct {DBID = dbid};
|
||||
BlockPlacementScaleEntityStruct placementScale = new BlockPlacementScaleEntityStruct
|
||||
{
|
||||
|
@ -76,21 +75,10 @@ namespace GamecraftModdingAPI.Blocks
|
|||
unitSnapOffset = 0, isUsingUnitSize = true
|
||||
};
|
||||
EquippedColourStruct colour = new EquippedColourStruct {indexInPalette = color};
|
||||
EGID newBlockID;
|
||||
switch (category.category)
|
||||
{
|
||||
case CubeCategory.SpawnPoint:
|
||||
case CubeCategory.BuildingSpawnPoint:
|
||||
newBlockID = MachineEditingGroups.NewUncheckedBlockEGID;
|
||||
break;
|
||||
default:
|
||||
newBlockID = MachineEditingGroups.NewBlockID;
|
||||
break;
|
||||
}
|
||||
|
||||
EntityComponentInitializer
|
||||
structInitializer =
|
||||
_blockEntityFactory.Build(newBlockID, dbid); //The ghost block index is only used for triggers
|
||||
_blockEntityFactory.Build(CommonExclusiveGroups.nextBlockEntityID, dbid); //The ghost block index is only used for triggers
|
||||
if (colour.indexInPalette != byte.MaxValue)
|
||||
structInitializer.Init(new ColourParameterEntityStruct
|
||||
{
|
||||
|
@ -117,10 +105,9 @@ namespace GamecraftModdingAPI.Blocks
|
|||
PrimaryRotationUtility.InitialisePrimaryDirection(rotation.rotation, ref structInitializer);
|
||||
EGID playerEGID = new EGID(playerId, CharacterExclusiveGroups.OnFootGroup);
|
||||
ref PickedBlockExtraDataStruct pickedBlock = ref entitiesDB.QueryEntity<PickedBlockExtraDataStruct>(playerEGID);
|
||||
pickedBlock.placedBlockEntityID = playerEGID;
|
||||
pickedBlock.placedBlockEntityID = structInitializer.EGID;
|
||||
pickedBlock.placedBlockWasAPickedBlock = false;
|
||||
Block.BlockEngine.Synced = false; // Block entities will need to be submitted before properties can be used
|
||||
return newBlockID;
|
||||
return structInitializer;
|
||||
}
|
||||
|
||||
public string Name { get; } = "GamecraftModdingAPIPlacementGameEngine";
|
||||
|
|
|
@ -35,37 +35,55 @@ namespace GamecraftModdingAPI.Blocks
|
|||
|
||||
// implementations for Rotation static class
|
||||
|
||||
public float3 RotateBlock(uint blockID, Vector3 vector)
|
||||
internal float3 RotateBlock(EGID blockID, BlockEngine.BlockInitData data, Vector3 vector)
|
||||
{
|
||||
ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntity<RotationEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
if (!entitiesDB.Exists<RotationEntityStruct>(blockID))
|
||||
{
|
||||
if (data.Group == null) return float3.zero;
|
||||
var init = new EntityComponentInitializer(blockID, data.Group);
|
||||
init.Init(new RotationEntityStruct {rotation = new Quaternion {eulerAngles = vector}});
|
||||
init.Init(new GridRotationStruct {rotation = new Quaternion {eulerAngles = vector}});
|
||||
init.Init(new LocalTransformEntityStruct {rotation = new Quaternion {eulerAngles = vector}});
|
||||
return vector;
|
||||
}
|
||||
ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntity<RotationEntityStruct>(blockID);
|
||||
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID);
|
||||
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID);
|
||||
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID);
|
||||
// main (persistent) position
|
||||
Quaternion newRotation = (Quaternion)rotStruct.rotation;
|
||||
newRotation.eulerAngles += vector;
|
||||
rotStruct.rotation = (quaternion)newRotation;
|
||||
Quaternion newRotation = rotStruct.rotation;
|
||||
newRotation.eulerAngles = vector;
|
||||
rotStruct.rotation = newRotation;
|
||||
// placement grid rotation
|
||||
Quaternion newGridRotation = (Quaternion)gridStruct.rotation;
|
||||
newGridRotation.eulerAngles += vector;
|
||||
gridStruct.rotation = (quaternion)newGridRotation;
|
||||
Quaternion newGridRotation = gridStruct.rotation;
|
||||
newGridRotation.eulerAngles = vector;
|
||||
gridStruct.rotation = newGridRotation;
|
||||
// rendered position
|
||||
Quaternion newTransRotation = (Quaternion)rotStruct.rotation;
|
||||
newTransRotation.eulerAngles += vector;
|
||||
Quaternion newTransRotation = rotStruct.rotation;
|
||||
newTransRotation.eulerAngles = vector;
|
||||
transStruct.rotation = newTransRotation;
|
||||
// collision position
|
||||
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Unity.Transforms.Rotation
|
||||
{
|
||||
Value = rotStruct.rotation
|
||||
});
|
||||
entitiesDB.QueryEntity<GridConnectionsEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP).isProcessed = false;
|
||||
entitiesDB.QueryEntity<GridConnectionsEntityStruct>(blockID).isProcessed = false;
|
||||
return ((Quaternion)rotStruct.rotation).eulerAngles;
|
||||
|
||||
}
|
||||
|
||||
public float3 GetRotation(uint blockID)
|
||||
internal float3 GetRotation(EGID blockID, BlockEngine.BlockInitData data)
|
||||
{
|
||||
ref RotationEntityStruct rotStruct = ref entitiesDB.QueryEntity<RotationEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
|
||||
if (!entitiesDB.Exists<RotationEntityStruct>(blockID))
|
||||
{
|
||||
if (data.Group == null) return float3.zero;
|
||||
var init = new EntityComponentInitializer(blockID, data.Group);
|
||||
return init.Has<RotationEntityStruct>()
|
||||
? (float3) ((Quaternion) init.Get<RotationEntityStruct>().rotation).eulerAngles
|
||||
: float3.zero;
|
||||
}
|
||||
|
||||
ref RotationEntityStruct rotStruct = ref entitiesDB.QueryEntity<RotationEntityStruct>(blockID);
|
||||
return ((Quaternion) rotStruct.rotation).eulerAngles;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
|
||||
using RobocraftX.Blocks;
|
||||
using RobocraftX.Common;
|
||||
using Svelto.ECS;
|
||||
using Unity.Mathematics;
|
||||
|
||||
|
@ -8,45 +9,14 @@ using GamecraftModdingAPI.Utility;
|
|||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
public class Servo : Block
|
||||
public class Servo : SignalingBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Places a new servo.
|
||||
/// Any valid servo type is accepted.
|
||||
/// This re-implements Block.PlaceNew(...)
|
||||
/// </summary>
|
||||
public static new Servo PlaceNew(BlockIDs block, float3 position,
|
||||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
|
||||
int uscale = 1, float3 scale = default, Player player = null)
|
||||
{
|
||||
if (!(block == BlockIDs.ServoAxle || block == BlockIDs.ServoHinge || block == BlockIDs.ServoPiston))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {nameof(Servo)} block");
|
||||
}
|
||||
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
|
||||
{
|
||||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness,
|
||||
position, uscale, scale, player, rotation);
|
||||
return new Servo(id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Servo(EGID id) : base(id)
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<ServoReadOnlyStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
public Servo(uint id) : base(id)
|
||||
public Servo(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_SERVO_BLOCK_GROUP))
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<ServoReadOnlyStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
// custom servo properties
|
||||
|
@ -55,13 +25,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// The servo's minimum angle.
|
||||
/// </summary>
|
||||
public float MinimumAngle
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id).minDeviation;
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.minDeviation);
|
||||
|
||||
set
|
||||
{
|
||||
ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id);
|
||||
servo.minDeviation = value;
|
||||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.minDeviation = val, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,13 +39,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public float MaximumAngle
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id).maxDeviation;
|
||||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.maxDeviation);
|
||||
|
||||
set
|
||||
{
|
||||
ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id);
|
||||
servo.maxDeviation = value;
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.maxDeviation = val, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -84,13 +52,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public float MaximumForce
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id).maxForce;
|
||||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.maxForce);
|
||||
|
||||
set
|
||||
{
|
||||
ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id);
|
||||
servo.maxForce = value;
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.maxForce = val, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -98,13 +65,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public bool Reverse
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id).reverse;
|
||||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.reverse);
|
||||
|
||||
set
|
||||
{
|
||||
ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id);
|
||||
servo.reverse = value;
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, bool val) => st.reverse = val, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using Svelto.ECS;
|
||||
using System;
|
||||
using Svelto.ECS;
|
||||
using Svelto.DataStructures;
|
||||
using Gamecraft.Wires;
|
||||
|
||||
using GamecraftModdingAPI.Engines;
|
||||
|
@ -8,7 +10,7 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// <summary>
|
||||
/// Engine which executes signal actions
|
||||
/// </summary>
|
||||
public class SignalEngine : IApiEngine
|
||||
public class SignalEngine : IApiEngine, IFactoryEngine
|
||||
{
|
||||
public const float POSITIVE_HIGH = 1.0f;
|
||||
public const float NEGATIVE_HIGH = -1.0f;
|
||||
|
@ -19,6 +21,8 @@ namespace GamecraftModdingAPI.Blocks
|
|||
|
||||
public EntitiesDB entitiesDB { set; private get; }
|
||||
|
||||
public IEntityFactory Factory { get; set; }
|
||||
|
||||
public bool isRemovable => false;
|
||||
|
||||
public bool IsInGame = false;
|
||||
|
@ -33,7 +37,74 @@ namespace GamecraftModdingAPI.Blocks
|
|||
IsInGame = true;
|
||||
}
|
||||
|
||||
// implementations for Signal static class
|
||||
// implementations for block wiring
|
||||
|
||||
public WireEntityStruct CreateNewWire(EGID startBlock, byte startPort, EGID endBlock, byte endPort)
|
||||
{
|
||||
EGID wireEGID = new EGID(WiresExclusiveGroups.NewWireEntityId, NamedExclusiveGroup<WiresGroup>.Group);
|
||||
EntityComponentInitializer wireInitializer = Factory.BuildEntity<WireEntityDescriptor>(wireEGID);
|
||||
wireInitializer.Init(new WireEntityStruct
|
||||
{
|
||||
sourceBlockEGID = startBlock,
|
||||
sourcePortUsage = startPort,
|
||||
destinationBlockEGID = endBlock,
|
||||
destinationPortUsage = endPort,
|
||||
ID = wireEGID
|
||||
});
|
||||
return wireInitializer.Get<WireEntityStruct>();
|
||||
}
|
||||
|
||||
public ref WireEntityStruct GetWire(EGID wire)
|
||||
{
|
||||
if (!entitiesDB.Exists<WireEntityStruct>(wire))
|
||||
{
|
||||
throw new WiringException($"Wire {wire} does not exist");
|
||||
}
|
||||
return ref entitiesDB.QueryEntity<WireEntityStruct>(wire);
|
||||
}
|
||||
|
||||
public ref PortEntityStruct GetPort(EGID port)
|
||||
{
|
||||
if (!entitiesDB.Exists<PortEntityStruct>(port))
|
||||
{
|
||||
throw new WiringException($"Port {port} does not exist (yet?)");
|
||||
}
|
||||
return ref entitiesDB.QueryEntity<PortEntityStruct>(port);
|
||||
}
|
||||
|
||||
public ref PortEntityStruct GetPortByOffset(BlockPortsStruct bps, byte portNumber, bool input)
|
||||
{
|
||||
ExclusiveGroup group = input
|
||||
? NamedExclusiveGroup<InputPortsGroup>.Group
|
||||
: NamedExclusiveGroup<OutputPortsGroup>.Group;
|
||||
uint id = (input ? bps.firstInputID : bps.firstOutputID) + portNumber;
|
||||
EGID egid = new EGID(id, group);
|
||||
if (!entitiesDB.Exists<PortEntityStruct>(egid))
|
||||
{
|
||||
throw new WiringException("Port does not exist");
|
||||
}
|
||||
return ref entitiesDB.QueryEntity<PortEntityStruct>(egid);
|
||||
}
|
||||
|
||||
public ref PortEntityStruct GetPortByOffset(Block block, byte portNumber, bool input)
|
||||
{
|
||||
BlockPortsStruct bps = GetFromDbOrInitData<BlockPortsStruct>(block, block.Id, out bool exists);
|
||||
if (!exists)
|
||||
{
|
||||
throw new BlockException("Block does not exist");
|
||||
}
|
||||
return ref GetPortByOffset(bps, portNumber, input);
|
||||
}
|
||||
|
||||
public ref T GetComponent<T>(EGID egid) where T : unmanaged, IEntityComponent
|
||||
{
|
||||
return ref entitiesDB.QueryEntity<T>(egid);
|
||||
}
|
||||
|
||||
public bool Exists<T>(EGID egid) where T : struct, IEntityComponent
|
||||
{
|
||||
return entitiesDB.Exists<T>(egid);
|
||||
}
|
||||
|
||||
public bool SetSignal(EGID blockID, float signal, out uint signalID, bool input = true)
|
||||
{
|
||||
|
@ -122,7 +193,7 @@ namespace GamecraftModdingAPI.Blocks
|
|||
return inputs;
|
||||
}
|
||||
|
||||
public EGID[] GetSignalOutputs(EGID blockID)
|
||||
public EGID[] GetSignalOutputs(EGID blockID)
|
||||
{
|
||||
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(blockID);
|
||||
EGID[] outputs = new EGID[ports.outputCount];
|
||||
|
@ -133,6 +204,42 @@ namespace GamecraftModdingAPI.Blocks
|
|||
return outputs;
|
||||
}
|
||||
|
||||
public EGID MatchBlockInputToPort(Block block, byte portUsage, out bool exists)
|
||||
{
|
||||
BlockPortsStruct ports = GetFromDbOrInitData<BlockPortsStruct>(block, block.Id, out exists);
|
||||
return new EGID(ports.firstInputID + portUsage, NamedExclusiveGroup<InputPortsGroup>.Group);
|
||||
}
|
||||
|
||||
public EGID MatchBlockInputToPort(EGID block, byte portUsage, out bool exists)
|
||||
{
|
||||
if (!entitiesDB.Exists<BlockPortsStruct>(block))
|
||||
{
|
||||
exists = false;
|
||||
return default;
|
||||
}
|
||||
exists = true;
|
||||
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block);
|
||||
return new EGID(ports.firstInputID + portUsage, NamedExclusiveGroup<InputPortsGroup>.Group);
|
||||
}
|
||||
|
||||
public EGID MatchBlockOutputToPort(Block block, byte portUsage, out bool exists)
|
||||
{
|
||||
BlockPortsStruct ports = GetFromDbOrInitData<BlockPortsStruct>(block, block.Id, out exists);
|
||||
return new EGID(ports.firstOutputID + portUsage, NamedExclusiveGroup<OutputPortsGroup>.Group);
|
||||
}
|
||||
|
||||
public EGID MatchBlockOutputToPort(EGID block, byte portUsage, out bool exists)
|
||||
{
|
||||
if (!entitiesDB.Exists<BlockPortsStruct>(block))
|
||||
{
|
||||
exists = false;
|
||||
return default;
|
||||
}
|
||||
exists = true;
|
||||
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(block);
|
||||
return new EGID(ports.firstOutputID + portUsage, NamedExclusiveGroup<OutputPortsGroup>.Group);
|
||||
}
|
||||
|
||||
public ref WireEntityStruct MatchPortToWire(EGID portID, EGID blockID, out bool exists)
|
||||
{
|
||||
ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID);
|
||||
|
@ -151,6 +258,57 @@ namespace GamecraftModdingAPI.Blocks
|
|||
return ref defRef[0];
|
||||
}
|
||||
|
||||
public ref WireEntityStruct MatchBlocksToWire(EGID startBlock, EGID endBlock, out bool exists, byte startPort = byte.MaxValue,
|
||||
byte endPort = byte.MaxValue)
|
||||
{
|
||||
EGID[] startPorts;
|
||||
if (startPort == byte.MaxValue)
|
||||
{
|
||||
// search all output ports on source block
|
||||
startPorts = GetSignalOutputs(startBlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(startBlock);
|
||||
startPorts = new EGID[] {new EGID(ports.firstOutputID + startPort, NamedExclusiveGroup<OutputPortsGroup>.Group) };
|
||||
}
|
||||
|
||||
EGID[] endPorts;
|
||||
if (startPort == byte.MaxValue)
|
||||
{
|
||||
// search all input ports on destination block
|
||||
endPorts = GetSignalInputs(endBlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(endBlock);
|
||||
endPorts = new EGID[] {new EGID(ports.firstInputID + endPort, NamedExclusiveGroup<InputPortsGroup>.Group) };
|
||||
}
|
||||
|
||||
EntityCollection<WireEntityStruct> wires = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<WiresGroup>.Group);
|
||||
for (int endIndex = 0; endIndex < endPorts.Length; endIndex++)
|
||||
{
|
||||
PortEntityStruct endPES = entitiesDB.QueryEntity<PortEntityStruct>(endPorts[endIndex]);
|
||||
for (int startIndex = 0; startIndex < startPorts.Length; startIndex++)
|
||||
{
|
||||
PortEntityStruct startPES = entitiesDB.QueryEntity<PortEntityStruct>(startPorts[startIndex]);
|
||||
for (int w = 0; w < wires.count; w++)
|
||||
{
|
||||
if ((wires[w].destinationPortUsage == endPES.usage && wires[w].destinationBlockEGID == endBlock)
|
||||
&& (wires[w].sourcePortUsage == startPES.usage && wires[w].sourceBlockEGID == startBlock))
|
||||
{
|
||||
exists = true;
|
||||
return ref wires[w];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exists = false;
|
||||
WireEntityStruct[] defRef = new WireEntityStruct[1];
|
||||
return ref defRef[0];
|
||||
}
|
||||
|
||||
public ref ChannelDataStruct GetChannelDataStruct(EGID portID, out bool exists)
|
||||
{
|
||||
ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID);
|
||||
|
@ -166,21 +324,75 @@ namespace GamecraftModdingAPI.Blocks
|
|||
}
|
||||
|
||||
public EGID[] GetElectricBlocks()
|
||||
{
|
||||
uint count = entitiesDB.Count<BlockPortsStruct>(BlockIdentifiers.OWNED_BLOCKS) + entitiesDB.Count<BlockPortsStruct>(BlockIdentifiers.FUNCTIONAL_BLOCK_PARTS);
|
||||
uint i = 0;
|
||||
EGID[] res = new EGID[count];
|
||||
foreach (ref BlockPortsStruct s in entitiesDB.QueryEntities<BlockPortsStruct>(BlockIdentifiers.OWNED_BLOCKS))
|
||||
{
|
||||
res[i] = s.ID;
|
||||
i++;
|
||||
}
|
||||
foreach (ref BlockPortsStruct s in entitiesDB.QueryEntities<BlockPortsStruct>(BlockIdentifiers.FUNCTIONAL_BLOCK_PARTS))
|
||||
{
|
||||
res[i] = s.ID;
|
||||
i++;
|
||||
}
|
||||
return res;
|
||||
{
|
||||
var res = new FasterList<EGID>();
|
||||
foreach (var (coll, _) in entitiesDB.QueryEntities<BlockPortsStruct>())
|
||||
foreach (ref BlockPortsStruct s in coll)
|
||||
res.Add(s.ID);
|
||||
return res.ToArray();
|
||||
}
|
||||
|
||||
public EGID[] WiredToInput(EGID block, byte port)
|
||||
{
|
||||
WireEntityStruct[] wireEntityStructs = Search(NamedExclusiveGroup<WiresGroup>.Group,
|
||||
(WireEntityStruct wes) => wes.destinationPortUsage == port && wes.destinationBlockEGID == block);
|
||||
EGID[] result = new EGID[wireEntityStructs.Length];
|
||||
for (uint i = 0; i < wireEntityStructs.Length; i++)
|
||||
{
|
||||
result[i] = wireEntityStructs[i].ID;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public EGID[] WiredToOutput(EGID block, byte port)
|
||||
{
|
||||
WireEntityStruct[] wireEntityStructs = Search(NamedExclusiveGroup<WiresGroup>.Group,
|
||||
(WireEntityStruct wes) => wes.sourcePortUsage == port && wes.sourceBlockEGID == block);
|
||||
EGID[] result = new EGID[wireEntityStructs.Length];
|
||||
for (uint i = 0; i < wireEntityStructs.Length; i++)
|
||||
{
|
||||
result[i] = wireEntityStructs[i].ID;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private T[] Search<T>(ExclusiveGroup group, Func<T, bool> isMatch) where T : struct, IEntityComponent
|
||||
{
|
||||
FasterList<T> results = new FasterList<T>();
|
||||
EntityCollection<T> components = entitiesDB.QueryEntities<T>(group);
|
||||
for (uint i = 0; i < components.count; i++)
|
||||
{
|
||||
if (isMatch(components[i]))
|
||||
{
|
||||
results.Add(components[i]);
|
||||
}
|
||||
}
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
private ref T GetFromDbOrInitData<T>(Block block, EGID id, out bool exists) where T : unmanaged, IEntityComponent
|
||||
{
|
||||
T[] defRef = new T[1];
|
||||
if (entitiesDB.Exists<T>(id))
|
||||
{
|
||||
exists = true;
|
||||
return ref entitiesDB.QueryEntity<T>(id);
|
||||
}
|
||||
if (block == null || block.InitData.Group == null)
|
||||
{
|
||||
exists = false;
|
||||
return ref defRef[0];
|
||||
}
|
||||
EntityComponentInitializer initializer = new EntityComponentInitializer(block.Id, block.InitData.Group);
|
||||
if (initializer.Has<T>())
|
||||
{
|
||||
exists = true;
|
||||
return ref initializer.Get<T>();
|
||||
}
|
||||
exists = false;
|
||||
return ref defRef[0];
|
||||
}
|
||||
|
||||
private EntityCollection<ChannelDataStruct> GetSignalStruct(uint signalID, out uint index, bool input = true)
|
||||
|
|
|
@ -14,44 +14,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public class SignalingBlock : Block
|
||||
{
|
||||
/// <summary>
|
||||
/// Places a new signaling block.
|
||||
/// Any valid functional block type with IO ports will work.
|
||||
/// This re-implements Block.PlaceNew(...)
|
||||
/// </summary>
|
||||
public static new SignalingBlock PlaceNew(BlockIDs block, float3 position,
|
||||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
|
||||
int uscale = 1, float3 scale = default, Player player = null)
|
||||
{
|
||||
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
|
||||
{
|
||||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness,
|
||||
position, uscale, scale, player, rotation);
|
||||
return new SignalingBlock(id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public SignalingBlock(EGID id) : base(id)
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<BlockPortsStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
public SignalingBlock(uint id) : base(id)
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<BlockPortsStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
protected ref BlockPortsStruct GetBlockPortsStruct()
|
||||
{
|
||||
return ref BlockEngine.GetBlockInfo<BlockPortsStruct>(Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -72,16 +40,6 @@ namespace GamecraftModdingAPI.Blocks
|
|||
return SignalEngine.GetSignalOutputs(Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the port struct.
|
||||
/// </summary>
|
||||
/// <returns>The port struct.</returns>
|
||||
/// <param name="portId">Port identifier.</param>
|
||||
protected ref PortEntityStruct GetPortStruct(EGID portId)
|
||||
{
|
||||
return ref BlockEngine.GetBlockInfo<PortEntityStruct>(portId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connected wire.
|
||||
/// </summary>
|
||||
|
@ -108,16 +66,102 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// The input port count.
|
||||
/// </summary>
|
||||
public uint InputCount
|
||||
{
|
||||
get => GetBlockPortsStruct().inputCount;
|
||||
}
|
||||
{
|
||||
get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.inputCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The output port count.
|
||||
/// </summary>
|
||||
public uint OutputCount
|
||||
{
|
||||
get => GetBlockPortsStruct().outputCount;
|
||||
get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.outputCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect an output on this block to an input on another block.
|
||||
/// </summary>
|
||||
/// <param name="sourcePort">Output port number.</param>
|
||||
/// <param name="destination">Input block.</param>
|
||||
/// <param name="destinationPort">Input port number.</param>
|
||||
/// <returns>The wire connection</returns>
|
||||
/// <exception cref="WiringException">The wire could not be created.</exception>
|
||||
public Wire Connect(byte sourcePort, SignalingBlock destination, byte destinationPort)
|
||||
{
|
||||
if (sourcePort >= OutputCount)
|
||||
{
|
||||
throw new WiringException("Source port does not exist");
|
||||
}
|
||||
|
||||
if (destinationPort >= destination.InputCount)
|
||||
{
|
||||
throw new WiringException("Destination port does not exist");
|
||||
}
|
||||
return Wire.Connect(this, sourcePort, destination, destinationPort);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The port's name.
|
||||
/// This is localized to the user's language, so this is not reliable for port identification.
|
||||
/// </summary>
|
||||
/// <param name="port">Port number.</param>
|
||||
/// <param name="input">Whether the port is an input (true) or an output (false).</param>
|
||||
/// <returns>The localized port name.</returns>
|
||||
public string PortName(byte port, bool input)
|
||||
{
|
||||
BlockPortsStruct bps = BlockEngine.GetBlockInfo(this, (BlockPortsStruct a) => a);
|
||||
PortEntityStruct pes = SignalEngine.GetPortByOffset(this, port, input);
|
||||
return pes.portNameLocalised;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The input port's name.
|
||||
/// </summary>
|
||||
/// <param name="port">Input port number.</param>
|
||||
/// <returns>The port name, localized to the user's language.</returns>
|
||||
public string InputPortName(byte port) => PortName(port, true);
|
||||
|
||||
/// <summary>
|
||||
/// The output port's name.
|
||||
/// </summary>
|
||||
/// <param name="port">Output port number.</param>
|
||||
/// <returns>The port name, localized to the user's language.</returns>
|
||||
public string OutputPortName(byte port) => PortName(port, false);
|
||||
|
||||
/// <summary>
|
||||
/// All wires connected to the input port.
|
||||
/// These wires will always be wired output -> input.
|
||||
/// </summary>
|
||||
/// <param name="port">Port number.</param>
|
||||
/// <returns>Wires connected to the input port.</returns>
|
||||
public Wire[] ConnectedToInput(byte port)
|
||||
{
|
||||
if (port >= InputCount) throw new WiringException($"Port input {port} does not exist");
|
||||
EGID[] wireEgids = SignalEngine.WiredToInput(Id, port);
|
||||
Wire[] wires = new Wire[wireEgids.Length];
|
||||
for (uint i = 0; i < wireEgids.Length; i++)
|
||||
{
|
||||
wires[i] = new Wire(wireEgids[i]);
|
||||
}
|
||||
return wires;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All wires connected to the output port.
|
||||
/// These wires will always be wired output -> input.
|
||||
/// </summary>
|
||||
/// <param name="port">Port number.</param>
|
||||
/// <returns>Wires connected to the output port.</returns>
|
||||
public Wire[] ConnectedToOutput(byte port)
|
||||
{
|
||||
if (port >= OutputCount) throw new WiringException($"Port output {port} does not exist");
|
||||
EGID[] wireEgids = SignalEngine.WiredToOutput(Id, port);
|
||||
Wire[] wires = new Wire[wireEgids.Length];
|
||||
for (uint i = 0; i < wireEgids.Length; i++)
|
||||
{
|
||||
wires[i] = new Wire(wireEgids[i]);
|
||||
}
|
||||
return wires;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
|
||||
using RobocraftX.Blocks;
|
||||
using RobocraftX.Common;
|
||||
using Gamecraft.CharacterVulnerability;
|
||||
using Svelto.ECS;
|
||||
using Unity.Mathematics;
|
||||
|
@ -12,43 +13,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
{
|
||||
public class SpawnPoint : Block
|
||||
{
|
||||
/// <summary>
|
||||
/// Places a new spawn point.
|
||||
/// Any valid spawn block type is accepted.
|
||||
/// This re-implements Block.PlaceNew(...)
|
||||
/// </summary>
|
||||
public static new SpawnPoint PlaceNew(BlockIDs block, float3 position,
|
||||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
|
||||
int uscale = 1, float3 scale = default, Player player = null)
|
||||
public SpawnPoint(EGID id) : base(id)
|
||||
{
|
||||
if (!(block == BlockIDs.LargeSpawn || block == BlockIDs.SmallSpawn || block == BlockIDs.MediumSpawn || block == BlockIDs.PlayerSpawn))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {nameof(SpawnPoint)} block");
|
||||
}
|
||||
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
|
||||
{
|
||||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness,
|
||||
position, uscale, scale, player, rotation);
|
||||
return new SpawnPoint(id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public SpawnPoint(EGID id) : base(id)
|
||||
public SpawnPoint(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_SPAWNPOINT_BLOCK_GROUP))
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<SpawnPointStatsEntityStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
public SpawnPoint(uint id) : base(id)
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<SpawnPointStatsEntityStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
// custom spawn point properties
|
||||
|
@ -58,16 +28,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public uint Lives
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id).lives;
|
||||
}
|
||||
get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.lives);
|
||||
|
||||
set
|
||||
{
|
||||
ref SpawnPointStatsEntityStruct spses = ref BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id);
|
||||
spses.lives = value;
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, uint val) => st.lives = val, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -75,16 +41,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public bool Damageable
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id).canTakeDamage;
|
||||
}
|
||||
get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.canTakeDamage);
|
||||
|
||||
set
|
||||
{
|
||||
ref SpawnPointStatsEntityStruct spses = ref BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id);
|
||||
spses.canTakeDamage = value;
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, bool val) => st.canTakeDamage = val, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -92,16 +54,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public bool GameOverEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id).gameOverScreen;
|
||||
}
|
||||
get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.gameOverScreen);
|
||||
|
||||
set
|
||||
{
|
||||
ref SpawnPointStatsEntityStruct spses = ref BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id);
|
||||
spses.gameOverScreen = value;
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, bool val) => st.gameOverScreen = val, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -109,16 +67,12 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public byte Team
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<SpawnPointIdsEntityStruct>(Id).teamId;
|
||||
}
|
||||
get => BlockEngine.GetBlockInfo(this, (SpawnPointIdsEntityStruct st) => st.teamId);
|
||||
|
||||
set
|
||||
{
|
||||
ref SpawnPointIdsEntityStruct spses = ref BlockEngine.GetBlockInfo<SpawnPointIdsEntityStruct>(Id);
|
||||
spses.teamId = value;
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref SpawnPointIdsEntityStruct st, byte val) => st.teamId = val, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
|
||||
using Gamecraft.Blocks.GUI;
|
||||
using RobocraftX.Common;
|
||||
using Svelto.ECS;
|
||||
using Unity.Mathematics;
|
||||
|
||||
|
@ -9,37 +10,14 @@ using GamecraftModdingAPI.Utility;
|
|||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
public class TextBlock : Block
|
||||
public class TextBlock : SignalingBlock
|
||||
{
|
||||
|
||||
public static TextBlock PlaceNew(float3 position,
|
||||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
|
||||
int uscale = 1, float3 scale = default, Player player = null)
|
||||
{
|
||||
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
|
||||
{
|
||||
EGID id = PlacementEngine.PlaceBlock(BlockIDs.TextBlock, color, darkness,
|
||||
position, uscale, scale, player, rotation);
|
||||
return new TextBlock(id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public TextBlock(EGID id) : base(id)
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<TextBlockDataStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
public TextBlock(uint id) : base(id)
|
||||
public TextBlock(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_TEXT_BLOCK_GROUP))
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<TextBlockDataStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
// custom text block properties
|
||||
|
@ -49,35 +27,34 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<TextBlockDataStruct>(Id).textCurrent;
|
||||
}
|
||||
get => BlockEngine.GetBlockInfo(this, (TextBlockDataStruct st) => st.textCurrent);
|
||||
|
||||
set
|
||||
{
|
||||
ref TextBlockDataStruct tbds = ref BlockEngine.GetBlockInfo<TextBlockDataStruct>(Id);
|
||||
tbds.textCurrent.Set(value);
|
||||
tbds.textStored.Set(value);
|
||||
BlockEngine.GetBlockInfo<TextBlockNetworkDataStruct>(Id).newTextBlockStringContent.Set(value);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) =>
|
||||
{
|
||||
tbds.textCurrent.Set(val);
|
||||
tbds.textStored.Set(val);
|
||||
}, value);
|
||||
BlockEngine.SetBlockInfo(this,
|
||||
(ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockStringContent.Set(val), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The text block's current text block ID (used in ChangeTextBlockCommand).
|
||||
/// The text block's current text block ID (used in ChangeTextBlockCommand).
|
||||
/// </summary>
|
||||
public string TextBlockId
|
||||
public string TextBlockId
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<TextBlockDataStruct>(Id).textBlockID;
|
||||
}
|
||||
get => BlockEngine.GetBlockInfo(this, (TextBlockDataStruct st) => st.textBlockID);
|
||||
|
||||
set
|
||||
{
|
||||
BlockEngine.GetBlockInfo<TextBlockDataStruct>(Id).textBlockID.Set(value);
|
||||
BlockEngine.GetBlockInfo<TextBlockNetworkDataStruct>(Id).newTextBlockID.Set(value);
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) =>
|
||||
tbds.textBlockID.Set(val), value);
|
||||
BlockEngine.SetBlockInfo(this,
|
||||
(ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockID.Set(val), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
|
||||
using RobocraftX.Blocks;
|
||||
using RobocraftX.Common;
|
||||
using Gamecraft.Blocks.TimerBlock;
|
||||
using Svelto.ECS;
|
||||
using Unity.Mathematics;
|
||||
|
@ -10,39 +11,14 @@ using GamecraftModdingAPI.Utility;
|
|||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
public class Timer : Block
|
||||
public class Timer : SignalingBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Places a new timer block.
|
||||
/// </summary>
|
||||
public static Timer PlaceNew(float3 position,
|
||||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
|
||||
int uscale = 1, float3 scale = default, Player player = null)
|
||||
{
|
||||
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
|
||||
{
|
||||
EGID id = PlacementEngine.PlaceBlock(BlockIDs.Timer, color, darkness,
|
||||
position, uscale, scale, player, rotation);
|
||||
return new Timer(id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Timer(EGID id) : base(id)
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<TimerBlockDataStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
public Timer(uint id) : base(id)
|
||||
public Timer(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_TIMER_BLOCK_GROUP))
|
||||
{
|
||||
if (!BlockEngine.GetBlockInfoExists<TimerBlockDataStruct>(this.Id))
|
||||
{
|
||||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
|
||||
}
|
||||
}
|
||||
|
||||
// custom timer properties
|
||||
|
@ -52,16 +28,13 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public float Start
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id).startTime;
|
||||
}
|
||||
get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.startTime);
|
||||
|
||||
set
|
||||
{
|
||||
ref TimerBlockDataStruct tbds = ref BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id);
|
||||
tbds.startTime = value;
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, float val) => tbds.startTime = val,
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -69,16 +42,13 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public float End
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id).endTime;
|
||||
}
|
||||
get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.endTime);
|
||||
|
||||
set
|
||||
{
|
||||
ref TimerBlockDataStruct tbds = ref BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id);
|
||||
tbds.endTime = value;
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, float val) => tbds.endTime = val,
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -86,16 +56,13 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public bool DisplayMilliseconds
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id).outputFormatHasMS;
|
||||
}
|
||||
get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.outputFormatHasMS);
|
||||
|
||||
set
|
||||
{
|
||||
ref TimerBlockDataStruct tbds = ref BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id);
|
||||
tbds.outputFormatHasMS = value;
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, bool val) => tbds.outputFormatHasMS = val,
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -103,16 +70,13 @@ namespace GamecraftModdingAPI.Blocks
|
|||
/// </summary>
|
||||
public int CurrentTime
|
||||
{
|
||||
get
|
||||
{
|
||||
return BlockEngine.GetBlockInfo<TimerBlockLabelCacheEntityStruct>(Id).timeLastRenderFrameMS;
|
||||
}
|
||||
get => BlockEngine.GetBlockInfo(this, (TimerBlockLabelCacheEntityStruct st) => st.timeLastRenderFrameMS);
|
||||
|
||||
set
|
||||
{
|
||||
ref TimerBlockLabelCacheEntityStruct tblces = ref BlockEngine.GetBlockInfo<TimerBlockLabelCacheEntityStruct>(Id);
|
||||
tblces.timeLastRenderFrameMS = value;
|
||||
}
|
||||
set
|
||||
{
|
||||
BlockEngine.SetBlockInfo(this, (ref TimerBlockLabelCacheEntityStruct tbds, int val) => tbds.timeLastRenderFrameMS = val,
|
||||
value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
355
GamecraftModdingAPI/Blocks/Wire.cs
Normal file
355
GamecraftModdingAPI/Blocks/Wire.cs
Normal file
|
@ -0,0 +1,355 @@
|
|||
using System;
|
||||
|
||||
using Gamecraft.Wires;
|
||||
using Svelto.ECS;
|
||||
using Svelto.ECS.Experimental;
|
||||
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
namespace GamecraftModdingAPI.Blocks
|
||||
{
|
||||
public class Wire
|
||||
{
|
||||
internal static SignalEngine signalEngine;
|
||||
|
||||
protected EGID startPortEGID;
|
||||
|
||||
protected EGID endPortEGID;
|
||||
|
||||
protected EGID startBlockEGID;
|
||||
|
||||
protected EGID endBlockEGID;
|
||||
|
||||
protected EGID wireEGID;
|
||||
|
||||
protected bool inputToOutput;
|
||||
|
||||
protected byte startPort;
|
||||
|
||||
protected byte endPort;
|
||||
|
||||
public static Wire Connect(SignalingBlock start, byte startPort, SignalingBlock end, byte endPort)
|
||||
{
|
||||
WireEntityStruct wire = signalEngine.CreateNewWire(start.Id, startPort, end.Id, endPort);
|
||||
return new Wire(wire, start, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An existing wire connection ending at the specified input.
|
||||
/// If multiple exist, this will return the first one found.
|
||||
/// </summary>
|
||||
/// <param name="end">Destination block.</param>
|
||||
/// <param name="endPort">Port number.</param>
|
||||
/// <returns>The wire, where the end of the wire is the block port specified, or null if does not exist.</returns>
|
||||
public static Wire ConnectedToInputPort(SignalingBlock end, byte endPort)
|
||||
{
|
||||
EGID port = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists);
|
||||
if (!exists) return null;
|
||||
WireEntityStruct wire = signalEngine.MatchPortToWire(port, end.Id, out exists);
|
||||
if (exists)
|
||||
{
|
||||
return new Wire(new Block(wire.sourceBlockEGID), end, wire.sourcePortUsage, endPort);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An existing wire connection starting at the specified output.
|
||||
/// If multiple exist, this will return the first one found.
|
||||
/// </summary>
|
||||
/// <param name="start">Source block entity ID.</param>
|
||||
/// <param name="startPort">Port number.</param>
|
||||
/// <returns>The wire, where the start of the wire is the block port specified, or null if does not exist.</returns>
|
||||
public static Wire ConnectedToOutputPort(SignalingBlock start, byte startPort)
|
||||
{
|
||||
EGID port = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists);
|
||||
if (!exists) return null;
|
||||
WireEntityStruct wire = signalEngine.MatchPortToWire(port, start.Id, out exists);
|
||||
if (exists)
|
||||
{
|
||||
return new Wire(start, new Block(wire.destinationBlockEGID), startPort, wire.destinationPortUsage);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a wire object from an existing connection.
|
||||
/// </summary>
|
||||
/// <param name="start">Starting block ID.</param>
|
||||
/// <param name="end">Ending block ID.</param>
|
||||
/// <param name="startPort">Starting port number, or guess if omitted.</param>
|
||||
/// <param name="endPort">Ending port number, or guess if omitted.</param>
|
||||
/// <exception cref="WireInvalidException">Guessing failed or wire does not exist.</exception>
|
||||
public Wire(Block start, Block end, byte startPort = Byte.MaxValue, byte endPort = Byte.MaxValue)
|
||||
{
|
||||
startBlockEGID = start.Id;
|
||||
endBlockEGID = end.Id;
|
||||
// find block ports
|
||||
WireEntityStruct wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, out bool exists, startPort, endPort);
|
||||
if (exists)
|
||||
{
|
||||
wireEGID = wire.ID;
|
||||
endPortEGID = signalEngine.MatchBlockInputToPort(end, wire.destinationPortUsage, out exists);
|
||||
if (!exists) throw new WireInvalidException("Wire end port not found");
|
||||
startPortEGID = signalEngine.MatchBlockOutputToPort(start, wire.sourcePortUsage, out exists);
|
||||
if (!exists) throw new WireInvalidException("Wire start port not found");
|
||||
inputToOutput = false;
|
||||
endPort = wire.destinationPortUsage;
|
||||
startPort = wire.sourcePortUsage;
|
||||
}
|
||||
else
|
||||
{
|
||||
// flip I/O around and try again
|
||||
wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, out exists, endPort, startPort);
|
||||
if (exists)
|
||||
{
|
||||
wireEGID = wire.ID;
|
||||
endPortEGID = signalEngine.MatchBlockOutputToPort(end, wire.sourcePortUsage, out exists);
|
||||
if (!exists) throw new WireInvalidException("Wire end port not found");
|
||||
startPortEGID = signalEngine.MatchBlockInputToPort(start, wire.destinationPortUsage, out exists);
|
||||
if (!exists) throw new WireInvalidException("Wire start port not found");
|
||||
inputToOutput = true; // end is actually the source
|
||||
// NB: start and end are handled exactly as they're received as params.
|
||||
// This makes wire traversal easier, but makes logic in this class a bit more complex
|
||||
endPort = wire.sourcePortUsage;
|
||||
startPort = wire.destinationPortUsage;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new WireInvalidException("Wire not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a wire object from an existing wire connection.
|
||||
/// </summary>
|
||||
/// <param name="start">Starting block ID.</param>
|
||||
/// <param name="end">Ending block ID.</param>
|
||||
/// <param name="startPort">Starting port number.</param>
|
||||
/// <param name="endPort">Ending port number.</param>
|
||||
/// <param name="wire">The wire ID.</param>
|
||||
/// <param name="inputToOutput">Whether the wire direction goes input -> output (true) or output -> input (false, preferred).</param>
|
||||
public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool inputToOutput)
|
||||
{
|
||||
this.startBlockEGID = start.Id;
|
||||
this.endBlockEGID = end.Id;
|
||||
this.inputToOutput = inputToOutput;
|
||||
this.wireEGID = wire;
|
||||
if (inputToOutput)
|
||||
{
|
||||
endPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out bool exists);
|
||||
if (!exists) throw new WireInvalidException("Wire end port not found");
|
||||
startPortEGID = signalEngine.MatchBlockInputToPort(end, endPort, out exists);
|
||||
if (!exists) throw new WireInvalidException("Wire start port not found");
|
||||
}
|
||||
else
|
||||
{
|
||||
endPortEGID = signalEngine.MatchBlockInputToPort(end, endPort, out bool exists);
|
||||
if (!exists) throw new WireInvalidException("Wire end port not found");
|
||||
startPortEGID = signalEngine.MatchBlockOutputToPort(start, startPort, out exists);
|
||||
if (!exists) throw new WireInvalidException("Wire start port not found");
|
||||
}
|
||||
this.startPort = startPort;
|
||||
this.endPort = endPort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a wire object from an existing wire connection.
|
||||
/// </summary>
|
||||
/// <param name="wireEgid">The wire ID.</param>
|
||||
public Wire(EGID wireEgid)
|
||||
{
|
||||
this.wireEGID = wireEgid;
|
||||
WireEntityStruct wire = signalEngine.GetWire(wireEGID);
|
||||
this.startBlockEGID = wire.sourceBlockEGID;
|
||||
this.endBlockEGID = wire.destinationBlockEGID;
|
||||
this.inputToOutput = false;
|
||||
endPortEGID = signalEngine.MatchBlockInputToPort(wire.destinationBlockEGID, wire.destinationPortUsage, out bool exists);
|
||||
if (!exists) throw new WireInvalidException("Wire end port not found");
|
||||
startPortEGID = signalEngine.MatchBlockOutputToPort(wire.sourceBlockEGID, wire.sourcePortUsage, out exists);
|
||||
if (!exists) throw new WireInvalidException("Wire start port not found");
|
||||
this.endPort = wire.destinationPortUsage;
|
||||
this.startPort = wire.sourcePortUsage;
|
||||
}
|
||||
|
||||
internal Wire(WireEntityStruct wire, SignalingBlock src, SignalingBlock dest)
|
||||
{
|
||||
this.wireEGID = wire.ID;
|
||||
this.startBlockEGID = wire.sourceBlockEGID;
|
||||
this.endBlockEGID = wire.destinationBlockEGID;
|
||||
inputToOutput = false;
|
||||
endPortEGID = signalEngine.MatchBlockInputToPort(dest, wire.destinationPortUsage, out bool exists);
|
||||
if (!exists) throw new WireInvalidException("Wire end port not found");
|
||||
startPortEGID = signalEngine.MatchBlockOutputToPort(src, wire.sourcePortUsage, out exists);
|
||||
if (!exists) throw new WireInvalidException("Wire start port not found");
|
||||
this.endPort = wire.destinationPortUsage;
|
||||
this.startPort = wire.sourcePortUsage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The wire's in-game id.
|
||||
/// </summary>
|
||||
public EGID Id
|
||||
{
|
||||
get => wireEGID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The wire's signal value, as a float.
|
||||
/// </summary>
|
||||
public float Float
|
||||
{
|
||||
get
|
||||
{
|
||||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
|
||||
if (!exists) return 0f;
|
||||
return cds.valueAsFloat;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
|
||||
if (!exists) return;
|
||||
cds.valueAsFloat = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The wire's string signal.
|
||||
/// </summary>
|
||||
public string String
|
||||
{
|
||||
get
|
||||
{
|
||||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
|
||||
if (!exists) return "";
|
||||
return cds.valueAsEcsString;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
|
||||
if (!exists) return;
|
||||
cds.valueAsEcsString.Set(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The wire's raw string signal.
|
||||
/// </summary>
|
||||
public ECSString ECSString
|
||||
{
|
||||
get
|
||||
{
|
||||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
|
||||
if (!exists) return default;
|
||||
return cds.valueAsEcsString;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
|
||||
if (!exists) return;
|
||||
cds.valueAsEcsString = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The wire's signal id.
|
||||
/// I'm 50% sure this is useless.
|
||||
/// </summary>
|
||||
public uint SignalId
|
||||
{
|
||||
get
|
||||
{
|
||||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
|
||||
if (!exists) return uint.MaxValue;
|
||||
return cds.valueAsID;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
ref ChannelDataStruct cds = ref signalEngine.GetChannelDataStruct(startPortEGID, out bool exists);
|
||||
if (!exists) return;
|
||||
cds.valueAsID = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The block at the beginning of the wire.
|
||||
/// </summary>
|
||||
public SignalingBlock Start
|
||||
{
|
||||
get => new SignalingBlock(startBlockEGID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The port number that the beginning of the wire connects to.
|
||||
/// </summary>
|
||||
public byte StartPort
|
||||
{
|
||||
get => startPort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The block at the end of the wire.
|
||||
/// </summary>
|
||||
public SignalingBlock End
|
||||
{
|
||||
get => new SignalingBlock(endBlockEGID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The port number that the end of the wire connects to.
|
||||
/// </summary>
|
||||
public byte EndPort
|
||||
{
|
||||
get => endPort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a copy of the wire object where the direction of the wire is guaranteed to be from a block output to a block input.
|
||||
/// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input).
|
||||
/// </summary>
|
||||
/// <returns>A copy of the wire object.</returns>
|
||||
public Wire OutputToInputCopy()
|
||||
{
|
||||
return new Wire(wireEGID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the wire object to the direction the signal flows.
|
||||
/// Signals on wires always flow from a block output port to a block input port.
|
||||
/// This is simply a different memory configuration and does not affect the in-game wire (which is always output -> input).
|
||||
/// </summary>
|
||||
public void OutputToInputInPlace()
|
||||
{
|
||||
if (inputToOutput)
|
||||
{
|
||||
inputToOutput = false;
|
||||
// swap inputs and outputs
|
||||
EGID temp = endBlockEGID;
|
||||
endBlockEGID = startBlockEGID;
|
||||
startBlockEGID = temp;
|
||||
temp = endPortEGID;
|
||||
endPortEGID = startPortEGID;
|
||||
startPortEGID = temp;
|
||||
byte tempPortNumber = endPort;
|
||||
endPort = startPort;
|
||||
startPort = tempPortNumber;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (signalEngine.Exists<WireEntityStruct>(wireEGID))
|
||||
{
|
||||
return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(End.Id)}: {End.Id}, ({Start.Type}::{StartPort} aka {Start.PortName(StartPort, inputToOutput)}) -> ({End.Type}::{EndPort} aka {End.PortName(EndPort, !inputToOutput)})";
|
||||
}
|
||||
return $"{nameof(Id)}: {Id}, Start{nameof(Start.Id)}: {Start.Id}, End{nameof(End.Id)}: {End.Id}, ({Start.Type}::{StartPort} -> {End.Type}::{EndPort})";
|
||||
}
|
||||
|
||||
internal static void Init() { }
|
||||
}
|
||||
}
|
41
GamecraftModdingAPI/Cluster.cs
Normal file
41
GamecraftModdingAPI/Cluster.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using Gamecraft.Damage;
|
||||
using RobocraftX.Common;
|
||||
using Svelto.ECS;
|
||||
|
||||
namespace GamecraftModdingAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// Represnts a cluster of blocks in time running mode, meaning blocks that are connected either directly or via joints.
|
||||
/// </summary>
|
||||
public class Cluster
|
||||
{
|
||||
public EGID Id { get; }
|
||||
|
||||
public Cluster(EGID id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public Cluster(uint id) : this(new EGID(id, CommonExclusiveGroups.SIMULATION_CLUSTERS_GROUP))
|
||||
{
|
||||
}
|
||||
|
||||
public float InitialHealth
|
||||
{
|
||||
get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(Id).initialHealth;
|
||||
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(Id).initialHealth = value;
|
||||
}
|
||||
|
||||
public float CurrentHealth
|
||||
{
|
||||
get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(Id).currentHealth;
|
||||
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(Id).currentHealth = value;
|
||||
}
|
||||
|
||||
public float HealthMultiplier
|
||||
{
|
||||
get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(Id).healthMultiplier;
|
||||
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(Id).healthMultiplier = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,8 @@ namespace GamecraftModdingAPI.Events
|
|||
/// Patch of RobocraftX.StateSync.DeterministicStepCompositionRoot.ComposeEnginesGroups(...)
|
||||
/// </summary>
|
||||
//[HarmonyPatch(typeof(DeterministicStepCompositionRoot), "DeterministicCompose")]
|
||||
[HarmonyPatch]
|
||||
[Obsolete]
|
||||
[HarmonyPatch]
|
||||
class GameHostTransitionDeterministicGroupEnginePatch
|
||||
{
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ using Svelto.ECS;
|
|||
|
||||
namespace GamecraftModdingAPI.Events
|
||||
{
|
||||
[Obsolete]
|
||||
public class EmitterBuilder
|
||||
{
|
||||
private string name;
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// <summary>
|
||||
/// Convenient factories for mod event engines
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public static class EventEngineFactory
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// Keeps track of event handlers and emitters.
|
||||
/// This is used to add, remove and get API event handlers and emitters.
|
||||
/// </summary>
|
||||
[Obsolete("This will be removed in an upcoming update. Use the new C# event architecture from GamecraftModdingAPI.App")]
|
||||
public static class EventManager
|
||||
{
|
||||
private static Dictionary<string, IEventEmitterEngine> _eventEmitters = new Dictionary<string, IEventEmitterEngine>();
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// <summary>
|
||||
/// Patch of RobocraftX.FullGameCompositionRoot.ActivateGame()
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
[HarmonyPatch]
|
||||
class GameActivatedComposePatch
|
||||
{
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// <summary>
|
||||
/// Patch of RobocraftX.FullGameCompositionRoot.ReloadGame()
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
[HarmonyPatch(typeof(FullGameCompositionRoot), "ReloadGame")]
|
||||
class GameReloadedPatch
|
||||
{
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// <summary>
|
||||
/// Event emitter engine for switching to to build mode.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public class GameStateBuildEmitterEngine : IEventEmitterEngine, IUnorderedInitializeOnTimeStoppedModeEntered
|
||||
{
|
||||
public string Name { get; } = "GamecraftModdingAPIGameStateBuildEventEmitter" ;
|
||||
|
@ -43,10 +44,10 @@ namespace GamecraftModdingAPI.Events
|
|||
}
|
||||
}
|
||||
|
||||
public JobHandle OnInitializeTimeStoppedMode()
|
||||
public JobHandle OnInitializeTimeStoppedMode(JobHandle inputDeps)
|
||||
{
|
||||
Emit();
|
||||
return default(JobHandle);
|
||||
return inputDeps;
|
||||
}
|
||||
|
||||
public void Ready() { }
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// <summary>
|
||||
/// Event emitter engine for switching to simulation mode.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public class GameStateSimulationEmitterEngine : IEventEmitterEngine, IUnorderedInitializeOnTimeRunningModeEntered
|
||||
{
|
||||
public string Name { get; } = "GamecraftModdingAPIGameStateSimulationEventEmitter" ;
|
||||
|
@ -42,10 +43,10 @@ namespace GamecraftModdingAPI.Events
|
|||
}
|
||||
}
|
||||
|
||||
public JobHandle OnInitializeTimeRunningMode()
|
||||
public JobHandle OnInitializeTimeRunningMode(JobHandle inputDeps)
|
||||
{
|
||||
Emit();
|
||||
return default(JobHandle);
|
||||
return inputDeps;
|
||||
}
|
||||
|
||||
public void Ready() { }
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// Patch of RobocraftX.FullGameCompositionRoot.ActivateGame()
|
||||
/// (scheduled for execution during RobocraftX.FullGameCompositionRoot.SwitchToGame())
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
[HarmonyPatch(typeof(FullGameCompositionRoot), "SwitchToGame")]
|
||||
class GameSwitchedToPatch
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ using Svelto.ECS;
|
|||
|
||||
namespace GamecraftModdingAPI.Events
|
||||
{
|
||||
[Obsolete]
|
||||
public class HandlerBuilder
|
||||
{
|
||||
private string name;
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// <summary>
|
||||
/// Engine interface to create a ModEventEntityStruct in entitiesDB when a specific event occurs.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public interface IEventEmitterEngine : IFactoryEngine
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// <summary>
|
||||
/// Engine interface to handle ModEventEntityStruct events emitted by IEventEmitterEngines.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public interface IEventHandlerEngine : IReactionaryEngine<ModEventEntityStruct>
|
||||
{
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// <summary>
|
||||
/// Patch of RobocraftX.FullGameCompositionRoot.ActivateMenu()
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
[HarmonyPatch(typeof(FullGameCompositionRoot), "ActivateMenu")]
|
||||
class MenuActivatedPatch
|
||||
{
|
||||
|
@ -31,6 +32,7 @@ namespace GamecraftModdingAPI.Events
|
|||
{
|
||||
firstLoad = false;
|
||||
FullGameFields.Init(__instance);
|
||||
//Application.Application.SetFullGameCompositionRoot(__instance);
|
||||
Logging.Log("Dispatching App Init event");
|
||||
EventManager.GetEventEmitter("GamecraftModdingAPIApplicationInitializedEventEmitter").Emit();
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// <summary>
|
||||
/// Patch of RobocraftX.FullGameCompositionRoot.SwitchToMenu()
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
[HarmonyPatch(typeof(FullGameCompositionRoot), "SwitchToMenu")]
|
||||
class MenuSwitchedToPatch
|
||||
{
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// <summary>
|
||||
/// A simple implementation of IEventEmitterEngine sufficient for most uses
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public class SimpleEventEmitterEngine : IEventEmitterEngine
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace GamecraftModdingAPI.Events
|
|||
/// <summary>
|
||||
/// A simple implementation of IEventHandlerEngine sufficient for most uses
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public class SimpleEventHandlerEngine : IEventHandlerEngine
|
||||
{
|
||||
public int type { get; set; }
|
||||
|
@ -20,6 +21,10 @@ namespace GamecraftModdingAPI.Events
|
|||
|
||||
private bool isActivated = false;
|
||||
|
||||
private bool jankActivateFix = false;
|
||||
|
||||
private bool jankDestroyFix = false;
|
||||
|
||||
private readonly Action<EntitiesDB> onActivated;
|
||||
|
||||
private readonly Action<EntitiesDB> onDestroyed;
|
||||
|
@ -32,6 +37,8 @@ namespace GamecraftModdingAPI.Events
|
|||
{
|
||||
if (entityView.type.Equals(this.type))
|
||||
{
|
||||
jankActivateFix = !jankActivateFix;
|
||||
if (jankActivateFix) return;
|
||||
isActivated = true;
|
||||
onActivatedInvokeCatchError(entitiesDB);
|
||||
}
|
||||
|
@ -51,12 +58,19 @@ namespace GamecraftModdingAPI.Events
|
|||
}
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
isActivated = false;
|
||||
}
|
||||
|
||||
public void Ready() { }
|
||||
|
||||
public void Remove(ref ModEventEntityStruct entityView, EGID egid)
|
||||
{
|
||||
if (entityView.type.Equals(this.type) && isActivated)
|
||||
{
|
||||
jankDestroyFix = !jankDestroyFix;
|
||||
if (jankDestroyFix) return;
|
||||
isActivated = false;
|
||||
onDestroyedInvokeCatchError(entitiesDB);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -4,7 +4,7 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
|
||||
using GamecraftModdingAPI.Blocks;
|
||||
using HarmonyLib;
|
||||
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
@ -49,6 +49,7 @@ namespace GamecraftModdingAPI
|
|||
harmony.PatchAll(currentAssembly);
|
||||
// init utility
|
||||
Logging.MetaDebugLog($"Initializing Utility");
|
||||
#pragma warning disable 0612,0618
|
||||
Utility.GameState.Init();
|
||||
Utility.VersionTracking.Init();
|
||||
// create default event emitters
|
||||
|
@ -61,6 +62,7 @@ namespace GamecraftModdingAPI
|
|||
EventManager.AddEventEmitter(new SimpleEventEmitterEngine(EventType.GameSwitchedTo, "GamecraftModdingAPIGameSwitchedToEventEmitter", false));
|
||||
EventManager.AddEventEmitter(GameHostTransitionDeterministicGroupEnginePatch.buildEngine);
|
||||
EventManager.AddEventEmitter(GameHostTransitionDeterministicGroupEnginePatch.simEngine);
|
||||
#pragma warning restore 0612,0618
|
||||
// init block implementors
|
||||
Logging.MetaDebugLog($"Initializing Blocks");
|
||||
// init inventory
|
||||
|
@ -70,8 +72,11 @@ namespace GamecraftModdingAPI
|
|||
// init object-oriented classes
|
||||
Player.Init();
|
||||
Block.Init();
|
||||
Wire.Init();
|
||||
GameClient.Init();
|
||||
AsyncUtils.Init();
|
||||
GamecraftModdingAPI.App.Client.Init();
|
||||
GamecraftModdingAPI.App.Game.Init();
|
||||
Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} initialized");
|
||||
}
|
||||
|
||||
|
|
|
@ -12,13 +12,7 @@ namespace GamecraftModdingAPI.Persistence
|
|||
/// </summary>
|
||||
public interface IEntitySerializer : IDeserializationFactory, IQueryingEntitiesEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity factory used for creating entities and entity components.
|
||||
/// </summary>
|
||||
/// <value>The entity factory.</value>
|
||||
IEntityFactory EntityFactory { set; }
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Serialize the entities.
|
||||
/// </summary>
|
||||
/// <returns>Whether serialization was successful.</returns>
|
||||
|
|
|
@ -29,7 +29,6 @@ namespace GamecraftModdingAPI.Persistence
|
|||
_registrations[name] = (IEntitySerialization ies) => { ies.RegisterSerializationFactory<T>(serializer); };
|
||||
if (_lastEnginesRoot != null)
|
||||
{
|
||||
serializer.EntityFactory = _lastEnginesRoot.GenerateEntityFactory();
|
||||
_registrations[name].Invoke(_lastEnginesRoot.GenerateEntitySerializer());
|
||||
_lastEnginesRoot.AddEngine(serializer);
|
||||
}
|
||||
|
@ -63,12 +62,10 @@ namespace GamecraftModdingAPI.Persistence
|
|||
public static void RegisterSerializers(EnginesRoot enginesRoot)
|
||||
{
|
||||
_lastEnginesRoot = enginesRoot;
|
||||
IEntityFactory factory = enginesRoot.GenerateEntityFactory();
|
||||
IEntitySerialization ies = enginesRoot.GenerateEntitySerializer();
|
||||
foreach (string key in _serializers.Keys)
|
||||
{
|
||||
Logging.MetaDebugLog($"Registering IEntitySerializer for {key}");
|
||||
_serializers[key].EntityFactory = factory;
|
||||
_registrations[key].Invoke(ies);
|
||||
enginesRoot.AddEngine(_serializers[key]);
|
||||
}
|
||||
|
|
|
@ -21,13 +21,11 @@ namespace GamecraftModdingAPI.Persistence
|
|||
|
||||
protected int serializationType;
|
||||
|
||||
public IEntityFactory EntityFactory { set; protected get; }
|
||||
|
||||
public EntitiesDB entitiesDB { set; protected get; }
|
||||
|
||||
public EntityComponentInitializer BuildDeserializedEntity(EGID egid, ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor, int serializationType, IEntitySerialization entitySerialization)
|
||||
public EntityComponentInitializer BuildDeserializedEntity(EGID egid, ISerializationData serializationData, ISerializableEntityDescriptor entityDescriptor, int serializationType, IEntitySerialization entitySerialization, IEntityFactory factory, bool enginesRootIsDeserializationOnly)
|
||||
{
|
||||
EntityComponentInitializer esi = EntityFactory.BuildEntity<Descriptor>(egid);
|
||||
EntityComponentInitializer esi = factory.BuildEntity<Descriptor>(egid);
|
||||
entitySerialization.DeserializeEntityComponents(serializationData, entityDescriptor, ref esi, serializationType);
|
||||
return esi;
|
||||
}
|
||||
|
|
|
@ -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 (uint) playerEngine.GetAllPlayerCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:GamecraftModdingAPI.Player"/> class.
|
||||
/// </summary>
|
||||
|
@ -348,7 +357,7 @@ namespace GamecraftModdingAPI
|
|||
public Block GetBlockLookedAt(float maxDistance = -1f)
|
||||
{
|
||||
var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
|
||||
return egid.HasValue && egid.Value.groupID == CommonExclusiveGroups.OWNED_BLOCKS_GROUP
|
||||
return egid.HasValue && egid.Value.groupID != CommonExclusiveGroups.SIMULATION_BODIES_GROUP
|
||||
? new Block(egid.Value)
|
||||
: null;
|
||||
}
|
||||
|
@ -404,7 +413,7 @@ namespace GamecraftModdingAPI
|
|||
|
||||
// internal methods
|
||||
|
||||
public static void Init()
|
||||
internal static void Init()
|
||||
{
|
||||
Utility.GameEngineManager.AddGameEngine(playerEngine);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using RobocraftX.Character;
|
||||
|
@ -10,14 +12,16 @@ using RobocraftX.Physics;
|
|||
using RobocraftX.Blocks.Ghost;
|
||||
using RobocraftX.Character.Camera;
|
||||
using RobocraftX.Character.Factories;
|
||||
using Gamecraft.CharacterVulnerability;
|
||||
using Gamecraft.CharacterVulnerability.Entities;
|
||||
using Gamecraft.GUI.HUDFeedbackBlocks;
|
||||
using Svelto.ECS;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Physics;
|
||||
using UnityEngine;
|
||||
|
||||
using GamecraftModdingAPI.Engines;
|
||||
using HarmonyLib;
|
||||
using RobocraftX.Common;
|
||||
using Svelto.ECS.DataStructures;
|
||||
|
||||
namespace GamecraftModdingAPI.Players
|
||||
{
|
||||
|
@ -65,14 +69,39 @@ namespace GamecraftModdingAPI.Players
|
|||
return uint.MaxValue;
|
||||
}
|
||||
|
||||
public long GetAllPlayerCount()
|
||||
{
|
||||
if (entitiesDB == null) return 0;
|
||||
long count = 0;
|
||||
foreach (ExclusiveGroupStruct eg in PlayersExclusiveGroups.AllPlayers)
|
||||
{
|
||||
count += entitiesDB.Count<PlayerIDStruct>(eg);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public long GetLocalPlayerCount()
|
||||
{
|
||||
if (entitiesDB == null) return 0;
|
||||
return entitiesDB.Count<PlayerIDStruct>(PlayersExclusiveGroups.LocalPlayers);
|
||||
}
|
||||
|
||||
public long GetRemotePlayerCount()
|
||||
{
|
||||
if (entitiesDB == null) return 0;
|
||||
return entitiesDB.Count<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers);
|
||||
}
|
||||
|
||||
public bool ExistsById(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return false;
|
||||
return entitiesDB.Exists<PlayerIDStruct>(playerId, PlayersExclusiveGroups.LocalPlayers)
|
||||
|| entitiesDB.Exists<PlayerIDStruct>(playerId, PlayersExclusiveGroups.RemotePlayers);
|
||||
}
|
||||
|
||||
public float3 GetLocation(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return float3.zero;
|
||||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -83,6 +112,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public bool SetLocation(uint playerId, float3 location, bool exitSeat = true)
|
||||
{
|
||||
if (entitiesDB == null) return false;
|
||||
var characterGroups = CharacterExclusiveGroups.AllCharacters;
|
||||
for (int i = 0; i < characterGroups.count; i++)
|
||||
{
|
||||
|
@ -104,16 +134,18 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public float3 GetRotation(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return float3.zero;
|
||||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
return ((Quaternion) rbes.rotation).eulerAngles;
|
||||
}
|
||||
return default;
|
||||
return default(float3);
|
||||
}
|
||||
|
||||
public bool SetRotation(uint playerId, float3 value)
|
||||
{
|
||||
if (entitiesDB == null) return false;
|
||||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -127,6 +159,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public float3 GetLinearVelocity(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return float3.zero;
|
||||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -137,6 +170,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public bool SetLinearVelocity(uint playerId, float3 value)
|
||||
{
|
||||
if (entitiesDB == null) return false;
|
||||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -148,6 +182,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public float3 GetAngularVelocity(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return float3.zero;
|
||||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -158,6 +193,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public bool SetAngularVelocity(uint playerId, float3 value)
|
||||
{
|
||||
if (entitiesDB == null) return false;
|
||||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -169,16 +205,18 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public PhysicsMass GetMass(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return default(PhysicsMass);
|
||||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
return rbes.physicsMass;
|
||||
}
|
||||
return default;
|
||||
return default(PhysicsMass);
|
||||
}
|
||||
|
||||
public bool SetInverseMass(uint playerId, float inverseMass)
|
||||
{
|
||||
if (entitiesDB == null) return false;
|
||||
ref var rbes = ref GetCharacterStruct<RigidBodyEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -190,6 +228,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public float? GetLastPingTime(uint playerId, PlayerType type)
|
||||
{
|
||||
if (entitiesDB == null) return null;
|
||||
EGID egid = new EGID(playerId, PlayerGroupFromEnum(type));
|
||||
if (entitiesDB.Exists<PlayerNetworkStatsEntityStruct>(egid))
|
||||
{
|
||||
|
@ -200,6 +239,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public float GetInitialHealth(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return 0;
|
||||
ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -210,6 +250,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public bool SetInitialHealth(uint playerId, float val)
|
||||
{
|
||||
if (entitiesDB == null) return false;
|
||||
ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -221,6 +262,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public float GetCurrentHealth(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return 0;
|
||||
ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -231,6 +273,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public bool SetCurrentHealth(uint playerId, float val)
|
||||
{
|
||||
if (entitiesDB == null) return false;
|
||||
ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -242,18 +285,13 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public bool DamagePlayer(uint playerId, float amount)
|
||||
{
|
||||
Factory.BuildEntity<DamageEntityDescriptor>(
|
||||
new EGID(CharacterVulnerabilityExclusiveGroups.NextDamageEntityId, CharacterVulnerabilityExclusiveGroups.CharacterDamageExclusiveGroup)
|
||||
).Init(new DamageEntityStruct
|
||||
{
|
||||
damage = amount,
|
||||
targetPlayerEntityId = playerId,
|
||||
});
|
||||
return true;
|
||||
if (entitiesDB == null) return false;
|
||||
return SetCurrentHealth(playerId, GetCurrentHealth(playerId) - amount);
|
||||
}
|
||||
|
||||
public bool GetDamageable(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return false;
|
||||
ref var c = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -264,6 +302,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public bool SetDamageable(uint playerId, bool val)
|
||||
{
|
||||
if (entitiesDB == null) return false;
|
||||
ref var ches = ref GetCharacterStruct<CharacterHealthEntityStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -276,6 +315,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public uint GetInitialLives(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return 0;
|
||||
ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -286,6 +326,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public bool SetInitialLives(uint playerId, uint val)
|
||||
{
|
||||
if (entitiesDB == null) return false;
|
||||
ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -297,6 +338,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public uint GetCurrentLives(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return 0;
|
||||
ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -307,6 +349,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public bool SetCurrentLives(uint playerId, uint val)
|
||||
{
|
||||
if (entitiesDB == null) return false;
|
||||
ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -318,21 +361,21 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public bool GetGameOverScreen(uint playerId)
|
||||
{
|
||||
ref var c = ref GetCharacterStruct<CharacterLivesEntityComponent>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
return c.gameOverScreen;
|
||||
}
|
||||
return false;
|
||||
if (entitiesDB == null) return false;
|
||||
ref HudActivatedBlocksEntityStruct habes = ref entitiesDB.QueryEntity<HudActivatedBlocksEntityStruct>(HUDFeedbackBlocksGUIExclusiveGroups.GameOverHudEgid);
|
||||
NativeDynamicArrayCast<EGID> nativeDynamicArrayCast = new NativeDynamicArrayCast<EGID>(habes.activatedBlocksOrdered);
|
||||
return nativeDynamicArrayCast.count > 0;
|
||||
}
|
||||
|
||||
public bool IsDead(uint playerId)
|
||||
public bool IsDead(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return true;
|
||||
return entitiesDB.Exists<RigidBodyEntityStruct>(playerId, CharacterExclusiveGroups.DeadCharacters);
|
||||
}
|
||||
|
||||
public int GetSelectedBlock(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return 0;
|
||||
ref var c = ref GetCharacterStruct<EquippedPartStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
@ -343,6 +386,7 @@ namespace GamecraftModdingAPI.Players
|
|||
|
||||
public byte GetSelectedColor(uint playerId)
|
||||
{
|
||||
if (entitiesDB == null) return 0;
|
||||
ref var c = ref GetCharacterStruct<EquippedColourStruct>(playerId, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
|
|
42
GamecraftModdingAPI/Players/PlayerTests.cs
Normal file
42
GamecraftModdingAPI/Players/PlayerTests.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
|
||||
using Unity.Mathematics;
|
||||
|
||||
using GamecraftModdingAPI;
|
||||
using GamecraftModdingAPI.Tests;
|
||||
|
||||
namespace GamecraftModdingAPI.Players
|
||||
{
|
||||
#if TEST
|
||||
/// <summary>
|
||||
/// Player test cases. Not accessible in release versions.
|
||||
/// </summary>
|
||||
[APITestClass]
|
||||
public static class PlayerTests
|
||||
{
|
||||
[APITestCase(TestType.EditMode)]
|
||||
public static void ExistsTest()
|
||||
{
|
||||
if (!Assert.Equal(Player.Exists(PlayerType.Local), true, "Local player does not exist.", "Local player detected.")) return;
|
||||
Assert.Equal(Player.Count(), 1u, "Player.Count() is not one, possibly because it failed silently.", "Player count is one for single player game.");
|
||||
}
|
||||
|
||||
[APITestCase(TestType.EditMode)]
|
||||
public static void PositionTest()
|
||||
{
|
||||
Player p = new Player(PlayerType.Local);
|
||||
if (!Assert.Errorless(() => { p.Teleport(0, 0, 0, relative: false); }, "Player.Teleport(origin) errored: ", "Player teleported to origin successfully.")) return;
|
||||
if (!Assert.CloseTo(p.Position, float3.zero, "Player is not close to origin despite being teleported there.", "Player.Position is at origin.")) return;
|
||||
if (!Assert.Errorless(() => { p.Position = float3.zero + 1; }, "Player.Position = origin+1 errored: ", "Player moved to origin+1.")) return;
|
||||
Assert.CloseTo(p.Position, float3.zero + 1, "Player is not close to origin+1 despite being teleported there.", "Player.Position is at origin+1.");
|
||||
}
|
||||
|
||||
[APITestCase(TestType.Menu)]
|
||||
public static void InvalidStateTest()
|
||||
{
|
||||
if (!Assert.Errorless(() => { Player.Count(); }, "Player.Count() errored in menu.", "Player.Count() succeeded in menu.")) return;
|
||||
Assert.Equal(Player.Count(), 0u, "Player.Count() is not zero in menu.", "Player count is zero in menu as expected.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
|
@ -3,18 +3,27 @@ using Svelto.ECS;
|
|||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
using Gamecraft.Damage;
|
||||
using RobocraftX.Common;
|
||||
using RobocraftX.Physics;
|
||||
|
||||
namespace GamecraftModdingAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// A rigid body (like a cluster of connected blocks) during simulation.
|
||||
/// A rigid body (like a chunk of connected blocks) during simulation.
|
||||
/// </summary>
|
||||
public class SimBody : IEquatable<SimBody>, IEquatable<EGID>
|
||||
{
|
||||
public EGID Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The cluster this chunk belongs to, or null if the chunk doesn't exist. Get the SimBody from a Block if possible for good performance here.
|
||||
/// </summary>
|
||||
public Cluster Cluster => cluster ?? (cluster = clusterId == uint.MaxValue ? Block.BlockEngine.GetCluster(Id.entityID) : new Cluster(clusterId));
|
||||
|
||||
private Cluster cluster;
|
||||
private uint clusterId;
|
||||
|
||||
public SimBody(EGID id)
|
||||
{
|
||||
Id = id;
|
||||
|
@ -24,6 +33,11 @@ namespace GamecraftModdingAPI
|
|||
{
|
||||
}
|
||||
|
||||
internal SimBody(uint id, uint clusterID) : this(id)
|
||||
{
|
||||
clusterId = clusterID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The position of this body. When setting the position, update the position of the connected bodies as well,
|
||||
/// otherwise unexpected forces may arise.
|
||||
|
@ -70,6 +84,29 @@ namespace GamecraftModdingAPI
|
|||
//set => GetStruct().physicsMass.CenterOfMass = value;
|
||||
}
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get => GetStruct().volume;
|
||||
}
|
||||
|
||||
public float InitialHealth
|
||||
{
|
||||
get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(Id).initialHealth;
|
||||
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(Id).initialHealth = value;
|
||||
}
|
||||
|
||||
public float CurrentHealth
|
||||
{
|
||||
get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(Id).currentHealth;
|
||||
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(Id).currentHealth = value;
|
||||
}
|
||||
|
||||
public float HealthMultiplier
|
||||
{
|
||||
get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(Id).healthMultiplier;
|
||||
set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(Id).healthMultiplier = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the body can be moved or static.
|
||||
/// </summary>
|
||||
|
|
65
GamecraftModdingAPI/Tests/APITestAttributes.cs
Normal file
65
GamecraftModdingAPI/Tests/APITestAttributes.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
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,
|
||||
Game,
|
||||
SimulationMode,
|
||||
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
|
||||
{
|
||||
internal string Name;
|
||||
|
||||
public APITestClassAttribute(string name = "")
|
||||
{
|
||||
this.Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
internal TestType TestType;
|
||||
|
||||
public APITestCaseAttribute(TestType testType)
|
||||
{
|
||||
this.TestType = testType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
220
GamecraftModdingAPI/Tests/Assert.cs
Normal file
220
GamecraftModdingAPI/Tests/Assert.cs
Normal file
|
@ -0,0 +1,220 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace GamecraftModdingAPI.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// API test system assertion utilities.
|
||||
/// </summary>
|
||||
public static class Assert
|
||||
{
|
||||
private static StreamWriter logFile = null;
|
||||
|
||||
private static ConcurrentDictionary<string, string> callbacks = new ConcurrentDictionary<string, string>();
|
||||
|
||||
private const string PASS = "SUCCESS: ";
|
||||
|
||||
private const string FAIL = "FAILURE: ";
|
||||
|
||||
private const string WARN = "WARNING: ";
|
||||
|
||||
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")
|
||||
{
|
||||
if (logFile == null) openTestLog();
|
||||
logFile.Write(msg + end);
|
||||
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...";
|
||||
callbacks[eventName] = eventMsg;
|
||||
|
||||
return (sender, args) =>
|
||||
{
|
||||
string value = null;
|
||||
if (!callbacks.TryRemove(eventName, out value)) { Log(WARN + $"callback to {eventName} occurred again or a related error occurred... (Received '{args.ToString()}' from '{(sender == null ? (string)sender : sender.ToString())}')"); }
|
||||
Log(PASS + $"callback to {eventName} occurred... (Received '{args.ToString()}' from '{(sender == null ? (string)sender : sender.ToString())}')");
|
||||
TestRoot.TestsPassed = true;
|
||||
};
|
||||
}
|
||||
|
||||
public static bool NotNull<T>(T obj, string err = null, string success = null)
|
||||
{
|
||||
if (err == null) err = $"{nameof(T)} object was null.";
|
||||
if (success == null) success = $"{nameof(T)} '{obj}' not null";
|
||||
if (obj == null)
|
||||
{
|
||||
Log(FAIL + err);
|
||||
TestRoot.TestsPassed = false;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(PASS + success);
|
||||
TestRoot.TestsPassed = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Equal<T>(T obj1, T obj2, string err = null, string success = null)
|
||||
{
|
||||
if (err == null) err = $"{nameof(T)} '{obj1}' is not equal to '{obj2}'.";
|
||||
if (success == null) success = $"{nameof(T)} '{obj1}' is equal to '{obj2}'.";
|
||||
if ((obj1 == null && obj2 == null)
|
||||
|| (obj1 != null && obj2 != null && obj1.Equals(obj2) && obj2.Equals(obj1)))
|
||||
{
|
||||
// pass
|
||||
Log(PASS + success);
|
||||
TestRoot.TestsPassed = true;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// fail
|
||||
Log(FAIL + err);
|
||||
TestRoot.TestsPassed = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Errorless(Action tryThis, string err = null, string success = null)
|
||||
{
|
||||
if (err == null) err = $"{tryThis} raised an exception: ";
|
||||
if (success == null) success = $"{tryThis} completed without raising an exception.";
|
||||
try
|
||||
{
|
||||
tryThis();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log(FAIL + err + e);
|
||||
TestRoot.TestsPassed = false;
|
||||
return false;
|
||||
}
|
||||
TestRoot.TestsPassed = true;
|
||||
Log(PASS + success);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool CloseTo(float a, float b, string err = null, string success = null, float delta = float.Epsilon)
|
||||
{
|
||||
if (err == null) err = $"{a} is not within {delta} of {b}.";
|
||||
if (success == null) success = $"{a} is close enough to {b}.";
|
||||
if (Math.Abs(a - b) > delta)
|
||||
{
|
||||
Log(FAIL + err);
|
||||
TestRoot.TestsPassed = false;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
TestRoot.TestsPassed = true;
|
||||
Log(PASS + success);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CloseTo(double a, double b, string err = null, string success = null, double delta = double.Epsilon)
|
||||
{
|
||||
if (err == null) err = $"{a} is not within {delta} of {b}.";
|
||||
if (success == null) success = $"{a} is close enough to {b}.";
|
||||
if (Math.Abs(a - b) > delta)
|
||||
{
|
||||
Log(FAIL + err);
|
||||
TestRoot.TestsPassed = false;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
TestRoot.TestsPassed = true;
|
||||
Log(PASS + success);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CloseTo(float3 a, float3 b, string err = null, string success = null, float delta = float.Epsilon)
|
||||
{
|
||||
if (err == null) err = $"{a} is not within {delta} of {b} in every direction.";
|
||||
if (success == null) success = $"{a} is close enough to {b}.";
|
||||
bool xClose = CloseTo(a.x, b.x, err, success, delta);
|
||||
bool yClose = CloseTo(a.y, b.y, err, success, delta);
|
||||
bool zClose = CloseTo(a.z, b.z, err, success, delta);
|
||||
if (xClose && yClose && zClose)
|
||||
{
|
||||
//TestRoot.TestsPassed = true;
|
||||
//Log(PASS + success);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Log(FAIL + err);
|
||||
//TestRoot.TestsPassed = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Fail(string msg = null)
|
||||
{
|
||||
if (msg == null) msg = $"Manual test failure with no message provided.";
|
||||
Log(FAIL + msg);
|
||||
TestRoot.TestsPassed = false;
|
||||
}
|
||||
|
||||
public static void Pass(string msg = null)
|
||||
{
|
||||
if (msg == null) msg = $"Manual test pass with no message provided.";
|
||||
Log(PASS + msg);
|
||||
TestRoot.TestsPassed = true;
|
||||
}
|
||||
|
||||
public static void Warn(string msg = null)
|
||||
{
|
||||
if (msg == null) msg = $"Manual test warning with no message provided.";
|
||||
Log(WARN + msg);
|
||||
TestRoot.TestsPassed = true;
|
||||
}
|
||||
|
||||
internal static void CallsComplete()
|
||||
{
|
||||
foreach(string key in callbacks.Keys)
|
||||
{
|
||||
Log(FAIL + callbacks[key]);
|
||||
TestRoot.TestsPassed = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void CloseLog()
|
||||
{
|
||||
if (logFile != null) logFile.Close();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void openTestLog()
|
||||
{
|
||||
logFile = File.CreateText(TestRoot.ReportFile);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,37 +25,33 @@ using GamecraftModdingAPI.Players;
|
|||
|
||||
namespace GamecraftModdingAPI.Tests
|
||||
{
|
||||
#if DEBUG
|
||||
// unused by design
|
||||
/// <summary>
|
||||
/// Modding API implemented as a standalone IPA Plugin.
|
||||
/// Ideally, GamecraftModdingAPI should be loaded by another mod; not itself
|
||||
/// </summary>
|
||||
public class GamecraftModdingAPIPluginTest
|
||||
#if DEBUG
|
||||
: IllusionPlugin.IEnhancedPlugin
|
||||
#endif
|
||||
public class GamecraftModdingAPIPluginTest : IllusionPlugin.IEnhancedPlugin
|
||||
{
|
||||
|
||||
private static Harmony harmony { get; set; }
|
||||
|
||||
public string[] Filter { get; } = new string[] { "Gamecraft", "GamecraftPreview" };
|
||||
public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
|
||||
|
||||
public string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
|
||||
|
||||
public string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
|
||||
public string HarmonyID { get; } = "org.git.exmods.modtainers.gamecraftmoddingapi";
|
||||
|
||||
public void OnApplicationQuit()
|
||||
public override void OnApplicationQuit()
|
||||
{
|
||||
GamecraftModdingAPI.Main.Shutdown();
|
||||
}
|
||||
|
||||
public void OnApplicationStart()
|
||||
{
|
||||
public override void OnApplicationStart()
|
||||
{
|
||||
FileLog.Reset();
|
||||
Harmony.DEBUG = true;
|
||||
GamecraftModdingAPI.Main.Init();
|
||||
GamecraftModdingAPI.Main.Init();
|
||||
Logging.MetaDebugLog($"Version group id {(uint)ApiExclusiveGroups.versionGroup}");
|
||||
// in case Steam is not installed/running
|
||||
// this will crash the game slightly later during startup
|
||||
|
@ -65,12 +61,13 @@ namespace GamecraftModdingAPI.Tests
|
|||
// disable some Gamecraft analytics
|
||||
//AnalyticsDisablerPatch.DisableAnalytics = true;
|
||||
// disable background music
|
||||
Logging.MetaDebugLog("Audio Mixers: "+string.Join(",", AudioTools.GetMixers()));
|
||||
Logging.MetaDebugLog("Audio Mixers: " + string.Join(",", AudioTools.GetMixers()));
|
||||
//AudioTools.SetVolume(0.0f, "Music"); // The game now sets this from settings again after this is called :(
|
||||
|
||||
//Utility.VersionTracking.Enable();//(very) unstable
|
||||
|
||||
// debug/test handlers
|
||||
#pragma warning disable 0612
|
||||
HandlerBuilder.Builder()
|
||||
.Name("appinit API debug")
|
||||
.Handle(EventType.ApplicationInitialized)
|
||||
|
@ -79,60 +76,68 @@ namespace GamecraftModdingAPI.Tests
|
|||
|
||||
HandlerBuilder.Builder("menuact API debug")
|
||||
.Handle(EventType.Menu)
|
||||
.OnActivation(() => { Logging.Log("Menu Activated event!"); })
|
||||
.OnActivation(() => { Logging.Log("Menu Activated event!"); })
|
||||
.OnDestruction(() => { Logging.Log("Menu Destroyed event!"); })
|
||||
.Build();
|
||||
|
||||
HandlerBuilder.Builder("menuswitch API debug")
|
||||
.Handle(EventType.MenuSwitchedTo)
|
||||
.OnActivation(() => { Logging.Log("Menu Switched To event!"); })
|
||||
.Build();
|
||||
.Handle(EventType.MenuSwitchedTo)
|
||||
.OnActivation(() => { Logging.Log("Menu Switched To event!"); })
|
||||
.Build();
|
||||
|
||||
HandlerBuilder.Builder("gameact API debug")
|
||||
.Handle(EventType.Menu)
|
||||
.OnActivation(() => { Logging.Log("Game Activated event!"); })
|
||||
.OnDestruction(() => { Logging.Log("Game Destroyed event!"); })
|
||||
.Build();
|
||||
.Handle(EventType.Menu)
|
||||
.OnActivation(() => { Logging.Log("Game Activated event!"); })
|
||||
.OnDestruction(() => { Logging.Log("Game Destroyed event!"); })
|
||||
.Build();
|
||||
|
||||
HandlerBuilder.Builder("gamerel API debug")
|
||||
.Handle(EventType.GameReloaded)
|
||||
.OnActivation(() => { Logging.Log("Game Reloaded event!"); })
|
||||
.Build();
|
||||
.Handle(EventType.GameReloaded)
|
||||
.OnActivation(() => { Logging.Log("Game Reloaded event!"); })
|
||||
.Build();
|
||||
|
||||
HandlerBuilder.Builder("gameswitch API debug")
|
||||
.Handle(EventType.GameSwitchedTo)
|
||||
.OnActivation(() => { Logging.Log("Game Switched To event!"); })
|
||||
.Build();
|
||||
.Handle(EventType.GameSwitchedTo)
|
||||
.OnActivation(() => { Logging.Log("Game Switched To event!"); })
|
||||
.Build();
|
||||
|
||||
HandlerBuilder.Builder("simulationswitch API debug")
|
||||
.Handle(EventType.SimulationSwitchedTo)
|
||||
.OnActivation(() => { Logging.Log("Game Mode Simulation Switched To event!"); })
|
||||
.Build();
|
||||
.Handle(EventType.SimulationSwitchedTo)
|
||||
.OnActivation(() => { Logging.Log("Game Mode Simulation Switched To event!"); })
|
||||
.Build();
|
||||
|
||||
HandlerBuilder.Builder("buildswitch API debug")
|
||||
.Handle(EventType.BuildSwitchedTo)
|
||||
.OnActivation(() => { Logging.Log("Game Mode Build Switched To event!"); })
|
||||
.Build();
|
||||
.Handle(EventType.BuildSwitchedTo)
|
||||
.OnActivation(() => { Logging.Log("Game Mode Build Switched To event!"); })
|
||||
.Build();
|
||||
|
||||
HandlerBuilder.Builder("menu activated API error thrower test")
|
||||
.Handle(EventType.Menu)
|
||||
.OnActivation(() => { throw new Exception("Event Handler always throws an exception!"); })
|
||||
.Build();
|
||||
#pragma warning restore 0612
|
||||
/*HandlerBuilder.Builder("enter game from menu test")
|
||||
.Handle(EventType.Menu)
|
||||
.OnActivation(() =>
|
||||
{
|
||||
Tasks.Scheduler.Schedule(new Tasks.Repeatable(enterGame, shouldRetry, 0.2f));
|
||||
})
|
||||
.Build();*/
|
||||
|
||||
// debug/test commands
|
||||
// debug/test commands
|
||||
if (Dependency.Hell("ExtraCommands"))
|
||||
{
|
||||
CommandBuilder.Builder()
|
||||
.Name("Exit")
|
||||
.Description("Close Gamecraft immediately, without any prompts")
|
||||
.Action(() => { UnityEngine.Application.Quit(); })
|
||||
.Build();
|
||||
|
||||
.Name("Exit")
|
||||
.Description("Close Gamecraft immediately, without any prompts")
|
||||
.Action(() => { UnityEngine.Application.Quit(); })
|
||||
.Build();
|
||||
|
||||
CommandBuilder.Builder()
|
||||
.Name("SetFOV")
|
||||
.Description("Set the player camera's field of view")
|
||||
.Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; })
|
||||
.Build();
|
||||
.Name("SetFOV")
|
||||
.Description("Set the player camera's field of view")
|
||||
.Action((float d) => { UnityEngine.Camera.main.fieldOfView = d; })
|
||||
.Build();
|
||||
|
||||
CommandBuilder.Builder()
|
||||
.Name("MoveLastBlock")
|
||||
|
@ -147,14 +152,14 @@ namespace GamecraftModdingAPI.Tests
|
|||
}).Build();
|
||||
|
||||
CommandBuilder.Builder()
|
||||
.Name("PlaceAluminium")
|
||||
.Description("Place a block of aluminium at the given coordinates")
|
||||
.Action((float x, float y, float z) =>
|
||||
{
|
||||
var block = Block.PlaceNew(BlockIDs.AluminiumCube, new float3(x, y, z));
|
||||
Logging.CommandLog("Block placed with type: " + block.Type);
|
||||
})
|
||||
.Build();
|
||||
.Name("PlaceAluminium")
|
||||
.Description("Place a block of aluminium at the given coordinates")
|
||||
.Action((float x, float y, float z) =>
|
||||
{
|
||||
var block = Block.PlaceNew(BlockIDs.AluminiumCube, new float3(x, y, z));
|
||||
Logging.CommandLog("Block placed with type: " + block.Type);
|
||||
})
|
||||
.Build();
|
||||
|
||||
CommandBuilder.Builder()
|
||||
.Name("PlaceAluminiumLots")
|
||||
|
@ -164,8 +169,8 @@ namespace GamecraftModdingAPI.Tests
|
|||
Logging.CommandLog("Starting...");
|
||||
var sw = Stopwatch.StartNew();
|
||||
for (int i = 0; i < 100; i++)
|
||||
for (int j = 0; j < 100; j++)
|
||||
Block.PlaceNew(BlockIDs.AluminiumCube, new float3(x + i, y, z + j));
|
||||
for (int j = 0; j < 100; j++)
|
||||
Block.PlaceNew(BlockIDs.AluminiumCube, new float3(x + i, y, z + j));
|
||||
//Block.Sync();
|
||||
sw.Stop();
|
||||
Logging.CommandLog("Finished in " + sw.ElapsedMilliseconds + "ms");
|
||||
|
@ -174,10 +179,10 @@ namespace GamecraftModdingAPI.Tests
|
|||
//With Sync(): 1135ms
|
||||
//Without Sync(): 134ms
|
||||
//Async: 348 794ms, doesn't freeze game
|
||||
//Without Sync() but wait for submission: 530ms
|
||||
//With Sync() at the end: 380ms
|
||||
//Without Sync() but wait for submission: 530ms
|
||||
//With Sync() at the end: 380ms
|
||||
|
||||
Block b = null;
|
||||
Block b = null;
|
||||
CommandBuilder.Builder("moveBlockInSim", "Run in build mode first while looking at a block, then in sim to move it up")
|
||||
.Action(() =>
|
||||
{
|
||||
|
@ -207,7 +212,7 @@ namespace GamecraftModdingAPI.Tests
|
|||
return;
|
||||
}
|
||||
new Player(PlayerType.Local).GetBlockLookedAt().Color =
|
||||
new BlockColor {Color = color};
|
||||
new BlockColor { Color = color };
|
||||
Logging.CommandLog("Colored block to " + color);
|
||||
|
||||
}).Build();
|
||||
|
@ -227,13 +232,58 @@ namespace GamecraftModdingAPI.Tests
|
|||
}
|
||||
}).Build();
|
||||
|
||||
CommandBuilder.Builder()
|
||||
.Name("PlaceConsole")
|
||||
.Description("Place a bunch of console block with a given text - entering simulation with them crashes the game as the cmd doesn't exist")
|
||||
.Action((float x, float y, float z) =>
|
||||
{
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
for (int j = 0; j < 100; j++)
|
||||
{
|
||||
var block = Block.PlaceNew<ConsoleBlock>(BlockIDs.ConsoleBlock,
|
||||
new float3(x + i, y, z + j));
|
||||
block.Command = "test_command";
|
||||
}
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
Logging.CommandLog($"Blocks placed in {sw.ElapsedMilliseconds} ms");
|
||||
})
|
||||
.Build();
|
||||
|
||||
CommandBuilder.Builder()
|
||||
.Name("WireTest")
|
||||
.Description("Place two blocks and then wire them together")
|
||||
.Action(() =>
|
||||
{
|
||||
LogicGate notBlock = Block.PlaceNew<LogicGate>(BlockIDs.NOTLogicBlock, new float3(1, 2, 0));
|
||||
LogicGate andBlock = Block.PlaceNew<LogicGate>(BlockIDs.ANDLogicBlock, new float3(2, 2, 0));
|
||||
// connect NOT Gate output to AND Gate input #2 (ports are zero-indexed, so 1 is 2nd position and 0 is 1st position)
|
||||
Wire conn = notBlock.Connect(0, andBlock, 1);
|
||||
Logging.CommandLog(conn.ToString());
|
||||
})
|
||||
.Build();
|
||||
|
||||
CommandBuilder.Builder("TestChunkHealth", "Sets the chunk looked at to the given health.")
|
||||
.Action((float val, float max) =>
|
||||
{
|
||||
var body = new Player(PlayerType.Local).GetSimBodyLookedAt();
|
||||
if (body == null) return;
|
||||
body.CurrentHealth = val;
|
||||
body.InitialHealth = max;
|
||||
Logging.CommandLog("Health set to: " + val);
|
||||
}).Build();
|
||||
|
||||
GameClient.SetDebugInfo("InstalledMods", InstalledMods);
|
||||
Block.Placed += (sender, args) =>
|
||||
Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID);
|
||||
Block.Removed += (sender, args) =>
|
||||
Logging.MetaDebugLog("Removed block " + args.Type + " with ID " + args.ID);
|
||||
|
||||
/*
|
||||
/*
|
||||
CommandManager.AddCommand(new SimpleCustomCommandEngine<float>((float d) => { UnityEngine.Camera.main.fieldOfView = d; },
|
||||
"SetFOV", "Set the player camera's field of view"));
|
||||
CommandManager.AddCommand(new SimpleCustomCommandEngine<float, float, float>(
|
||||
|
@ -277,8 +327,8 @@ namespace GamecraftModdingAPI.Tests
|
|||
*/
|
||||
}
|
||||
|
||||
// dependency test
|
||||
if (Dependency.Hell("GamecraftScripting", new Version("0.0.1.0")))
|
||||
// dependency test
|
||||
if (Dependency.Hell("GamecraftScripting", new Version("0.0.1.0")))
|
||||
{
|
||||
Logging.LogWarning("You're in GamecraftScripting dependency hell");
|
||||
}
|
||||
|
@ -302,7 +352,11 @@ namespace GamecraftModdingAPI.Tests
|
|||
o1.Merge(o2, new JsonMergeSettings {MergeArrayHandling = MergeArrayHandling.Union});
|
||||
File.WriteAllText(@"Gamecraft_Data\StreamingAssets\aa\Windows\catalog.json", o1.ToString());*/
|
||||
CustomBlock.Prep().RunOn(ExtraLean.UIScheduler);
|
||||
}
|
||||
|
||||
#if TEST
|
||||
TestRoot.RunTests();
|
||||
#endif
|
||||
}
|
||||
|
||||
private string modsString;
|
||||
private string InstalledMods()
|
||||
|
@ -314,15 +368,39 @@ namespace GamecraftModdingAPI.Tests
|
|||
return modsString = sb.ToString();
|
||||
}
|
||||
|
||||
public void OnFixedUpdate() { }
|
||||
private bool retry = true;
|
||||
|
||||
public void OnLateUpdate() { }
|
||||
private bool shouldRetry()
|
||||
{
|
||||
return retry;
|
||||
}
|
||||
|
||||
public void OnLevelWasInitialized(int level) { }
|
||||
|
||||
public void OnLevelWasLoaded(int level) { }
|
||||
|
||||
public void OnUpdate() { }
|
||||
private void enterGame()
|
||||
{
|
||||
App.Client app = new App.Client();
|
||||
App.Game[] myGames = app.MyGames;
|
||||
Logging.MetaDebugLog($"MyGames count {myGames.Length}");
|
||||
if (myGames.Length != 0)
|
||||
{
|
||||
Logging.MetaDebugLog($"MyGames[0] EGID {myGames[0].EGID}");
|
||||
retry = false;
|
||||
try
|
||||
{
|
||||
//myGames[0].Description = "test msg pls ignore"; // make sure game exists first
|
||||
Logging.MetaDebugLog($"Entering game {myGames[0].Name}");
|
||||
myGames[0].EnterGame();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.MetaDebugLog($"Failed to enter game; exception: {e}");
|
||||
retry = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.MetaDebugLog("MyGames not populated yet :(");
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
public class MinimumSpecsPatch
|
||||
|
@ -338,4 +416,5 @@ namespace GamecraftModdingAPI.Tests
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
295
GamecraftModdingAPI/Tests/TestRoot.cs
Normal file
295
GamecraftModdingAPI/Tests/TestRoot.cs
Normal file
|
@ -0,0 +1,295 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Linq; // welcome to the dark side
|
||||
|
||||
using Svelto.Tasks;
|
||||
using Svelto.Tasks.Lean;
|
||||
using Svelto.Tasks.Enumerators;
|
||||
using UnityEngine;
|
||||
|
||||
using GamecraftModdingAPI.App;
|
||||
using GamecraftModdingAPI.Tasks;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
namespace GamecraftModdingAPI.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// API test system root class.
|
||||
/// </summary>
|
||||
public static class TestRoot
|
||||
{
|
||||
public static bool AutoShutdown = true;
|
||||
|
||||
public const string ReportFile = "GamecraftModdingAPI_tests.log";
|
||||
|
||||
private static bool _testsPassed = false;
|
||||
|
||||
private static uint _testsCount = 0;
|
||||
|
||||
private static uint _testsCountPassed = 0;
|
||||
|
||||
private static uint _testsCountFailed = 0;
|
||||
|
||||
private static string state = "StartingUp";
|
||||
|
||||
private static Stopwatch timer;
|
||||
|
||||
private static List<Type> testTypes = null;
|
||||
|
||||
public static bool TestsPassed
|
||||
{
|
||||
get => _testsPassed;
|
||||
set
|
||||
{
|
||||
_testsPassed = _testsPassed && value;
|
||||
_testsCount++;
|
||||
if (value)
|
||||
{
|
||||
_testsCountPassed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
_testsCountFailed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void StartUp()
|
||||
{
|
||||
// init
|
||||
timer = Stopwatch.StartNew();
|
||||
_testsPassed = true;
|
||||
_testsCount = 0;
|
||||
_testsCountPassed = 0;
|
||||
_testsCountFailed = 0;
|
||||
// flow control
|
||||
Game.Enter += (sender, args) => { GameTests().RunOn(RobocraftX.Schedulers.Lean.EveryFrameStepRunner_RUNS_IN_TIME_STOPPED_AND_RUNNING); };
|
||||
Game.Exit += (s, a) => state = "ReturningFromGame";
|
||||
Client.EnterMenu += (sender, args) =>
|
||||
{
|
||||
if (state == "EnteringMenu")
|
||||
{
|
||||
MenuTests().RunOn(Scheduler.leanRunner);
|
||||
state = "EnteringGame";
|
||||
}
|
||||
if (state == "ReturningFromGame")
|
||||
{
|
||||
TearDown().RunOn(Scheduler.leanRunner);
|
||||
state = "ShuttingDown";
|
||||
}
|
||||
};
|
||||
// init tests here
|
||||
foreach (Type t in testTypes)
|
||||
{
|
||||
foreach (MethodBase m in t.GetMethods())
|
||||
{
|
||||
if (m.GetCustomAttribute<APITestStartUpAttribute>() != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
m.Invoke(null, new object[0]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Fail($"Start up method '{m}' raised an exception: {e.ToString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
state = "EnteringMenu";
|
||||
}
|
||||
|
||||
private static IEnumerator<TaskContract> MenuTests()
|
||||
{
|
||||
yield return Yield.It;
|
||||
// menu tests
|
||||
foreach (Type t in testTypes)
|
||||
{
|
||||
foreach (MethodBase m in t.GetMethods())
|
||||
{
|
||||
APITestCaseAttribute a = m.GetCustomAttribute<APITestCaseAttribute>();
|
||||
if (a != null && a.TestType == TestType.Menu)
|
||||
{
|
||||
try
|
||||
{
|
||||
m.Invoke(null, new object[0]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Fail($"Menu test '{m}' raised an exception: {e.ToString()}");
|
||||
}
|
||||
yield return Yield.It;
|
||||
}
|
||||
}
|
||||
}
|
||||
// load game
|
||||
yield return GoToGameTests().Continue();
|
||||
}
|
||||
|
||||
private static IEnumerator<TaskContract> GoToGameTests()
|
||||
{
|
||||
Client app = new Client();
|
||||
int oldLength = 0;
|
||||
while (app.MyGames.Length == 0 || oldLength != app.MyGames.Length)
|
||||
{
|
||||
oldLength = app.MyGames.Length;
|
||||
yield return new WaitForSecondsEnumerator(1).Continue();
|
||||
}
|
||||
yield return Yield.It;
|
||||
app.MyGames[0].EnterGame();
|
||||
/*Game newGame = Game.NewGame();
|
||||
yield return new WaitForSecondsEnumerator(5).Continue(); // wait for sync
|
||||
newGame.EnterGame();*/
|
||||
}
|
||||
|
||||
private static IEnumerator<TaskContract> GameTests()
|
||||
{
|
||||
yield return Yield.It;
|
||||
Game currentGame = Game.CurrentGame();
|
||||
// in-game tests
|
||||
yield return new WaitForSecondsEnumerator(5).Continue(); // wait for game to finish loading
|
||||
foreach (Type t in testTypes)
|
||||
{
|
||||
foreach (MethodBase m in t.GetMethods())
|
||||
{
|
||||
APITestCaseAttribute a = m.GetCustomAttribute<APITestCaseAttribute>();
|
||||
if (a != null && a.TestType == TestType.Game)
|
||||
{
|
||||
try
|
||||
{
|
||||
m.Invoke(null, new object[0]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Fail($"Game test '{m}' raised an exception: {e.ToString()}");
|
||||
}
|
||||
yield return Yield.It;
|
||||
}
|
||||
}
|
||||
}
|
||||
currentGame.ToggleTimeMode();
|
||||
yield return new WaitForSecondsEnumerator(5).Continue();
|
||||
// simulation tests
|
||||
foreach (Type t in testTypes)
|
||||
{
|
||||
foreach (MethodBase m in t.GetMethods())
|
||||
{
|
||||
APITestCaseAttribute a = m.GetCustomAttribute<APITestCaseAttribute>();
|
||||
if (a != null && a.TestType == TestType.SimulationMode)
|
||||
{
|
||||
try
|
||||
{
|
||||
m.Invoke(null, new object[0]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Fail($"Simulation test '{m}' raised an exception: {e.ToString()}");
|
||||
}
|
||||
yield return Yield.It;
|
||||
}
|
||||
}
|
||||
}
|
||||
currentGame.ToggleTimeMode();
|
||||
yield return new WaitForSecondsEnumerator(5).Continue();
|
||||
// build tests
|
||||
foreach (Type t in testTypes)
|
||||
{
|
||||
foreach (MethodBase m in t.GetMethods())
|
||||
{
|
||||
APITestCaseAttribute a = m.GetCustomAttribute<APITestCaseAttribute>();
|
||||
if (a != null && a.TestType == TestType.EditMode)
|
||||
{
|
||||
try
|
||||
{
|
||||
m.Invoke(null, new object[0]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Fail($"Build test '{m}' raised an exception: {e.ToString()}");
|
||||
}
|
||||
yield return Yield.It;
|
||||
}
|
||||
}
|
||||
}
|
||||
// exit game
|
||||
yield return new WaitForSecondsEnumerator(5).Continue();
|
||||
yield return ReturnToMenu().Continue();
|
||||
}
|
||||
|
||||
private static IEnumerator<TaskContract> ReturnToMenu()
|
||||
{
|
||||
Logging.MetaLog("Returning to main menu");
|
||||
yield return Yield.It;
|
||||
Game.CurrentGame().ExitGame();
|
||||
}
|
||||
|
||||
private static IEnumerator<TaskContract> TearDown()
|
||||
{
|
||||
yield return new WaitForSecondsEnumerator(5).Continue();
|
||||
Logging.MetaLog("Tearing down test run");
|
||||
// dispose tests here
|
||||
foreach (Type t in testTypes)
|
||||
{
|
||||
foreach (MethodBase m in t.GetMethods())
|
||||
{
|
||||
if (m.GetCustomAttribute<APITestTearDownAttribute>() != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
m.Invoke(null, new object[0]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Warn($"Tear down method '{m}' raised an exception: {e.ToString()}");
|
||||
}
|
||||
yield return Yield.It;
|
||||
}
|
||||
}
|
||||
}
|
||||
// finish up
|
||||
Assert.CallsComplete();
|
||||
timer.Stop();
|
||||
string verdict = _testsPassed ? "--- PASSED :) ---" : "--- FAILED :( ---";
|
||||
Assert.Log($"VERDICT: {verdict} ({_testsCountPassed}/{_testsCountFailed}/{_testsCount} P/F/T in {timer.ElapsedMilliseconds}ms)");
|
||||
yield return Yield.It;
|
||||
// end game
|
||||
Logging.MetaLog("Completed test run: " + verdict);
|
||||
yield return Yield.It;
|
||||
Assert.CloseLog();
|
||||
if (AutoShutdown) Application.Quit();
|
||||
}
|
||||
|
||||
private static void FindTests(Assembly asm)
|
||||
{
|
||||
testTypes = new List<Type>();
|
||||
foreach (Type t in asm.GetTypes())
|
||||
{
|
||||
if (t.GetCustomAttribute<APITestClassAttribute>() != null)
|
||||
{
|
||||
testTypes.Add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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();
|
||||
FindTests(asm);
|
||||
Logging.MetaLog("Starting test run");
|
||||
// log metadata
|
||||
Assert.Log($"Unity {Application.unityVersion}");
|
||||
Assert.Log($"Gamecraft {Application.version}");
|
||||
Assert.Log($"GamecraftModdingAPI {Assembly.GetExecutingAssembly().GetName().Version}");
|
||||
Assert.Log($"Testing {asm.GetName().Name} {asm.GetName().Version}");
|
||||
Assert.Log($"START: --- {DateTime.Now.ToString()} --- ({testTypes.Count} tests classes detected)");
|
||||
StartUp();
|
||||
Logging.MetaLog("Test StartUp complete");
|
||||
}
|
||||
}
|
||||
}
|
52
GamecraftModdingAPI/Tests/TestTest.cs
Normal file
52
GamecraftModdingAPI/Tests/TestTest.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
using HarmonyLib;
|
||||
|
||||
namespace GamecraftModdingAPI.Tests
|
||||
{
|
||||
#if TEST
|
||||
/// <summary>
|
||||
/// Test test test.
|
||||
/// </summary>
|
||||
[APITestClass]
|
||||
public static class TestTest
|
||||
{
|
||||
public static event EventHandler<TestEventArgs> StartUp;
|
||||
|
||||
public static event EventHandler<TestEventArgs> Test;
|
||||
|
||||
public static event EventHandler<TestEventArgs> TearDown;
|
||||
|
||||
[APITestStartUp]
|
||||
public static void Init()
|
||||
{
|
||||
StartUp += Assert.CallsBack<TestEventArgs>("TestStartUp");
|
||||
Test += Assert.CallsBack<TestEventArgs>("TestCase");
|
||||
TearDown += Assert.CallsBack<TestEventArgs>("TestTearDown");
|
||||
StartUp(null, default(TestEventArgs));
|
||||
}
|
||||
|
||||
[APITestCase(TestType.Menu)]
|
||||
public static void RunTest()
|
||||
{
|
||||
Test(null, default(TestEventArgs));
|
||||
}
|
||||
|
||||
[APITestTearDown]
|
||||
public static void End()
|
||||
{
|
||||
TearDown(null, default(TestEventArgs));
|
||||
}
|
||||
}
|
||||
|
||||
public struct TestEventArgs
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "TestEventArgs{}";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Text;
|
||||
using System.Text.Formatting;
|
||||
using GamecraftModdingAPI.Blocks;
|
||||
using GamecraftModdingAPI.Engines;
|
||||
|
@ -46,9 +47,9 @@ namespace GamecraftModdingAPI.Utility
|
|||
var array = new CodeInstruction[]
|
||||
{
|
||||
new CodeInstruction(OpCodes.Ldloc_0), //StringBuffer
|
||||
new CodeInstruction(OpCodes.Call, ((Action<StringBuffer>)AddInfo).Method)
|
||||
new CodeInstruction(OpCodes.Call, ((Action<StringBuilder>)AddInfo).Method)
|
||||
};
|
||||
list.InsertRange(index, array);
|
||||
list.InsertRange(index - 1, array); //-1: ldloc.1 ("local") before ldfld
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -58,13 +59,15 @@ namespace GamecraftModdingAPI.Utility
|
|||
return list;
|
||||
}
|
||||
|
||||
public static void AddInfo(StringBuffer sb)
|
||||
public static void AddInfo(StringBuilder sb)
|
||||
{
|
||||
foreach (var info in _extraInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
sb.Append(info.Value() + "\n");
|
||||
string text = info.Value().Trim();
|
||||
if (text.Length != 0)
|
||||
sb.Append(text + "\n");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
using System;
|
||||
|
||||
using RobocraftX.StateSync;
|
||||
using Svelto.ECS;
|
||||
|
||||
using HarmonyLib;
|
||||
|
||||
namespace GamecraftModdingAPI.Utility
|
||||
{
|
||||
[HarmonyPatch(typeof(DeterministicStepCompositionRoot), "ResetWorld")]
|
||||
public static class DeterministicStepCompositionRootPatch
|
||||
{
|
||||
private static SimpleEntitiesSubmissionScheduler engineRootScheduler;
|
||||
public static void Postfix(SimpleEntitiesSubmissionScheduler scheduler)
|
||||
{
|
||||
engineRootScheduler = scheduler;
|
||||
}
|
||||
|
||||
internal static void SubmitEntitiesNow()
|
||||
{
|
||||
if (engineRootScheduler != null)
|
||||
engineRootScheduler.SubmitEntities();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,8 +25,15 @@ namespace GamecraftModdingAPI.Utility
|
|||
/// </summary>
|
||||
public static class FullGameFields
|
||||
{
|
||||
public static FullGameCompositionRoot Instance
|
||||
{
|
||||
private set;
|
||||
get;
|
||||
} = null;
|
||||
|
||||
public static MultiplayerInitParameters _multiplayerParams
|
||||
{ get
|
||||
{
|
||||
get
|
||||
{
|
||||
return (MultiplayerInitParameters)fgcr?.Field("_multiplayerParams").GetValue();
|
||||
}
|
||||
|
@ -157,6 +164,7 @@ namespace GamecraftModdingAPI.Utility
|
|||
public static void Init(FullGameCompositionRoot instance)
|
||||
{
|
||||
fgcr = new Traverse(instance);
|
||||
FullGameFields.Instance = instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace GamecraftModdingAPI.Utility
|
|||
/// Tracks the API version the current game was built for.
|
||||
/// For compatibility reasons, this must be enabled before it will work.
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public static class VersionTracking
|
||||
{
|
||||
private static readonly VersionTrackingEngine versionEngine = new VersionTrackingEngine();
|
||||
|
@ -58,6 +59,7 @@ namespace GamecraftModdingAPI.Utility
|
|||
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
internal class VersionTrackingEngine : IEventEmitterEngine
|
||||
{
|
||||
public string Name { get; } = "GamecraftModdingAPIVersionTrackingGameEngine";
|
||||
|
@ -94,11 +96,13 @@ namespace GamecraftModdingAPI.Utility
|
|||
public void Emit() { }
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public struct ModVersionStruct : IEntityComponent
|
||||
{
|
||||
public uint version;
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public class ModVersionDescriptor: SerializableEntityDescriptor<ModVersionDescriptor._ModVersionDescriptor>
|
||||
{
|
||||
[HashName("GamecraftModdingAPIVersionV0")]
|
||||
|
|
11
README.md
11
README.md
|
@ -10,10 +10,16 @@ This means your code won't break when the GamecraftModdingAPI or Gamecraft updat
|
|||
|
||||
For more info, please check out the [official documentation](https://mod.exmods.org).
|
||||
|
||||
For more support, join the ExMods [Discord](https://discord.gg/xjnFxQV).
|
||||
For more support, join the ExMods [Discord](https://discord.exmods.org).
|
||||
|
||||
## Installation
|
||||
[Please follow the official mod installation guide](https://www.exmods.org/guides/install.html)
|
||||
[Please follow the official mod installation guide](https://www.exmods.org/guides/install.html) or use GCMM.
|
||||
|
||||
## Development
|
||||
To get started, create a symbolic link called `ref` in the root of the project, or one folder higher, linking to the Gamecraft install folder.
|
||||
This will allow your IDE to resolve references to Gamecraft files for building and IDE tools.
|
||||
|
||||
GamecraftModdingAPI version numbers follow the [Semantic Versioning guidelines](https://semver.org/).
|
||||
|
||||
## External Libraries
|
||||
GamecraftModdingAPI includes [Harmony](https://github.com/pardeike/Harmony) to modify the behaviour of existing Gamecraft code.
|
||||
|
@ -21,4 +27,5 @@ GamecraftModdingAPI includes [Harmony](https://github.com/pardeike/Harmony) to m
|
|||
# Disclaimer
|
||||
This API is an unofficial modification of Gamecraft software, and is not endorsed or supported by FreeJam or Gamecraft.
|
||||
The GamecraftModdingAPI developer(s) claim no rights on the Gamecraft code referenced within this project.
|
||||
All code contained in this project is licensed under the [GNU Public License v3](https://git.exmods.org/modtainers/GamecraftModdingAPI/src/branch/master/LICENSE).
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ PROJECT_NAME = "GamecraftModdingAPI"
|
|||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = "v1.2.0"
|
||||
PROJECT_NUMBER = "v1.5.0"
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
|
Loading…
Reference in a new issue