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
{
// extensible engines
protected static GameGameEngine gameEngine = new GameGameEngine();
protected internal static GameMenuEngine menuEngine = new GameMenuEngine();
protected static DebugInterfaceEngine debugOverlayEngine = new DebugInterfaceEngine();
protected static GameBuildSimEventEngine buildSimEventEngine = new GameBuildSimEventEngine();
protected static GameGameEngine gameEngine = new();
protected internal static GameMenuEngine menuEngine = new();
protected static DebugInterfaceEngine debugOverlayEngine = new();
protected static GameBuildSimEventEngine buildSimEventEngine = new();
private List<string> debugIds = new List<string>();
private List<string> debugIds = new();
private bool menuMode = true;
private bool hasId = false;

View file

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

View file

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

View file

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

View file

@ -25,16 +25,16 @@ namespace TechbloxModdingAPI
/// </summary>
public class Block : EcsObjectBase, IEquatable<Block>, IEquatable<EGID>
{
protected static readonly PlacementEngine PlacementEngine = new PlacementEngine();
protected static readonly MovementEngine MovementEngine = new MovementEngine();
protected static readonly RotationEngine RotationEngine = new RotationEngine();
protected static readonly RemovalEngine RemovalEngine = new RemovalEngine();
protected static readonly SignalEngine SignalEngine = new SignalEngine();
protected static readonly BlockEventsEngine BlockEventsEngine = new BlockEventsEngine();
protected static readonly ScalingEngine ScalingEngine = new ScalingEngine();
protected static readonly BlockCloneEngine BlockCloneEngine = new BlockCloneEngine();
protected static readonly PlacementEngine PlacementEngine = new();
protected static readonly MovementEngine MovementEngine = new();
protected static readonly RotationEngine RotationEngine = new();
protected static readonly RemovalEngine RemovalEngine = new();
protected static readonly SignalEngine SignalEngine = new();
protected static readonly BlockEventsEngine BlockEventsEngine = new();
protected static readonly ScalingEngine ScalingEngine = new();
protected static readonly BlockCloneEngine BlockCloneEngine = new();
protected internal static readonly BlockEngine BlockEngine = new BlockEngine();
protected internal static readonly BlockEngine BlockEngine = new();
/// <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.
@ -96,7 +96,7 @@ namespace TechbloxModdingAPI
}
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.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)
{
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);
}
@ -133,7 +133,7 @@ namespace TechbloxModdingAPI
: GetInstance(egid, e => new Block(e));
}
public Block(EGID id) : base(id)
public Block(EGID id) : base(id, typeof(BlockEntityDescriptor))
{
Type expectedType;
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;
/// <summary>

View file

@ -18,14 +18,14 @@ namespace TechbloxModdingAPI
/// </summary>
public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable
{
internal static BlueprintEngine _engine = new BlueprintEngine();
internal static BlueprintEngine _engine = new();
private readonly Block sourceBlock;
private readonly List<Block> blocks;
private float3 position, rotation;
internal bool PosAndRotCalculated;
internal BlockGroup(int id, Block block) : base(new EGID((uint)id,
BlockGroupExclusiveGroups.BlockGroupEntityGroup))
BlockGroupExclusiveGroups.BlockGroupEntityGroup), typeof(BlockGroupEntityDescriptor))
{
if (id == BlockGroupUtility.GROUP_UNASSIGNED)
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 Svelto.DataStructures;
using Svelto.ECS;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Blocks.Engines

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,8 @@
using Gamecraft.Wires;
using Svelto.DataStructures;
using Svelto.ECS;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
@ -260,7 +261,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
else
{
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;
@ -272,7 +273,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
else
{
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++)

View file

@ -69,45 +69,6 @@ namespace TechbloxModdingAPI.Blocks
: 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>
/// Construct a wire object from an existing wire connection.
/// </summary>
@ -120,9 +81,9 @@ namespace TechbloxModdingAPI.Blocks
public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool 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);
}
@ -145,7 +106,7 @@ namespace TechbloxModdingAPI.Blocks
/// Construct a wire object from an existing wire connection.
/// </summary>
/// <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);
Construct(wire.sourceBlockEGID, wire.destinationBlockEGID, wire.sourcePortUsage, wire.destinationPortUsage,
@ -188,22 +149,6 @@ namespace TechbloxModdingAPI.Blocks
signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString.Set(value);
}
}
/// <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>
/// The wire's signal id.
@ -292,9 +237,7 @@ namespace TechbloxModdingAPI.Blocks
inputToOutput = false;
// swap inputs and outputs
(endBlockEGID, startBlockEGID) = (startBlockEGID, endBlockEGID);
var tempPort = endPortEGID;
endPortEGID = startPortEGID;
startPortEGID = tempPort;
(endPortEGID, startPortEGID) = (startPortEGID, endPortEGID);
(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>
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>
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;

View file

@ -14,7 +14,7 @@ namespace TechbloxModdingAPI.Commands
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)
{
_commands.Add(name, new CommandData
@ -51,7 +51,6 @@ namespace TechbloxModdingAPI.Commands
public static bool Exists(string name) => _commands.ContainsKey(name);
public static ReadOnlyDictionary<string, CommandData> GetAllCommandData() =>
new ReadOnlyDictionary<string, CommandData>(_commands);
public static ReadOnlyDictionary<string, CommandData> GetAllCommandData() => new(_commands);
}
}

