Added machine and environment data and new engine manager

This commit is contained in:
Norbi Peti 2023-10-07 22:51:02 +02:00
parent 9be1b5fdaf
commit 8a52095263
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
58 changed files with 516 additions and 626 deletions

View file

@ -1,171 +0,0 @@
using System;
using System.Reflection;
using HarmonyLib;
using RobocraftX.Services;
using UnityEngine;
using RobocraftX.Common;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App
{
/// <summary>
/// The Techblox application that is running this code right now.
/// </summary>
public class Client
{
public static Client Instance { get; } = new Client();
protected static Func<object> ErrorHandlerInstanceGetter;
protected static Action<object, Error> EnqueueError;
/// <summary>
/// An event that fires whenever the main menu is loaded.
/// </summary>
public static event EventHandler<MenuEventArgs> EnterMenu
{
add => Game.menuEngine.EnterMenu += value;
remove => Game.menuEngine.EnterMenu -= value;
}
/// <summary>
/// An event that fire whenever the main menu is exited.
/// </summary>
public static event EventHandler<MenuEventArgs> ExitMenu
{
add => Game.menuEngine.ExitMenu += value;
remove => Game.menuEngine.ExitMenu -= value;
}
/// <summary>
/// Techblox 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 (!Game.menuEngine.IsInMenu) return Array.Empty<Game>();
return Game.menuEngine.GetMyGames();
}
}
/// <summary>
/// Whether Techblox 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 => Game.menuEngine.IsInMenu;
}
/// <summary>
/// Open a popup which prompts the user to click a button.
/// This reuses Techblox'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);
}
public void CloseCurrentPrompt()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.Close();
}
public void SelectFirstPromptButton()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.FirstButton();
}
public void SelectSecondPromptButton()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.SecondButton();
}
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("TechbloxModdingAPI.App.Client:GenInstanceGetter")
.MakeGenericMethod(errorHandler)
.Invoke(null, new object[0]);
EnqueueError = (Action<object, Error>) AccessTools.Method("TechbloxModdingAPI.App.Client:GenEnqueueError")
.MakeGenericMethod(errorHandler, errorHandle)
.Invoke(null, new object[0]);
}
// 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 = () => 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 Close, Action FirstButton, Action SecondButton) _errorPopup;
private static (Action Close, Action FirstButton, Action SecondButton) GetPopupCloseMethods(object handler)
{
if (_errorPopup.Close != null)
return _errorPopup;
Type errorHandler = handler.GetType();
FieldInfo field = AccessTools.Field(errorHandler, "errorPopup");
var errorPopup = (ErrorPopup)field.GetValue(handler);
MethodInfo info = AccessTools.Method(errorPopup.GetType(), "ClosePopup");
var close = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
info = AccessTools.Method(errorPopup.GetType(), "HandleFirstOption");
var first = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
info = AccessTools.Method(errorPopup.GetType(), "HandleSecondOption");
var second = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
_errorPopup = (close, first, second);
return _errorPopup;
}
}
}

View file

