diff --git a/GamecraftModdingAPI/Block.cs b/GamecraftModdingAPI/Block.cs
index 16a0c81..673ef49 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;
@@ -67,10 +68,6 @@ namespace GamecraftModdingAPI
/// 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 +78,15 @@ 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);
+ return New(egid.entityID, egid.groupID);
}
return null;
@@ -109,7 +98,7 @@ namespace GamecraftModdingAPI
/// The block object
public static Block GetLastPlacedBlock()
{
- return new Block(BlockIdentifiers.LatestBlockID);
+ return New(BlockIdentifiers.LatestBlockID);
}
///
@@ -130,6 +119,75 @@ 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}}
+ };
+
+ 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);
+ 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;
@@ -344,12 +402,9 @@ 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
+ return New(Id.entityID, Id.groupID);
}
#if DEBUG
diff --git a/GamecraftModdingAPI/Blocks/BlockTests.cs b/GamecraftModdingAPI/Blocks/BlockTests.cs
index 418fa43..133db5e 100644
--- a/GamecraftModdingAPI/Blocks/BlockTests.cs
+++ b/GamecraftModdingAPI/Blocks/BlockTests.cs
@@ -30,9 +30,8 @@ namespace GamecraftModdingAPI.Blocks
[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.");
+ 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.Specialize() 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..e132029 100644
--- a/GamecraftModdingAPI/Blocks/ConsoleBlock.cs
+++ b/GamecraftModdingAPI/Blocks/ConsoleBlock.cs
@@ -12,33 +12,19 @@ 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;
+ if (!BlockEngine.GetBlockInfoExists(this.Id))
+ {
+ throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
+ }
}
- 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))
+ if (!BlockEngine.GetBlockInfoExists(this.Id))
{
- throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
+ throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
}
diff --git a/GamecraftModdingAPI/Blocks/Motor.cs b/GamecraftModdingAPI/Blocks/Motor.cs
index 55f3649..fde2920 100644
--- a/GamecraftModdingAPI/Blocks/Motor.cs
+++ b/GamecraftModdingAPI/Blocks/Motor.cs
@@ -11,29 +11,6 @@ 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))
diff --git a/GamecraftModdingAPI/Blocks/Piston.cs b/GamecraftModdingAPI/Blocks/Piston.cs
index b96a806..2586b60 100644
--- a/GamecraftModdingAPI/Blocks/Piston.cs
+++ b/GamecraftModdingAPI/Blocks/Piston.cs
@@ -11,30 +11,7 @@ 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)
- {
- 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)
+ public Piston(EGID id) : base(id)
{
if (!BlockEngine.GetBlockInfoExists(this.Id))
{
diff --git a/GamecraftModdingAPI/Blocks/Servo.cs b/GamecraftModdingAPI/Blocks/Servo.cs
index ef7225b..c4b57fb 100644
--- a/GamecraftModdingAPI/Blocks/Servo.cs
+++ b/GamecraftModdingAPI/Blocks/Servo.cs
@@ -11,29 +11,6 @@ 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))
diff --git a/GamecraftModdingAPI/Blocks/SignalingBlock.cs b/GamecraftModdingAPI/Blocks/SignalingBlock.cs
index f8006f6..8a62d64 100644
--- a/GamecraftModdingAPI/Blocks/SignalingBlock.cs
+++ b/GamecraftModdingAPI/Blocks/SignalingBlock.cs
@@ -14,25 +14,6 @@ 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))
diff --git a/GamecraftModdingAPI/Blocks/SpawnPoint.cs b/GamecraftModdingAPI/Blocks/SpawnPoint.cs
index 65ef750..4419e38 100644
--- a/GamecraftModdingAPI/Blocks/SpawnPoint.cs
+++ b/GamecraftModdingAPI/Blocks/SpawnPoint.cs
@@ -13,30 +13,7 @@ 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)
- {
- 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)
+ public SpawnPoint(EGID id) : base(id)
{
if (!BlockEngine.GetBlockInfoExists(this.Id))
{
diff --git a/GamecraftModdingAPI/Blocks/TextBlock.cs b/GamecraftModdingAPI/Blocks/TextBlock.cs
index c1a9344..94cf212 100644
--- a/GamecraftModdingAPI/Blocks/TextBlock.cs
+++ b/GamecraftModdingAPI/Blocks/TextBlock.cs
@@ -12,21 +12,6 @@ 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))
diff --git a/GamecraftModdingAPI/Blocks/Timer.cs b/GamecraftModdingAPI/Blocks/Timer.cs
index 0e7f744..2acab5b 100644
--- a/GamecraftModdingAPI/Blocks/Timer.cs
+++ b/GamecraftModdingAPI/Blocks/Timer.cs
@@ -13,23 +13,6 @@ 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))
diff --git a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
index fa65ebb..d5cf714 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")
+ .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);