Compare commits

..

No commits in common. "feature/refactor.v3" and "master" have entirely different histories.

83 changed files with 1211 additions and 1506 deletions

View file

@ -8,7 +8,7 @@
<PackageProjectUrl>https://git.exmods.org/modtainers/GamecraftModdingAPI</PackageProjectUrl> <PackageProjectUrl>https://git.exmods.org/modtainers/GamecraftModdingAPI</PackageProjectUrl>
<NeutralLanguage>en-CA</NeutralLanguage> <NeutralLanguage>en-CA</NeutralLanguage>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion> <LangVersion>9</LangVersion>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

View file

@ -1,67 +0,0 @@
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Gamecraft.Tweaks;
using HarmonyLib;
using Svelto.ECS;
namespace CodeGenerator
{
public static class ECSAnalyzer
{
public static ECSClassInfo AnalyzeEntityDescriptor(Type entityDescriptorType)
{
// TODO: Add support for creating/deleting entities (getting an up to date server/client engines root)
var templateType = typeof(EntityDescriptorTemplate<>).MakeGenericType(entityDescriptorType);
var templateDescriptor = AccessTools.Property(templateType, "descriptor");
var getDescriptorExpr = Expression.MakeMemberAccess(null, templateDescriptor ?? throw new InvalidOperationException());
var getTemplateDescriptorExpr =
Expression.Lambda<Func<IEntityDescriptor>>(getDescriptorExpr);
var getTemplateDescriptor = getTemplateDescriptorExpr.Compile();
// TODO: Crashes on constructing the descriptor type. Maybe move the analysis part to a mod.
var builders = getTemplateDescriptor().componentsToBuild;
return new ECSClassInfo
{
Name = entityDescriptorType.Name.Replace("EntityComponent", "").Replace("EntityStruct", ""),
Properties = builders.Select(builder => builder.GetEntityComponentType()).SelectMany(AnalyzeFields).ToArray()
};
}
private static ECSPropertyInfo[] AnalyzeFields(Type componentType)
{
bool useReflection = componentType.IsNotPublic;
var result = new List<ECSPropertyInfo>();
foreach (var field in componentType.GetFields())
{
var attr = field.GetCustomAttribute<TweakableStatAttribute>();
string propName = field.Name;
if (attr != null)
propName = attr.propertyName;
propName = char.ToUpper(propName[0]) + propName[1..];
if (useReflection)
{
result.Add(new ECSReflectedPropertyInfo
{
Name = propName,
Type = field.FieldType,
OriginalClassName = componentType.FullName,
ComponentType = componentType
});
}
result.Add(new ECSPropertyInfo
{
Name = propName,
Type = field.FieldType,
ComponentType = componentType
});
}
return result.ToArray();
}
}
}

View file

@ -1,129 +0,0 @@
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
namespace CodeGenerator
{
public static class ECSClassGenerator
{
public static void Generate(Type entityDescriptorType)
{
var info = ECSAnalyzer.AnalyzeEntityDescriptor(entityDescriptorType);
var codeUnit = new CodeCompileUnit();
var ns = new CodeNamespace("TechbloxModdingAPI.Blocks");
ns.Imports.Add(new CodeNamespaceImport("RobocraftX.Common"));
ns.Imports.Add(new CodeNamespaceImport("Svelto.ECS"));
var cl = new CodeTypeDeclaration(info.Name);
cl.BaseTypes.Add(new CodeTypeReference("SignalingBlock"));
cl.Members.Add(new CodeConstructor
{
Parameters = {new CodeParameterDeclarationExpression("EGID", "egid")},
Comments =
{
_start,
new CodeCommentStatement($"Constructs a(n) {info.Name} object representing an existing block.", true),
_end
},
BaseConstructorArgs = {new CodeVariableReferenceExpression("egid")},
Attributes = MemberAttributes.Public | MemberAttributes.Final
});
foreach (var propertyInfo in info.Properties)
{
if (propertyInfo is ECSReflectedPropertyInfo reflectedPropertyInfo)
GenerateReflectedProperty(reflectedPropertyInfo, cl);
else
GenerateProperty(propertyInfo, cl);
}
ns.Types.Add(cl);
codeUnit.Namespaces.Add(ns);
var provider = CodeDomProvider.CreateProvider("CSharp");
var path = $"../../../../TechbloxModdingAPI/Blocks/{info.Name}.cs";
using (var sw = new StreamWriter(path))
{
provider.GenerateCodeFromCompileUnit(codeUnit, sw, new CodeGeneratorOptions {BracingStyle = "C"});
}
File.WriteAllLines(path,
File.ReadAllLines(path).SkipWhile(line => line.StartsWith("//") || line.Length == 0));
}
private static void GenerateProperty(ECSPropertyInfo info, CodeTypeDeclaration cl)
{
var getStruct = new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"),
"GetBlockInfo", new CodeTypeReference(info.ComponentType)),
new CodeThisReferenceExpression());
CodeExpression structFieldReference = new CodeFieldReferenceExpression(getStruct, info.Name);
cl.Members.Add(new CodeMemberProperty
{
Name = info.Name,
HasGet = true,
HasSet = true,
GetStatements =
{
new CodeMethodReturnStatement(structFieldReference)
},
SetStatements =
{
new CodeAssignStatement(structFieldReference, new CodePropertySetValueReferenceExpression())
},
Type = new CodeTypeReference(info.Type),
Attributes = MemberAttributes.Public | MemberAttributes.Final,
Comments =
{
_start,
new CodeCommentStatement($"Gets or sets the {info.ComponentType.Name}'s {info.Name} property." +
" May not be saved.", // TODO: Doesn't know if tweakable stat
true),
_end
}
});
}
private static void GenerateReflectedProperty(ECSReflectedPropertyInfo info, CodeTypeDeclaration cl)
{
var reflectedType = new CodeSnippetExpression($"HarmonyLib.AccessTools.TypeByName(\"{info.OriginalClassName}\")");
CodeExpression reflectedGet = new CodeCastExpression(info.Type, new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"),
"GetBlockInfo"),
new CodeThisReferenceExpression(), reflectedType, new CodePrimitiveExpression(info.Name)));
CodeExpression reflectedSet = new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"),
"SetBlockInfo"),
new CodeThisReferenceExpression(), reflectedType, new CodePrimitiveExpression(info.Name),
new CodePropertySetValueReferenceExpression());
cl.Members.Add(new CodeMemberProperty
{
Name = info.Name,
HasGet = true,
HasSet = true,
GetStatements =
{
new CodeMethodReturnStatement(reflectedGet)
},
SetStatements =
{
(CodeStatement)new CodeExpressionStatement(reflectedSet)
},
Type = new CodeTypeReference(info.Type),
Attributes = MemberAttributes.Public | MemberAttributes.Final,
Comments =
{
_start,
new CodeCommentStatement($"Gets or sets the {info.ComponentType.Name}'s {info.Name} property." +
" May not be saved.", // TODO: Doesn't know if tweakable stat
true),
_end
}
});
}
private static readonly CodeCommentStatement _start = new("<summary>", true);
private static readonly CodeCommentStatement _end = new("</summary>", true);
}
}

View file

@ -1,10 +0,0 @@
using System;
namespace CodeGenerator
{
public class ECSClassInfo
{
public string Name { get; set; }
public ECSPropertyInfo[] Properties { get; set; }
}
}

View file

@ -1,11 +0,0 @@
using System;
namespace CodeGenerator
{
public class ECSPropertyInfo
{
public string Name { get; set; }
public Type Type { get; set; }
public Type ComponentType { get; set; }
}
}

View file

@ -1,7 +0,0 @@
namespace CodeGenerator
{
public class ECSReflectedPropertyInfo : ECSPropertyInfo
{
public string OriginalClassName { get; set; }
}
}

View file

@ -15,8 +15,7 @@ namespace CodeGenerator
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
//GenerateBlockClasses(); GenerateBlockClasses();
ECSClassGenerator.Generate(typeof(EngineBlockEntityDescriptor));
} }
private static void GenerateBlockClasses() private static void GenerateBlockClasses()

View file

@ -20,8 +20,8 @@ namespace TechbloxModdingAPI.App
Game.Exit += Assert.CallsBack<GameEventArgs>("GameExit"); Game.Exit += Assert.CallsBack<GameEventArgs>("GameExit");
Game.Simulate += Assert.CallsBack<GameEventArgs>("GameSimulate"); Game.Simulate += Assert.CallsBack<GameEventArgs>("GameSimulate");
Game.Edit += Assert.CallsBack<GameEventArgs>("GameEdit"); Game.Edit += Assert.CallsBack<GameEventArgs>("GameEdit");
/*Client.EnterMenu += Assert.CallsBack<MenuEventArgs>("MenuEnter"); Client.EnterMenu += Assert.CallsBack<MenuEventArgs>("MenuEnter");
Client.ExitMenu += Assert.CallsBack<MenuEventArgs>("MenuExit");*/ Client.ExitMenu += Assert.CallsBack<MenuEventArgs>("MenuExit");
} }
[APITestCase(TestType.Game)] [APITestCase(TestType.Game)]

View file

@ -0,0 +1,171 @@
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 = () => (object) getterSimple();
return getterCasted;
}
private static Action<object, Error> GenEnqueueError<T, TRes>()
{
Type errorHandler = AccessTools.TypeByName("RobocraftX.Services.ErrorHandler");
MethodInfo enqueueError = AccessTools.Method(errorHandler, "EnqueueError");
Func<T, Error, TRes> enqueueSimple =
(Func<T, Error, TRes>) Delegate.CreateDelegate(typeof(Func<T, Error, TRes>), enqueueError);
Action<object, Error> enqueueCasted =
(object instance, Error error) => { enqueueSimple((T) instance, error); };
return enqueueCasted;
}
private static (Action 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

@ -2,7 +2,7 @@ using System;
using HarmonyLib; using HarmonyLib;
using RobocraftX.Services; using RobocraftX.Services;
using TechbloxModdingAPI.Client.App;
using TechbloxModdingAPI.Tests; using TechbloxModdingAPI.Tests;
namespace TechbloxModdingAPI.App namespace TechbloxModdingAPI.App
@ -42,25 +42,25 @@ namespace TechbloxModdingAPI.App
[APITestCase(TestType.Menu)] [APITestCase(TestType.Menu)]
public static void TestPopUp2() public static void TestPopUp2()
{ {
Popup.PromptUser(popup2); Client.Instance.PromptUser(popup2);
} }
[APITestCase(TestType.Menu)] [APITestCase(TestType.Menu)]
public static void TestPopUp1() public static void TestPopUp1()
{ {
Popup.PromptUser(popup1); Client.Instance.PromptUser(popup1);
} }
[APITestCase(TestType.Menu)] [APITestCase(TestType.Menu)]
public static void TestPopUpClose1() public static void TestPopUpClose1()
{ {
Popup.CloseCurrentPrompt(); Client.Instance.CloseCurrentPrompt();
} }
[APITestCase(TestType.Menu)] [APITestCase(TestType.Menu)]
public static void TestPopUpClose2() public static void TestPopUpClose2()
{ {
Popup.CloseCurrentPrompt(); Client.Instance.CloseCurrentPrompt();
} }
} }
#endif #endif

View file

@ -7,7 +7,6 @@ using Svelto.ECS;
using Techblox.GameSelection; using Techblox.GameSelection;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Tasks; using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
@ -21,12 +20,12 @@ namespace TechbloxModdingAPI.App
public class Game public class Game
{ {
// extensible engines // extensible engines
protected static GameGameEngine gameEngine = new(); protected static GameGameEngine gameEngine = new GameGameEngine();
protected internal static GameMenuEngine menuEngine = new(); protected internal static GameMenuEngine menuEngine = new GameMenuEngine();
protected static DebugInterfaceEngine debugOverlayEngine = new(); protected static DebugInterfaceEngine debugOverlayEngine = new DebugInterfaceEngine();
protected static GameBuildSimEventEngine buildSimEventEngine = new(); protected static GameBuildSimEventEngine buildSimEventEngine = new GameBuildSimEventEngine();
private List<string> debugIds = new(); private List<string> debugIds = new List<string>();
private bool menuMode = true; private bool menuMode = true;
private bool hasId = false; private bool hasId = false;
@ -482,10 +481,10 @@ namespace TechbloxModdingAPI.App
internal static void Init() internal static void Init()
{ {
EngineManager.AddEngine(gameEngine, ApiEngineType.Build, ApiEngineType.PlayClient, ApiEngineType.PlayServer); GameEngineManager.AddGameEngine(gameEngine);
EngineManager.AddEngine(debugOverlayEngine, ApiEngineType.Build, ApiEngineType.PlayClient, ApiEngineType.PlayServer); GameEngineManager.AddGameEngine(debugOverlayEngine);
EngineManager.AddEngine(buildSimEventEngine, ApiEngineType.Build, ApiEngineType.PlayClient, ApiEngineType.PlayServer); GameEngineManager.AddGameEngine(buildSimEventEngine);
EngineManager.AddEngine(menuEngine, ApiEngineType.Menu); MenuEngineManager.AddMenuEngine(menuEngine);
} }
} }
} }

View file

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

View file

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

View file

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

View file

