diff --git a/GamecraftModdingAPI/Block.cs b/GamecraftModdingAPI/Block.cs index 16a0c81..5f30a64 100644 --- a/GamecraftModdingAPI/Block.cs +++ b/GamecraftModdingAPI/Block.cs @@ -1,6 +1,7 @@ using System; -using System.Reflection; -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; using Svelto.ECS; using Svelto.ECS.EntityStructs; @@ -54,23 +55,13 @@ namespace GamecraftModdingAPI float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, int uscale = 1, float3 scale = default, Player player = null) { - if (PlacementEngine.IsInGame && GameState.IsBuildMode()) - { - return new Block(PlacementEngine.PlaceBlock(block, color, darkness, - position, uscale, scale, player, rotation)); - } - - return null; + return PlaceNew(block, position, rotation, color, darkness, uscale, scale, player); } /// /// Place a new block at the given position. If scaled, position means the center of the block. The default block size is 0.2 in terms of position. /// Place blocks next to each other to connect them. /// The placed block will be a complete block with a placement grid and collision which will be saved along with the game. - /// - /// This method waits for the block to be constructed in the game which may take a significant amount of time. - /// Only use this to place a single block. - /// For placing multiple blocks, use PlaceNew() then AsyncUtils.WaitForSubmission() when done with placing blocks. /// /// The block's type /// The block's color @@ -81,23 +72,18 @@ namespace GamecraftModdingAPI /// The block's non-uniform scale - 0 means is used /// The player who placed the block /// The placed block or null if failed - public static async Task PlaceNewAsync(BlockIDs block, float3 position, + public static T PlaceNew(BlockIDs block, float3 position, float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, - int uscale = 1, float3 scale = default, Player player = null) + int uscale = 1, float3 scale = default, Player player = null) where T : Block { if (PlacementEngine.IsInGame && GameState.IsBuildMode()) { - try - { - var ret = new Block(PlacementEngine.PlaceBlock(block, color, darkness, - position, uscale, scale, player, rotation)); - await AsyncUtils.WaitForSubmission(); - return ret; - } - catch (Exception e) - { - Logging.MetaDebugLog(e); - } + var egid = PlacementEngine.PlaceBlock(block, color, darkness, + position, uscale, scale, player, rotation, out var initializer); + var bl = New(egid.entityID, egid.groupID); + bl.InitData.Group = BlockEngine.InitGroup(initializer); + Placed += bl.OnPlacedInit; + return bl; } return null; @@ -109,7 +95,7 @@ namespace GamecraftModdingAPI /// The block object public static Block GetLastPlacedBlock() { - return new Block(BlockIdentifiers.LatestBlockID); + return New(BlockIdentifiers.LatestBlockID); } /// @@ -130,9 +116,91 @@ namespace GamecraftModdingAPI remove => BlockEventsEngine.Removed -= value; } + private static Dictionary> initializers = new Dictionary>(); + + private static Dictionary typeToGroup = + new Dictionary + { + {typeof(ConsoleBlock), new[] {CommonExclusiveGroups.BUILD_CONSOLE_BLOCK_GROUP}}, + {typeof(Motor), new[] {CommonExclusiveGroups.BUILD_MOTOR_BLOCK_GROUP}}, + {typeof(Piston), new[] {CommonExclusiveGroups.BUILD_PISTON_BLOCK_GROUP}}, + {typeof(Servo), new[] {CommonExclusiveGroups.BUILD_SERVO_BLOCK_GROUP}}, + { + typeof(SpawnPoint), + new[] + { + CommonExclusiveGroups.BUILD_SPAWNPOINT_BLOCK_GROUP, + CommonExclusiveGroups.BUILD_BUILDINGSPAWN_BLOCK_GROUP + } + }, + {typeof(TextBlock), new[] {CommonExclusiveGroups.BUILD_TEXT_BLOCK_GROUP}}, + {typeof(Timer), new[] {CommonExclusiveGroups.BUILD_TIMER_BLOCK_GROUP}} + }; + + /// + /// Constructs a new instance of T with the given ID and group using dynamically created delegates. + /// It's equivalent to new T(EGID) with a minimal overhead thanks to caching the created delegates. + /// + /// The block ID + /// The block group + /// The block's type or Block itself + /// An instance of the provided type + /// The block group doesn't match or cannot be found + /// The block class doesn't have the needed constructor + private static T New(uint id, ExclusiveGroupStruct? group = null) where T : Block + { + var type = typeof(T); + EGID egid; + if (!group.HasValue) + { + if (typeToGroup.TryGetValue(type, out var gr) && gr.Length == 1) + egid = new EGID(id, gr[0]); + else + egid = BlockEngine.FindBlockEGID(id) ?? throw new BlockTypeException("Could not find block group!"); + } + else + { + egid = new EGID(id, group.Value); + if (typeToGroup.TryGetValue(type, out var gr) + && gr.All(egs => egs != group.Value)) //If this subclass has a specific group, then use that - so Block should still work + throw new BlockTypeException($"Incompatible block type! Type {type.Name} belongs to group {gr.Select(g => g.ToString()).Aggregate((a, b) => a + ", " + b)} instead of {group.Value}"); + } + + if (initializers.TryGetValue(type, out var func)) + { + var bl = (T) func(egid); + return bl; + } + + //https://stackoverflow.com/a/10593806/2703239 + var ctor = type.GetConstructor(new[] {typeof(EGID)}); + if (ctor == null) + throw new MissingMethodException("There is no constructor with an EGID parameter for this object"); + DynamicMethod dynamic = new DynamicMethod(string.Empty, + type, + new[] {typeof(EGID)}, + type); + ILGenerator il = dynamic.GetILGenerator(); + + il.DeclareLocal(type); + il.Emit(OpCodes.Ldarg_0); //Load EGID and pass to constructor + il.Emit(OpCodes.Newobj, ctor); //Call constructor + //il.Emit(OpCodes.Stloc_0); - doesn't seem like we need these + //il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ret); + + func = (Func) dynamic.CreateDelegate(typeof(Func)); + initializers.Add(type, func); + var block = (T) func(egid); + return block; + } + public Block(EGID id) { Id = id; + if (typeToGroup.TryGetValue(GetType(), out var groups) && groups.All(gr => gr != id.groupID)) + throw new BlockTypeException("The block has the wrong group! The type is " + GetType() + + " while the group is " + id.groupID); } /// @@ -147,16 +215,18 @@ namespace GamecraftModdingAPI public EGID Id { get; } + internal BlockEngine.BlockInitData InitData; + /// /// The block's current position or zero if the block no longer exists. /// A block is 0.2 wide by default in terms of position. /// public float3 Position { - get => Exists ? MovementEngine.GetPosition(Id) : float3.zero; + get => MovementEngine.GetPosition(Id, InitData); set { - if (Exists) MovementEngine.MoveBlock(Id, value); + MovementEngine.MoveBlock(Id, InitData, value); } } @@ -165,10 +235,10 @@ namespace GamecraftModdingAPI /// public float3 Rotation { - get => Exists ? RotationEngine.GetRotation(Id) : float3.zero; + get => RotationEngine.GetRotation(Id, InitData); set { - if (Exists) RotationEngine.RotateBlock(Id, value); + RotationEngine.RotateBlock(Id, InitData, value); } } @@ -178,12 +248,11 @@ namespace GamecraftModdingAPI /// public float3 Scale { - get => BlockEngine.GetBlockInfo(Id).scale; + get => BlockEngine.GetBlockInfo(this, (ScalingEntityStruct st) => st.scale); set { + BlockEngine.SetBlockInfo(this, (ref ScalingEntityStruct st, float3 val) => st.scale = val, value); if (!Exists) return; //UpdateCollision needs the block to exist - ref var scaling = ref BlockEngine.GetBlockInfo(Id); - scaling.scale = value; ScalingEngine.UpdateCollision(Id); } } @@ -194,11 +263,11 @@ namespace GamecraftModdingAPI /// public int UniformScale { - get => BlockEngine.GetBlockInfo(Id).scaleFactor; + get => BlockEngine.GetBlockInfo(this, (UniformBlockScaleEntityStruct st) => st.scaleFactor); set { - ref var scaleStruct = ref BlockEngine.GetBlockInfo(Id); - scaleStruct.scaleFactor = value; + BlockEngine.SetBlockInfo(this, (ref UniformBlockScaleEntityStruct st, int val) => st.scaleFactor = val, + value); Scale = new float3(value, value, value); } } @@ -210,8 +279,7 @@ namespace GamecraftModdingAPI { get { - var id = (BlockIDs) BlockEngine.GetBlockInfo(Id, out var exists).DBID; - return exists ? id : BlockIDs.Invalid; + return BlockEngine.GetBlockInfo(this, (DBEntityStruct st) => (BlockIDs) st.DBID, BlockIDs.Invalid); } } @@ -222,17 +290,19 @@ namespace GamecraftModdingAPI { get { - byte index = BlockEngine.GetBlockInfo(Id, out var exists).indexInPalette; - if (!exists) index = byte.MaxValue; + byte index = BlockEngine.GetBlockInfo(this, (ColourParameterEntityStruct st) => st.indexInPalette, + byte.MaxValue); return new BlockColor(index); } set { - ref var color = ref BlockEngine.GetBlockInfo(Id); - color.indexInPalette = (byte)(value.Color + value.Darkness * 10); - color.overridePaletteColour = false; - color.needsUpdate = true; - BlockEngine.SetBlockColorFromPalette(ref color); + BlockEngine.SetBlockInfo(this, (ref ColourParameterEntityStruct color, BlockColor val) => + { + color.indexInPalette = (byte) (val.Color + val.Darkness * 10); + color.overridePaletteColour = false; + color.needsUpdate = true; + BlockEngine.SetBlockColorFromPalette(ref color); + }, value); } } @@ -241,32 +311,37 @@ namespace GamecraftModdingAPI /// public float4 CustomColor { - get => BlockEngine.GetBlockInfo(Id).overriddenColour; + get => BlockEngine.GetBlockInfo(this, (ColourParameterEntityStruct st) => st.overriddenColour); set { - ref var color = ref BlockEngine.GetBlockInfo(Id); - color.overriddenColour = value; - color.overridePaletteColour = true; - color.needsUpdate = true; + BlockEngine.SetBlockInfo(this, (ref ColourParameterEntityStruct color, float4 val) => + { + color.overriddenColour = val; + color.overridePaletteColour = true; + color.needsUpdate = true; + }, value); } } /// - /// The short text displayed on the block if applicable, or null. + /// The text displayed on the block if applicable, or null. /// Setting it is temporary to the session, it won't be saved. /// public string Label { - get => BlockEngine.GetBlockInfo(Id).textLabelComponent?.text; + get => BlockEngine.GetBlockInfo(this, (TextLabelEntityViewStruct st) => st.textLabelComponent?.text); set { - ref var text = ref BlockEngine.GetBlockInfo(Id); - if (text.textLabelComponent != null) text.textLabelComponent.text = value; + BlockEngine.SetBlockInfo(this, (ref TextLabelEntityViewStruct text, string val) => + { + if (text.textLabelComponent != null) text.textLabelComponent.text = val; + }, value); } } /// /// 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. /// public bool Exists => BlockEngine.BlockExists(Id); @@ -282,14 +357,21 @@ namespace GamecraftModdingAPI public bool Remove() => RemovalEngine.RemoveBlock(Id); /// - /// Returns the rigid body of the cluster of blocks this one belongs to during simulation. + /// Returns the rigid body of the chunk of blocks this one belongs to during simulation. /// Can be used to apply forces or move the block around while the simulation is running. /// - /// The SimBody of the cluster or null if the block doesn't exist. + /// The SimBody of the chunk or null if the block doesn't exist. public SimBody GetSimBody() { - uint id = BlockEngine.GetBlockInfo(Id, out var exists).machineRigidBodyId; - return exists ? new SimBody(id) : null; + return BlockEngine.GetBlockInfo(this, + (GridConnectionsEntityStruct st) => new SimBody(st.machineRigidBodyId)); + } + + private void OnPlacedInit(object sender, BlockPlacedRemovedEventArgs e) + { //Member method instead of lambda to avoid constantly creating delegates + if (e.ID != Id) return; + Placed -= OnPlacedInit; //And we can reference it + InitData = default; //Remove initializer as it's no longer valid - if the block gets removed it shouldn't be used again } public override string ToString() @@ -344,12 +426,11 @@ namespace GamecraftModdingAPI // C# can't cast to a child of Block unless the object was originally that child type // And C# doesn't let me make implicit cast operators for child types // So thanks to Microsoft, we've got this horrible implementation using reflection - ConstructorInfo ctor = typeof(T).GetConstructor(types: new System.Type[] { typeof(EGID) }); - if (ctor == null) - { - throw new BlockSpecializationException("Specialized block constructor does not accept an EGID"); - } - return (T)ctor.Invoke(new object[] { Id }); + + //Lets improve that using delegates + var block = New(Id.entityID, Id.groupID); + block.InitData = this.InitData; + return block; } #if DEBUG diff --git a/GamecraftModdingAPI/Blocks/BlockEngine.cs b/GamecraftModdingAPI/Blocks/BlockEngine.cs index 81a1630..f97e405 100644 --- a/GamecraftModdingAPI/Blocks/BlockEngine.cs +++ b/GamecraftModdingAPI/Blocks/BlockEngine.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Gamecraft.Wires; @@ -10,14 +11,13 @@ using Svelto.DataStructures; using Svelto.ECS; using GamecraftModdingAPI.Engines; -using GamecraftModdingAPI.Utility; namespace GamecraftModdingAPI.Blocks { /// /// Engine for executing general block actions /// - public class BlockEngine : IApiEngine + public partial class BlockEngine : IApiEngine { public string Name { get; } = "GamecraftModdingAPIBlockGameEngine"; @@ -25,8 +25,6 @@ namespace GamecraftModdingAPI.Blocks public bool isRemovable => false; - internal bool Synced = true; - public void Dispose() { } @@ -61,66 +59,55 @@ namespace GamecraftModdingAPI.Blocks color.paletteColour = paletteEntry.Colour; } - /// - /// Get a struct of a block. Can be used to set properties. - /// Returns a default value if not found. - /// - /// The block's ID - /// The struct to query - /// An editable reference to the struct public ref T GetBlockInfo(EGID blockID) where T : struct, IEntityComponent { - if (!Synced) - { - Sync(); - Synced = true; - } if (entitiesDB.Exists(blockID)) return ref entitiesDB.QueryEntity(blockID); T[] structHolder = new T[1]; //Create something that can be referenced return ref structHolder[0]; //Gets a default value automatically } - /// - /// Get a struct of a block. Can be used to set properties. - /// Returns a default value if not found. - /// - /// The block's ID - /// Whether the specified struct exists for the block - /// The struct to query - /// An editable reference to the struct - public ref T GetBlockInfo(EGID blockID, out bool exists) where T : struct, IEntityComponent + public U GetBlockInfo(Block block, Func getter, + U def = default) where T : struct, IEntityComponent { - if (!Synced) + if (entitiesDB.Exists(block.Id)) + return getter(entitiesDB.QueryEntity(block.Id)); + if (block.InitData.Group == null) return def; + var initializer = new EntityComponentInitializer(block.Id, block.InitData.Group); + if (initializer.Has()) + return getter(initializer.Get()); + return def; + } + + public delegate void Setter(ref T component, U value) where T : struct, IEntityComponent; + + public void SetBlockInfo(Block block, Setter setter, U value) where T : struct, IEntityComponent + { + if (entitiesDB.Exists(block.Id)) + setter(ref entitiesDB.QueryEntity(block.Id), value); + else if (block.InitData.Group != null) { - Sync(); - Synced = true; + var initializer = new EntityComponentInitializer(block.Id, block.InitData.Group); + T component = initializer.Has() ? initializer.Get() : default; + ref T structRef = ref component; + setter(ref structRef, value); + initializer.Init(structRef); } - exists = entitiesDB.Exists(blockID); - if (exists) - return ref entitiesDB.QueryEntity(blockID); - T[] structHolder = new T[1]; - return ref structHolder[0]; } - public bool BlockExists(EGID id) + public bool BlockExists(EGID blockID) { - if (!Synced) - { - Sync(); - Synced = true; - } - return entitiesDB.Exists(id); + return entitiesDB.Exists(blockID); } - public bool GetBlockInfoExists(EGID blockID) where T : struct, IEntityComponent + public bool GetBlockInfoExists(Block block) where T : struct, IEntityComponent { - if (!Synced) - { - Sync(); - Synced = true; - } - return entitiesDB.Exists(blockID); + if (entitiesDB.Exists(block.Id)) + return true; + if (block.InitData.Group == null) + return false; + var init = new EntityComponentInitializer(block.Id, block.InitData.Group); + return init.Has(); } public SimBody[] GetSimBodiesFromID(byte id) @@ -183,17 +170,6 @@ namespace GamecraftModdingAPI.Blocks return null; } - /// - /// Synchronize newly created entity components with entities DB. - /// This forces a partial game tick, so it may be slow. - /// This also has the potential to make Gamecraft unstable. - /// Use this sparingly. - /// - private static void Sync() - { - DeterministicStepCompositionRootPatch.SubmitEntitiesNow(); - } - #if DEBUG public EntitiesDB GetEntitiesDB() { diff --git a/GamecraftModdingAPI/Blocks/BlockEngineInit.cs b/GamecraftModdingAPI/Blocks/BlockEngineInit.cs new file mode 100644 index 0000000..70f713a --- /dev/null +++ b/GamecraftModdingAPI/Blocks/BlockEngineInit.cs @@ -0,0 +1,52 @@ +using System; +using System.Linq.Expressions; + +using Svelto.DataStructures; +using Svelto.ECS; +using Svelto.ECS.Internal; + +namespace GamecraftModdingAPI.Blocks +{ + public partial class BlockEngine + { + /// + /// Holds information needed to construct a component initializer + /// + internal struct BlockInitData + { + public FasterDictionary, ITypeSafeDictionary> Group; + } + + internal delegate FasterDictionary, ITypeSafeDictionary> GetInitGroup( + EntityComponentInitializer initializer); + + /// + /// Accesses the group field of the initializer + /// + internal GetInitGroup InitGroup = CreateAccessor("_group"); + + //https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection + internal static TDelegate CreateAccessor(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(returnExpr, $"Access{paramType.Name}_{memberName}", new[] {objParam}); + return lambda.Compile(); + } + } +} \ No newline at end of file diff --git a/GamecraftModdingAPI/Blocks/BlockEventsEngine.cs b/GamecraftModdingAPI/Blocks/BlockEventsEngine.cs index 3c34b65..1e5ce21 100644 --- a/GamecraftModdingAPI/Blocks/BlockEventsEngine.cs +++ b/GamecraftModdingAPI/Blocks/BlockEventsEngine.cs @@ -1,9 +1,11 @@ using System; -using GamecraftModdingAPI.Engines; -using GamecraftModdingAPI.Utility; + using RobocraftX.Common; using Svelto.ECS; +using GamecraftModdingAPI.Engines; +using GamecraftModdingAPI.Utility; + namespace GamecraftModdingAPI.Blocks { public class BlockEventsEngine : IReactionaryEngine @@ -16,6 +18,7 @@ namespace GamecraftModdingAPI.Blocks } public EntitiesDB entitiesDB { get; set; } + public void Dispose() { } @@ -23,14 +26,21 @@ namespace GamecraftModdingAPI.Blocks public string Name { get; } = "GamecraftModdingAPIBlockEventsEngine"; public bool isRemovable { get; } = false; + private bool shouldAddRemove; public void Add(ref DBEntityStruct entityComponent, EGID egid) { - ExceptionUtil.InvokeEvent(Placed, this, new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID}); + if (!(shouldAddRemove = !shouldAddRemove)) + return; + ExceptionUtil.InvokeEvent(Placed, this, + new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID}); } public void Remove(ref DBEntityStruct entityComponent, EGID egid) { - ExceptionUtil.InvokeEvent(Removed, this, new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID}); + if (!(shouldAddRemove = !shouldAddRemove)) + return; + ExceptionUtil.InvokeEvent(Removed, this, + new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID}); } } @@ -38,5 +48,8 @@ namespace GamecraftModdingAPI.Blocks { public EGID ID; public BlockIDs Type; + private Block block; + + public Block Block => block ?? (block = new Block(ID)); } } \ No newline at end of file diff --git a/GamecraftModdingAPI/Blocks/BlockIDs.cs b/GamecraftModdingAPI/Blocks/BlockIDs.cs index b0eca31..596fb38 100644 --- a/GamecraftModdingAPI/Blocks/BlockIDs.cs +++ b/GamecraftModdingAPI/Blocks/BlockIDs.cs @@ -6,7 +6,7 @@ namespace GamecraftModdingAPI.Blocks public enum BlockIDs : ushort { /// - /// A custom value for the API. Doesn't exist for Gamecraft. + /// Called "nothing" in Gamecraft. (DBID.NOTHING) /// Invalid = ushort.MaxValue, AluminiumCube = 0, diff --git a/GamecraftModdingAPI/Blocks/BlockTests.cs b/GamecraftModdingAPI/Blocks/BlockTests.cs index ed42024..85328d2 100644 --- a/GamecraftModdingAPI/Blocks/BlockTests.cs +++ b/GamecraftModdingAPI/Blocks/BlockTests.cs @@ -22,20 +22,19 @@ namespace GamecraftModdingAPI.Blocks } [APITestCase(TestType.EditMode)] - public static void TestSync() + public static void TestInitProperty() { Block newBlock = Block.PlaceNew(BlockIDs.AluminiumCube, Unity.Mathematics.float3.zero + 2); if (!Assert.CloseTo(newBlock.Position, (Unity.Mathematics.float3.zero + 2), $"Newly placed block at {newBlock.Position} is expected at {Unity.Mathematics.float3.zero + 2}.", "Newly placed block position matches.")) return; - Assert.Equal(newBlock.Exists, true, "Newly placed block does not exist, possibly because Sync() skipped/missed/failed.", "Newly placed block exists, Sync() successful."); + //Assert.Equal(newBlock.Exists, true, "Newly placed block does not exist, possibly because Sync() skipped/missed/failed.", "Newly placed block exists, Sync() successful."); } [APITestCase(TestType.EditMode)] public static void TestTextBlock() { - Block newBlock = Block.PlaceNew(BlockIDs.TextBlock, Unity.Mathematics.float3.zero + 1); TextBlock textBlock = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler - Assert.Errorless(() => { textBlock = newBlock.Specialise(); }, "Block.Specialize() raised an exception: ", "Block.Specialize() completed without issue."); - if (!Assert.NotNull(textBlock, "Block.Specialize() returned null, possibly because it failed silently.", "Specialized TextBlock is not null.")) return; + Assert.Errorless(() => { textBlock = Block.PlaceNew(BlockIDs.TextBlock, Unity.Mathematics.float3.zero + 1); }, "Block.PlaceNew() raised an exception: ", "Block.PlaceNew() completed without issue."); + if (!Assert.NotNull(textBlock, "Block.PlaceNew() returned null, possibly because it failed silently.", "Specialized TextBlock is not null.")) return; if (!Assert.NotNull(textBlock.Text, "TextBlock.Text is null, possibly because it failed silently.", "TextBlock.Text is not null.")) return; if (!Assert.NotNull(textBlock.TextBlockId, "TextBlock.TextBlockId is null, possibly because it failed silently.", "TextBlock.TextBlockId is not null.")) return; } diff --git a/GamecraftModdingAPI/Blocks/ConsoleBlock.cs b/GamecraftModdingAPI/Blocks/ConsoleBlock.cs index 6d4217d..edf3e76 100644 --- a/GamecraftModdingAPI/Blocks/ConsoleBlock.cs +++ b/GamecraftModdingAPI/Blocks/ConsoleBlock.cs @@ -12,79 +12,64 @@ namespace GamecraftModdingAPI.Blocks { public class ConsoleBlock : Block { - public static ConsoleBlock PlaceNew(float3 position, - float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, - int uscale = 1, float3 scale = default, Player player = null) + public ConsoleBlock(EGID id): base(id) { - if (PlacementEngine.IsInGame && GameState.IsBuildMode()) - { - EGID id = PlacementEngine.PlaceBlock(BlockIDs.ConsoleBlock, color, darkness, - position, uscale, scale, player, rotation); - return new ConsoleBlock(id); - } - - return null; } - public ConsoleBlock(EGID id): base(id) + public ConsoleBlock(uint id): base(new EGID(id, CommonExclusiveGroups.BUILD_CONSOLE_BLOCK_GROUP)) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } - } - - public ConsoleBlock(uint id): base(new EGID(id, CommonExclusiveGroups.BUILD_CONSOLE_BLOCK_GROUP)) - { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } // custom console block properties + /// + /// Setting a nonexistent command will crash the game when switching to simulation + /// public string Command { get { - return BlockEngine.GetBlockInfo(Id).commandName; + return BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.commandName); } set { - BlockEngine.GetBlockInfo(Id).commandName.Set(value); + BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.commandName.Set(val), + value); } } public string Arg1 { - get => BlockEngine.GetBlockInfo(Id).arg1; + get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg1); set { - BlockEngine.GetBlockInfo(Id).arg1.Set(value); + BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg1.Set(val), + value); } } public string Arg2 { - get => BlockEngine.GetBlockInfo(Id).arg2; + get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg2); - set - { - BlockEngine.GetBlockInfo(Id).arg2.Set(value); - } + set + { + BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg2.Set(val), + value); + } } public string Arg3 { - get => BlockEngine.GetBlockInfo(Id).arg3; + get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg3); - set - { - BlockEngine.GetBlockInfo(Id).arg3.Set(value); - } + set + { + BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg3.Set(val), + value); + } } } } diff --git a/GamecraftModdingAPI/Blocks/Motor.cs b/GamecraftModdingAPI/Blocks/Motor.cs index 55f3649..fdadd26 100644 --- a/GamecraftModdingAPI/Blocks/Motor.cs +++ b/GamecraftModdingAPI/Blocks/Motor.cs @@ -11,43 +11,12 @@ namespace GamecraftModdingAPI.Blocks { public class Motor : Block { - /// - /// Places a new motor. - /// Any valid motor type is accepted. - /// This re-implements Block.PlaceNew(...) - /// - public static new Motor PlaceNew(BlockIDs block, float3 position, - float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, - int uscale = 1, float3 scale = default, Player player = null) - { - if (!(block == BlockIDs.MotorS || block == BlockIDs.MotorM)) - { - throw new BlockTypeException($"Block is not a {typeof(Motor).Name} block"); - } - if (PlacementEngine.IsInGame && GameState.IsBuildMode()) - { - EGID id = PlacementEngine.PlaceBlock(block, color, darkness, - position, uscale, scale, player, rotation); - return new Motor(id); - } - - return null; - } - public Motor(EGID id) : base(id) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } public Motor(uint id): base(new EGID(id, CommonExclusiveGroups.BUILD_MOTOR_BLOCK_GROUP)) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } // custom motor properties @@ -59,13 +28,12 @@ namespace GamecraftModdingAPI.Blocks { get { - return BlockEngine.GetBlockInfo(Id).maxVelocity; + return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.maxVelocity); } set { - ref MotorReadOnlyStruct motor = ref BlockEngine.GetBlockInfo(Id); - motor.maxVelocity = value; + BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, float val) => st.maxVelocity = val, value); } } @@ -76,13 +44,12 @@ namespace GamecraftModdingAPI.Blocks { get { - return BlockEngine.GetBlockInfo(Id).maxForce; + return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.maxForce); } set { - ref MotorReadOnlyStruct motor = ref BlockEngine.GetBlockInfo(Id); - motor.maxForce = value; + BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, float val) => st.maxForce = val, value); } } @@ -93,13 +60,12 @@ namespace GamecraftModdingAPI.Blocks { get { - return BlockEngine.GetBlockInfo(Id).reverse; + return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.reverse); } set { - ref MotorReadOnlyStruct motor = ref BlockEngine.GetBlockInfo(Id); - motor.reverse = value; + BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, bool val) => st.reverse = val, value); } } } diff --git a/GamecraftModdingAPI/Blocks/MovementEngine.cs b/GamecraftModdingAPI/Blocks/MovementEngine.cs index 0ff119a..a4ac0fa 100644 --- a/GamecraftModdingAPI/Blocks/MovementEngine.cs +++ b/GamecraftModdingAPI/Blocks/MovementEngine.cs @@ -35,8 +35,17 @@ namespace GamecraftModdingAPI.Blocks // implementations for Movement static class - public float3 MoveBlock(EGID blockID, float3 vector) + internal float3 MoveBlock(EGID blockID, BlockEngine.BlockInitData data, float3 vector) { + if (!entitiesDB.Exists(blockID)) + { + if (data.Group == null) return float3.zero; + var init = new EntityComponentInitializer(blockID, data.Group); + init.Init(new PositionEntityStruct {position = vector}); + init.Init(new GridRotationStruct {position = vector}); + init.Init(new LocalTransformEntityStruct {position = vector}); + return vector; + } ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity(blockID); ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity(blockID); ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity(blockID); @@ -56,8 +65,14 @@ namespace GamecraftModdingAPI.Blocks return posStruct.position; } - public float3 GetPosition(EGID blockID) + internal float3 GetPosition(EGID blockID, BlockEngine.BlockInitData data) { + if (!entitiesDB.Exists(blockID)) + { + if (data.Group == null) return float3.zero; + var init = new EntityComponentInitializer(blockID, data.Group); + return init.Has() ? init.Get().position : float3.zero; + } ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity(blockID); return posStruct.position; } diff --git a/GamecraftModdingAPI/Blocks/ObjectIdentifier.cs b/GamecraftModdingAPI/Blocks/ObjectIdentifier.cs index 9af96c2..0dc835a 100644 --- a/GamecraftModdingAPI/Blocks/ObjectIdentifier.cs +++ b/GamecraftModdingAPI/Blocks/ObjectIdentifier.cs @@ -8,27 +8,22 @@ namespace GamecraftModdingAPI.Blocks { public ObjectIdentifier(EGID id) : base(id) { - if (!BlockEngine.GetBlockInfoExists(Id)) - { - throw new BlockTypeException($"Block is not a {GetType().Name} block"); - } } public ObjectIdentifier(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_OBJID_BLOCK_GROUP)) { - if (!BlockEngine.GetBlockInfoExists(Id)) - { - throw new BlockTypeException($"Block is not a {GetType().Name} block"); - } } public char Identifier { - get => (char) (BlockEngine.GetBlockInfo(Id).objectId + 'A'); + get => (char) BlockEngine.GetBlockInfo(this, (ObjectIdEntityStruct st) => st.objectId + 'A'); set { - BlockEngine.GetBlockInfo(Id).objectId = (byte) (value - 'A'); - Label = value + ""; //The label isn't updated automatically + BlockEngine.SetBlockInfo(this, (ref ObjectIdEntityStruct st, char val) => + { + st.objectId = (byte) (val - 'A'); + Label = val + ""; //The label isn't updated automatically + }, value); } } @@ -37,7 +32,7 @@ namespace GamecraftModdingAPI.Blocks /// public byte SimID { - get => BlockEngine.GetBlockInfo(Id).simObjectId; + get => BlockEngine.GetBlockInfo(this, (ObjectIdEntityStruct st) => st.simObjectId); } /// diff --git a/GamecraftModdingAPI/Blocks/Piston.cs b/GamecraftModdingAPI/Blocks/Piston.cs index b96a806..aed8c33 100644 --- a/GamecraftModdingAPI/Blocks/Piston.cs +++ b/GamecraftModdingAPI/Blocks/Piston.cs @@ -11,43 +11,12 @@ namespace GamecraftModdingAPI.Blocks { public class Piston : Block { - /// - /// Places a new piston. - /// Any valid piston type is accepted. - /// This re-implements Block.PlaceNew(...) - /// - public static new Piston PlaceNew(BlockIDs block, float3 position, - float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, - int uscale = 1, float3 scale = default, Player player = null) + public Piston(EGID id) : base(id) { - if (!(block == BlockIDs.ServoPiston || block == BlockIDs.StepperPiston || block == BlockIDs.PneumaticPiston)) - { - throw new BlockTypeException($"Block is not a {typeof(Piston).Name} block"); - } - if (PlacementEngine.IsInGame && GameState.IsBuildMode()) - { - EGID id = PlacementEngine.PlaceBlock(block, color, darkness, - position, uscale, scale, player, rotation); - return new Piston(id); - } - - return null; - } - - public Piston(EGID id) : base(id) - { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } public Piston(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_PISTON_BLOCK_GROUP)) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } // custom piston properties @@ -56,13 +25,13 @@ namespace GamecraftModdingAPI.Blocks /// The piston's max extension distance. /// public float MaximumExtension - { - get => BlockEngine.GetBlockInfo(Id).maxDeviation; + { + get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.maxDeviation); set { - ref PistonReadOnlyStruct piston = ref BlockEngine.GetBlockInfo(Id); - piston.maxDeviation = value; + BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.maxDeviation = val, + value); } } @@ -70,13 +39,12 @@ namespace GamecraftModdingAPI.Blocks /// The piston's max extension force. /// public float MaximumForce - { - get => BlockEngine.GetBlockInfo(Id).maxForce; + { + get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.maxForce); set { - ref PistonReadOnlyStruct piston = ref BlockEngine.GetBlockInfo(Id); - piston.maxForce = value; + BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.maxForce = val, value); } } } diff --git a/GamecraftModdingAPI/Blocks/PlacementEngine.cs b/GamecraftModdingAPI/Blocks/PlacementEngine.cs index 2297d90..e111598 100644 --- a/GamecraftModdingAPI/Blocks/PlacementEngine.cs +++ b/GamecraftModdingAPI/Blocks/PlacementEngine.cs @@ -40,15 +40,16 @@ namespace GamecraftModdingAPI.Blocks private static BlockEntityFactory _blockEntityFactory; //Injected from PlaceBlockEngine public EGID PlaceBlock(BlockIDs block, BlockColors color, byte darkness, float3 position, int uscale, - float3 scale, Player player, float3 rotation) + float3 scale, Player player, float3 rotation, out EntityComponentInitializer initializer) { //It appears that only the non-uniform scale has any visible effect, but if that's not given here it will be set to the uniform one if (darkness > 9) throw new Exception("That is too dark. Make sure to use 0-9 as darkness. (0 is default.)"); - return BuildBlock((ushort) block, (byte) (color + darkness * 10), position, uscale, scale, rotation, + initializer = BuildBlock((ushort) block, (byte) (color + darkness * 10), position, uscale, scale, rotation, (player ?? new Player(PlayerType.Local)).Id); + return initializer.EGID; } - private EGID BuildBlock(ushort block, byte color, float3 position, int uscale, float3 scale, float3 rot, uint playerId) + private EntityComponentInitializer BuildBlock(ushort block, byte color, float3 position, int uscale, float3 scale, float3 rot, uint playerId) { if (_blockEntityFactory == null) throw new Exception("The factory is null."); @@ -106,8 +107,7 @@ namespace GamecraftModdingAPI.Blocks ref PickedBlockExtraDataStruct pickedBlock = ref entitiesDB.QueryEntity(playerEGID); pickedBlock.placedBlockEntityID = structInitializer.EGID; pickedBlock.placedBlockWasAPickedBlock = false; - Block.BlockEngine.Synced = false; // Block entities will need to be submitted before properties can be used - return structInitializer.EGID; + return structInitializer; } public string Name { get; } = "GamecraftModdingAPIPlacementGameEngine"; diff --git a/GamecraftModdingAPI/Blocks/RotationEngine.cs b/GamecraftModdingAPI/Blocks/RotationEngine.cs index e9cd1ef..ca97874 100644 --- a/GamecraftModdingAPI/Blocks/RotationEngine.cs +++ b/GamecraftModdingAPI/Blocks/RotationEngine.cs @@ -35,23 +35,32 @@ namespace GamecraftModdingAPI.Blocks // implementations for Rotation static class - public float3 RotateBlock(EGID blockID, Vector3 vector) + internal float3 RotateBlock(EGID blockID, BlockEngine.BlockInitData data, Vector3 vector) { + if (!entitiesDB.Exists(blockID)) + { + if (data.Group == null) return float3.zero; + var init = new EntityComponentInitializer(blockID, data.Group); + init.Init(new RotationEntityStruct {rotation = new Quaternion {eulerAngles = vector}}); + init.Init(new GridRotationStruct {rotation = new Quaternion {eulerAngles = vector}}); + init.Init(new LocalTransformEntityStruct {rotation = new Quaternion {eulerAngles = vector}}); + return vector; + } ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntity(blockID); ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity(blockID); ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity(blockID); ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity(blockID); // main (persistent) position - Quaternion newRotation = (Quaternion)rotStruct.rotation; - newRotation.eulerAngles += vector; - rotStruct.rotation = (quaternion)newRotation; + Quaternion newRotation = rotStruct.rotation; + newRotation.eulerAngles = vector; + rotStruct.rotation = newRotation; // placement grid rotation - Quaternion newGridRotation = (Quaternion)gridStruct.rotation; - newGridRotation.eulerAngles += vector; - gridStruct.rotation = (quaternion)newGridRotation; + Quaternion newGridRotation = gridStruct.rotation; + newGridRotation.eulerAngles = vector; + gridStruct.rotation = newGridRotation; // rendered position - Quaternion newTransRotation = (Quaternion)rotStruct.rotation; - newTransRotation.eulerAngles += vector; + Quaternion newTransRotation = rotStruct.rotation; + newTransRotation.eulerAngles = vector; transStruct.rotation = newTransRotation; // collision position FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Unity.Transforms.Rotation @@ -63,8 +72,17 @@ namespace GamecraftModdingAPI.Blocks } - public float3 GetRotation(EGID blockID) + internal float3 GetRotation(EGID blockID, BlockEngine.BlockInitData data) { + if (!entitiesDB.Exists(blockID)) + { + if (data.Group == null) return float3.zero; + var init = new EntityComponentInitializer(blockID, data.Group); + return init.Has() + ? (float3) ((Quaternion) init.Get().rotation).eulerAngles + : float3.zero; + } + ref RotationEntityStruct rotStruct = ref entitiesDB.QueryEntity(blockID); return ((Quaternion) rotStruct.rotation).eulerAngles; } diff --git a/GamecraftModdingAPI/Blocks/Servo.cs b/GamecraftModdingAPI/Blocks/Servo.cs index ef7225b..730749a 100644 --- a/GamecraftModdingAPI/Blocks/Servo.cs +++ b/GamecraftModdingAPI/Blocks/Servo.cs @@ -11,43 +11,12 @@ namespace GamecraftModdingAPI.Blocks { public class Servo : Block { - /// - /// Places a new servo. - /// Any valid servo type is accepted. - /// This re-implements Block.PlaceNew(...) - /// - public static new Servo PlaceNew(BlockIDs block, float3 position, - float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, - int uscale = 1, float3 scale = default, Player player = null) - { - if (!(block == BlockIDs.ServoAxle || block == BlockIDs.ServoHinge || block == BlockIDs.ServoPiston)) - { - throw new BlockTypeException($"Block is not a {nameof(Servo)} block"); - } - if (PlacementEngine.IsInGame && GameState.IsBuildMode()) - { - EGID id = PlacementEngine.PlaceBlock(block, color, darkness, - position, uscale, scale, player, rotation); - return new Servo(id); - } - - return null; - } - public Servo(EGID id) : base(id) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } public Servo(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_SERVO_BLOCK_GROUP)) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } // custom servo properties @@ -56,13 +25,12 @@ namespace GamecraftModdingAPI.Blocks /// The servo's minimum angle. /// public float MinimumAngle - { - get => BlockEngine.GetBlockInfo(Id).minDeviation; + { + get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.minDeviation); set { - ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo(Id); - servo.minDeviation = value; + BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.minDeviation = val, value); } } @@ -71,13 +39,12 @@ namespace GamecraftModdingAPI.Blocks /// public float MaximumAngle { - get => BlockEngine.GetBlockInfo(Id).maxDeviation; + get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.maxDeviation); - set - { - ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo(Id); - servo.maxDeviation = value; - } + set + { + BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.maxDeviation = val, value); + } } /// @@ -85,13 +52,12 @@ namespace GamecraftModdingAPI.Blocks /// public float MaximumForce { - get => BlockEngine.GetBlockInfo(Id).maxForce; + get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.maxForce); - set - { - ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo(Id); - servo.maxForce = value; - } + set + { + BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.maxForce = val, value); + } } /// @@ -99,13 +65,12 @@ namespace GamecraftModdingAPI.Blocks /// public bool Reverse { - get => BlockEngine.GetBlockInfo(Id).reverse; + get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.reverse); - set - { - ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo(Id); - servo.reverse = value; - } + set + { + BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, bool val) => st.reverse = val, value); + } } } } diff --git a/GamecraftModdingAPI/Blocks/SignalingBlock.cs b/GamecraftModdingAPI/Blocks/SignalingBlock.cs index f8006f6..149e450 100644 --- a/GamecraftModdingAPI/Blocks/SignalingBlock.cs +++ b/GamecraftModdingAPI/Blocks/SignalingBlock.cs @@ -14,28 +14,9 @@ namespace GamecraftModdingAPI.Blocks /// public class SignalingBlock : Block { - /// - /// Places a new signaling block. - /// Any valid functional block type with IO ports will work. - /// This re-implements Block.PlaceNew(...) - /// - public static new SignalingBlock PlaceNew(BlockIDs block, float3 position, - float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, - int uscale = 1, float3 scale = default, Player player = null) - { - if (PlacementEngine.IsInGame && GameState.IsBuildMode()) - { - EGID id = PlacementEngine.PlaceBlock(block, color, darkness, - position, uscale, scale, player, rotation); - return new SignalingBlock(id); - } - - return null; - } - public SignalingBlock(EGID id) : base(id) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) + if (!BlockEngine.GetBlockInfoExists(this)) { throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); } @@ -43,17 +24,12 @@ namespace GamecraftModdingAPI.Blocks public SignalingBlock(uint id) : base(id) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) + if (!BlockEngine.GetBlockInfoExists(this)) { throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); } } - protected ref BlockPortsStruct GetBlockPortsStruct() - { - return ref BlockEngine.GetBlockInfo(Id); - } - /// /// Generates the input port identifiers. /// @@ -72,16 +48,6 @@ namespace GamecraftModdingAPI.Blocks return SignalEngine.GetSignalOutputs(Id); } - /// - /// Gets the port struct. - /// - /// The port struct. - /// Port identifier. - protected ref PortEntityStruct GetPortStruct(EGID portId) - { - return ref BlockEngine.GetBlockInfo(portId); - } - /// /// Gets the connected wire. /// @@ -108,16 +74,16 @@ namespace GamecraftModdingAPI.Blocks /// The input port count. /// public uint InputCount - { - get => GetBlockPortsStruct().inputCount; - } + { + get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.inputCount); + } /// /// The output port count. /// public uint OutputCount { - get => GetBlockPortsStruct().outputCount; + get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.outputCount); } } } diff --git a/GamecraftModdingAPI/Blocks/SpawnPoint.cs b/GamecraftModdingAPI/Blocks/SpawnPoint.cs index 65ef750..7616acb 100644 --- a/GamecraftModdingAPI/Blocks/SpawnPoint.cs +++ b/GamecraftModdingAPI/Blocks/SpawnPoint.cs @@ -13,43 +13,12 @@ namespace GamecraftModdingAPI.Blocks { public class SpawnPoint : Block { - /// - /// Places a new spawn point. - /// Any valid spawn block type is accepted. - /// This re-implements Block.PlaceNew(...) - /// - public static new SpawnPoint PlaceNew(BlockIDs block, float3 position, - float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, - int uscale = 1, float3 scale = default, Player player = null) + public SpawnPoint(EGID id) : base(id) { - if (!(block == BlockIDs.LargeSpawn || block == BlockIDs.SmallSpawn || block == BlockIDs.MediumSpawn || block == BlockIDs.PlayerSpawn)) - { - throw new BlockTypeException($"Block is not a {nameof(SpawnPoint)} block"); - } - if (PlacementEngine.IsInGame && GameState.IsBuildMode()) - { - EGID id = PlacementEngine.PlaceBlock(block, color, darkness, - position, uscale, scale, player, rotation); - return new SpawnPoint(id); - } - - return null; - } - - public SpawnPoint(EGID id) : base(id) - { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } public SpawnPoint(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_SPAWNPOINT_BLOCK_GROUP)) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } // custom spawn point properties @@ -59,16 +28,12 @@ namespace GamecraftModdingAPI.Blocks /// public uint Lives { - get - { - return BlockEngine.GetBlockInfo(Id).lives; - } + get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.lives); - set - { - ref SpawnPointStatsEntityStruct spses = ref BlockEngine.GetBlockInfo(Id); - spses.lives = value; - } + set + { + BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, uint val) => st.lives = val, value); + } } /// @@ -76,16 +41,12 @@ namespace GamecraftModdingAPI.Blocks /// public bool Damageable { - get - { - return BlockEngine.GetBlockInfo(Id).canTakeDamage; - } + get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.canTakeDamage); - set - { - ref SpawnPointStatsEntityStruct spses = ref BlockEngine.GetBlockInfo(Id); - spses.canTakeDamage = value; - } + set + { + BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, bool val) => st.canTakeDamage = val, value); + } } /// @@ -93,16 +54,12 @@ namespace GamecraftModdingAPI.Blocks /// public bool GameOverEnabled { - get - { - return BlockEngine.GetBlockInfo(Id).gameOverScreen; - } + get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.gameOverScreen); - set - { - ref SpawnPointStatsEntityStruct spses = ref BlockEngine.GetBlockInfo(Id); - spses.gameOverScreen = value; - } + set + { + BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, bool val) => st.gameOverScreen = val, value); + } } /// @@ -110,16 +67,12 @@ namespace GamecraftModdingAPI.Blocks /// public byte Team { - get - { - return BlockEngine.GetBlockInfo(Id).teamId; - } + get => BlockEngine.GetBlockInfo(this, (SpawnPointIdsEntityStruct st) => st.teamId); - set - { - ref SpawnPointIdsEntityStruct spses = ref BlockEngine.GetBlockInfo(Id); - spses.teamId = value; - } + set + { + BlockEngine.SetBlockInfo(this, (ref SpawnPointIdsEntityStruct st, byte val) => st.teamId = val, value); + } } } } diff --git a/GamecraftModdingAPI/Blocks/TextBlock.cs b/GamecraftModdingAPI/Blocks/TextBlock.cs index c1a9344..6096dd4 100644 --- a/GamecraftModdingAPI/Blocks/TextBlock.cs +++ b/GamecraftModdingAPI/Blocks/TextBlock.cs @@ -12,35 +12,12 @@ namespace GamecraftModdingAPI.Blocks { public class TextBlock : Block { - - public static TextBlock PlaceNew(float3 position, - float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, - int uscale = 1, float3 scale = default, Player player = null) - { - if (PlacementEngine.IsInGame && GameState.IsBuildMode()) - { - EGID id = PlacementEngine.PlaceBlock(BlockIDs.TextBlock, color, darkness, - position, uscale, scale, player, rotation); - return new TextBlock(id); - } - - return null; - } - public TextBlock(EGID id) : base(id) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } public TextBlock(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_TEXT_BLOCK_GROUP)) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } // custom text block properties @@ -50,35 +27,34 @@ namespace GamecraftModdingAPI.Blocks /// public string Text { - get - { - return BlockEngine.GetBlockInfo(Id).textCurrent; - } + get => BlockEngine.GetBlockInfo(this, (TextBlockDataStruct st) => st.textCurrent); - set - { - ref TextBlockDataStruct tbds = ref BlockEngine.GetBlockInfo(Id); - tbds.textCurrent.Set(value); - tbds.textStored.Set(value); - BlockEngine.GetBlockInfo(Id).newTextBlockStringContent.Set(value); - } - } + set + { + BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) => + { + tbds.textCurrent.Set(val); + tbds.textStored.Set(val); + }, value); + BlockEngine.SetBlockInfo(this, + (ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockStringContent.Set(val), value); + } + } /// - /// The text block's current text block ID (used in ChangeTextBlockCommand). + /// The text block's current text block ID (used in ChangeTextBlockCommand). /// - public string TextBlockId + public string TextBlockId { - get - { - return BlockEngine.GetBlockInfo(Id).textBlockID; - } + get => BlockEngine.GetBlockInfo(this, (TextBlockDataStruct st) => st.textBlockID); - set - { - BlockEngine.GetBlockInfo(Id).textBlockID.Set(value); - BlockEngine.GetBlockInfo(Id).newTextBlockID.Set(value); - } + set + { + BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) => + tbds.textBlockID.Set(val), value); + BlockEngine.SetBlockInfo(this, + (ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockID.Set(val), value); + } } } } diff --git a/GamecraftModdingAPI/Blocks/Timer.cs b/GamecraftModdingAPI/Blocks/Timer.cs index 0e7f744..5766a41 100644 --- a/GamecraftModdingAPI/Blocks/Timer.cs +++ b/GamecraftModdingAPI/Blocks/Timer.cs @@ -13,37 +13,12 @@ namespace GamecraftModdingAPI.Blocks { public class Timer : Block { - /// - /// Places a new timer block. - /// - public static Timer PlaceNew(float3 position, - float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, - int uscale = 1, float3 scale = default, Player player = null) - { - if (PlacementEngine.IsInGame && GameState.IsBuildMode()) - { - EGID id = PlacementEngine.PlaceBlock(BlockIDs.Timer, color, darkness, - position, uscale, scale, player, rotation); - return new Timer(id); - } - - return null; - } - public Timer(EGID id) : base(id) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } public Timer(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_TIMER_BLOCK_GROUP)) { - if (!BlockEngine.GetBlockInfoExists(this.Id)) - { - throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); - } } // custom timer properties @@ -53,16 +28,13 @@ namespace GamecraftModdingAPI.Blocks /// public float Start { - get - { - return BlockEngine.GetBlockInfo(Id).startTime; - } + get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.startTime); - set - { - ref TimerBlockDataStruct tbds = ref BlockEngine.GetBlockInfo(Id); - tbds.startTime = value; - } + set + { + BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, float val) => tbds.startTime = val, + value); + } } /// @@ -70,16 +42,13 @@ namespace GamecraftModdingAPI.Blocks /// public float End { - get - { - return BlockEngine.GetBlockInfo(Id).endTime; - } + get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.endTime); - set - { - ref TimerBlockDataStruct tbds = ref BlockEngine.GetBlockInfo(Id); - tbds.endTime = value; - } + set + { + BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, float val) => tbds.endTime = val, + value); + } } /// @@ -87,16 +56,13 @@ namespace GamecraftModdingAPI.Blocks /// public bool DisplayMilliseconds { - get - { - return BlockEngine.GetBlockInfo(Id).outputFormatHasMS; - } + get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.outputFormatHasMS); - set - { - ref TimerBlockDataStruct tbds = ref BlockEngine.GetBlockInfo(Id); - tbds.outputFormatHasMS = value; - } + set + { + BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, bool val) => tbds.outputFormatHasMS = val, + value); + } } /// @@ -104,16 +70,13 @@ namespace GamecraftModdingAPI.Blocks /// public int CurrentTime { - get - { - return BlockEngine.GetBlockInfo(Id).timeLastRenderFrameMS; - } + get => BlockEngine.GetBlockInfo(this, (TimerBlockLabelCacheEntityStruct st) => st.timeLastRenderFrameMS); - set - { - ref TimerBlockLabelCacheEntityStruct tblces = ref BlockEngine.GetBlockInfo(Id); - tblces.timeLastRenderFrameMS = value; - } + set + { + BlockEngine.SetBlockInfo(this, (ref TimerBlockLabelCacheEntityStruct tbds, int val) => tbds.timeLastRenderFrameMS = val, + value); + } } } } diff --git a/GamecraftModdingAPI/SimBody.cs b/GamecraftModdingAPI/SimBody.cs index 420a655..19f4285 100644 --- a/GamecraftModdingAPI/SimBody.cs +++ b/GamecraftModdingAPI/SimBody.cs @@ -9,7 +9,7 @@ using RobocraftX.Physics; namespace GamecraftModdingAPI { /// - /// A rigid body (like a cluster of connected blocks) during simulation. + /// A rigid body (like a chunk of connected blocks) during simulation. /// public class SimBody : IEquatable, IEquatable { diff --git a/GamecraftModdingAPI/Tests/Assert.cs b/GamecraftModdingAPI/Tests/Assert.cs index eec9c5c..78f0597 100644 --- a/GamecraftModdingAPI/Tests/Assert.cs +++ b/GamecraftModdingAPI/Tests/Assert.cs @@ -83,32 +83,12 @@ namespace GamecraftModdingAPI.Tests { if (err == null) err = $"{nameof(T)} '{obj1}' is not equal to '{obj2}'."; if (success == null) success = $"{nameof(T)} '{obj1}' is equal to '{obj2}'."; - if (obj1 == null && obj2 == null) + if ((obj1 == null && obj2 == null) + || (obj1 != null && obj2 != null && obj1.Equals(obj2) && obj2.Equals(obj1))) { // pass Log(PASS + success); TestRoot.TestsPassed = true; - return true; - } - else if (!(obj1 == null && obj2 == null) && obj1.Equals(obj2) && obj2.Equals(obj1)) - { - // pass - Log(PASS + success); - TestRoot.TestsPassed = true; - return true; - } - else if (obj1 != null && (obj1 != null && !obj1.Equals(obj2))) - { - // pass - Log(PASS + success); - TestRoot.TestsPassed = true; - return true; - } - else if (obj2 != null && !obj2.Equals(obj1)) - { - // pass - Log(PASS + success); - TestRoot.TestsPassed = true; return true; } else diff --git a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs index fa65ebb..d8b50fa 100644 --- a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs +++ b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs @@ -232,6 +232,28 @@ namespace GamecraftModdingAPI.Tests } }).Build(); + CommandBuilder.Builder() + .Name("PlaceConsole") + .Description("Place a bunch of console block with a given text - entering simulation with them crashes the game as the cmd doesn't exist") + .Action((float x, float y, float z) => + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + for (int i = 0; i < 100; i++) + { + for (int j = 0; j < 100; j++) + { + var block = Block.PlaceNew(BlockIDs.ConsoleBlock, + new float3(x + i, y, z + j)); + block.Command = "test_command"; + } + } + + sw.Stop(); + Logging.CommandLog($"Blocks placed in {sw.ElapsedMilliseconds} ms"); + }) + .Build(); + GameClient.SetDebugInfo("InstalledMods", InstalledMods); Block.Placed += (sender, args) => Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID); diff --git a/GamecraftModdingAPI/Utility/DeterministicStepCompositionRootPatch.cs b/GamecraftModdingAPI/Utility/DeterministicStepCompositionRootPatch.cs deleted file mode 100644 index 9cabbe0..0000000 --- a/GamecraftModdingAPI/Utility/DeterministicStepCompositionRootPatch.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -using RobocraftX.StateSync; -using Svelto.ECS; - -using HarmonyLib; - -namespace GamecraftModdingAPI.Utility -{ - [HarmonyPatch(typeof(DeterministicStepCompositionRoot), "ResetWorld")] - public static class DeterministicStepCompositionRootPatch - { - private static SimpleEntitiesSubmissionScheduler engineRootScheduler; - public static void Postfix(SimpleEntitiesSubmissionScheduler scheduler) - { - engineRootScheduler = scheduler; - } - - internal static void SubmitEntitiesNow() - { - if (engineRootScheduler != null) - engineRootScheduler.SubmitEntities(); - } - } -}