@ -20,12 +20,12 @@ namespace TechbloxModdingAPI.App
public class Game public class Game
{ {
// extensible engines // extensible engines
protected static GameGameEngine gameEngine = new GameGameEngine(); protected static GameGameEngine gameEngine = new();
protected internal static GameMenuEngine menuEngine = new GameMenuEngine(); protected internal static GameMenuEngine menuEngine = new();
protected static DebugInterfaceEngine debugOverlayEngine = new DebugInterfaceEngine(); protected static DebugInterfaceEngine debugOverlayEngine = new();
protected static GameBuildSimEventEngine buildSimEventEngine = new GameBuildSimEventEngine(); protected static GameBuildSimEventEngine buildSimEventEngine = new();
private List<string> debugIds = new List<string>(); private List<string> debugIds = new();
private bool menuMode = true; private bool menuMode = true;
private bool hasId = false; private bool hasId = false;

View file

@ -1,10 +1,7 @@
using System; using RobocraftX.StateSync;
using RobocraftX.Common;
using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Common;
using Unity.Jobs; using Unity.Jobs;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App namespace TechbloxModdingAPI.App

View file

@ -16,7 +16,8 @@ using Techblox.Environment.Transition;
using Techblox.GameSelection; using Techblox.GameSelection;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Input; using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Players; using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;

View file

@ -9,8 +9,7 @@ using RobocraftX.Multiplayer;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Experimental; using Svelto.ECS.Experimental;
using Techblox.GameSelection; using Techblox.GameSelection;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.App namespace TechbloxModdingAPI.App

View file

@ -25,16 +25,16 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public class Block : EcsObjectBase, IEquatable<Block>, IEquatable<EGID> public class Block : EcsObjectBase, IEquatable<Block>, IEquatable<EGID>
{ {
protected static readonly PlacementEngine PlacementEngine = new PlacementEngine(); protected static readonly PlacementEngine PlacementEngine = new();
protected static readonly MovementEngine MovementEngine = new MovementEngine(); protected static readonly MovementEngine MovementEngine = new();
protected static readonly RotationEngine RotationEngine = new RotationEngine(); protected static readonly RotationEngine RotationEngine = new();
protected static readonly RemovalEngine RemovalEngine = new RemovalEngine(); protected static readonly RemovalEngine RemovalEngine = new();
protected static readonly SignalEngine SignalEngine = new SignalEngine(); protected static readonly SignalEngine SignalEngine = new();
protected static readonly BlockEventsEngine BlockEventsEngine = new BlockEventsEngine(); protected static readonly BlockEventsEngine BlockEventsEngine = new();
protected static readonly ScalingEngine ScalingEngine = new ScalingEngine(); protected static readonly ScalingEngine ScalingEngine = new();
protected static readonly BlockCloneEngine BlockCloneEngine = new BlockCloneEngine(); protected static readonly BlockCloneEngine BlockCloneEngine = new();
protected internal static readonly BlockEngine BlockEngine = new BlockEngine(); protected internal static readonly BlockEngine BlockEngine = new();
/// <summary> /// <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 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.
@ -96,7 +96,7 @@ namespace TechbloxModdingAPI
} }
private static readonly Dictionary<ExclusiveBuildGroup, (Func<EGID, Block> Constructor, Type Type)> GroupToConstructor = private static readonly Dictionary<ExclusiveBuildGroup, (Func<EGID, Block> Constructor, Type Type)> GroupToConstructor =
new Dictionary<ExclusiveBuildGroup, (Func<EGID, Block>, Type)> new()
{ {
{CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP, (id => new DampedSpring(id), typeof(DampedSpring))}, {CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP, (id => new DampedSpring(id), typeof(DampedSpring))},
{CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP, (id => new Engine(id), typeof(Engine))}, {CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP, (id => new Engine(id), typeof(Engine))},
@ -122,9 +122,9 @@ namespace TechbloxModdingAPI
internal static Block New(EGID egid, bool signaling = false) internal static Block New(EGID egid, bool signaling = false)
{ {
if (egid == default) return null; if (egid == default) return null;
if (GroupToConstructor.ContainsKey(egid.groupID)) if (GroupToConstructor.TryGetValue(egid.groupID, out var value))
{ {
var (constructor, type) = GroupToConstructor[egid.groupID]; var (constructor, type) = value;
return GetInstance(egid, constructor, type); return GetInstance(egid, constructor, type);
} }
@ -133,7 +133,7 @@ namespace TechbloxModdingAPI
: GetInstance(egid, e => new Block(e)); : GetInstance(egid, e => new Block(e));
} }
public Block(EGID id) : base(id) public Block(EGID id) : base(id, typeof(BlockEntityDescriptor))
{ {
Type expectedType; Type expectedType;
if (GroupToConstructor.ContainsKey(id.groupID) && if (GroupToConstructor.ContainsKey(id.groupID) &&
@ -153,26 +153,6 @@ namespace TechbloxModdingAPI
{ {
} }
/// <summary>
/// Places a new block in the world.
/// </summary>
/// <param name="type">The block's type</param>
/// <param name="position">The block's position (a block is 0.2 wide in terms of position)</param>
/// <param name="autoWire">Whether the block should be auto-wired (if functional)</param>
/// <param name="player">The player who placed the block</param>
public Block(BlockIDs type, float3 position, bool autoWire = false, Player player = null)
: base(block =>
{
if (!PlacementEngine.IsInGame || !GameState.IsBuildMode())
throw new BlockException("Blocks can only be placed in build mode.");
var initializer = PlacementEngine.PlaceBlock(type, position, player, autoWire);
block.InitData = initializer;
Placed += ((Block)block).OnPlacedInit;
return initializer.EGID;
})
{
}
private EGID copiedFrom; private EGID copiedFrom;
/// <summary> /// <summary>

View file

@ -18,14 +18,14 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable
{ {
internal static BlueprintEngine _engine = new BlueprintEngine(); internal static BlueprintEngine _engine = new();
private readonly Block sourceBlock; private readonly Block sourceBlock;
private readonly List<Block> blocks; private readonly List<Block> blocks;
private float3 position, rotation; private float3 position, rotation;
internal bool PosAndRotCalculated; internal bool PosAndRotCalculated;
internal BlockGroup(int id, Block block) : base(new EGID((uint)id, internal BlockGroup(int id, Block block) : base(new EGID((uint)id,
BlockGroupExclusiveGroups.BlockGroupEntityGroup)) BlockGroupExclusiveGroups.BlockGroupEntityGroup), typeof(BlockGroupEntityDescriptor))
{ {
if (id == BlockGroupUtility.GROUP_UNASSIGNED) if (id == BlockGroupUtility.GROUP_UNASSIGNED)
throw new BlockException("Cannot create a block group for blocks without a group!"); throw new BlockException("Cannot create a block group for blocks without a group!");

View file

@ -8,6 +8,7 @@ using RobocraftX.Character;
using RobocraftX.Common; using RobocraftX.Common;
using Svelto.DataStructures; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines

View file

@ -17,6 +17,7 @@ using Svelto.ECS.Experimental;
using Svelto.ECS.Hybrid; using Svelto.ECS.Hybrid;
using Techblox.BuildingDrone; using Techblox.BuildingDrone;
using Techblox.ObjectIDBlockServer; using Techblox.ObjectIDBlockServer;
using TechbloxModdingAPI.Common;
using Unity.Mathematics; using Unity.Mathematics;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;

View file

@ -19,6 +19,7 @@ using Svelto.ECS.EntityStructs;
using Svelto.ECS.Native; using Svelto.ECS.Native;
using Svelto.ECS.Serialization; using Svelto.ECS.Serialization;
using Techblox.Blocks.Connections; using Techblox.Blocks.Connections;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS; using TechbloxModdingAPI.Utility.ECS;
@ -35,7 +36,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
AccessTools.Method("RobocraftX.CR.MachineEditing.PlaceBlockUtility:GetBlocksSharingBlockgroup"); AccessTools.Method("RobocraftX.CR.MachineEditing.PlaceBlockUtility:GetBlocksSharingBlockgroup");
private NativeDynamicArray selectedBlocksInGroup; private NativeDynamicArray selectedBlocksInGroup;
private NativeHashSet<ulong> removedConnections = new NativeHashSet<ulong>(); private NativeHashSet<ulong> removedConnections = new();
private int addingToBlockGroup = -1; private int addingToBlockGroup = -1;
private static readonly Type PlaceBlueprintUtilityType = private static readonly Type PlaceBlueprintUtilityType =

View file

@ -2,10 +2,9 @@
using RobocraftX.DOTS; using RobocraftX.DOTS;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.EntityStructs; using Svelto.ECS.EntityStructs;
using TechbloxModdingAPI.Common;
using Unity.Mathematics; using Unity.Mathematics;
using Unity.Transforms; using Unity.Transforms;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS; using TechbloxModdingAPI.Utility.ECS;

View file

@ -12,9 +12,8 @@ using RobocraftX.Rendering;
using RobocraftX.Rendering.GPUI; using RobocraftX.Rendering.GPUI;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.EntityStructs; using Svelto.ECS.EntityStructs;
using TechbloxModdingAPI.Common;
using Unity.Mathematics; using Unity.Mathematics;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS; using TechbloxModdingAPI.Utility.ECS;

View file

@ -9,6 +9,7 @@ using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Native; using Svelto.ECS.Native;
using Techblox.Blocks.Connections; using Techblox.Blocks.Connections;
using TechbloxModdingAPI.Common;
using Unity.Collections; using Unity.Collections;
using Unity.Jobs; using Unity.Jobs;
using Allocator = Unity.Collections.Allocator; using Allocator = Unity.Collections.Allocator;

View file

@ -2,6 +2,7 @@
using RobocraftX.DOTS; using RobocraftX.DOTS;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.EntityStructs; using Svelto.ECS.EntityStructs;
using TechbloxModdingAPI.Common;
using Unity.Mathematics; using Unity.Mathematics;
using UnityEngine; using UnityEngine;

View file

@ -4,6 +4,7 @@ using HarmonyLib;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.DOTS; using RobocraftX.DOTS;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Common;
using Unity.Entities; using Unity.Entities;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;

View file

@ -3,7 +3,8 @@
using Gamecraft.Wires; using Gamecraft.Wires;
using Svelto.DataStructures; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS; using TechbloxModdingAPI.Utility.ECS;
@ -260,7 +261,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
else else
{ {
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(startBlock); BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(startBlock);
startPorts = new EGID[] {new EGID(ports.firstOutputID + startPort, NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group) }; startPorts = new EGID[] {new(ports.firstOutputID + startPort, NamedExclusiveGroup<BuildModeWiresGroups.OutputPortsGroup>.Group) };
} }
EGID[] endPorts; EGID[] endPorts;
@ -272,7 +273,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
else else
{ {
BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(endBlock); BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(endBlock);
endPorts = new EGID[] {new EGID(ports.firstInputID + endPort, NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group) }; endPorts = new EGID[] {new(ports.firstInputID + endPort, NamedExclusiveGroup<BuildModeWiresGroups.InputPortsGroup>.Group) };
} }
for (int endIndex = 0; endIndex < endPorts.Length; endIndex++) for (int endIndex = 0; endIndex < endPorts.Length; endIndex++)

View file

@ -69,45 +69,6 @@ namespace TechbloxModdingAPI.Blocks
: null; : null;
} }
/// <summary>
/// Construct a wire object froam n 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) : base(ecs =>
{
var th = (Wire)ecs;
th.startBlockEGID = start.Id;
th.endBlockEGID = end.Id;
bool flipped = false;
// find block ports
EGID wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, startPort, endPort);
if (wire == default)
{
// flip I/O around and try again
wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, endPort, startPort);
flipped = true;
// 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
}
if (wire != default)
{
th.Construct(start.Id, end.Id, startPort, endPort, wire, flipped);
}
else
{
throw new WireInvalidException("Wire not found");
}
return th.wireEGID;
})
{
}
/// <summary> /// <summary>
/// Construct a wire object from an existing wire connection. /// Construct a wire object from an existing wire connection.
/// </summary> /// </summary>
@ -120,9 +81,9 @@ namespace TechbloxModdingAPI.Blocks
public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool inputToOutput) public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool inputToOutput)
: this(start.Id, end.Id, startPort, endPort, wire, inputToOutput) : this(start.Id, end.Id, startPort, endPort, wire, inputToOutput)
{ {
} } // TODO: Convert all constructors (including the removed one) to static methods
private Wire(EGID startBlock, EGID endBlock, byte startPort, byte endPort, EGID wire, bool inputToOutput) : base(wire) private Wire(EGID startBlock, EGID endBlock, byte startPort, byte endPort, EGID wire, bool inputToOutput) : base(wire, typeof(WireEntityDescriptor))
{ {
Construct(startBlock, endBlock, startPort, endPort, wire, inputToOutput); Construct(startBlock, endBlock, startPort, endPort, wire, inputToOutput);
} }
@ -145,7 +106,7 @@ namespace TechbloxModdingAPI.Blocks
/// Construct a wire object from an existing wire connection. /// Construct a wire object from an existing wire connection.
/// </summary> /// </summary>
/// <param name="wireEgid">The wire ID.</param> /// <param name="wireEgid">The wire ID.</param>
public Wire(EGID wireEgid) : base(wireEgid) public Wire(EGID wireEgid) : base(wireEgid, typeof(WireEntityDescriptor))
{ {
WireEntityStruct wire = signalEngine.GetWire(wireEGID); WireEntityStruct wire = signalEngine.GetWire(wireEGID);
Construct(wire.sourceBlockEGID, wire.destinationBlockEGID, wire.sourcePortUsage, wire.destinationPortUsage, Construct(wire.sourceBlockEGID, wire.destinationBlockEGID, wire.sourcePortUsage, wire.destinationPortUsage,
@ -189,22 +150,6 @@ namespace TechbloxModdingAPI.Blocks
} }
} }
/// <summary>
/// The wire's raw string signal.
/// </summary>
public ECSString ECSString
{
get
{
return signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString;
}
set
{
signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString = value;
}
}
/// <summary> /// <summary>
/// The wire's signal id. /// The wire's signal id.
/// I'm 50% sure this is useless. /// I'm 50% sure this is useless.
@ -292,9 +237,7 @@ namespace TechbloxModdingAPI.Blocks
inputToOutput = false; inputToOutput = false;
// swap inputs and outputs // swap inputs and outputs
(endBlockEGID, startBlockEGID) = (startBlockEGID, endBlockEGID); (endBlockEGID, startBlockEGID) = (startBlockEGID, endBlockEGID);
var tempPort = endPortEGID; (endPortEGID, startPortEGID) = (startPortEGID, endPortEGID);
endPortEGID = startPortEGID;
startPortEGID = tempPort;
(endPort, startPort) = (startPort, endPort); (endPort, startPort) = (startPort, endPort);
} }
} }

View file

@ -1,127 +0,0 @@
using System;
using System.Reflection;
using HarmonyLib;
using RobocraftX.Services;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Client.Game;
using TechbloxModdingAPI.Common.Utils;
using UnityEngine;
namespace TechbloxModdingAPI.Client.App;
/// <summary>
/// Contains information about the game client's current state.
/// </summary>
public static class Client
{ // TODO
public static GameState CurrentState { get; }
private static Func<object> ErrorHandlerInstanceGetter;
private static Action<object, Error> EnqueueError;
/// <summary>
/// An event that fires whenever the game's state changes
/// </summary>
public static event EventHandler<MenuEventArgs> StateChanged
{
add => Game.menuEngine.EnterMenu += value;
remove => Game.menuEngine.EnterMenu -= value;
}
/// <summary>
/// Techblox build version string.
/// Usually this is in the form YYYY.mm.DD.HH.MM.SS
/// </summary>
/// <value>The version.</value>
public static string Version => Application.version;
/// <summary>
/// Unity version string.
/// </summary>
/// <value>The unity version.</value>
public static string UnityVersion => Application.unityVersion;
/// <summary>
/// Environments (maps) currently visible in the menu.
/// These take a second to completely populate after the EnterMenu event fires.
/// </summary>
/// <value>Available environments.</value>
public static ClientEnvironment[] Environments
{
get;
}
/// <summary>
/// Open a popup which prompts the user to click a button.
/// This reuses Techblox's error dialog popup
/// </summary>
/// <param name="popup">The popup to display. Use an instance of SingleChoicePrompt or DualChoicePrompt.</param>
public static 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);
}
public static void CloseCurrentPrompt()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.Close();
}
public static void SelectFirstPromptButton()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.FirstButton();
}
public static void SelectSecondPromptButton()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.SecondButton();
}
internal static void Init()
{
var errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
ErrorHandlerInstanceGetter = GenInstanceGetter(errorHandler);
EnqueueError = GenEnqueueError(errorHandler);
}
// 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(Type handler)
{
return Reflections.CreateAccessor<Func<object>>("Instance", handler);
}
private static Action<object, Error> GenEnqueueError(Type handler)
{
var enqueueError = AccessTools.Method(handler, "EnqueueError");
return Reflections.CreateMethodCall<Action<object, Error>>(enqueueError, handler);
}
private static (Action Close, Action FirstButton, Action SecondButton) _errorPopup;
private static (Action Close, Action FirstButton, Action SecondButton) GetPopupCloseMethods(object handler)
{
if (_errorPopup.Close != null)
return _errorPopup;
Type errorHandler = handler.GetType();
FieldInfo field = AccessTools.Field(errorHandler, "errorPopup");
var errorPopup = (ErrorPopup)field.GetValue(handler);
MethodInfo info = AccessTools.Method(errorPopup.GetType(), "ClosePopup");
var close = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
info = AccessTools.Method(errorPopup.GetType(), "HandleFirstOption");
var first = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
info = AccessTools.Method(errorPopup.GetType(), "HandleSecondOption");
var second = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
_errorPopup = (close, first, second);
return _errorPopup;
}
}