@ -8,19 +8,14 @@ using Svelto.ECS.EntityStructs;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.Blocks; using RobocraftX.Blocks;
using Unity.Mathematics; using Unity.Mathematics;
using HarmonyLib;
using RobocraftX.PilotSeat; using RobocraftX.PilotSeat;
using RobocraftX.Rendering; using RobocraftX.Rendering;
using Techblox.BlockLabelsServer; using Techblox.BlockLabelsServer;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Blocks.Engines; using TechbloxModdingAPI.Blocks.Engines;
using TechbloxModdingAPI.Client.App;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Common.Traits;
using TechbloxModdingAPI.Tests; using TechbloxModdingAPI.Tests;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using Unity.Transforms;
using UnityEngine;
namespace TechbloxModdingAPI namespace TechbloxModdingAPI
{ {
@ -28,16 +23,18 @@ namespace TechbloxModdingAPI
/// A single (perhaps scaled) block. Properties may return default values if the block is removed and then setting them is ignored. /// A single (perhaps scaled) block. Properties may return default values if the block is removed and then setting them is ignored.
/// For specific block type operations, use the specialised block classes in the TechbloxModdingAPI.Blocks namespace. /// For specific block type operations, use the specialised block classes in the TechbloxModdingAPI.Blocks namespace.
/// </summary> /// </summary>
public class Block : EcsObjectBase<BlockEntityDescriptor>, IHasPhysics, IEquatable<Block>, IEquatable<EGID> public class Block : EcsObjectBase, IEquatable<Block>, IEquatable<EGID>
{ {
protected static readonly PlacementEngine PlacementEngine = new(); protected static readonly PlacementEngine PlacementEngine = new PlacementEngine();
protected static readonly RemovalEngine RemovalEngine = new(); protected static readonly MovementEngine MovementEngine = new MovementEngine();
protected static readonly SignalEngine SignalEngine = new(); protected static readonly RotationEngine RotationEngine = new RotationEngine();
protected static readonly BlockEventsEngine BlockEventsEngine = new(); protected static readonly RemovalEngine RemovalEngine = new RemovalEngine();
protected static readonly ScalingEngine ScalingEngine = new(); protected static readonly SignalEngine SignalEngine = new SignalEngine();
protected static readonly BlockCloneEngine BlockCloneEngine = new(); protected static readonly BlockEventsEngine BlockEventsEngine = new BlockEventsEngine();
protected static readonly ScalingEngine ScalingEngine = new ScalingEngine();
protected static readonly BlockCloneEngine BlockCloneEngine = new BlockCloneEngine();
protected internal static readonly BlockEngine BlockEngine = new(); protected internal static readonly BlockEngine BlockEngine = new BlockEngine();
/// <summary> /// <summary>
/// Place a new block at the given position. If scaled, position means the center of the block. The default block size is 0.2 in terms of position. /// Place a new block at the given position. If scaled, position means the center of the block. The default block size is 0.2 in terms of position.
@ -51,10 +48,12 @@ namespace TechbloxModdingAPI
/// <returns>The placed block or null if failed</returns> /// <returns>The placed block or null if failed</returns>
public static Block PlaceNew(BlockIDs block, float3 position, bool autoWire = false, Player player = null) public static Block PlaceNew(BlockIDs block, float3 position, bool autoWire = false, Player player = null)
{ {
if (PlacementEngine.IsInGame && GameClient.IsBuildMode) if (PlacementEngine.IsInGame && GameState.IsBuildMode())
{ {
var initializer = PlacementEngine.PlaceBlock(block, position, player, autoWire); var initializer = PlacementEngine.PlaceBlock(block, position, player, autoWire);
var bl = New(initializer); var egid = initializer.EGID;
var bl = New(egid);
bl.InitData = initializer;
Placed += bl.OnPlacedInit; Placed += bl.OnPlacedInit;
return bl; return bl;
} }
@ -97,7 +96,7 @@ namespace TechbloxModdingAPI
} }
private static readonly Dictionary<ExclusiveBuildGroup, (Func<EGID, Block> Constructor, Type Type)> GroupToConstructor = private static readonly Dictionary<ExclusiveBuildGroup, (Func<EGID, Block> Constructor, Type Type)> GroupToConstructor =
new() new Dictionary<ExclusiveBuildGroup, (Func<EGID, Block>, Type)>
{ {
{CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP, (id => new DampedSpring(id), typeof(DampedSpring))}, {CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP, (id => new DampedSpring(id), typeof(DampedSpring))},
{CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP, (id => new Engine(id), typeof(Engine))}, {CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP, (id => new Engine(id), typeof(Engine))},
@ -121,28 +120,17 @@ namespace TechbloxModdingAPI
/// <param name="signaling">Whether the block is definitely a signaling block</param> /// <param name="signaling">Whether the block is definitely a signaling block</param>
/// <returns></returns> /// <returns></returns>
internal static Block New(EGID egid, bool signaling = false) internal static Block New(EGID egid, bool signaling = false)
{
return New(egid, default, signaling);
}
private static Block New(EcsInitData initData, bool signaling = false)
{
return New(initData.EGID, initData, signaling);
}
private static Block New(EGID egid, EcsInitData initData, bool signaling)
{ {
if (egid == default) return null; if (egid == default) return null;
Func<EGID, Block> constructor; if (GroupToConstructor.ContainsKey(egid.groupID))
Type type = null; {
if (GroupToConstructor.TryGetValue(egid.groupID, out var value)) var (constructor, type) = GroupToConstructor[egid.groupID];
(constructor, type) = value; return GetInstance(egid, constructor, type);
else }
constructor = signaling ? e => new SignalingBlock(e) : e => new Block(e);
return initData != default return signaling
? GetInstanceNew(initData, constructor, type) ? GetInstance(egid, e => new SignalingBlock(e))
: GetInstanceExisting(egid, constructor, type); : GetInstance(egid, e => new Block(e));
} }
public Block(EGID id) : base(id) public Block(EGID id) : base(id)
@ -165,6 +153,26 @@ namespace TechbloxModdingAPI
{ {
} }
/// <summary>
/// Places a new block in the world.
/// </summary>
/// <param name="type">The block's type</param>
/// <param name="position">The block's position (a block is 0.2 wide in terms of position)</param>
/// <param name="autoWire">Whether the block should be auto-wired (if functional)</param>
/// <param name="player">The player who placed the block</param>
public Block(BlockIDs type, float3 position, bool autoWire = false, Player player = null)
: base(block =>
{
if (!PlacementEngine.IsInGame || !GameState.IsBuildMode())
throw new BlockException("Blocks can only be placed in build mode.");
var initializer = PlacementEngine.PlaceBlock(type, position, player, autoWire);
block.InitData = initializer;
Placed += ((Block)block).OnPlacedInit;
return initializer.EGID;
})
{
}
private EGID copiedFrom; private EGID copiedFrom;
/// <summary> /// <summary>
@ -173,21 +181,13 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public float3 Position public float3 Position
{ {
get => GetComponent<PositionEntityStruct>().position; get => MovementEngine.GetPosition(this);
set set
{ {
// main (persistent) position MovementEngine.MoveBlock(this, value);
GetComponent<PositionEntityStruct>().position = value;
// placement grid position
GetComponent<GridRotationStruct>().position = value;
// rendered position
GetComponent<LocalTransformEntityStruct>().position = value;
this.UpdatePhysicsUECSComponent(new Translation { Value = value });
GetComponent<GridConnectionsEntityStruct>().areConnectionsAssigned = false;
if (blockGroup != null) if (blockGroup != null)
blockGroup.PosAndRotCalculated = false; blockGroup.PosAndRotCalculated = false;
BlockEngine.UpdateDisplayData(Id); BlockEngine.UpdateDisplayedBlock(Id);
} }
} }
@ -196,24 +196,13 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public float3 Rotation public float3 Rotation
{ {
get => ((Quaternion)GetComponent<RotationEntityStruct>().rotation).eulerAngles; get => RotationEngine.GetRotation(this);
set set
{ {
// main (persistent) rotation RotationEngine.RotateBlock(this, value);
Quaternion newRotation = GetComponent<RotationEntityStruct>().rotation;
newRotation.eulerAngles = value;
GetComponent<RotationEntityStruct>().rotation = newRotation;
// placement grid rotation
GetComponent<GridRotationStruct>().rotation = newRotation;
// rendered rotation
GetComponent<LocalTransformEntityStruct>().rotation = newRotation;
this.UpdatePhysicsUECSComponent(new Rotation { Value = newRotation });
// They are assigned during machine processing anyway
GetComponent<GridConnectionsEntityStruct>().areConnectionsAssigned = false;
if (blockGroup != null) if (blockGroup != null)
blockGroup.PosAndRotCalculated = false; blockGroup.PosAndRotCalculated = false;
BlockEngine.UpdateDisplayData(Id); BlockEngine.UpdateDisplayedBlock(Id);
} }
} }
@ -223,19 +212,18 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public float3 Scale public float3 Scale
{ {
get => GetComponent<ScalingEntityStruct>().scale; get => BlockEngine.GetBlockInfo<ScalingEntityStruct>(this).scale;
set set
{ {
int uscale = UniformScale; int uscale = UniformScale;
if (value.x < 4e-5) value.x = uscale; if (value.x < 4e-5) value.x = uscale;
if (value.y < 4e-5) value.y = uscale; if (value.y < 4e-5) value.y = uscale;
if (value.z < 4e-5) value.z = uscale; if (value.z < 4e-5) value.z = uscale;
GetComponent<ScalingEntityStruct>().scale = value; BlockEngine.GetBlockInfo<ScalingEntityStruct>(this).scale = value;
GetComponent<GridConnectionsEntityStruct>().areConnectionsAssigned = false;
//BlockEngine.GetBlockInfo<GridScaleStruct>(this).gridScale = value - (int3) value + 1; //BlockEngine.GetBlockInfo<GridScaleStruct>(this).gridScale = value - (int3) value + 1;
if (!Exists) return; //UpdateCollision needs the block to exist if (!Exists) return; //UpdateCollision needs the block to exist
ScalingEngine.UpdateCollision(Id); ScalingEngine.UpdateCollision(Id);
BlockEngine.UpdateDisplayData(Id); BlockEngine.UpdateDisplayedBlock(Id);
} }
} }
@ -245,12 +233,11 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public int UniformScale public int UniformScale
{ {
get => GetComponent<UniformBlockScaleEntityStruct>().scaleFactor; get => BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(this).scaleFactor;
set set
{ {
//It appears that only the non-uniform scale has any visible effect so we'll set that as well
if (value < 1) value = 1; if (value < 1) value = 1;
GetComponent<UniformBlockScaleEntityStruct>().scaleFactor = value; BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(this).scaleFactor = value;
Scale = new float3(value, value, value); Scale = new float3(value, value, value);
} }
} }
@ -260,12 +247,12 @@ namespace TechbloxModdingAPI
*/ */
public bool Flipped public bool Flipped
{ {
get => GetComponent<ScalingEntityStruct>().scale.x < 0; get => BlockEngine.GetBlockInfo<ScalingEntityStruct>(this).scale.x < 0;
set set
{ {
ref var st = ref GetComponent<ScalingEntityStruct>(); ref var st = ref BlockEngine.GetBlockInfo<ScalingEntityStruct>(this);
st.scale.x = math.abs(st.scale.x) * (value ? -1 : 1); st.scale.x = math.abs(st.scale.x) * (value ? -1 : 1);
BlockEngine.UpdateDisplayedPrefab(this, (byte) Material, value); BlockEngine.UpdatePrefab(this, (byte) Material, value);
} }
} }
@ -276,7 +263,7 @@ namespace TechbloxModdingAPI
{ {
get get
{ {
var opt = GetComponentOptional<DBEntityStruct>(); var opt = BlockEngine.GetBlockInfoOptional<DBEntityStruct>(this);
return opt ? (BlockIDs) opt.Get().DBID : BlockIDs.Invalid; return opt ? (BlockIDs) opt.Get().DBID : BlockIDs.Invalid;
} }
} }
@ -288,17 +275,16 @@ namespace TechbloxModdingAPI
{ {
get get
{ {
var opt = GetComponentOptional<ColourParameterEntityStruct>(); var opt = BlockEngine.GetBlockInfoOptional<ColourParameterEntityStruct>(this);
return new BlockColor(opt ? opt.Get().indexInPalette : byte.MaxValue); return new BlockColor(opt ? opt.Get().indexInPalette : byte.MaxValue);
} }
set set
{ {
// TODO: Expose CubeListData in the API
if (value.Color == BlockColors.Default) if (value.Color == BlockColors.Default)
value = new BlockColor(FullGameFields._dataDb.TryGetValue((int) Type, out CubeListData cld) value = new BlockColor(FullGameFields._dataDb.TryGetValue((int) Type, out CubeListData cld)
? cld.DefaultColour ? cld.DefaultColour
: throw new BlockTypeException("Unknown block type! Could not set default color.")); : throw new BlockTypeException("Unknown block type! Could not set default color."));
ref var color = ref GetComponent<ColourParameterEntityStruct>(); ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(this);
color.indexInPalette = value.Index; color.indexInPalette = value.Index;
color.hasNetworkChange = true; color.hasNetworkChange = true;
color.paletteColour = BlockEngine.ConvertBlockColor(color.indexInPalette); //Setting to 255 results in black color.paletteColour = BlockEngine.ConvertBlockColor(color.indexInPalette); //Setting to 255 results in black
@ -311,10 +297,10 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public float4 CustomColor public float4 CustomColor
{ {
get => GetComponent<ColourParameterEntityStruct>().paletteColour; get => BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(this).paletteColour;
set set
{ {
ref var color = ref GetComponent<ColourParameterEntityStruct>(); ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(this);
color.paletteColour = value; color.paletteColour = value;
color.hasNetworkChange = true; color.hasNetworkChange = true;
} }
@ -327,7 +313,7 @@ namespace TechbloxModdingAPI
{ {
get get
{ {
var opt = GetComponentOptional<CubeMaterialStruct>(); var opt = BlockEngine.GetBlockInfoOptional<CubeMaterialStruct>(this);
return opt ? (BlockMaterial) opt.Get().materialId : BlockMaterial.Default; return opt ? (BlockMaterial) opt.Get().materialId : BlockMaterial.Default;
} }
set set
@ -339,11 +325,11 @@ namespace TechbloxModdingAPI
: throw new BlockTypeException("Unknown block type! Could not set default material."); : throw new BlockTypeException("Unknown block type! Could not set default material.");
if (!FullGameFields._dataDb.ContainsKey<MaterialPropertiesData>(val)) if (!FullGameFields._dataDb.ContainsKey<MaterialPropertiesData>(val))
throw new BlockException($"Block material {value} does not exist!"); throw new BlockException($"Block material {value} does not exist!");
ref var comp = ref GetComponent<CubeMaterialStruct>(); ref var comp = ref BlockEngine.GetBlockInfo<CubeMaterialStruct>(this);
if (comp.materialId == val) if (comp.materialId == val)
return; return;
comp.materialId = val; comp.materialId = val;
BlockEngine.UpdateDisplayedPrefab(this, val, Flipped); //The default causes the screen to go black BlockEngine.UpdatePrefab(this, val, Flipped); //The default causes the screen to go black
} }
} }
@ -356,12 +342,12 @@ namespace TechbloxModdingAPI
{ {
get get
{ {
var opt = GetComponentOptional<LabelResourceIDComponent>(); var opt = BlockEngine.GetBlockInfoOptional<LabelResourceIDComponent>(this);
return opt ? FullGameFields._managers.blockLabelResourceManager.GetText(opt.Get().instanceID) : null; return opt ? FullGameFields._managers.blockLabelResourceManager.GetText(opt.Get().instanceID) : null;
} }
set set
{ {
var opt = GetComponentOptional<LabelResourceIDComponent>(); var opt = BlockEngine.GetBlockInfoOptional<LabelResourceIDComponent>(this);
if (opt) FullGameFields._managers.blockLabelResourceManager.SetText(opt.Get().instanceID, value); if (opt) FullGameFields._managers.blockLabelResourceManager.SetText(opt.Get().instanceID, value);
} }
} }
@ -380,11 +366,11 @@ namespace TechbloxModdingAPI
get get
{ {
if (blockGroup != null) return blockGroup; if (blockGroup != null) return blockGroup;
if (!GameClient.IsBuildMode) return null; // Breaks in simulation if (!GameState.IsBuildMode()) return null; // Breaks in simulation
var bgec = GetComponent<BlockGroupEntityComponent>(); var bgec = BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this);
return blockGroup = bgec.currentBlockGroup == -1 return blockGroup = bgec.currentBlockGroup == -1
? null ? null
: GetInstanceExisting(new EGID((uint)bgec.currentBlockGroup, BlockGroupExclusiveGroups.BlockGroupEntityGroup), : GetInstance(new EGID((uint)bgec.currentBlockGroup, BlockGroupExclusiveGroups.BlockGroupEntityGroup),
egid => new BlockGroup((int)egid.entityID, this)); egid => new BlockGroup((int)egid.entityID, this));
} }
set set
@ -398,7 +384,7 @@ namespace TechbloxModdingAPI
blockGroup?.RemoveInternal(this); blockGroup?.RemoveInternal(this);
if (!InitData.Valid) if (!InitData.Valid)
return; return;
GetComponent<BlockGroupEntityComponent>().currentBlockGroup = (int?) value?.Id.entityID ?? -1; BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this).currentBlockGroup = (int?) value?.Id.entityID ?? -1;
value?.AddInternal(this); value?.AddInternal(this);
blockGroup = value; blockGroup = value;
} }
@ -409,8 +395,8 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public bool Static public bool Static
{ {
get => GetComponent<BlockStaticComponent>().isStatic; get => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic;
set => GetComponent<BlockStaticComponent>().isStatic = value; set => BlockEngine.GetBlockInfo<BlockStaticComponent>(this).isStatic = value;
} }
/// <summary> /// <summary>
@ -418,7 +404,7 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public float Mass public float Mass
{ {
get => GetComponent<MassStruct>().mass; get => BlockEngine.GetBlockInfo<MassStruct>(this).mass;
} }
/// <summary> /// <summary>
@ -426,10 +412,16 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public BlockComplexity Complexity public BlockComplexity Complexity
{ {
get => new(GetComponent<BlockComplexityComponent>()); get => new(BlockEngine.GetBlockInfo<BlockComplexityComponent>(this));
set => GetComponent<BlockComplexityComponent>() = value; set => BlockEngine.GetBlockInfo<BlockComplexityComponent>(this) = value;
} }
/// <summary>
/// Whether the block exists. The other properties will return a default value if the block doesn't exist.
/// If the block was just placed, then this will also return false but the properties will work correctly.
/// </summary>
public bool Exists => BlockEngine.BlockExists(Id);
/// <summary> /// <summary>
/// Returns an array of blocks that are connected to this one. Returns an empty array if the block doesn't exist. /// Returns an array of blocks that are connected to this one. Returns an empty array if the block doesn't exist.
/// </summary> /// </summary>
@ -448,7 +440,7 @@ namespace TechbloxModdingAPI
/// <returns>The SimBody of the chunk or null if the block doesn't exist or not in simulation mode.</returns> /// <returns>The SimBody of the chunk or null if the block doesn't exist or not in simulation mode.</returns>
public SimBody GetSimBody() public SimBody GetSimBody()
{ {
var st = GetComponent<GridConnectionsEntityStruct>(); var st = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(this);
/*return st.machineRigidBodyId != uint.MaxValue /*return st.machineRigidBodyId != uint.MaxValue
? new SimBody(st.machineRigidBodyId, st.clusterId) - TODO: ? new SimBody(st.machineRigidBodyId, st.clusterId) - TODO:
: null;*/ : null;*/
@ -475,6 +467,7 @@ namespace TechbloxModdingAPI
{ //Member method instead of lambda to avoid constantly creating delegates { //Member method instead of lambda to avoid constantly creating delegates
if (e.ID != Id) return; if (e.ID != Id) return;
Placed -= OnPlacedInit; //And we can reference it Placed -= OnPlacedInit; //And we can reference it
InitData = default; //Remove initializer as it's no longer valid - if the block gets removed it shouldn't be used again
if (copiedFrom != default) if (copiedFrom != default)
BlockCloneEngine.CopyBlockStats(copiedFrom, Id); BlockCloneEngine.CopyBlockStats(copiedFrom, Id);
} }
@ -511,13 +504,15 @@ namespace TechbloxModdingAPI
public static void Init() public static void Init()
{ {
EngineManager.AddEngine(PlacementEngine, ApiEngineType.Build); GameEngineManager.AddGameEngine(PlacementEngine);
EngineManager.AddEngine(RemovalEngine, ApiEngineType.Build); GameEngineManager.AddGameEngine(MovementEngine);
EngineManager.AddEngine(BlockEngine, ApiEngineType.Build, ApiEngineType.PlayServer, ApiEngineType.PlayClient); GameEngineManager.AddGameEngine(RotationEngine);
EngineManager.AddEngine(BlockEventsEngine, ApiEngineType.Build); GameEngineManager.AddGameEngine(RemovalEngine);
EngineManager.AddEngine(ScalingEngine, ApiEngineType.Build); GameEngineManager.AddGameEngine(BlockEngine);
EngineManager.AddEngine(SignalEngine, ApiEngineType.Build); GameEngineManager.AddGameEngine(BlockEventsEngine);
EngineManager.AddEngine(BlockCloneEngine, ApiEngineType.Build); GameEngineManager.AddGameEngine(ScalingEngine);
GameEngineManager.AddGameEngine(SignalEngine);
GameEngineManager.AddGameEngine(BlockCloneEngine);
Wire.signalEngine = SignalEngine; // requires same functionality, no need to duplicate the engine Wire.signalEngine = SignalEngine; // requires same functionality, no need to duplicate the engine
} }
} }

View file

@ -9,8 +9,7 @@ using UnityEngine;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Blocks.Engines; using TechbloxModdingAPI.Blocks.Engines;
using TechbloxModdingAPI.Common; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Common.Engines;
namespace TechbloxModdingAPI namespace TechbloxModdingAPI
{ {
@ -19,14 +18,14 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable public class BlockGroup : EcsObjectBase, ICollection<Block>, IDisposable
{ {
internal static BlueprintEngine _engine = new(); internal static BlueprintEngine _engine = new BlueprintEngine();
private readonly Block sourceBlock; private readonly Block sourceBlock;
private readonly List<Block> blocks; private readonly List<Block> blocks;
private float3 position, rotation; private float3 position, rotation;
internal bool PosAndRotCalculated; internal bool PosAndRotCalculated;
internal BlockGroup(int id, Block block) : base(new EGID((uint)id, internal BlockGroup(int id, Block block) : base(new EGID((uint)id,
BlockGroupExclusiveGroups.BlockGroupEntityGroup), typeof(BlockGroupEntityDescriptor)) BlockGroupExclusiveGroups.BlockGroupEntityGroup))
{ {
if (id == BlockGroupUtility.GROUP_UNASSIGNED) if (id == BlockGroupUtility.GROUP_UNASSIGNED)
throw new BlockException("Cannot create a block group for blocks without a group!"); throw new BlockException("Cannot create a block group for blocks without a group!");
@ -150,7 +149,7 @@ namespace TechbloxModdingAPI
internal static void Init() internal static void Init()
{ {
EngineManager.AddEngine(_engine, ApiEngineType.Build); GameEngineManager.AddGameEngine(_engine);
} }
public IEnumerator<Block> GetEnumerator() => blocks.GetEnumerator(); public IEnumerator<Block> GetEnumerator() => blocks.GetEnumerator();

View file

@ -2,7 +2,7 @@ using RobocraftX.Blocks;
namespace TechbloxModdingAPI.Blocks namespace TechbloxModdingAPI.Blocks
{ {
public readonly record struct BlockComplexity(int Cpu, int Power) public record BlockComplexity(int Cpu, int Power)
{ {
public BlockComplexity(BlockComplexityComponent component) : this(component.cpu, component.power) public BlockComplexity(BlockComplexityComponent component) : this(component.cpu, component.power)
{ {

View file

@ -23,18 +23,18 @@ namespace TechbloxModdingAPI.Blocks
{ {
} }
/// <summary> /*/// <summary> - TODO: Internal struct access
/// Gets or sets the Engine's On property. May not be saved. /// Gets or sets the Engine's On property. May not be saved.
/// </summary> /// </summary>
public bool On public bool On
{ {
get get
{ {
return ((bool)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "engineOn"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).engineOn;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "engineOn", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).engineOn = value;
} }
} }
@ -45,11 +45,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((int)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "currentGear"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentGear;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "currentGear", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentGear = value;
} }
} }
@ -60,11 +60,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((float)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "gearChangeCountdown"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).gearChangeCountdown;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "gearChangeCountdown", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).gearChangeCountdown = value;
} }
} }
@ -75,11 +75,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((float)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "currentRpmAV"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmAV;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "currentRpmAV", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmAV = value;
} }
} }
@ -90,11 +90,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((float)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "currentRpmLV"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmLV;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "currentRpmLV", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentRpmLV = value;
} }
} }
@ -105,11 +105,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((float)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "targetRpmAV"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmAV;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "targetRpmAV", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmAV = value;
} }
} }
@ -120,11 +120,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((float)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "targetRpmLV"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmLV;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "targetRpmLV", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).targetRpmLV = value;
} }
} }
@ -135,11 +135,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((float)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "currentTorque"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentTorque;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "currentTorque", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).currentTorque = value;
} }
} }
@ -150,11 +150,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((float)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "totalWheelVelocityAV"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityAV;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "totalWheelVelocityAV", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityAV = value;
} }
} }
@ -165,11 +165,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((float)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "totalWheelVelocityLV"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityLV;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "totalWheelVelocityLV", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelVelocityLV = value;
} }
} }
@ -180,11 +180,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((int)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "totalWheelCount"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelCount;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "totalWheelCount", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).totalWheelCount = value;
} }
} }
@ -195,11 +195,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((bool)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "lastGearUpInput"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearUpInput;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "lastGearUpInput", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearUpInput = value;
} }
} }
@ -210,11 +210,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((bool)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "lastGearDownInput"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearDownInput;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "lastGearDownInput", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).lastGearDownInput = value;
} }
} }
@ -225,11 +225,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((float)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "manualToAutoGearCoolOffCounter"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).manualToAutoGearCoolOffCounter;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "manualToAutoGearCoolOffCounter", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).manualToAutoGearCoolOffCounter = value;
} }
} }
@ -240,11 +240,11 @@ namespace TechbloxModdingAPI.Blocks
{ {
get get
{ {
return ((float)(BlockEngine.GetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "load"))); return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).load;
} }
set set
{ {
BlockEngine.SetBlockInfo(this, HarmonyLib.AccessTools.TypeByName("Techblox.EngineBlock.EngineBlockComponent"), "load", value); BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockComponent>(this).load = value;
} }
} }
@ -324,17 +324,13 @@ namespace TechbloxModdingAPI.Blocks
} }
/// <summary> /// <summary>
/// Gets or sets the Engine's GearDownRpms property. May not be saved. /// Gets the Engine's GearDownRpms property. May not be saved.
/// </summary> /// </summary>
public Svelto.ECS.DataStructures.NativeDynamicArray GearDownRpms public float[] GearDownRpms
{ {
get get
{ {
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearDownRpms; return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearDownRpms.ToManagedArray<float>();
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).gearDownRpms = value;
} }
} }
@ -381,21 +377,6 @@ namespace TechbloxModdingAPI.Blocks
{ {
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime = value; BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime = value;
} }
} }*/
/// <summary>
/// Gets or sets the Engine's EngineBlockDataId property. May not be saved.
/// </summary>
public int EngineBlockDataId
{
get
{
return BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).engineBlockDataId;
}
set
{
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).engineBlockDataId = value;
}
}
} }
} }

