@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
@@ -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 | |||
@@ -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; | |||
@@ -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 | |||
@@ -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> | |||
@@ -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!"); | |||
@@ -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 | |||
@@ -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; | |||
@@ -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 = | |||
@@ -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; | |||
@@ -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; | |||
@@ -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; | |||
@@ -2,6 +2,7 @@ | |||
using RobocraftX.DOTS; | |||
using Svelto.ECS; | |||
using Svelto.ECS.EntityStructs; | |||
using TechbloxModdingAPI.Common; | |||
using Unity.Mathematics; | |||
using UnityEngine; | |||
@@ -4,6 +4,7 @@ using HarmonyLib; | |||
using RobocraftX.Common; | |||
using RobocraftX.DOTS; | |||
using Svelto.ECS; | |||
using TechbloxModdingAPI.Common; | |||
using Unity.Entities; | |||
using TechbloxModdingAPI.Engines; | |||
@@ -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++) | |||
@@ -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); | |||
} | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -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 | |||
} | |||
} |
@@ -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; } | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -5,5 +5,10 @@ namespace TechbloxModdingAPI.Client.Game; | |||
/// </summary> | |||
public class ClientEnvironment | |||
{ | |||
public string Id { get; } | |||
public ClientEnvironment(string id) | |||
{ | |||
Id = id; | |||
} | |||
} |
@@ -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; } | |||
} |
@@ -0,0 +1,10 @@ | |||
using Svelto.ECS; | |||
namespace TechbloxModdingAPI.Client.Game; | |||
public class ClientWorld : ClientMachine | |||
{ | |||
public ClientWorld(EGID id) : base(id) | |||
{ | |||
} | |||
} |
@@ -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; | |||
@@ -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); | |||
} | |||
} |
@@ -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 | |||
@@ -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 | |||
} |
@@ -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; | |||
} | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
@@ -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; } | |||
} |
@@ -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; } | |||
} |
@@ -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 | |||
{ | |||
} | |||
} |
@@ -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 | |||
{ | |||
} |
@@ -0,0 +1,9 @@ | |||
namespace TechbloxModdingAPI.Common; | |||
public interface INamedApiEngine : IApiEngine | |||
{ | |||
/// <summary> | |||
/// The name of the engine | |||
/// </summary> | |||
string Name { get; } | |||
} |
@@ -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; } | |||
} | |||
} |
@@ -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; } | |||
} | |||
} |
@@ -1,9 +0,0 @@ | |||
using Svelto.ECS; | |||
namespace TechbloxModdingAPI.Engines | |||
{ | |||
public interface IFunEngine : IApiEngine | |||
{ | |||
public IEntityFunctions Functions { set; } | |||
} | |||
} |
@@ -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 | |||
{ | |||
} | |||
} |
@@ -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. | |||
@@ -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 | |||
{ | |||
@@ -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 | |||
@@ -70,7 +70,6 @@ namespace TechbloxModdingAPI | |||
Wire.Init(); | |||
// init client | |||
Logging.MetaDebugLog($"Initializing Client"); | |||
Client.Init(); | |||
Game.Init(); | |||
// init UI | |||
Logging.MetaDebugLog($"Initializing UI"); | |||
@@ -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; | |||
@@ -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> | |||
@@ -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() | |||
@@ -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 | |||
{ | |||
@@ -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(); | |||
@@ -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)) | |||
{ | |||
} | |||
@@ -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. | |||
@@ -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: "; | |||
@@ -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) | |||
{ | |||
@@ -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 | |||
} | |||
@@ -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; | |||
@@ -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? | |||
@@ -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 | |||
{ | |||
@@ -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; | |||
@@ -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) | |||
{ | |||