View file

@ -0,0 +1,46 @@
using RobocraftX.GUI.MyGamesScreen;
using RobocraftX.Multiplayer;
using Svelto.ECS;
using Techblox.GameSelection;
using TechbloxModdingAPI.Client.Game;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Client.App;
internal class ClientEngine : IApiEngine
{
public void Ready()
{
}
public EntitiesDB entitiesDB { get; set; }
public void Dispose()
{
}
public ClientMachine[] GetMachines()
{
var (mgsevs, count) = entitiesDB.QueryEntities<MyGameDataEntityStruct>(MyGamesScreenExclusiveGroups.MyGames);
var games = new ClientMachine[count];
for (int i = 0; i < count; i++)
{
Logging.MetaDebugLog($"Found game named {mgsevs[i].GameName}");
games[i] = new ClientMachine(mgsevs[i].ID);
}
return games;
}
public void EnterBuildMode(ClientEnvironment environment, ClientMachine machine)
{ // TODO: Move to using a single engine per 'type' (see AddEngine())
FullGameFields._multiplayerParams.MultiplayerMode = MultiplayerMode.SinglePlayer;
ref var selection = ref entitiesDB.QueryEntity<GameSelectionComponent>(GameSelectionConstants.GameSelectionEGID);
selection.userContentID.Set(machine.ContentID);
selection.triggerStart = true;
selection.saveType = SaveType.ExistingSave;
selection.saveName.Set(machine.Name);
selection.gameMode = machine is ClientWorld ? GameMode.CreateWorld : GameMode.PlayGame;
selection.gameID.Set(environment.Id); //TODO: Expose to the API
}
}