View file

@ -5,9 +5,10 @@ using Gamecraft.Wires;
using HarmonyLib; using HarmonyLib;
using RobocraftX.Blocks; using RobocraftX.Blocks;
using RobocraftX.Character; using RobocraftX.Character;
using RobocraftX.Common;
using Svelto.DataStructures; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Common.Engines; using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {
@ -97,5 +98,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
}; };
} }
} }
public string Name { get; } = "TechbloxModdingAPIBlockCloneGameEngine";
public bool isRemovable { get; } = false;
} }
} }

View file

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using HarmonyLib;
using Gamecraft.ColourPalette; using Gamecraft.ColourPalette;
using Gamecraft.Wires; using Gamecraft.Wires;
using RobocraftX.Blocks; using RobocraftX.Blocks;
@ -12,9 +14,13 @@ using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.EntityStructs; using Svelto.ECS.EntityStructs;
using Svelto.ECS.Experimental; using Svelto.ECS.Experimental;
using Svelto.ECS.Hybrid;
using Techblox.BuildingDrone;
using Techblox.ObjectIDBlockServer; using Techblox.ObjectIDBlockServer;
using TechbloxModdingAPI.Common.Engines;
using Unity.Mathematics; using Unity.Mathematics;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS; using TechbloxModdingAPI.Utility.ECS;
using PrefabsID = RobocraftX.Common.PrefabsID; using PrefabsID = RobocraftX.Common.PrefabsID;
@ -68,7 +74,50 @@ namespace TechbloxModdingAPI.Blocks.Engines
: entitiesDB.QueryEntity<PaletteEntryEntityStruct>(index, : entitiesDB.QueryEntity<PaletteEntryEntityStruct>(index,
ColourPaletteExclusiveGroups.COLOUR_PALETTE_GROUP).Colour; ColourPaletteExclusiveGroups.COLOUR_PALETTE_GROUP).Colour;
public void UpdateDisplayData(EGID id) public OptionalRef<T> GetBlockInfoOptional<T>(Block block) where T : unmanaged, IEntityComponent
{
return entitiesDB.QueryEntityOptional<T>(block);
}
public ref T GetBlockInfo<T>(Block block) where T : unmanaged, IEntityComponent
{
#if DEBUG
if (!typeof(BlockTagEntityStruct).IsAssignableFrom(typeof(T)) && block.Exists && block.InitData.Valid)
throw new ArgumentException("The block exists but the init data has not been removed!");
#endif
return ref entitiesDB.QueryEntityOrDefault<T>(block);
}
internal ref T GetBlockInfo<T>(EcsObjectBase obj) where T : unmanaged, IEntityComponent
{
return ref entitiesDB.QueryEntityOrDefault<T>(obj);
}
public ref T GetBlockInfoViewComponent<T>(Block block) where T : struct, IEntityViewComponent
{
return ref entitiesDB.QueryEntityOrDefault<T>(block);
}
internal object GetBlockInfo(Block block, Type type, string name)
{
var opt = AccessTools.Method(typeof(NativeApiExtensions), "QueryEntityOptional",
new[] { typeof(EntitiesDB), typeof(EcsObjectBase), typeof(ExclusiveGroupStruct) }, new[] { type })
.Invoke(null, new object[] { entitiesDB, block, null });
var str = AccessTools.Property(opt.GetType(), "Value").GetValue(opt);
return AccessTools.Field(str.GetType(), name).GetValue(str);
}
internal void SetBlockInfo(Block block, Type type, string name, object value)
{
var opt = AccessTools.Method(typeof(BlockEngine), "GetBlockInfoOptional", generics: new[] { type })
.Invoke(this, new object[] { block });
var prop = AccessTools.Property(opt.GetType(), "Value");
var str = prop.GetValue(opt);
AccessTools.Field(str.GetType(), name).SetValue(str, value);
prop.SetValue(opt, str);
}
public void UpdateDisplayedBlock(EGID id)
{ {
if (!BlockExists(id)) return; if (!BlockExists(id)) return;
var pos = entitiesDB.QueryEntity<PositionEntityStruct>(id); var pos = entitiesDB.QueryEntity<PositionEntityStruct>(id);
@ -80,7 +129,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(id); // Signal a prefab change so it updates the render buffers entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(id); // Signal a prefab change so it updates the render buffers
} }
internal void UpdateDisplayedPrefab(Block block, byte material, bool flipped) internal void UpdatePrefab(Block block, byte material, bool flipped)
{ {
var prefabAssetIDOpt = entitiesDB.QueryEntityOptional<PrefabAssetIDComponent>(block); var prefabAssetIDOpt = entitiesDB.QueryEntityOptional<PrefabAssetIDComponent>(block);
uint prefabAssetID = prefabAssetIDOpt uint prefabAssetID = prefabAssetIDOpt
@ -101,12 +150,12 @@ namespace TechbloxModdingAPI.Blocks.Engines
entitiesDB.PublishEntityChangeDelayed<CubeMaterialStruct>(block.Id); entitiesDB.PublishEntityChangeDelayed<CubeMaterialStruct>(block.Id);
entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(block.Id); entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(block.Id);
/*ref BuildingActionComponent local = ref BuildingActionComponent local =
ref entitiesDB.QueryEntity<BuildingActionComponent>(BuildingDroneUtility ref entitiesDB.QueryEntity<BuildingActionComponent>(BuildingDroneUtility
.GetLocalBuildingDrone(entitiesDB).ToEGID(entitiesDB)); .GetLocalBuildingDrone(entitiesDB).ToEGID(entitiesDB));
local.buildAction = BuildAction.ChangeMaterial; local.buildAction = BuildAction.ChangeMaterial;
local.targetPosition = block.Position; - TODO: This probably only plays the audio local.targetPosition = block.Position;
this.entitiesDB.PublishEntityChangeDelayed<BuildingActionComponent>(local.ID);*/ this.entitiesDB.PublishEntityChangeDelayed<BuildingActionComponent>(local.ID);
} }
//Phyiscs prefab: prefabAssetID, set on block creation from the CubeListData //Phyiscs prefab: prefabAssetID, set on block creation from the CubeListData
} }