View file

@ -1,13 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Common;
namespace TechbloxModdingAPI.Commands
{
@ -16,7 +7,7 @@ namespace TechbloxModdingAPI.Commands
/// 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.
/// </summary>
public interface ICustomCommandEngine : IApiEngine
public interface ICustomCommandEngine : INamedApiEngine
{
/// <summary>
/// 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.StateSync;
using Svelto.ECS;
using Svelto.ECS.Schedulers;
using TechbloxModdingAPI.Commands;
using TechbloxModdingAPI.Utility;
using GameState = TechbloxModdingAPI.Client.App.GameState;
namespace TechbloxModdingAPI.Engines
namespace TechbloxModdingAPI.Common.Engines
{
[HarmonyPatch]
static class GameLoadedTimeStoppedEnginePatch
{
public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
{
Client.App.GameClient.CurrentState = GameState.InMachineEditor; // TODO: World editor
// register all game engines, including deterministic
GameEngineManager.RegisterEngines(stateSyncReg);
// register command engines
/*CommandLineCompositionRoot.Compose(contextHolder, stateSyncReg.enginesRoot, reloadGame, multiplayerParameters,
stateSyncReg); - uREPL C# compilation not supported anymore */
CommandManager.RegisterEngines(stateSyncReg.enginesRoot);
}
@ -35,6 +34,7 @@ namespace TechbloxModdingAPI.Engines
{
public static void Postfix(StateSyncRegistrationHelper stateSyncReg)
{
Client.App.GameClient.CurrentState = GameState.InTestMode; // TODO: Client/server
GameEngineManager.RegisterEngines(stateSyncReg);
CommandManager.RegisterEngines(stateSyncReg.enginesRoot);
}
@ -49,21 +49,36 @@ namespace TechbloxModdingAPI.Engines
static class GameReloadedPatch
{
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");
}
[HarmonyPatch]
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");
}
[HarmonyPatch]
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");
}
@ -72,6 +87,7 @@ namespace TechbloxModdingAPI.Engines
{
public static void Postfix(EnginesRoot enginesRoot)
{
Client.App.GameClient.CurrentState = GameState.InMenu; // TODO: Loaded states
// register menu engines
MenuEngineManager.RegisterEngines(enginesRoot);
}
@ -87,6 +103,7 @@ namespace TechbloxModdingAPI.Engines
{
public static void Postfix(FullGameCompositionRoot __instance)
{
Client.App.GameClient.CurrentState = GameState.Loading;
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
{
internal static readonly FakeInputEngine inputEngine = new FakeInputEngine();
internal static readonly FakeInputEngine inputEngine = new();
/// <summary>
/// Customize the local input.

View file

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

View file

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

View file

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

View file

@ -15,9 +15,9 @@ namespace TechbloxModdingAPI.Persistence
/// </summary>
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;

View file

@ -24,8 +24,8 @@ namespace TechbloxModdingAPI
public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID>
{
// static functionality
private static readonly PlayerEngine playerEngine = new PlayerEngine();
private static readonly PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine();
private static readonly PlayerEngine playerEngine = new();
private static readonly PlayerEventsEngine playerEventsEngine = new();
private static Player localPlayer;
/// <summary>
@ -38,7 +38,7 @@ namespace TechbloxModdingAPI
switch (player)
{
case PlayerType.Remote:
return playerEngine.GetRemotePlayer() != uint.MaxValue;
return playerEngine.GetRemotePlayers().Length > 0;
case PlayerType.Local:
return playerEngine.GetLocalPlayer() != uint.MaxValue;
}
@ -90,7 +90,7 @@ namespace TechbloxModdingAPI
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary>
/// <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;
if (!Exists(id))
@ -100,38 +100,6 @@ namespace TechbloxModdingAPI
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
/// <summary>

View file

@ -14,11 +14,9 @@ using RobocraftX.SimulationModeState;
using Svelto.ECS;
using Techblox.Camera;
using Unity.Mathematics;
using Svelto.ECS.DataStructures;
using Techblox.BuildingDrone;
using Techblox.Character;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
@ -58,15 +56,17 @@ namespace TechbloxModdingAPI.Players
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);
if (count > 0)
{
return localPlayers[0].ID.entityID;
}
return uint.MaxValue;
var players = new uint[count];
for (int i = 0; i < count; i++)
{
players[i] = localPlayers[i].ID.entityID;
}
return players;
}
public long GetAllPlayerCount()

View file

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

View file

@ -7,6 +7,7 @@ using Unity.Mathematics;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Client.App;
using TechbloxModdingAPI.Tests;
using TechbloxModdingAPI.Utility;
@ -73,7 +74,7 @@ namespace TechbloxModdingAPI.Players
while (Player.LocalPlayer.State != PlayerState.InSeat)
{
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)
yield return Yield.It;
yield return new WaitForSecondsEnumerator(5f).Continue();

View file

@ -27,7 +27,7 @@ namespace TechbloxModdingAPI
private Cluster cluster;
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>
/// Schedule a task to run asynchronously.

View file

@ -14,7 +14,7 @@ namespace TechbloxModdingAPI.Tests
{
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: ";

View file

@ -8,11 +8,12 @@ using Svelto.Tasks;
using Svelto.Tasks.Lean;
using Svelto.Tasks.Enumerators;
using Svelto.Tasks.Lean.Unity;
using TechbloxModdingAPI.Client.App;
using UnityEngine;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility;
using GameState = TechbloxModdingAPI.Client.App.GameState;
namespace TechbloxModdingAPI.Tests
{
@ -66,19 +67,33 @@ namespace TechbloxModdingAPI.Tests
_testsCountPassed = 0;
_testsCountFailed = 0;
// flow control
Game.Enter += (sender, args) => { GameTests().RunOn(new UpdateMonoRunner("TechbloxModdingAPITestRunner")); };
Game.Exit += (s, a) => state = "ReturningFromGame";
Client.EnterMenu += (sender, args) =>
Client.App.GameClient.StateChanged += (sender, args) =>
{
if (state == "EnteringMenu")
switch (args.NewState)
{
MenuTests().RunOn(Scheduler.leanRunner);
state = "EnteringGame";
}
if (state == "ReturningFromGame")
{
TearDown().RunOn(Scheduler.leanRunner);
state = "ShuttingDown";
case GameState.InMenu:
{
if (state == "EnteringMenu")
{
MenuTests().RunOn(Scheduler.leanRunner);
state = "EnteringGame";
}
if (state == "ReturningFromGame")
{
TearDown().RunOn(Scheduler.leanRunner);
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
@ -131,17 +146,16 @@ namespace TechbloxModdingAPI.Tests
private static IEnumerator<TaskContract> GoToGameTests()
{
Client app = Client.Instance;
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 Yield.It;
try
{
app.MyGames[0].EnterGame();
GameClient.Machines[0].EnterGame();
}
catch (Exception e)
{

View file

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

View file

@ -3,7 +3,8 @@ using System.Linq;
using RobocraftX.StateSync;
using Svelto.ECS;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Common.Engines;
namespace TechbloxModdingAPI.Utility
{
@ -12,7 +13,7 @@ namespace TechbloxModdingAPI.Utility
/// </summary>
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;

View file

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

View file

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

View file

@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Common.Engines;
namespace TechbloxModdingAPI.Utility
{
@ -14,7 +11,7 @@ namespace TechbloxModdingAPI.Utility
/// </summary>
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;

View file

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