View file

@ -0,0 +1,79 @@
using System;
using TechbloxModdingAPI.Client.Game;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Utility;
using UnityEngine;
namespace TechbloxModdingAPI.Client.App;
/// <summary>
/// Contains information about the game client's current state.
/// </summary>
public static class GameClient
{
private static readonly ClientEngine _engine = new();
public static GameState CurrentState
{
get => _currentState;
internal set
{
_currentState = value;
var old = _currentState;
_stateChanged.Invoke(null, new GameStateChangedArgs { OldState = old, NewState = value });
}
}
private static GameState _currentState;
/// <summary>
/// An event that fires whenever the game's state changes
/// </summary>
public static event EventHandler<GameStateChangedArgs> StateChanged
{
add => _stateChanged += value;
remove => _stateChanged -= value;
}
private static WrappedHandler<GameStateChangedArgs> _stateChanged;
/// <summary>
/// Techblox build version string.
/// Usually this is in the form YYYY.mm.DD.HH.MM.SS
/// </summary>
/// <value>The version.</value>
public static string Version => Application.version;
/// <summary>
/// Unity version string.
/// </summary>
/// <value>The unity version.</value>
public static string UnityVersion => Application.unityVersion;
/// <summary>
/// Environments (maps) currently visible in the menu.
/// These take a second to completely populate after the EnterMenu event fires.
/// </summary>
/// <value>Available environments.</value>
public static ClientEnvironment[] Environments { get; }
public static ClientMachine[] Machines { get; }
public static void EnterBuildMode(ClientEnvironment environment, ClientMachine machine)
{
var env = new ClientEnvironment("GAMEID_Road_Track"); // TODO: The options are hardcoded
_engine.EnterBuildMode(env, machine);
}
public static void Init()
{
EngineManager.AddEngine(_engine, ApiEngineType.Menu);
}
public struct GameStateChangedArgs
{
public GameState OldState { get; set; }
public GameState NewState { get; set; }
}
}

View file

@ -0,0 +1,88 @@
using System;
using System.Reflection;
using HarmonyLib;
using RobocraftX.Services;
using TechbloxModdingAPI.Common.Utils;
namespace TechbloxModdingAPI.Client.App;
public static class Popup
{
private static Func<object> ErrorHandlerInstanceGetter;
private static Action<object, Error> EnqueueError;
/// <summary>
/// Open a popup which prompts the user to click a button.
/// This reuses Techblox's error dialog popup
/// </summary>
/// <param name="popup">The popup to display. Use an instance of SingleChoicePrompt or DualChoicePrompt.</param>
public static void PromptUser(Error popup)
{
// if the stuff wasn't mostly set to internal, this would be written as:
// RobocraftX.Services.ErrorHandler.Instance.EnqueueError(error);
object errorHandlerInstance = ErrorHandlerInstanceGetter();
EnqueueError(errorHandlerInstance, popup);
}
public static void CloseCurrentPrompt()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.Close();
}
public static void SelectFirstPromptButton()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.FirstButton();
}
public static void SelectSecondPromptButton()
{
object errorHandlerInstance = ErrorHandlerInstanceGetter();
var popup = GetPopupCloseMethods(errorHandlerInstance);
popup.SecondButton();
}
internal static void Init()
{
var errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
ErrorHandlerInstanceGetter = GenInstanceGetter(errorHandler);
EnqueueError = GenEnqueueError(errorHandler);
}
// 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(Type handler)
{
return Reflections.CreateAccessor<Func<object>>("Instance", handler);
}
private static Action<object, Error> GenEnqueueError(Type handler)
{
var enqueueError = AccessTools.Method(handler, "EnqueueError");
return Reflections.CreateMethodCall<Action<object, Error>>(enqueueError, handler);
}
private static (Action Close, Action FirstButton, Action SecondButton) _errorPopup;
private static (Action Close, Action FirstButton, Action SecondButton) GetPopupCloseMethods(object handler)
{
if (_errorPopup.Close != null)
return _errorPopup;
Type errorHandler = handler.GetType();
FieldInfo field = AccessTools.Field(errorHandler, "errorPopup");
var errorPopup = (ErrorPopup)field.GetValue(handler);
MethodInfo info = AccessTools.Method(errorPopup.GetType(), "ClosePopup");
var close = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
info = AccessTools.Method(errorPopup.GetType(), "HandleFirstOption");
var first = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
info = AccessTools.Method(errorPopup.GetType(), "HandleSecondOption");
var second = (Action)Delegate.CreateDelegate(typeof(Action), errorPopup, info);
_errorPopup = (close, first, second);
return _errorPopup;
}
}

View file

@ -5,5 +5,10 @@ namespace TechbloxModdingAPI.Client.Game;
/// </summary> /// </summary>
public class ClientEnvironment public class ClientEnvironment
{ {
public string Id { get; }
public ClientEnvironment(string id)
{
Id = id;
}
} }

View file

@ -0,0 +1,15 @@
using System;
using RobocraftX.GUI.MyGamesScreen;
using Svelto.ECS;
namespace TechbloxModdingAPI.Client.Game;
public class ClientMachine : EcsObjectBase
{
public ClientMachine(EGID id) : base(id, typeof(MyGameDataEntityDescriptor))
{
}
public string ContentID { get; set; } // TODO
public string Name { get; set; }
}

View file