View file

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

View file

@ -0,0 +1,69 @@
using RobocraftX.Common;
using RobocraftX.DOTS;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Unity.Mathematics;
using Unity.Transforms;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines
{
/// <summary>
/// Engine which executes block movement actions
/// </summary>
public class MovementEngine : IApiEngine
{
public string Name { get; } = "TechbloxModdingAPIMovementGameEngine";
public EntitiesDB entitiesDB { set; private get; }
public bool isRemovable => false;
public bool IsInGame = false;
public void Dispose()
{
IsInGame = false;
}
public void Ready()
{
IsInGame = true;
}
// implementations for Movement static class
internal float3 MoveBlock(Block block, float3 vector)
{
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntityOrDefault<PositionEntityStruct>(block);
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntityOrDefault<GridRotationStruct>(block);
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntityOrDefault<LocalTransformEntityStruct>(block);
var phyStruct = this.entitiesDB.QueryEntityOptional<DOTSPhysicsEntityStruct>(block);
// main (persistent) position
posStruct.position = vector;
// placement grid position
gridStruct.position = vector;
// rendered position
transStruct.position = vector;
// collision position
if (phyStruct)
{ //It exists
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.Get().dotsEntity, new Translation
{
Value = posStruct.position
});
}
entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).areConnectionsAssigned = false;
return posStruct.position;
}
internal float3 GetPosition(Block block)
{
return entitiesDB.QueryEntityOrDefault<PositionEntityStruct>(block).position;
}
}
}

View file

@ -12,8 +12,9 @@ using RobocraftX.Rendering;
using RobocraftX.Rendering.GPUI; using RobocraftX.Rendering.GPUI;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.EntityStructs; using Svelto.ECS.EntityStructs;
using TechbloxModdingAPI.Common.Engines;
using Unity.Mathematics; using Unity.Mathematics;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS; using TechbloxModdingAPI.Utility.ECS;
@ -41,7 +42,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
private static IEntityFactory _entityFactory; private static IEntityFactory _entityFactory;
public EntityInitializer PlaceBlock(BlockIDs block, float3 position, Player player, bool autoWire) public EntityInitializer PlaceBlock(BlockIDs block, float3 position, Player player, bool autoWire)
{ { //It appears that only the non-uniform scale has any visible effect, but if that's not given here it will be set to the uniform one
return BuildBlock((ushort) block, position, autoWire, (player ?? Player.LocalPlayer).Id); return BuildBlock((ushort) block, position, autoWire, (player ?? Player.LocalPlayer).Id);
} }
@ -52,19 +53,15 @@ namespace TechbloxModdingAPI.Blocks.Engines
if(!FullGameFields._dataDb.ContainsKey<CubeListData>(block)) if(!FullGameFields._dataDb.ContainsKey<CubeListData>(block))
throw new BlockException("Block with ID " + block + " not found!"); throw new BlockException("Block with ID " + block + " not found!");
//RobocraftX.CR.MachineEditing.PlaceSingleBlockEngine //RobocraftX.CR.MachineEditing.PlaceSingleBlockEngine
DBEntityStruct dbEntity = new DBEntityStruct {DBID = block};
var structInitializer = _blockEntityFactory.Build(CommonExclusiveGroups.blockIDGeneratorClient.Next(), block); //The ghost block index is only used for triggers EntityInitializer structInitializer = _blockEntityFactory.Build(CommonExclusiveGroups.blockIDGeneratorClient.Next(), block); //The ghost block index is only used for triggers
// Use the default steel material for the block until it's changed and set up the graphics for it
uint prefabAssetID = structInitializer.Has<PrefabAssetIDComponent>() uint prefabAssetID = structInitializer.Has<PrefabAssetIDComponent>()
? structInitializer.Get<PrefabAssetIDComponent>().prefabAssetID ? structInitializer.Get<PrefabAssetIDComponent>().prefabAssetID
: throw new BlockException("Prefab asset ID not found!"); //Set by the game : throw new BlockException("Prefab asset ID not found!"); //Set by the game
uint prefabId = PrefabsID.GetOrAddPrefabID((ushort) prefabAssetID, (byte) BlockMaterial.SteelBodywork, 1, false); uint prefabId = PrefabsID.GetOrAddPrefabID((ushort) prefabAssetID, (byte) BlockMaterial.SteelBodywork, 1, false);
structInitializer.Init(new GFXPrefabEntityStructGPUI(prefabId)); structInitializer.Init(new GFXPrefabEntityStructGPUI(prefabId));
structInitializer.Get<CubeMaterialStruct>().materialId = (byte) BlockMaterial.SteelBodywork; structInitializer.Init(dbEntity);
// The DBID is the block's type (cube, servo, seat etc.)
structInitializer.Init(new DBEntityStruct {DBID = block});
structInitializer.Init(new PositionEntityStruct {position = position}); structInitializer.Init(new PositionEntityStruct {position = position});
structInitializer.Init(new RotationEntityStruct {rotation = quaternion.identity}); structInitializer.Init(new RotationEntityStruct {rotation = quaternion.identity});
structInitializer.Init(new ScalingEntityStruct {scale = new float3(1, 1, 1)}); structInitializer.Init(new ScalingEntityStruct {scale = new float3(1, 1, 1)});
@ -74,6 +71,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
rotation = quaternion.identity rotation = quaternion.identity
}); });
structInitializer.Init(new UniformBlockScaleEntityStruct {scaleFactor = 1}); structInitializer.Init(new UniformBlockScaleEntityStruct {scaleFactor = 1});
structInitializer.Get<CubeMaterialStruct>().materialId = (byte) BlockMaterial.SteelBodywork;
var bssesopt = entitiesDB.QueryEntityOptional<BoxSelectStateEntityStruct>(new EGID(playerId, var bssesopt = entitiesDB.QueryEntityOptional<BoxSelectStateEntityStruct>(new EGID(playerId,
BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup)); BoxSelectExclusiveGroups.BoxSelectVolumeExclusiveGroup));
if (!bssesopt) if (!bssesopt)
@ -110,6 +108,10 @@ namespace TechbloxModdingAPI.Blocks.Engines
return structInitializer; return structInitializer;
} }
public string Name => "TechbloxModdingAPIPlacementGameEngine";
public bool isRemovable => false;
[HarmonyPatch] [HarmonyPatch]
class FactoryObtainerPatch class FactoryObtainerPatch
{ {

View file

@ -9,10 +9,11 @@ using RobocraftX.StateSync;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Native; using Svelto.ECS.Native;
using Techblox.Blocks.Connections; using Techblox.Blocks.Connections;
using TechbloxModdingAPI.Common.Engines;
using Unity.Collections; using Unity.Collections;
using Unity.Jobs; using Unity.Jobs;
using Allocator = Unity.Collections.Allocator; using Allocator = Unity.Collections.Allocator;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines

View file

@ -0,0 +1,76 @@
using RobocraftX.Common;
using RobocraftX.DOTS;
using Svelto.ECS;
using Svelto.ECS.EntityStructs;
using Unity.Mathematics;
using UnityEngine;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines
{
/// <summary>
/// Engine which executes block movement actions
/// </summary>
public class RotationEngine : IApiEngine
{
public string Name { get; } = "TechbloxModdingAPIRotationGameEngine";
public EntitiesDB entitiesDB { set; private get; }
public bool isRemovable => false;
public bool IsInGame = false;
public void Dispose()
{
IsInGame = false;
}
public void Ready()
{
IsInGame = true;
}
// implementations for Rotation static class
internal float3 RotateBlock(Block block, Vector3 vector)
{
ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntityOrDefault<RotationEntityStruct>(block);
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntityOrDefault<GridRotationStruct>(block);
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntityOrDefault<LocalTransformEntityStruct>(block);
var phyStruct = this.entitiesDB.QueryEntityOptional<DOTSPhysicsEntityStruct>(block);
// main (persistent) rotation
Quaternion newRotation = rotStruct.rotation;
newRotation.eulerAngles = vector;
rotStruct.rotation = newRotation;
// placement grid rotation
gridStruct.rotation = newRotation;
// rendered rotation
transStruct.rotation = newRotation;
// collision rotation
if (phyStruct)
{ //It exists
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.Get().dotsEntity,
new Unity.Transforms.Rotation
{
Value = rotStruct.rotation
});
}
// TODO: Connections probably need to be assigned (maybe)
// They are assigned during machine processing anyway
entitiesDB.QueryEntityOrDefault<GridConnectionsEntityStruct>(block).areConnectionsAssigned = false;
return ((Quaternion)rotStruct.rotation).eulerAngles;
}
internal float3 GetRotation(Block block)
{
ref RotationEntityStruct rotStruct = ref entitiesDB.QueryEntityOrDefault<RotationEntityStruct>(block);
return ((Quaternion) rotStruct.rotation).eulerAngles;
}
}
}

View file

@ -4,8 +4,9 @@ using HarmonyLib;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.DOTS; using RobocraftX.DOTS;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Common.Engines;
using Unity.Entities; using Unity.Entities;
using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
@ -29,7 +30,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
private EntityManager _entityManager; //Unity entity manager private EntityManager _entityManager; //Unity entity manager
public void UpdateCollision(EGID egid) public void UpdateCollision(EGID egid)
{ // TODO: Move to BlockEngine/IHasPhysics {
if (_entityManager == default) if (_entityManager == default)
_entityManager = FullGameFields._physicsWorld.EntityManager; _entityManager = FullGameFields._physicsWorld.EntityManager;
//Assuming the block exists //Assuming the block exists

View file

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

View file

@ -1,7 +1,11 @@
using System;
using Gamecraft.Wires; using Gamecraft.Wires;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Experimental;
using TechbloxModdingAPI.Blocks.Engines; using TechbloxModdingAPI.Blocks.Engines;
using TechbloxModdingAPI.Common; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks namespace TechbloxModdingAPI.Blocks
{ {
@ -65,6 +69,45 @@ namespace TechbloxModdingAPI.Blocks
: null; : null;
} }
/// <summary>
/// Construct a wire object froam n existing connection.
/// </summary>
/// <param name="start">Starting block ID.</param>
/// <param name="end">Ending block ID.</param>
/// <param name="startPort">Starting port number, or guess if omitted.</param>
/// <param name="endPort">Ending port number, or guess if omitted.</param>
/// <exception cref="WireInvalidException">Guessing failed or wire does not exist.</exception>
public Wire(Block start, Block end, byte startPort = Byte.MaxValue, byte endPort = Byte.MaxValue) : base(ecs =>
{
var th = (Wire)ecs;
th.startBlockEGID = start.Id;
th.endBlockEGID = end.Id;
bool flipped = false;
// find block ports
EGID wire = signalEngine.MatchBlocksToWire(start.Id, end.Id, startPort, endPort);
if (wire == default)
{
// flip I/O around and try again
wire = signalEngine.MatchBlocksToWire(end.Id, start.Id, endPort, startPort);
flipped = true;
// NB: start and end are handled exactly as they're received as params.
// This makes wire traversal easier, but makes logic in this class a bit more complex
}
if (wire != default)
{
th.Construct(start.Id, end.Id, startPort, endPort, wire, flipped);
}
else
{
throw new WireInvalidException("Wire not found");
}
return th.wireEGID;
})
{
}
/// <summary> /// <summary>
/// Construct a wire object from an existing wire connection. /// Construct a wire object from an existing wire connection.
/// </summary> /// </summary>
@ -77,9 +120,9 @@ namespace TechbloxModdingAPI.Blocks
public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool inputToOutput) public Wire(Block start, Block end, byte startPort, byte endPort, EGID wire, bool inputToOutput)
: this(start.Id, end.Id, startPort, endPort, wire, inputToOutput) : this(start.Id, end.Id, startPort, endPort, wire, inputToOutput)
{ {
} // TODO: Convert all constructors (including the removed one) to static methods }
private Wire(EGID startBlock, EGID endBlock, byte startPort, byte endPort, EGID wire, bool inputToOutput) : base(wire, typeof(WireEntityDescriptor)) private Wire(EGID startBlock, EGID endBlock, byte startPort, byte endPort, EGID wire, bool inputToOutput) : base(wire)
{ {
Construct(startBlock, endBlock, startPort, endPort, wire, inputToOutput); Construct(startBlock, endBlock, startPort, endPort, wire, inputToOutput);
} }
@ -102,7 +145,7 @@ namespace TechbloxModdingAPI.Blocks
/// Construct a wire object from an existing wire connection. /// Construct a wire object from an existing wire connection.
/// </summary> /// </summary>
/// <param name="wireEgid">The wire ID.</param> /// <param name="wireEgid">The wire ID.</param>
public Wire(EGID wireEgid) : base(wireEgid, typeof(WireEntityDescriptor)) public Wire(EGID wireEgid) : base(wireEgid)
{ {
WireEntityStruct wire = signalEngine.GetWire(wireEGID); WireEntityStruct wire = signalEngine.GetWire(wireEGID);
Construct(wire.sourceBlockEGID, wire.destinationBlockEGID, wire.sourcePortUsage, wire.destinationPortUsage, Construct(wire.sourceBlockEGID, wire.destinationBlockEGID, wire.sourcePortUsage, wire.destinationPortUsage,
@ -145,6 +188,22 @@ namespace TechbloxModdingAPI.Blocks
signalEngine.GetChannelDataStruct(startPortEGID).Get().valueAsEcsString.Set(value); 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> /// <summary>
/// The wire's signal id. /// The wire's signal id.
@ -218,7 +277,7 @@ namespace TechbloxModdingAPI.Blocks
/// <returns>A copy of the wire object.</returns> /// <returns>A copy of the wire object.</returns>
public Wire OutputToInputCopy() public Wire OutputToInputCopy()
{ {
return GetInstanceExisting(wireEGID, egid => new Wire(egid)); return GetInstance(wireEGID, egid => new Wire(egid));
} }
/// <summary> /// <summary>
@ -233,7 +292,9 @@ namespace TechbloxModdingAPI.Blocks
inputToOutput = false; inputToOutput = false;
// swap inputs and outputs // swap inputs and outputs
(endBlockEGID, startBlockEGID) = (startBlockEGID, endBlockEGID); (endBlockEGID, startBlockEGID) = (startBlockEGID, endBlockEGID);
(endPortEGID, startPortEGID) = (startPortEGID, endPortEGID); var tempPort = endPortEGID;
endPortEGID = startPortEGID;
startPortEGID = tempPort;
(endPort, startPort) = (startPort, endPort); (endPort, startPort) = (startPort, endPort);
} }
} }

View file

@ -1,46 +0,0 @@
using RobocraftX.GUI.MyGamesScreen;
using RobocraftX.Multiplayer;
using Svelto.ECS;
using Techblox.GameSelection;
using TechbloxModdingAPI.Client.Game;
using TechbloxModdingAPI.Common.Engines;
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

@ -1,116 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Svelto.Tasks;
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;
public static bool IsBuildMode =>
CurrentState is GameState.InMachineEditor or GameState.InWorldEditor;
public static bool IsSimulationMode =>
CurrentState is GameState.InTestMode or GameState.InWorldTestMode or GameState.InOnlineMatch;
/// <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)
{
if (CurrentState == GameState.InMenu)
throw new InvalidOperationException($"Can only enter test mode from build mode! Current mode: {CurrentState}");
var env = new ClientEnvironment("GAMEID_Road_Track"); // TODO: The options are hardcoded
_engine.EnterBuildMode(env, machine);
}
public static IEnumerator<TaskContract> EnterTestMode()
{
if (!IsBuildMode)
throw new InvalidOperationException($"Can only enter test mode from build mode! Current mode: {CurrentState}");
// TODO
//return Task.CompletedTask;
yield break;
}
public static IEnumerator<TaskContract> ExitSimulationMode()
{ // TODO: Separate these based on the current game state?
if (!IsSimulationMode)
throw new InvalidOperationException($"Can only exit test mode when in it! Current mode: {CurrentState}");
// TODO
//return Task.CompletedTask;
yield break;
}
public static IEnumerator<TaskContract> ExitBuildMode()
{
if (!IsBuildMode)
throw new InvalidOperationException($"Can only exit test mode when in it! Current mode: {CurrentState}");
// TODO
//return Task.CompletedTask;
yield break;
}
public static void Init()
{
EngineManager.AddEngine(_engine, ApiEngineType.Menu);
}
public struct GameStateChangedArgs
{
public GameState OldState { get; set; }
public GameState NewState { get; set; }
}
}

View file

@ -1,33 +0,0 @@
namespace TechbloxModdingAPI.Client.App;
public enum GameState
{
/// <summary>
/// In the environment/game selection menu.
/// </summary>
InMenu,
/// <summary>
/// In machine editor mode in a selected environment.
/// </summary>
InMachineEditor,
/// <summary>
/// In world editor mode.
/// </summary>
InWorldEditor,
/// <summary>
/// In test mode in a selected environment.
/// </summary>
InTestMode,
/// <summary>
/// In world test mode.
/// </summary>
InWorldTestMode,
/// <summary>
/// In an online match as a client.
/// </summary>
InOnlineMatch,
/// <summary>
/// The loading screen is active or otherwise transitioning between modes.
/// </summary>
Loading
}

View file

@ -1,88 +0,0 @@
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

@ -1,14 +0,0 @@
namespace TechbloxModdingAPI.Client.Game;
/// <summary>
/// A build or simulation environment.
/// </summary>
public class ClientEnvironment
{
public string Id { get; }
public ClientEnvironment(string id)
{
Id = id;
}
}

View file

@ -1,15 +0,0 @@
using RobocraftX.GUI.MyGamesScreen;
using Svelto.ECS;
using TechbloxModdingAPI.Common;
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

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

View file

@ -1,18 +1,16 @@
using Svelto.ECS; using Svelto.ECS;
using Techblox.Destruction;
using Techblox.TimeRunning.Clusters; using Techblox.TimeRunning.Clusters;
using TechbloxModdingAPI.Common;
namespace TechbloxModdingAPI namespace TechbloxModdingAPI
{ {
/// <summary> /// <summary>
/// Represents a cluster of blocks in time running mode, meaning blocks that are connected either directly or via joints. /// Represnts a cluster of blocks in time running mode, meaning blocks that are connected either directly or via joints.
/// Only exists if a cluster destruction manager is present. Static blocks like grass and dirt aren't part of a cluster. /// Only exists if a cluster destruction manager is present. Static blocks like grass and dirt aren't part of a cluster.
/// </summary> /// </summary>
public class Cluster : EcsObjectBase public class Cluster : EcsObjectBase
{ {
public Cluster(EGID id) : base(id, typeof(ResetDestructionUtility)) public Cluster(EGID id) : base(id)
{ // TODO: Damage has been connection-based for a while {
} }
public Cluster(uint id) : this(new EGID(id, ClustersExclusiveGroups.SIMULATION_CLUSTERS_GROUP)) public Cluster(uint id) : this(new EGID(id, ClustersExclusiveGroups.SIMULATION_CLUSTERS_GROUP))

View file

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

View file

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

View file

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

View file

@ -1,177 +0,0 @@
using System;
using System.Collections.Generic;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.Hybrid;
using Svelto.ECS.Internal;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Common.Utils;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Common;
public abstract class EcsObjectBase<TDescriptor> : EcsObjectBase where TDescriptor : IEntityDescriptor, new()
{
protected EcsObjectBase(EGID id) : base(id, typeof(TDescriptor))
{
}
protected EcsObjectBase(EntityReference reference) : base(reference, typeof(TDescriptor))
{
}
protected bool RemoveEntity()
{
if (!Exists) return false;
_engine.Functions.RemoveEntity<TDescriptor>(Id);
return true;
}
}
public abstract class EcsObjectBase {
public EGID Id => _engine.GetEgid(Reference);
/// <summary>
/// A reference to a specific entity that persists through group swaps and such.
/// May be an invalid reference, in that case operations do not have any effect.
/// </summary>
public EntityReference Reference { get; }
/// <summary>
/// Whether the entity reference is still valid. Returns false if this object no longer exists.
/// </summary>
public bool Exists => Id != default; // TODO: Might need extra code to support IDs during init
public readonly Type EntityDescriptorType;
public readonly Type[] AllowedEntityComponents;
private static readonly Dictionary<Type, WeakDictionary<EntityReference, EcsObjectBase>> _instances = new();
internal static readonly EcsObjectBaseEngine _engine = new();
private static WeakDictionary<EntityReference, EcsObjectBase> GetInstances(Type type)
{
return _instances.TryGetValue(type, out var dict) ? dict : null;
}
/// <summary>
/// Returns a cached instance if there's an actively used instance of the object already.
/// Objects still get garbage collected and then they will be removed from the cache.<br />
/// <b>Only use for existing entities!</b> Use the other overload for newly created entities.
/// </summary>
/// <param name="egid">The EGID of the entity</param>
/// <param name="constructor">The constructor to construct the object</param>
/// <typeparam name="T">The object type</typeparam>
/// <returns></returns>
internal static T GetInstanceExisting<T>(EGID egid, Func<EGID, T> constructor, Type type = null) where T : EcsObjectBase
{
var instances = GetInstances(type ?? typeof(T));
if (instances == null || !instances.TryGetValue(_engine.GetEntityReference(egid), out var instance))
return constructor(egid); // It will be added by the constructor
return (T)instance;
}
/// <summary>
/// Returns a cached instance if there's an actively used instance of the object already.
/// Objects still get garbage collected and then they will be removed from the cache.<br />
/// <b>Only use for newly created entities!</b> Use the other overload for existing entities.
/// </summary>
/// <param name="egid">The EGID of the entity</param>
/// <param name="constructor">The constructor to construct the object</param>
/// <typeparam name="T">The object type</typeparam>
/// <returns></returns>
internal static T GetInstanceNew<T>(EcsInitData initData, Func<EGID, T> constructor, Type type = null) where T : EcsObjectBase
{
var instances = GetInstances(type ?? typeof(T));
if (instances == null || !instances.TryGetValue(initData.Reference, out var instance))
{
var ret = constructor(initData.EGID);
ret.InitData = initData;
return ret; // It will be added by the constructor
}
return (T)instance;
}
protected static V CreateEntity<U, V>(EGID egid, Func<EGID, V> constructor, Type type = null) where U : IEntityDescriptor, new() where V : EcsObjectBase<U>
{
return GetInstanceNew(_engine.Factory.BuildEntity<U>(egid), constructor, type);
}
protected EcsObjectBase(EGID id, Type entityDescriptorType) : this(_engine.GetEntityReference(id), entityDescriptorType)
{
}
protected EcsObjectBase(EntityReference reference, Type entityDescriptorType)
{
if (!_instances.TryGetValue(GetType(), out var dict))
{
dict = new();
_instances.Add(GetType(), dict);
}
if (!dict.ContainsKey(reference)) // Multiple instances may be created
dict.Add(reference, this);
Reference = reference;
EntityDescriptorType = entityDescriptorType;
AllowedEntityComponents = EcsUtils.GetValidEntityComponents(entityDescriptorType);
// Remove init data once the entity gets submitted so that it won't be used again once the entity is removed
if (InitData != default) _engine.TrackNewEntity(this, obj => obj.InitData = default);
}
protected internal OptionalRef<T> GetComponentOptional<T>() where T : unmanaged, IEntityComponent
{
return _engine.GetComponentOptional<T>(this);
}
protected internal ref T GetComponent<T>() where T : unmanaged, IEntityComponent
{
return ref _engine.GetComponent<T>(this);
}
protected internal ref T GetViewComponent<T>() where T : struct, IEntityViewComponent
{
return ref _engine.GetViewComponent<T>(this);
}
protected internal object GetComponent(Type type, string name)
{
return _engine.GetComponent(this, type, name);
}
protected internal void SetComponent(Type type, string name, object value)
{
_engine.SetComponent(this, type, name, value);
}
#region ECS initializer stuff
internal EcsInitData InitData { get; private set; }
/// <summary>
/// Holds information needed to construct a component initializer.
/// Necessary because the initializer is a ref struct which cannot be assigned to a field.
/// </summary>
protected internal readonly record struct EcsInitData(FasterDictionary<RefWrapperType, ITypeSafeDictionary> group, EntityReference Reference, EGID EGID)
{
public static implicit operator EcsInitData(EntityInitializer initializer) => new(GetInitGroup(initializer), initializer.reference, initializer.EGID);
private readonly FasterDictionary<RefWrapperType, ITypeSafeDictionary> group = group;
public readonly EntityReference Reference = Reference;
public readonly EGID EGID = EGID;
public EntityInitializer Initializer(EGID id = default) => new(id == default ? EGID : id, group, Reference);
public bool Valid => group != null;
}
private delegate FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetInitGroupFunc(
EntityInitializer initializer);
/// <summary>
/// Accesses the group field of the initializer
/// </summary>
private static readonly GetInitGroupFunc GetInitGroup = Reflections.CreateAccessor<GetInitGroupFunc>("_group");
#endregion
public static void Init()
{
EngineManager.AddEngine(_engine, ApiEngineType.Build, ApiEngineType.Menu, ApiEngineType.PlayClient, ApiEngineType.PlayServer);
}
}

View file

@ -1,95 +0,0 @@
using System;
using System.Collections.Generic;
using HarmonyLib;
using RobocraftX.Schedulers;
using Svelto.ECS;
using Svelto.ECS.Hybrid;
using Svelto.Tasks;
using Svelto.Tasks.Lean;
using TechbloxModdingAPI.Blocks.Engines;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Common;
public class EcsObjectBaseEngine : IFactoryEngine, IFunEngine
{
public void Ready()
{
}
public EntitiesDB entitiesDB { get; set; }
public void Dispose()
{
}
public EntityReference GetEntityReference(EGID egid)
{
return entitiesDB is not null && egid != default ? egid.ToEntityReference(entitiesDB) : EntityReference.Invalid;
}
public EGID GetEgid(EntityReference reference)
{
return entitiesDB is not null && reference.ToEGID(entitiesDB, out var egid) ? egid : default;
}
public OptionalRef<T> GetComponentOptional<T>(EcsObjectBase obj) where T : unmanaged, IEntityComponent
{
return entitiesDB.QueryEntityOptional<T>(obj);
}
public ref T GetComponent<T>(EcsObjectBase obj) where T : unmanaged, IEntityComponent
{
#if DEBUG
if (entitiesDB.Exists<T>(obj.Id) && obj.InitData.Valid)
throw new ArgumentException("The block exists but the init data has not been removed!");
#endif
return ref entitiesDB.QueryEntityOrDefault<T>(obj);
}
public ref T GetViewComponent<T>(EcsObjectBase obj) where T : struct, IEntityViewComponent
{
return ref entitiesDB.QueryEntityOrDefault<T>(obj);
}
public object GetComponent(EcsObjectBase obj, Type type, string name)
{
var opt = AccessTools.Method(typeof(NativeApiExtensions), "QueryEntityOptional",
new[] { typeof(EntitiesDB), typeof(EcsObjectBase), typeof(ExclusiveGroupStruct) }, new[] { type })
.Invoke(null, new object[] { entitiesDB, obj, null });
var str = AccessTools.Property(opt.GetType(), "Value").GetValue(opt);
return AccessTools.Field(str.GetType(), name).GetValue(str);
}
public void SetComponent(EcsObjectBase obj, Type type, string name, object value)
{
var opt = AccessTools.Method(typeof(BlockEngine), "GetBlockInfoOptional", generics: new[] { type })
.Invoke(this, new object[] { obj });
var prop = AccessTools.Property(opt.GetType(), "Value");
var str = prop.GetValue(opt);
AccessTools.Field(str.GetType(), name).SetValue(str, value);
prop.SetValue(opt, str);
}
private readonly Dictionary<EcsObjectBase, Action<EcsObjectBase>> _waitingForSubmission = new();
public void TrackNewEntity(EcsObjectBase obj, Action<EcsObjectBase> done)
{
if (_waitingForSubmission.ContainsKey(obj))
throw new InvalidOperationException("Something has gone horribly wrong here");
_waitingForSubmission.Add(obj, done);
WaitUntilEntitySubmission().RunOn(ClientLean.UIScheduler); // TODO: Pick the right scheduler
}
private IEnumerator<TaskContract> WaitUntilEntitySubmission()
{
// TODO: Get the scheduler instance based on the engine (inject in engine manager)
yield return new WaitForSubmissionEnumerator(FullGameFields._mainGameEnginesRoot.scheduler).Continue();
foreach (var (obj, done) in _waitingForSubmission) done(obj);
}
public IEntityFactory Factory { get; set; }
public IEntityFunctions Functions { get; set; }
}

View file

@ -1,21 +0,0 @@
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

@ -1,54 +0,0 @@
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)
{
if (types.Length == 0)
Logging.LogWarning($"Engine {engine.GetType().FullName} added without any types! This doesn't do anything.");
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

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

View file

@ -1,15 +0,0 @@
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

@ -1,11 +0,0 @@
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

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

View file

@ -1,12 +0,0 @@
using Svelto.ECS;
using TechbloxModdingAPI.Common.Engines;
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

@ -1,21 +0,0 @@
using RobocraftX.DOTS;
using TechbloxModdingAPI.Utility;
using Unity.Entities;
using Unity.Transforms;
namespace TechbloxModdingAPI.Common.Traits;
public interface IHasPhysics
{
}
public static class HasPhysicsExtensions
{
internal static void UpdatePhysicsUECSComponent<T, O>(this O obj, T componentData)
where O : EcsObjectBase, IHasPhysics where T : struct, IComponentData
{
var phyStruct = obj.GetComponentOptional<DOTSPhysicsEntityStruct>();
if (phyStruct) //It exists
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.Get().dotsEntity, componentData);
}
}

View file

@ -1,38 +0,0 @@
using System.Threading.Tasks;
using RobocraftX.Schedulers;
using Svelto.ECS;
using Techblox.Server.Schedulers;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Common.Utils;
public static class AsyncUtils
{
private static AsyncUtilsEngine gameEngine = new();
/// <summary>
/// Waits for entity submission asynchronously.
/// Use after placing a block or otherwise creating things in the game to access their properties.
/// </summary>
public static async Task WaitForSubmission()
{
await gameEngine.WaitForSubmission();
}
public static async Task WaitForNextFrame()
{
await gameEngine.WaitForNextFrame();
}
public static void Setup(EnginesRoot enginesRoot, bool clientside)
{
gameEngine.Setup(enginesRoot,
clientside ? ClientExtraLean.UIScheduler : ServerExtraLean.DeterministicTimeRunningStepRunner);
}
public static void Init()
{
EngineManager.AddEngine(gameEngine, ApiEngineType.Build, ApiEngineType.Menu, ApiEngineType.PlayClient, ApiEngineType.PlayServer);
}
}

View file

@ -1,56 +0,0 @@
using System.Collections;
using System.Threading.Tasks;
using Svelto.ECS;
using Svelto.Tasks;
using Svelto.Tasks.ExtraLean;
using TechbloxModdingAPI.Common.Engines;
namespace TechbloxModdingAPI.Common.Utils;
public class AsyncUtilsEngine : IApiEngine
{
private EnginesRoot _enginesRoot;
private IRunner<ExtraLeanSveltoTask<IEnumerator>> _runner;
private IEnumerator WaitForSubmissionInternal(TaskCompletionSource<object> task)
{
var waitEnumerator = new WaitForSubmissionEnumerator(_enginesRoot.scheduler);
while (waitEnumerator.MoveNext())
yield return null;
task.SetResult(null);
}
private IEnumerator WaitForNextFrameInternal(TaskCompletionSource<object> task)
{
yield return null;
task.SetResult(null);
}
public Task WaitForSubmission()
{
var task = new TaskCompletionSource<object>();
WaitForSubmissionInternal(task).RunOn(_runner);
return task.Task;
}
public Task WaitForNextFrame()
{
var task = new TaskCompletionSource<object>();
WaitForNextFrameInternal(task).RunOn(_runner);
return task.Task;
}
public void Setup(EnginesRoot enginesRoot, IRunner<ExtraLeanSveltoTask<IEnumerator>> runner)
{ // TODO: Different engines roots for different sides
_enginesRoot = enginesRoot;
_runner = runner;
}
public void Ready()
{
}
public EntitiesDB entitiesDB { get; set; }
public void Dispose()
{
}
}

View file

@ -1,23 +0,0 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using HarmonyLib;
using Svelto.ECS;
namespace TechbloxModdingAPI.Common.Utils
{
public static class EcsUtils
{
public static Type[] GetValidEntityComponents(Type entityDescriptorType)
{
// TODO: Cache
var templateType = typeof(EntityDescriptorTemplate<>).MakeGenericType(entityDescriptorType);
var templateDescriptor = AccessTools.Property(templateType, "descriptor");
var getDescriptorExpr = Expression.MakeMemberAccess(null, templateDescriptor ?? throw new InvalidOperationException());
var getTemplateDescriptorExpr = Expression.Lambda<Func<IEntityDescriptor>>(getDescriptorExpr);
var getTemplateDescriptor = getTemplateDescriptorExpr.Compile();
var builders = getTemplateDescriptor().componentsToBuild;
return builders.Select(builder => builder.GetEntityComponentType()).ToArray();
}
}
}

View file

@ -1,45 +0,0 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace TechbloxModdingAPI.Common.Utils;
public static class Reflections
{
//https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection
public static TDelegate CreateAccessor<TDelegate>(string memberName, Type thisType = null) where TDelegate : Delegate
{
return CreateSomeCall<TDelegate>(memberName, thisType, (objParam, _) => Expression.PropertyOrField(objParam, memberName));
}
public static TDelegate CreateMethodCall<TDelegate>(MethodInfo method, Type thisType = null) where TDelegate : Delegate
{
return CreateSomeCall<TDelegate>(method.Name, thisType, (objParam, parameters) => Expression.Call(objParam, method, parameters));
}
private static TDelegate CreateSomeCall<TDelegate>(string memberName, Type thisType, Func<ParameterExpression, ParameterExpression[], Expression> memberExpressionGetter) where TDelegate : Delegate
{
var invokeMethod = typeof(TDelegate).GetMethod("Invoke");
if (invokeMethod == null)
throw new InvalidOperationException($"{typeof(TDelegate)} signature could not be determined.");
var delegateParameters = invokeMethod.GetParameters();
if (delegateParameters.Length != 1)
throw new InvalidOperationException("Delegate must have a single parameter.");
var paramType = thisType ?? delegateParameters[0].ParameterType;
var objParam = Expression.Parameter(paramType, "obj");
var otherParams = delegateParameters.Skip(1)
.Select(pinfo => Expression.Parameter(pinfo.ParameterType, pinfo.Name)).ToArray();
var memberExpr = memberExpressionGetter(objParam, otherParams);
Expression returnExpr = memberExpr;
if (invokeMethod.ReturnType != memberExpr.Type)
returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType);
var lambda =
Expression.Lambda<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] { objParam }.Concat(otherParams));
return lambda.Compile();
}
}