@ -0,0 +1,10 @@
using Svelto.ECS;
namespace TechbloxModdingAPI.Client.Game;
public class ClientWorld : ClientMachine
{
public ClientWorld(EGID id) : base(id)
{
}
}

View file

@ -15,7 +15,7 @@ namespace TechbloxModdingAPI.Commands
/// </summary> /// </summary>
public static class CommandManager public static class CommandManager
{ {
private static Dictionary<string, ICustomCommandEngine> _customCommands = new Dictionary<string, ICustomCommandEngine>(); private static Dictionary<string, ICustomCommandEngine> _customCommands = new();
private static EnginesRoot _lastEngineRoot; private static EnginesRoot _lastEngineRoot;

View file

@ -14,7 +14,7 @@ namespace TechbloxModdingAPI.Commands
public Delegate Action; public Delegate Action;
} }
private static Dictionary<string, CommandData> _commands = new Dictionary<string, CommandData>(); private static Dictionary<string, CommandData> _commands = new();
public static void Register(string name, Delegate action, string desc) public static void Register(string name, Delegate action, string desc)
{ {
_commands.Add(name, new CommandData _commands.Add(name, new CommandData
@ -51,7 +51,6 @@ namespace TechbloxModdingAPI.Commands
public static bool Exists(string name) => _commands.ContainsKey(name); public static bool Exists(string name) => _commands.ContainsKey(name);
public static ReadOnlyDictionary<string, CommandData> GetAllCommandData() => public static ReadOnlyDictionary<string, CommandData> GetAllCommandData() => new(_commands);
new ReadOnlyDictionary<string, CommandData>(_commands);
} }
} }

View file