View file

@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.Internal;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI
{
public abstract class EcsObjectBase
{
public EGID Id { get; }
private static readonly Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>> _instances =
new Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>>();
private static readonly WeakDictionary<EGID, EcsObjectBase> _noInstance =
new WeakDictionary<EGID, EcsObjectBase>();
internal static WeakDictionary<EGID, EcsObjectBase> GetInstances(Type type)
{
return _instances.TryGetValue(type, out var dict) ? dict : null;
}
/// <summary>
/// Returns a cached instance if there's an actively used instance of the object already.
/// Objects still get garbage collected and then they will be removed from the cache.
/// </summary>
/// <param name="egid">The EGID of the entity</param>
/// <param name="constructor">The constructor to construct the object</param>
/// <typeparam name="T">The object type</typeparam>
/// <returns></returns>
internal static T GetInstance<T>(EGID egid, Func<EGID, T> constructor, Type type = null) where T : EcsObjectBase
{
var instances = GetInstances(type ?? typeof(T));
if (instances == null || !instances.TryGetValue(egid, out var instance))
return constructor(egid); // It will be added by the constructor
return (T)instance;
}
protected EcsObjectBase(EGID id)
{
if (!_instances.TryGetValue(GetType(), out var dict))
{
dict = new WeakDictionary<EGID, EcsObjectBase>();
_instances.Add(GetType(), dict);
}
if (!dict.ContainsKey(id)) // Multiple instances may be created
dict.Add(id, this);
Id = id;
}
protected EcsObjectBase(Func<EcsObjectBase, EGID> initializer)
{
if (!_instances.TryGetValue(GetType(), out var dict))
{
dict = new WeakDictionary<EGID, EcsObjectBase>();
_instances.Add(GetType(), dict);
}
var id = initializer(this);
if (!dict.ContainsKey(id)) // Multiple instances may be created
dict.Add(id, this);
else
{
Logging.MetaDebugLog($"An object of this type and ID is already stored: {GetType()} - {id}");
Logging.MetaDebugLog(this);
Logging.MetaDebugLog(dict[id]);
}
Id = id;
}
#region ECS initializer stuff
protected internal EcsInitData InitData;
/// <summary>
/// Holds information needed to construct a component initializer
/// </summary>
protected internal struct EcsInitData
{
private FasterDictionary<RefWrapperType, ITypeSafeDictionary> group;
private EntityReference reference;
public static implicit operator EcsInitData(EntityInitializer initializer) => new EcsInitData
{ group = GetInitGroup(initializer), reference = initializer.reference };
public EntityInitializer Initializer(EGID id) => new EntityInitializer(id, group, reference);
public bool Valid => group != null;
}
private delegate FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetInitGroupFunc(
EntityInitializer initializer);
/// <summary>
/// Accesses the group field of the initializer
/// </summary>
private static GetInitGroupFunc GetInitGroup = CreateAccessor<GetInitGroupFunc>("_group");
//https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection
private static TDelegate CreateAccessor<TDelegate>(string memberName) where TDelegate : Delegate
{
var invokeMethod = typeof(TDelegate).GetMethod("Invoke");
if (invokeMethod == null)
throw new InvalidOperationException($"{typeof(TDelegate)} signature could not be determined.");
var delegateParameters = invokeMethod.GetParameters();
if (delegateParameters.Length != 1)
throw new InvalidOperationException("Delegate must have a single parameter.");
var paramType = delegateParameters[0].ParameterType;
var objParam = Expression.Parameter(paramType, "obj");
var memberExpr = Expression.PropertyOrField(objParam, memberName);
Expression returnExpr = memberExpr;
if (invokeMethod.ReturnType != memberExpr.Type)
returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType);
var lambda =
Expression.Lambda<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] { objParam });
return lambda.Compile();
}
#endregion
}
}

View file

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

View file

@ -0,0 +1,26 @@
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

@ -0,0 +1,24 @@
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

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

View file

@ -0,0 +1,20 @@
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

@ -1,14 +1,13 @@
using RobocraftX.Common.Input; using RobocraftX.Common.Input;
using TechbloxModdingAPI.App; using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Input namespace TechbloxModdingAPI.Input
{ {
public static class FakeInput public static class FakeInput
{ {
internal static readonly FakeInputEngine inputEngine = new(); internal static readonly FakeInputEngine inputEngine = new FakeInputEngine();
/// <summary> /// <summary>
/// Customize the local input. /// Customize the local input.
@ -139,7 +138,8 @@ namespace TechbloxModdingAPI.Input
public static void Init() public static void Init()
{ {
EngineManager.AddEngine(inputEngine, ApiEngineType.Build, ApiEngineType.Menu, ApiEngineType.PlayClient, ApiEngineType.PlayServer); GameEngineManager.AddGameEngine(inputEngine);
MenuEngineManager.AddMenuEngine(inputEngine);
} }
} }
} }

View file

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

View file

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

View file

@ -8,7 +8,6 @@ using Svelto.Context;
using TechbloxModdingAPI.App; using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Tasks; using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
@ -57,18 +56,21 @@ namespace TechbloxModdingAPI
try try
{ {
// init utility
Logging.MetaDebugLog($"Initializing Utility");
Utility.GameState.Init();
// init block implementors // init block implementors
Logging.MetaDebugLog($"Initializing Blocks"); Logging.MetaDebugLog($"Initializing Blocks");
// init input // init input
Input.FakeInput.Init(); Input.FakeInput.Init();
// init object-oriented classes // init object-oriented classes
EcsObjectBase.Init();
Player.Init(); Player.Init();
Block.Init(); Block.Init();
BlockGroup.Init(); BlockGroup.Init();
Wire.Init(); Wire.Init();
// init client // init client
Logging.MetaDebugLog($"Initializing Client"); Logging.MetaDebugLog($"Initializing Client");
Client.Init();
Game.Init(); Game.Init();
// init UI // init UI
Logging.MetaDebugLog($"Initializing UI"); Logging.MetaDebugLog($"Initializing UI");

View file

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

View file

@ -1,4 +1,6 @@
using Svelto.ECS; using System;
using Svelto.ECS;
using Svelto.ECS.Serialization; using Svelto.ECS.Serialization;
using RobocraftX.Common; using RobocraftX.Common;

View file

@ -12,10 +12,8 @@ using Techblox.BuildingDrone;
using Techblox.Camera; using Techblox.Camera;
using Techblox.Character; using Techblox.Character;
using TechbloxModdingAPI.Blocks; using TechbloxModdingAPI.Blocks;
using TechbloxModdingAPI.Client.App;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Common.Engines;
using TechbloxModdingAPI.Players; using TechbloxModdingAPI.Players;
using TechbloxModdingAPI.Utility;
using UnityEngine; using UnityEngine;
namespace TechbloxModdingAPI namespace TechbloxModdingAPI
@ -26,8 +24,8 @@ namespace TechbloxModdingAPI
public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID> public partial class Player : EcsObjectBase, IEquatable<Player>, IEquatable<EGID>
{ {
// static functionality // static functionality
private static readonly PlayerEngine playerEngine = new(); private static readonly PlayerEngine playerEngine = new PlayerEngine();
private static readonly PlayerEventsEngine playerEventsEngine = new(); private static readonly PlayerEventsEngine playerEventsEngine = new PlayerEventsEngine();
private static Player localPlayer; private static Player localPlayer;
/// <summary> /// <summary>
@ -40,7 +38,7 @@ namespace TechbloxModdingAPI
switch (player) switch (player)
{ {
case PlayerType.Remote: case PlayerType.Remote:
return playerEngine.GetRemotePlayers().Length > 0; return playerEngine.GetRemotePlayer() != uint.MaxValue;
case PlayerType.Local: case PlayerType.Local:
return playerEngine.GetLocalPlayer() != uint.MaxValue; return playerEngine.GetLocalPlayer() != uint.MaxValue;
} }
@ -84,7 +82,7 @@ namespace TechbloxModdingAPI
internal static Player GetInstance(uint id) internal static Player GetInstance(uint id)
{ {
return EcsObjectBase.GetInstanceExisting(new EGID(id, CharacterExclusiveGroups.OnFootGroup), return EcsObjectBase.GetInstance(new EGID(id, CharacterExclusiveGroups.OnFootGroup),
e => new Player(e.entityID)); e => new Player(e.entityID));
} }
@ -92,7 +90,7 @@ namespace TechbloxModdingAPI
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class. /// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary> /// </summary>
/// <param name="id">The player's unique identifier.</param> /// <param name="id">The player's unique identifier.</param>
public Player(uint id) : base(new EGID(id, CharacterExclusiveGroups.OnFootGroup), typeof(CharacterEntityDescriptor)) public Player(uint id) : base(new EGID(id, CharacterExclusiveGroups.OnFootGroup))
{ {
this.Id = id; this.Id = id;
if (!Exists(id)) if (!Exists(id))
@ -102,6 +100,38 @@ namespace TechbloxModdingAPI
this.Type = playerEngine.GetLocalPlayer() == id ? PlayerType.Local : PlayerType.Remote; this.Type = playerEngine.GetLocalPlayer() == id ? PlayerType.Local : PlayerType.Remote;
} }
/// <summary>
/// Initializes a new instance of the <see cref="T:TechbloxModdingAPI.Player"/> class.
/// </summary>
/// <param name="player">The player type. Chooses the first available player matching the criteria.</param>
public Player(PlayerType player) : base(ecs =>
{
uint id;
switch (player)
{
case PlayerType.Local:
id = playerEngine.GetLocalPlayer();
break;
case PlayerType.Remote:
id = playerEngine.GetRemotePlayer();
break;
default:
id = uint.MaxValue;
break;
}
if (id == uint.MaxValue)
{
throw new PlayerNotFoundException($"No player of {player} type exists");
}
return new EGID(id, CharacterExclusiveGroups.OnFootGroup);
})
{
this.Type = player;
Id = base.Id.entityID;
}
// object fields & properties // object fields & properties
/// <summary> /// <summary>
@ -133,10 +163,10 @@ namespace TechbloxModdingAPI
/// <value>The rotation.</value> /// <value>The rotation.</value>
public float3 Rotation public float3 Rotation
{ {
get => ((Quaternion) (GameClient.IsBuildMode get => ((Quaternion) (GameState.IsBuildMode()
? playerEngine.GetCameraStruct<CameraEntityStruct>(Id).Get().rotation ? playerEngine.GetCameraStruct<CameraEntityStruct>(Id).Get().rotation
: playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation)).eulerAngles; : playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation)).eulerAngles;
set => _ = GameClient.IsBuildMode set => _ = GameState.IsBuildMode()
? playerEngine.GetCameraStruct<CameraEntityStruct>(Id).Get().rotation = quaternion.Euler(value) ? playerEngine.GetCameraStruct<CameraEntityStruct>(Id).Get().rotation = quaternion.Euler(value)
: playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation = quaternion.Euler(value); : playerEngine.GetCharacterStruct<RigidBodyEntityStruct>(Id).Get().rotation = quaternion.Euler(value);
} }
@ -346,10 +376,10 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public bool Sprinting public bool Sprinting
{ {
get => GameClient.IsBuildMode get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting ? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting
: playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting; : playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting;
set => _ = GameClient.IsBuildMode set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting = value ? playerEngine.GetCharacterStruct<BuildingDroneMovementComponent>(Id).Get().sprinting = value
: playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting = value; : playerEngine.GetCharacterStruct<CharacterMovementEntityStruct>(Id).Get().isSprinting = value;
} }
@ -359,10 +389,10 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public float SpeedSetting public float SpeedSetting
{ {
get => GameClient.IsBuildMode get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed; : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed;
set => _ = GameClient.IsBuildMode set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed = value ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speed = value
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed = value; : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().moveSpeed = value;
} }
@ -372,10 +402,10 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public float SpeedSprintMultiplierSetting public float SpeedSprintMultiplierSetting
{ {
get => GameClient.IsBuildMode get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier; : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier;
set => _ = GameClient.IsBuildMode set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier = value ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().speedSprintMultiplier = value
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier = value; : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().sprintSpeedMultiplier = value;
} }
@ -385,10 +415,10 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public float AccelerationSetting public float AccelerationSetting
{ {
get => GameClient.IsBuildMode get => GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration; : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration;
set => _ = GameClient.IsBuildMode set => _ = GameState.IsBuildMode()
? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration = value ? playerEngine.GetCharacterStruct<BuildingDroneMovementSettingsComponent>(Id).Get().acceleration = value
: playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration = value; : playerEngine.GetCharacterStruct<CharacterMovementSettingsEntityStruct>(Id).Get().acceleration = value;
} }
@ -469,7 +499,7 @@ namespace TechbloxModdingAPI
{ {
var egid = playerEngine.GetThingLookedAt(Id, maxDistance); var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
return egid != default && egid.groupID == CommonExclusiveGroups.SIMULATION_BODIES_GROUP return egid != default && egid.groupID == CommonExclusiveGroups.SIMULATION_BODIES_GROUP
? EcsObjectBase.GetInstanceExisting(egid, e => new SimBody(e)) ? EcsObjectBase.GetInstance(egid, e => new SimBody(e))
: null; : null;
} }
@ -482,7 +512,7 @@ namespace TechbloxModdingAPI
{ {
var egid = playerEngine.GetThingLookedAt(Id, maxDistance); var egid = playerEngine.GetThingLookedAt(Id, maxDistance);
return egid != default && egid.groupID == WiresGUIExclusiveGroups.WireGroup return egid != default && egid.groupID == WiresGUIExclusiveGroups.WireGroup
? EcsObjectBase.GetInstanceExisting(new EGID(egid.entityID, BuildModeWiresGroups.WiresGroup.Group), ? EcsObjectBase.GetInstance(new EGID(egid.entityID, BuildModeWiresGroups.WiresGroup.Group),
e => new Wire(e)) e => new Wire(e))
: null; : null;
} }
@ -541,9 +571,8 @@ namespace TechbloxModdingAPI
internal static void Init() internal static void Init()
{ {
// TODO: Separate build mode, client and server into separate classes Utility.GameEngineManager.AddGameEngine(playerEngine);
EngineManager.AddEngine(playerEngine, ApiEngineType.Build, ApiEngineType.PlayClient, ApiEngineType.PlayServer); Utility.GameEngineManager.AddGameEngine(playerEventsEngine);
EngineManager.AddEngine(playerEventsEngine, ApiEngineType.Build, ApiEngineType.PlayClient, ApiEngineType.PlayServer);
} }
} }
} }

View file