@ -1,13 +1,4 @@
using System; using TechbloxModdingAPI.Common;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Commands namespace TechbloxModdingAPI.Commands
{ {
@ -16,7 +7,7 @@ namespace TechbloxModdingAPI.Commands
/// If you are using implementing this yourself, you must manually register the command. /// If you are using implementing this yourself, you must manually register the command.
/// See SimpleCustomCommandEngine's Ready() and Dispose() methods for an example of command registration. /// See SimpleCustomCommandEngine's Ready() and Dispose() methods for an example of command registration.
/// </summary> /// </summary>
public interface ICustomCommandEngine : IApiEngine public interface ICustomCommandEngine : INamedApiEngine
{ {
/// <summary> /// <summary>
/// The command's description, shown in command help messages /// The command's description, shown in command help messages

View file

@ -0,0 +1,21 @@
namespace TechbloxModdingAPI.Common.Engines;
public enum ApiEngineType
{
/// <summary>
/// Gets created and registered when loading the game and stays loaded until it's quit. Intended for menu changes.
/// </summary>
Menu,
/// <summary>
/// Gets created and registered when entering build mode.
/// </summary>
Build,
/// <summary>
/// Gets created and registered on the client's side when starting simulation (test or not).
/// </summary>
PlayClient,
/// <summary>
/// Gets created and registered on the server's side when starting simulation (test or not).
/// </summary>
PlayServer
}

View file

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using RobocraftX.StateSync;
using Svelto.ECS;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Common.Engines;
public class EngineManager
{
private static Dictionary<ApiEngineType, List<IApiEngine>> _engines = new();
/// <summary>
/// Register an engine to a given game state and type. Or multiple.
/// </summary>
/// <param name="engine">The engine</param>
/// <param name="types">The types to register to</param>
public static void AddEngine(IApiEngine engine, params ApiEngineType[] types)
{
foreach (var type in types)
{
if (!_engines.ContainsKey(type))
_engines.Add(type, new List<IApiEngine>());
_engines[type].Add(engine);
}
}
public static void RegisterEngines(StateSyncRegistrationHelper helper, EnginesRoot enginesRoot, ApiEngineType type)
{
IEntityFactory factory = enginesRoot.GenerateEntityFactory();
IEntityFunctions functions = enginesRoot.GenerateEntityFunctions();
foreach (var engine in _engines[type])
{
string name = engine is INamedApiEngine namedEngine ? namedEngine.Name : engine.ToString();
Logging.MetaDebugLog($"Registering {type} IApiEngine {name}");
if (engine is IDeterministicEngine detEngine)
if (helper is not null) helper.AddDeterministicEngine(detEngine);
else throw new InvalidOperationException($"Attempting to add deterministic engine to non-deterministic state {type}");
else
enginesRoot.AddEngine(engine);
switch (engine)
{
case IFactoryEngine factEngine:
factEngine.Factory = factory;
break;
case IFunEngine funEngine:
funEngine.Functions = functions;
break;
}
}
}
}

View file

@ -5,22 +5,21 @@ using RobocraftX.CR.MainGame;
using RobocraftX.FrontEnd; using RobocraftX.FrontEnd;
using RobocraftX.StateSync; using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Schedulers;
using TechbloxModdingAPI.Commands; using TechbloxModdingAPI.Commands;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using GameState = TechbloxModdingAPI.Client.App.GameState;
namespace TechbloxModdingAPI.Engines namespace TechbloxModdingAPI.Common.Engines
{ {
[HarmonyPatch] [HarmonyPatch]
static class GameLoadedTimeStoppedEnginePatch static class GameLoadedTimeStoppedEnginePatch
{ {
public static void Postfix(StateSyncRegistrationHelper stateSyncReg) public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
{ {
Client.App.GameClient.CurrentState = GameState.InMachineEditor; // TODO: World editor
// register all game engines, including deterministic // register all game engines, including deterministic
GameEngineManager.RegisterEngines(stateSyncReg); GameEngineManager.RegisterEngines(stateSyncReg);
// register command engines // register command engines
/*CommandLineCompositionRoot.Compose(contextHolder, stateSyncReg.enginesRoot, reloadGame, multiplayerParameters,
stateSyncReg); - uREPL C# compilation not supported anymore */
CommandManager.RegisterEngines(stateSyncReg.enginesRoot); CommandManager.RegisterEngines(stateSyncReg.enginesRoot);
} }
@ -35,6 +34,7 @@ namespace TechbloxModdingAPI.Engines
{ {
public static void Postfix(StateSyncRegistrationHelper stateSyncReg) public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
{ {
Client.App.GameClient.CurrentState = GameState.InTestMode; // TODO: Client/server
GameEngineManager.RegisterEngines(stateSyncReg); GameEngineManager.RegisterEngines(stateSyncReg);
CommandManager.RegisterEngines(stateSyncReg.enginesRoot); CommandManager.RegisterEngines(stateSyncReg.enginesRoot);
} }
@ -49,21 +49,36 @@ namespace TechbloxModdingAPI.Engines
static class GameReloadedPatch static class GameReloadedPatch
{ {
internal static bool IsReload; internal static bool IsReload;
public static void Prefix() => IsReload = true; public static void Prefix()
{
IsReload = true;
Client.App.GameClient.CurrentState = GameState.Loading;
}
public static MethodBase TargetMethod() => AccessTools.Method(typeof(FullGameCompositionRoot), "ReloadGame"); public static MethodBase TargetMethod() => AccessTools.Method(typeof(FullGameCompositionRoot), "ReloadGame");
} }
[HarmonyPatch] [HarmonyPatch]
static class GameSwitchedToPatch static class GameSwitchedToPatch
{ {
public static void Prefix() => GameReloadedPatch.IsReload = false; public static void Prefix()
{
GameReloadedPatch.IsReload = false;
Client.App.GameClient.CurrentState = GameState.Loading;
}
public static MethodBase TargetMethod() => AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame"); public static MethodBase TargetMethod() => AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToGame");
} }
[HarmonyPatch] [HarmonyPatch]
static class MenuSwitchedToPatch static class MenuSwitchedToPatch
{ {
public static void Prefix() => GameReloadedPatch.IsReload = false; public static void Prefix()
{
GameReloadedPatch.IsReload = false;
Client.App.GameClient.CurrentState = GameState.Loading;
}
public static MethodBase TargetMethod() => AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToMenu"); public static MethodBase TargetMethod() => AccessTools.Method(typeof(FullGameCompositionRoot), "SwitchToMenu");
} }
@ -72,6 +87,7 @@ namespace TechbloxModdingAPI.Engines
{ {
public static void Postfix(EnginesRoot enginesRoot) public static void Postfix(EnginesRoot enginesRoot)
{ {
Client.App.GameClient.CurrentState = GameState.InMenu; // TODO: Loaded states
// register menu engines // register menu engines
MenuEngineManager.RegisterEngines(enginesRoot); MenuEngineManager.RegisterEngines(enginesRoot);
} }
@ -87,6 +103,7 @@ namespace TechbloxModdingAPI.Engines
{ {
public static void Postfix(FullGameCompositionRoot __instance) public static void Postfix(FullGameCompositionRoot __instance)
{ {
Client.App.GameClient.CurrentState = GameState.Loading;
FullGameFields.Init(__instance); FullGameFields.Init(__instance);
} }

View file

@ -0,0 +1,15 @@
using Svelto.ECS;
namespace TechbloxModdingAPI.Common.Engines;
/// <summary>
/// Engine interface to create entities using the given Factory.
/// </summary>
public interface IFactoryEngine : IApiEngine
{
/// <summary>
/// The EntityFactory for the entitiesDB.
/// Use this to create entities in ECS.
/// </summary>
IEntityFactory Factory { set; }
}

View file

@ -0,0 +1,11 @@
using Svelto.ECS;
namespace TechbloxModdingAPI.Common.Engines;
/// <summary>
/// Engine interface to use entity functions to remove entities or swap their groups.
/// </summary>
public interface IFunEngine : IApiEngine
{
public IEntityFunctions Functions { set; }
}

View file

@ -0,0 +1,12 @@
using Svelto.ECS;
using TechbloxModdingAPI.Common;
namespace TechbloxModdingAPI.Engines
{
/// <summary>
/// Engine interface to react on an entity component being added or removed.
/// </summary>
public interface IReactionaryEngine<T> : IApiEngine, IReactOnAddAndRemove<T> where T : unmanaged, IEntityComponent
{
}
}

View file

@ -0,0 +1,11 @@
using System;
using Svelto.ECS;
namespace TechbloxModdingAPI.Common;
/// <summary>
/// Base engine interface used by all TechbloxModdingAPI engines
/// </summary>
public interface IApiEngine : IQueryingEntitiesEngine, IDisposable
{
}

View file

@ -0,0 +1,9 @@
namespace TechbloxModdingAPI.Common;
public interface INamedApiEngine : IApiEngine
{
/// <summary>
/// The name of the engine
/// </summary>
string Name { get; }
}

View file

@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
namespace TechbloxModdingAPI.Engines
{
/// <summary>
/// Base engine interface used by all TechbloxModdingAPI engines
/// </summary>
public interface IApiEngine : IEngine, IQueryingEntitiesEngine, IDisposable
{
/// <summary>
/// The name of the engine
/// </summary>
string Name { get; }
/// <summary>
/// Whether the emitter can be removed with Manager.RemoveEventEmitter(name)
/// </summary>
bool isRemovable { get; }
}
}

View file

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Engines
{
/// <summary>
/// Engine interface to create a ModEventEntityStruct in entitiesDB when Emit() is called.
/// </summary>
public interface IFactoryEngine : IApiEngine
{
/// <summary>
/// The EntityFactory for the entitiesDB.
/// Use this to create a ModEventEntityStruct when Emit() is called.
/// </summary>
IEntityFactory Factory { set; }
}
}

View file

@ -1,9 +0,0 @@
using Svelto.ECS;
namespace TechbloxModdingAPI.Engines
{
public interface IFunEngine : IApiEngine
{
public IEntityFunctions Functions { set; }
}
}

View file

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using Svelto.ECS.Internal;
using TechbloxModdingAPI.Events;
namespace TechbloxModdingAPI.Engines
{
/// <summary>
/// Engine interface to handle ModEventEntityStruct events emitted by IEventEmitterEngines.
/// </summary>
public interface IReactionaryEngine<T> : IApiEngine, IReactOnAddAndRemove<T> where T : unmanaged, IEntityComponent
{
}
}

View file

@ -7,7 +7,7 @@ namespace TechbloxModdingAPI.Input
{ {
public static class FakeInput public static class FakeInput
{ {
internal static readonly FakeInputEngine inputEngine = new FakeInputEngine(); internal static readonly FakeInputEngine inputEngine = new();
/// <summary> /// <summary>
/// Customize the local input. /// Customize the local input.

View file

@ -1,12 +1,8 @@
using System; using RobocraftX.Common;
using RobocraftX.Common;
using RobocraftX.Common.Input; using RobocraftX.Common.Input;
using RobocraftX.Players; using RobocraftX.Players;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Input namespace TechbloxModdingAPI.Input
{ {

View file

@ -13,7 +13,7 @@ namespace TechbloxModdingAPI.Interface.IMGUI
{ {
private bool automaticLayout; private bool automaticLayout;
private FasterList<UIElement> elements = new FasterList<UIElement>(); private FasterList<UIElement> elements = new();
/// <summary> /// <summary>
/// The rectangular area in the window that the UI group can use /// The rectangular area in the window that the UI group can use

View file

@ -70,7 +70,6 @@ namespace TechbloxModdingAPI
Wire.Init(); Wire.Init();
// init client // init client
Logging.MetaDebugLog($"Initializing Client"); Logging.MetaDebugLog($"Initializing Client");
Client.Init();
Game.Init(); Game.Init();
// init UI // init UI
Logging.MetaDebugLog($"Initializing UI"); Logging.MetaDebugLog($"Initializing UI");

View file

@ -15,9 +15,9 @@ namespace TechbloxModdingAPI.Persistence
/// </summary> /// </summary>
public static class SerializerManager public static class SerializerManager
{ {
private static Dictionary<string, IEntitySerializer> _serializers = new Dictionary<string, IEntitySerializer>(); private static Dictionary<string, IEntitySerializer> _serializers = new();
private static Dictionary<string, Action<IEntitySerialization>> _registrations = new Dictionary<string, Action<IEntitySerialization>>(); private static Dictionary<string, Action<IEntitySerialization>> _registrations = new();
private static EnginesRoot _lastEnginesRoot; private static EnginesRoot _lastEnginesRoot;

View file

@ -24,8 +24,8 @@ namespace TechbloxModdingAPI
public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID> public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID>
{ {
// static functionality // static functionality
private static readonly PlayerEngine playerEngine = new PlayerEngine(); private static readonly PlayerEngine playerEngine = new();
private static readonly PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine(); private static readonly PlayerEventsEngine playerEventsEngine = new();
private static Player localPlayer; private static Player localPlayer;
/// <summary> /// <summary>
@ -38,7 +38,7 @@ namespace TechbloxModdingAPI
switch (player) switch (player)
{ {
case PlayerType.Remote: case PlayerType.Remote:
return playerEngine.GetRemotePlayer() != uint.MaxValue; return playerEngine.GetRemotePlayers().Length > 0;
case PlayerType.Local: case PlayerType.Local:
return playerEngine.GetLocalPlayer() != uint.MaxValue; return playerEngine.GetLocalPlayer() != uint.MaxValue;
} }
@ -90,7 +90,7 @@ namespace TechbloxModdingAPI
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class. /// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary> /// </summary>
/// <param name="id">The player's unique identifier.</param> /// <param name="id">The player's unique identifier.</param>
public Player(uint id) : base(new EGID(id, CharacterExclusiveGroups.OnFootGroup)) public Player(uint id) : base(new EGID(id, CharacterExclusiveGroups.OnFootGroup), typeof(CharacterEntityDescriptor))
{ {
this.Id = id; this.Id = id;
if (!Exists(id)) if (!Exists(id))
@ -100,38 +100,6 @@ namespace TechbloxModdingAPI
this.Type = playerEngine.GetLocalPlayer() == id ? PlayerType.Local : PlayerType.Remote; this.Type = playerEngine.GetLocalPlayer() == id ? PlayerType.Local : PlayerType.Remote;
} }
/// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary>
/// <param name="player">The player type. Chooses the first available player matching the criteria.</param>
public Player(PlayerType player) : base(ecs =>
{
uint id;
switch (player)
{
case PlayerType.Local:
id = playerEngine.GetLocalPlayer();
break;
case PlayerType.Remote:
id = playerEngine.GetRemotePlayer();
break;
default:
id = uint.MaxValue;
break;
}
if (id == uint.MaxValue)
{
throw new PlayerNotFoundException($"No player of {player} type exists");
}
return new EGID(id, CharacterExclusiveGroups.OnFootGroup);
})
{
this.Type = player;
Id = base.Id.entityID;
}
// object fields & properties // object fields & properties
/// <summary> /// <summary>

View file

@ -14,11 +14,9 @@ using RobocraftX.SimulationModeState;
using Svelto.ECS; using Svelto.ECS;
using Techblox.Camera; using Techblox.Camera;
using Unity.Mathematics; using Unity.Mathematics;
using Svelto.ECS.DataStructures;
using Techblox.BuildingDrone; using Techblox.BuildingDrone;
using Techblox.Character; using Techblox.Character;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Input; using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS; using TechbloxModdingAPI.Utility.ECS;
@ -58,15 +56,17 @@ namespace TechbloxModdingAPI.Players
return uint.MaxValue; return uint.MaxValue;
} }
public uint GetRemotePlayer() public uint[] GetRemotePlayers()
{ {
if (!isReady) return uint.MaxValue; if (!isReady) return Array.Empty<uint>();
var (localPlayers, count) = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers); var (localPlayers, count) = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers);
if (count > 0) var players = new uint[count];
for (int i = 0; i < count; i++)
{ {
return localPlayers[0].ID.entityID; players[i] = localPlayers[i].ID.entityID;
} }
return uint.MaxValue;
return players;
} }
public long GetAllPlayerCount() public long GetAllPlayerCount()

View file

@ -1,10 +1,8 @@
using System;
using RobocraftX.Character; using RobocraftX.Character;
using RobocraftX.Character.Movement; using RobocraftX.Character.Movement;
using RobocraftX.Common.Input; using RobocraftX.Common.Input;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Players namespace TechbloxModdingAPI.Players
{ {

View file

@ -7,6 +7,7 @@ using Unity.Mathematics;
using TechbloxModdingAPI.App; using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Client.App;
using TechbloxModdingAPI.Tests; using TechbloxModdingAPI.Tests;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
@ -73,7 +74,7 @@ namespace TechbloxModdingAPI.Players
while (Player.LocalPlayer.State != PlayerState.InSeat) while (Player.LocalPlayer.State != PlayerState.InSeat)
{ {
bool cont = false; bool cont = false;
Client.Instance.PromptUser(new SingleChoicePrompt("Testing", $"Enter the seat at {seat.Position} pls", "OK", () => cont = true)); Popup.PromptUser(new SingleChoicePrompt("Testing", $"Enter the seat at {seat.Position} pls", "OK", () => cont = true));
while (!cont) while (!cont)
yield return Yield.It; yield return Yield.It;
yield return new WaitForSecondsEnumerator(5f).Continue(); yield return new WaitForSecondsEnumerator(5f).Continue();

View file

@ -27,7 +27,7 @@ namespace TechbloxModdingAPI
private Cluster cluster; private Cluster cluster;
private readonly uint clusterId = uint.MaxValue; private readonly uint clusterId = uint.MaxValue;
public SimBody(EGID id) : base(id) public SimBody(EGID id) : base(id, typeof(ClusterEntityDescriptor))
{ {
} }

View file

@ -32,9 +32,9 @@ namespace TechbloxModdingAPI.Tasks
} }
} }
public static readonly Svelto.Tasks.ExtraLean.Unity.UpdateMonoRunner extraLeanRunner = new Svelto.Tasks.ExtraLean.Unity.UpdateMonoRunner("TechbloxModdingAPIExtraLean"); public static readonly Svelto.Tasks.ExtraLean.Unity.UpdateMonoRunner extraLeanRunner = new("TechbloxModdingAPIExtraLean");
public static readonly Svelto.Tasks.Lean.Unity.UpdateMonoRunner leanRunner = new Svelto.Tasks.Lean.Unity.UpdateMonoRunner("TechbloxModdingAPILean"); public static readonly Svelto.Tasks.Lean.Unity.UpdateMonoRunner leanRunner = new("TechbloxModdingAPILean");
/// <summary> /// <summary>
/// Schedule a task to run asynchronously. /// Schedule a task to run asynchronously.

View file

@ -14,7 +14,7 @@ namespace TechbloxModdingAPI.Tests
{ {
private static StreamWriter logFile = null; private static StreamWriter logFile = null;
private static ConcurrentDictionary<string, string> callbacks = new ConcurrentDictionary<string, string>(); private static ConcurrentDictionary<string, string> callbacks = new();
private const string PASS = "SUCCESS: "; private const string PASS = "SUCCESS: ";

View file

@ -8,11 +8,12 @@ using Svelto.Tasks;
using Svelto.Tasks.Lean; using Svelto.Tasks.Lean;
using Svelto.Tasks.Enumerators; using Svelto.Tasks.Enumerators;
using Svelto.Tasks.Lean.Unity; using Svelto.Tasks.Lean.Unity;
using TechbloxModdingAPI.Client.App;
using UnityEngine; using UnityEngine;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Tasks; using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using GameState = TechbloxModdingAPI.Client.App.GameState;
namespace TechbloxModdingAPI.Tests namespace TechbloxModdingAPI.Tests
{ {
@ -66,20 +67,34 @@ namespace TechbloxModdingAPI.Tests
_testsCountPassed = 0; _testsCountPassed = 0;
_testsCountFailed = 0; _testsCountFailed = 0;
// flow control // flow control
Game.Enter += (sender, args) => { GameTests().RunOn(new UpdateMonoRunner("TechbloxModdingAPITestRunner")); }; Client.App.GameClient.StateChanged += (sender, args) =>
Game.Exit += (s, a) => state = "ReturningFromGame"; {
Client.EnterMenu += (sender, args) => switch (args.NewState)
{
case GameState.InMenu:
{ {
if (state == "EnteringMenu") if (state == "EnteringMenu")
{ {
MenuTests().RunOn(Scheduler.leanRunner); MenuTests().RunOn(Scheduler.leanRunner);
state = "EnteringGame"; state = "EnteringGame";
} }
if (state == "ReturningFromGame") if (state == "ReturningFromGame")
{ {
TearDown().RunOn(Scheduler.leanRunner); TearDown().RunOn(Scheduler.leanRunner);
state = "ShuttingDown"; state = "ShuttingDown";
} }
break;
}
case GameState.InMachineEditor:
GameTests().RunOn(new UpdateMonoRunner("TechbloxModdingAPITestRunner"));
break;
case GameState.Loading:
if (args.OldState == GameState.InTestMode)
state = "ReturningFromGame";
break;
}
}; };
// init tests here // init tests here
foreach (Type t in testTypes) foreach (Type t in testTypes)
@ -131,17 +146,16 @@ namespace TechbloxModdingAPI.Tests
private static IEnumerator<TaskContract> GoToGameTests() private static IEnumerator<TaskContract> GoToGameTests()
{ {
Client app = Client.Instance;
int oldLength = 0; int oldLength = 0;
while (app.MyGames.Length == 0 || oldLength != app.MyGames.Length) while (GameClient.Machines.Length == 0 || oldLength != GameClient.Machines.Length)
{ {
oldLength = app.MyGames.Length; oldLength = GameClient.Machines.Length;
yield return new WaitForSecondsEnumerator(1).Continue(); yield return new WaitForSecondsEnumerator(1).Continue();
} }
yield return Yield.It; yield return Yield.It;
try try
{ {
app.MyGames[0].EnterGame(); GameClient.Machines[0].EnterGame();
} }
catch (Exception e) catch (Exception e)
{ {

View file

@ -1,23 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
using System.Text; using System.Text;
using System.Text.Formatting;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Players;
using HarmonyLib; using HarmonyLib;
using RobocraftX.GUI.Debug;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Experimental; using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Utility namespace TechbloxModdingAPI.Utility
{ {
public class DebugInterfaceEngine : IApiEngine public class DebugInterfaceEngine : IApiEngine
{ {
private static Dictionary<string, Func<string>> _extraInfo=new Dictionary<string, Func<string>>(); private static Dictionary<string, Func<string>> _extraInfo=new();
public void Ready() public void Ready()
{ {
} }
@ -46,8 +40,8 @@ namespace TechbloxModdingAPI.Utility
int index = list.FindLastIndex(inst => inst.opcode == OpCodes.Ldfld); int index = list.FindLastIndex(inst => inst.opcode == OpCodes.Ldfld);
var array = new CodeInstruction[] var array = new CodeInstruction[]
{ {
new CodeInstruction(OpCodes.Ldloc_0), //StringBuffer new(OpCodes.Ldloc_0), //StringBuffer
new CodeInstruction(OpCodes.Call, ((Action<StringBuilder>)AddInfo).Method) new(OpCodes.Call, ((Action<StringBuilder>)AddInfo).Method)
}; };
list.InsertRange(index - 1, array); //-1: ldloc.1 ("local") before ldfld list.InsertRange(index - 1, array); //-1: ldloc.1 ("local") before ldfld
} }

View file

@ -3,7 +3,8 @@ using System.Linq;
using RobocraftX.StateSync; using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Common.Engines;
namespace TechbloxModdingAPI.Utility namespace TechbloxModdingAPI.Utility
{ {
@ -12,7 +13,7 @@ namespace TechbloxModdingAPI.Utility
/// </summary> /// </summary>
public static class GameEngineManager public static class GameEngineManager
{ {
private static Dictionary<string, IApiEngine> _gameEngines = new Dictionary<string, IApiEngine>(); private static Dictionary<string, IApiEngine> _gameEngines = new();
private static EnginesRoot _lastEngineRoot; private static EnginesRoot _lastEngineRoot;

View file

@ -11,7 +11,7 @@ namespace TechbloxModdingAPI.Utility
/// </summary> /// </summary>
public static class GameState public static class GameState
{ {
private static GameStateEngine gameEngine = new GameStateEngine(); private static GameStateEngine gameEngine = new();
/// <summary> /// <summary>
/// Is the game in edit mode? /// Is the game in edit mode?

View file

@ -1,12 +1,6 @@
using Svelto.ECS; using Svelto.ECS;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RobocraftX.SimulationModeState; using RobocraftX.SimulationModeState;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Common;
namespace TechbloxModdingAPI.Utility namespace TechbloxModdingAPI.Utility
{ {

View file

@ -1,11 +1,8 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Common.Engines;
namespace TechbloxModdingAPI.Utility namespace TechbloxModdingAPI.Utility
{ {
@ -14,7 +11,7 @@ namespace TechbloxModdingAPI.Utility
/// </summary> /// </summary>
public static class MenuEngineManager public static class MenuEngineManager
{ {
private static Dictionary<string, IApiEngine> _menuEngines = new Dictionary<string, IApiEngine>(); private static Dictionary<string, IApiEngine> _menuEngines = new();
private static EnginesRoot _lastEngineRoot; private static EnginesRoot _lastEngineRoot;

View file

@ -15,8 +15,7 @@ namespace TechbloxModdingAPI.Utility
/// <summary> /// <summary>
/// Store wrappers so we can unregister them properly /// Store wrappers so we can unregister them properly
/// </summary> /// </summary>
private static Dictionary<EventHandler<T>, EventHandler<T>> wrappers = private static Dictionary<EventHandler<T>, EventHandler<T>> wrappers = new();
new Dictionary<EventHandler<T>, EventHandler<T>>();
public static WrappedHandler<T> operator +(WrappedHandler<T> original, EventHandler<T> added) public static WrappedHandler<T> operator +(WrappedHandler<T> original, EventHandler<T> added)
{ {