@ -14,10 +14,11 @@ using RobocraftX.SimulationModeState;
using Svelto.ECS; using Svelto.ECS;
using Techblox.Camera; using Techblox.Camera;
using Unity.Mathematics; using Unity.Mathematics;
using Svelto.ECS.DataStructures;
using Techblox.BuildingDrone; using Techblox.BuildingDrone;
using Techblox.Character; using Techblox.Character;
using TechbloxModdingAPI.Client.App;
using TechbloxModdingAPI.Common.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Input; using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS; using TechbloxModdingAPI.Utility.ECS;
@ -57,17 +58,15 @@ namespace TechbloxModdingAPI.Players
return uint.MaxValue; return uint.MaxValue;
} }
public uint[] GetRemotePlayers() public uint GetRemotePlayer()
{ {
if (!isReady) return Array.Empty<uint>(); if (!isReady) return uint.MaxValue;
var (localPlayers, count) = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers); var (localPlayers, count) = entitiesDB.QueryEntities<PlayerIDStruct>(PlayersExclusiveGroups.RemotePlayers);
var players = new uint[count]; if (count > 0)
for (int i = 0; i < count; i++) {
{ return localPlayers[0].ID.entityID;
players[i] = localPlayers[i].ID.entityID; }
} return uint.MaxValue;
return players;
} }
public long GetAllPlayerCount() public long GetAllPlayerCount()
@ -130,7 +129,7 @@ namespace TechbloxModdingAPI.Players
public OptionalRef<T> GetCharacterStruct<T>(uint playerId, out ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent public OptionalRef<T> GetCharacterStruct<T>(uint playerId, out ExclusiveGroupStruct group) where T : unmanaged, IEntityComponent
{ {
group = default; group = default;
if (GameClient.IsBuildMode) if (GameState.IsBuildMode())
return entitiesDB.QueryEntityOptional<T>(new EGID(playerId, LocalBuildingDrone.BuildGroup)); return entitiesDB.QueryEntityOptional<T>(new EGID(playerId, LocalBuildingDrone.BuildGroup));
var characterGroups = CharacterExclusiveGroups.AllCharacters; var characterGroups = CharacterExclusiveGroups.AllCharacters;

View file

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

View file

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

View file

@ -2,10 +2,11 @@
using Svelto.ECS; using Svelto.ECS;
using Unity.Mathematics; using Unity.Mathematics;
using UnityEngine; using UnityEngine;
using Gamecraft.Damage;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.Physics; using RobocraftX.Physics;
using Techblox.TimeRunning.Clusters; using Techblox.TimeRunning.Clusters;
using TechbloxModdingAPI.Common;
namespace TechbloxModdingAPI namespace TechbloxModdingAPI
{ {
@ -20,13 +21,13 @@ namespace TechbloxModdingAPI
/// </summary> /// </summary>
public Cluster Cluster => cluster ??= clusterId == uint.MaxValue // Return cluster or if it's null then set it public Cluster Cluster => cluster ??= clusterId == uint.MaxValue // Return cluster or if it's null then set it
? Block.BlockEngine.GetCluster(Id.entityID) // If we don't have a clusterId set then get it from the game ? Block.BlockEngine.GetCluster(Id.entityID) // If we don't have a clusterId set then get it from the game
: GetInstanceExisting(new EGID(clusterId, ClustersExclusiveGroups.SIMULATION_CLUSTERS_GROUP), : GetInstance(new EGID(clusterId, ClustersExclusiveGroups.SIMULATION_CLUSTERS_GROUP),
egid => new Cluster(egid)); // Otherwise get the cluster from the ID egid => new Cluster(egid)); // Otherwise get the cluster from the ID
private Cluster cluster; private Cluster cluster;
private readonly uint clusterId = uint.MaxValue; private readonly uint clusterId = uint.MaxValue;
public SimBody(EGID id) : base(id, typeof(ClusterEntityDescriptor)) public SimBody(EGID id) : base(id)
{ {
} }

View file

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

View file

@ -11,8 +11,6 @@
<NeutralLanguage>en-CA</NeutralLanguage> <NeutralLanguage>en-CA</NeutralLanguage>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Configurations>Debug;Release;Test</Configurations>
<Platforms>AnyCPU</Platforms>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

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

View file

@ -8,13 +8,11 @@ using Svelto.Tasks;
using Svelto.Tasks.Lean; using Svelto.Tasks.Lean;
using Svelto.Tasks.Enumerators; using Svelto.Tasks.Enumerators;
using Svelto.Tasks.Lean.Unity; using Svelto.Tasks.Lean.Unity;
using TechbloxModdingAPI.Client.App;
using TechbloxModdingAPI.Client.Game;
using UnityEngine; using UnityEngine;
using TechbloxModdingAPI.App;
using TechbloxModdingAPI.Tasks; using TechbloxModdingAPI.Tasks;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using GameState = TechbloxModdingAPI.Client.App.GameState;
namespace TechbloxModdingAPI.Tests namespace TechbloxModdingAPI.Tests
{ {
@ -68,33 +66,19 @@ namespace TechbloxModdingAPI.Tests
_testsCountPassed = 0; _testsCountPassed = 0;
_testsCountFailed = 0; _testsCountFailed = 0;
// flow control // flow control
Client.App.GameClient.StateChanged += (sender, args) => Game.Enter += (sender, args) => { GameTests().RunOn(new UpdateMonoRunner("TechbloxModdingAPITestRunner")); };
Game.Exit += (s, a) => state = "ReturningFromGame";
Client.EnterMenu += (sender, args) =>
{ {
switch (args.NewState) if (state == "EnteringMenu")
{ {
case GameState.InMenu: MenuTests().RunOn(Scheduler.leanRunner);
{ state = "EnteringGame";
if (state == "EnteringMenu") }
{ if (state == "ReturningFromGame")
MenuTests().RunOn(Scheduler.leanRunner); {
state = "EnteringGame"; TearDown().RunOn(Scheduler.leanRunner);
} state = "ShuttingDown";
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 // init tests here
@ -147,16 +131,17 @@ namespace TechbloxModdingAPI.Tests
private static IEnumerator<TaskContract> GoToGameTests() private static IEnumerator<TaskContract> GoToGameTests()
{ {
Client app = Client.Instance;
int oldLength = 0; int oldLength = 0;
while (GameClient.Machines.Length == 0 || oldLength != GameClient.Machines.Length) while (app.MyGames.Length == 0 || oldLength != app.MyGames.Length)
{ {
oldLength = GameClient.Machines.Length; oldLength = app.MyGames.Length;
yield return new WaitForSecondsEnumerator(1).Continue(); yield return new WaitForSecondsEnumerator(1).Continue();
} }
yield return Yield.It; yield return Yield.It;
try try
{ {
GameClient.EnterBuildMode(new ClientEnvironment("GAMEID_Road_Track"), GameClient.Machines[0]); app.MyGames[0].EnterGame();
} }
catch (Exception e) catch (Exception e)
{ {
@ -171,6 +156,7 @@ namespace TechbloxModdingAPI.Tests
private static IEnumerator<TaskContract> GameTests() private static IEnumerator<TaskContract> GameTests()
{ {
yield return Yield.It; yield return Yield.It;
Game currentGame = Game.CurrentGame();
// in-game tests // in-game tests
yield return new WaitForSecondsEnumerator(5).Continue(); // wait for game to finish loading yield return new WaitForSecondsEnumerator(5).Continue(); // wait for game to finish loading
var testTypesToRun = new[] var testTypesToRun = new[]
@ -225,9 +211,13 @@ namespace TechbloxModdingAPI.Tests
if (index + 1 < testTypesToRun.Length) //Don't toggle on the last test if (index + 1 < testTypesToRun.Length) //Don't toggle on the last test
{ {
bool running = GameClient.IsSimulationMode; bool running = currentGame.IsTimeRunning;
if (running) yield return GameClient.EnterTestMode().Continue(); currentGame.ToggleTimeMode();
else yield return GameClient.ExitSimulationMode().Continue(); while (running ? !currentGame.IsTimeStopped : !currentGame.IsTimeRunning)
{
Logging.MetaLog($"Waiting for time to {(running?"stop":"start")}...");
yield return new WaitForSecondsEnumerator(1).Continue();
}
} }
yield return new WaitForSecondsEnumerator(5).Continue(); yield return new WaitForSecondsEnumerator(5).Continue();
@ -240,7 +230,7 @@ namespace TechbloxModdingAPI.Tests
{ {
Logging.MetaLog("Returning to main menu"); Logging.MetaLog("Returning to main menu");
yield return Yield.It; yield return Yield.It;
yield return GameClient.ExitSimulationMode().Continue(); Game.CurrentGame().ExitGame();
} }
private static IEnumerator<TaskContract> TearDown() private static IEnumerator<TaskContract> TearDown()

View file

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

View file

@ -1,7 +1,5 @@
using System.Linq;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Hybrid; using Svelto.ECS.Hybrid;
using TechbloxModdingAPI.Common;
namespace TechbloxModdingAPI.Utility.ECS namespace TechbloxModdingAPI.Utility.ECS
{ {
@ -55,10 +53,7 @@ namespace TechbloxModdingAPI.Utility.ECS
EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group); EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
var opt = QueryEntityOptional<T>(entitiesDB, id); var opt = QueryEntityOptional<T>(entitiesDB, id);
if (opt) return ref opt.Get(); if (opt) return ref opt.Get();
// If initializing the entity, check if the component is allowed by the descriptor, otherwise it could cause if (obj.InitData.Valid) return ref obj.InitData.Initializer(id).GetOrAdd<T>();
// issues in the game with Add() calls running unexpectedly
if (obj.InitData.Valid && obj.AllowedEntityComponents.Contains(typeof(T)))
return ref obj.InitData.Initializer(id).GetOrAdd<T>();
return ref opt.Get(); //Default value return ref opt.Get(); //Default value
} }

View file

@ -1,10 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using Svelto.Tasks; using Svelto.Tasks;
using Svelto.Tasks.Lean; using Svelto.Tasks.Lean;
using TechbloxModdingAPI.Common;
using TechbloxModdingAPI.Tasks; using TechbloxModdingAPI.Tasks;
namespace TechbloxModdingAPI.Utility.ECS namespace TechbloxModdingAPI.Utility.ECS
@ -59,10 +58,12 @@ namespace TechbloxModdingAPI.Utility.ECS
EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group); EGID id = group == ExclusiveGroupStruct.Invalid ? obj.Id : new EGID(obj.Id.entityID, group);
var opt = QueryEntityOptional<T>(entitiesDB, id); var opt = QueryEntityOptional<T>(entitiesDB, id);
if (opt) return ref opt.Get(); if (opt) return ref opt.Get();
// If initializing the entity, check if the component is allowed by the descriptor, otherwise it could cause if (obj.InitData.Valid) return ref obj.InitData.Initializer(id).GetOrAdd<T>();
// issues in the game with Add() calls running unexpectedly /*if (!obj.InitData.Valid) return ref opt.Get(); //Default value
if (obj.InitData.Valid && obj.AllowedEntityComponents.Contains(typeof(T))) var init = obj.InitData.Initializer(id);
return ref obj.InitData.Initializer(id).GetOrAdd<T>(); // Do not create the component if missing, as that can trigger Add() listeners that, in some cases, may be
// invalid if (ab)using the classes in an unusual way - TODO: Check entity descriptor or something
if (init.Has<T>()) return ref init.Get<T>();*/
return ref opt.Get(); //Default value return ref opt.Get(); //Default value
} }

View file

@ -0,0 +1,81 @@
using System.Collections.Generic;
using System.Linq;
using RobocraftX.StateSync;
using Svelto.ECS;
using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Utility
{
/// <summary>
/// Keeps track of custom game-modifying engines
/// </summary>
public static class GameEngineManager
{
private static Dictionary<string, IApiEngine> _gameEngines = new Dictionary<string, IApiEngine>();
private static EnginesRoot _lastEngineRoot;
public static void AddGameEngine(IApiEngine engine)
{
_gameEngines[engine.Name] = engine;
if (_lastEngineRoot != null)
{
Logging.MetaDebugLog($"Registering Game IApiEngine {engine.Name}");
_lastEngineRoot.AddEngine(engine);
if (engine is IFactoryEngine factoryEngine)
factoryEngine.Factory = _lastEngineRoot.GenerateEntityFactory();
if (engine is IFunEngine funEngine)
funEngine.Functions = _lastEngineRoot.GenerateEntityFunctions();
}
}
public static bool ExistsGameEngine(string name)
{
return _gameEngines.ContainsKey(name);
}
public static bool ExistsGameEngine(IApiEngine engine)
{
return ExistsGameEngine(engine.Name);
}
public static IApiEngine GetGameEngine(string name)
{
return _gameEngines[name];
}
public static string[] GetGameEngineNames()
{
return _gameEngines.Keys.ToArray();
}
public static void RemoveGameEngine(string name)
{
if (_gameEngines[name].isRemovable)
{
_gameEngines.Remove(name);
}
}
public static void RegisterEngines(StateSyncRegistrationHelper helper)
{
var enginesRoot = helper.enginesRoot;
_lastEngineRoot = enginesRoot;
IEntityFactory factory = enginesRoot.GenerateEntityFactory();
IEntityFunctions functions = enginesRoot.GenerateEntityFunctions();
foreach (var key in _gameEngines.Keys)
{
Logging.MetaDebugLog($"Registering Game IApiEngine {_gameEngines[key].Name}");
if (_gameEngines[key] is IDeterministicEngine detEngine)
helper.AddDeterministicEngine(detEngine);
else
enginesRoot.AddEngine(_gameEngines[key]);
if (_gameEngines[key] is IFactoryEngine factEngine)
factEngine.Factory = factory;
if (_gameEngines[key] is IFunEngine funEngine)
funEngine.Functions = functions;
}
}
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TechbloxModdingAPI.Utility
{
/// <summary>
/// Utility to get the state of the current Techblox game
/// </summary>
public static class GameState
{
private static GameStateEngine gameEngine = new GameStateEngine();
/// <summary>
/// Is the game in edit mode?
/// </summary>
/// <returns>Whether the game is in build mode</returns>
public static bool IsBuildMode()
{
return gameEngine.IsBuildMode();
}
/// <summary>
/// Is the game in simulation mode?
/// </summary>
/// <returns>Whether the game is in simulation mode</returns>
public static bool IsSimulationMode()
{
return gameEngine.IsSimulationMode();
}
/// <summary>
/// Is a game loaded?
/// </summary>
/// <returns>Whether Techblox has a game open (false = Main Menu)</returns>
public static bool IsInGame()
{
return gameEngine.IsInGame;
}
public static void Init()
{
GameEngineManager.AddGameEngine(gameEngine);
}
}
}

View file

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

View file

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svelto.ECS;
using TechbloxModdingAPI.Engines;
namespace TechbloxModdingAPI.Utility
{
/// <summary>
/// Keeps track of custom menu-modifying engines
/// </summary>
public static class MenuEngineManager
{
private static Dictionary<string, IApiEngine> _menuEngines = new Dictionary<string, IApiEngine>();
private static EnginesRoot _lastEngineRoot;
// menu engine management
public static void AddMenuEngine(IApiEngine engine)
{
_menuEngines[engine.Name] = engine;
if (_lastEngineRoot != null)
{
Logging.MetaDebugLog($"Registering Menu IApiEngine {engine.Name}");
_lastEngineRoot.AddEngine(engine);
if (engine is IFactoryEngine factoryEngine)
factoryEngine.Factory = _lastEngineRoot.GenerateEntityFactory();
if (engine is IFunEngine funEngine)
funEngine.Functions = _lastEngineRoot.GenerateEntityFunctions();
}
}
public static bool ExistsMenuEngine(string name)
{
return _menuEngines.ContainsKey(name);
}
public static bool ExistsMenuEngine(IApiEngine engine)
{
return ExistsMenuEngine(engine.Name);
}
public static IApiEngine GetMenuEngine(string name)
{
return _menuEngines[name];
}
public static string[] GetMenuEngineNames()
{
return _menuEngines.Keys.ToArray();
}
public static void RemoveMenuEngine(string name)
{
if (_menuEngines[name].isRemovable)
{
_menuEngines.Remove(name);
}
}
public static void RegisterEngines(EnginesRoot enginesRoot)
{
_lastEngineRoot = enginesRoot;
IEntityFactory factory = enginesRoot.GenerateEntityFactory();
IEntityFunctions functions = enginesRoot.GenerateEntityFunctions();
foreach (var key in _menuEngines.Keys)
{
Logging.MetaDebugLog($"Registering Menu IApiEngine {_menuEngines[key].Name}");
enginesRoot.AddEngine(_menuEngines[key]);
if (_menuEngines[key] is IFactoryEngine factEngine) factEngine.Factory = factory;
if(_menuEngines[key] is IFunEngine funEngine) funEngine.Functions = functions;
}
}
}
}

View file

@ -1,8 +1,6 @@
using System; using System;
using System.Linq;
using Svelto.DataStructures; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using TechbloxModdingAPI.Common;
namespace TechbloxModdingAPI.Utility namespace TechbloxModdingAPI.Utility
{ {
@ -15,7 +13,6 @@ namespace TechbloxModdingAPI.Utility
private MB<T> managedArray; private MB<T> managedArray;
private readonly EntityInitializer initializer; private readonly EntityInitializer initializer;
//The possible fields are: (index && (array || managedArray)) || initializer //The possible fields are: (index && (array || managedArray)) || initializer
private readonly EcsObjectBase obj;
public OptionalRef(NB<T> array, uint index, EGID entityId = default) public OptionalRef(NB<T> array, uint index, EGID entityId = default)
{ {
@ -25,7 +22,6 @@ namespace TechbloxModdingAPI.Utility
this.entityId = entityId; this.entityId = entityId;
initializer = default; initializer = default;
managedArray = default; managedArray = default;
obj = default;
} }
public OptionalRef(MB<T> array, uint index, EGID entityId = default) public OptionalRef(MB<T> array, uint index, EGID entityId = default)
@ -36,7 +32,6 @@ namespace TechbloxModdingAPI.Utility
this.entityId = entityId; this.entityId = entityId;
initializer = default; initializer = default;
this.array = default; this.array = default;
obj = default;
} }
/// <summary> /// <summary>
@ -60,7 +55,6 @@ namespace TechbloxModdingAPI.Utility
array = default; array = default;
index = default; index = default;
managedArray = default; managedArray = default;
this.obj = obj;
} }
/// <summary> /// <summary>
@ -71,10 +65,7 @@ namespace TechbloxModdingAPI.Utility
{ {
CompRefCache.Default = default; //The default value can be changed by mods CompRefCache.Default = default; //The default value can be changed by mods
if (state == State.Empty) return ref CompRefCache.Default; if (state == State.Empty) return ref CompRefCache.Default;
// If initializing the entity, check if the component is allowed by the descriptor, otherwise it could cause if ((state & State.Initializer) != State.Empty) return ref initializer.GetOrAdd<T>();
// issues in the game with Add() calls running unexpectedly
if ((state & State.Initializer) != State.Empty && obj.AllowedEntityComponents.Contains(typeof(T)))
return ref initializer.GetOrAdd<T>();
if ((state & State.Native) != State.Empty) return ref array[index]; if ((state & State.Native) != State.Empty) return ref array[index];
return ref managedArray[index]; return ref managedArray[index];
} }

View file

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