diff --git a/TechbloxModdingAPI/Block.cs b/TechbloxModdingAPI/Block.cs index 64b8231..e8626fc 100644 --- a/TechbloxModdingAPI/Block.cs +++ b/TechbloxModdingAPI/Block.cs @@ -39,45 +39,17 @@ namespace TechbloxModdingAPI /// The placed block will be a complete block with a placement grid and collision which will be saved along with the game. /// /// The block's type - /// The block's color - /// The block's material /// The block's position - default block size is 0.2 - /// The block's rotation in degrees - /// The block's uniform scale - default scale is 1 (with 0.2 width) - /// The block's non-uniform scale - 0 means is used - /// Whether the block should be flipped /// Whether the block should be auto-wired (if functional) /// The player who placed the block /// The placed block or null if failed public static Block PlaceNew(BlockIDs block, float3 position, bool autoWire = false, Player player = null) - { - return PlaceNew(block, position, autoWire, 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. - /// - /// The block's type - /// The block's color - /// The block's materialr - /// The block's position - default block size is 0.2 - /// The block's rotation in degrees - /// The block's uniform scale - default scale is 1 (with 0.2 width) - /// The block's non-uniform scale - 0 means is used - /// Whether the block should be flipped - /// Whether the block should be auto-wired (if functional) - /// The player who placed the block - /// The placed block or null if failed - public static T PlaceNew(BlockIDs block, float3 position, bool autoWire = false, Player player = null) - where T : Block { if (PlacementEngine.IsInGame && GameState.IsBuildMode()) { var initializer = PlacementEngine.PlaceBlock(block, position, player, autoWire); var egid = initializer.EGID; - var bl = New(egid.entityID, egid.groupID); + var bl = New(egid); bl.InitData = initializer; Placed += bl.OnPlacedInit; return bl; @@ -89,10 +61,11 @@ namespace TechbloxModdingAPI /// /// Returns the most recently placed block. /// - /// The block object + /// The block object or null if doesn't exist public static Block GetLastPlacedBlock() { - return New(BlockIdentifiers.LatestBlockID); + EGID? egid = BlockEngine.FindBlockEGID(BlockIdentifiers.LatestBlockID); + return egid.HasValue ? New(egid.Value) : null; } /// @@ -113,108 +86,35 @@ namespace TechbloxModdingAPI remove => BlockEventsEngine.Removed -= value; } - private static Dictionary> initializers = new Dictionary>(); - - private static Dictionary typeToGroup = - new Dictionary + private static readonly Dictionary> GroupToConstructor = + new Dictionary> { - {typeof(LogicGate), new [] {CommonExclusiveGroups.LOGIC_BLOCK_GROUP}}, - {typeof(Motor), new[] {CommonExclusiveGroups.MOTOR_BLOCK_GROUP}}, - {typeof(MusicBlock), new[] {CommonExclusiveGroups.MUSIC_BLOCK_GROUP}}, - {typeof(ObjectIdentifier), new[]{CommonExclusiveGroups.OBJID_BLOCK_GROUP}}, - {typeof(Piston), new[] {CommonExclusiveGroups.PISTON_BLOCK_GROUP}}, - {typeof(Servo), new[] {CommonExclusiveGroups.SERVO_BLOCK_GROUP}}, - { - typeof(SpawnPoint), - new[] - { - CommonExclusiveGroups.SPAWNPOINT_BLOCK_GROUP, - CommonExclusiveGroups.BUILDINGSPAWN_BLOCK_GROUP - } - }, - { - typeof(SfxBlock), - new[] - { - CommonExclusiveGroups.SIMPLESFX_BLOCK_GROUP, - CommonExclusiveGroups.LOOPEDSFX_BLOCK_GROUP - } - }, - {typeof(DampedSpring), new [] {CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP}}, - {typeof(TextBlock), new[] {CommonExclusiveGroups.TEXT_BLOCK_GROUP}}, - {typeof(Timer), new[] {CommonExclusiveGroups.TIMER_BLOCK_GROUP}} - }; + {CommonExclusiveGroups.LOGIC_BLOCK_GROUP, id => new LogicGate(id)}, + {CommonExclusiveGroups.MOTOR_BLOCK_GROUP, id => new Motor(id)}, + {CommonExclusiveGroups.MUSIC_BLOCK_GROUP, id => new MusicBlock(id)}, + {CommonExclusiveGroups.OBJID_BLOCK_GROUP, id => new ObjectIdentifier(id)}, + {CommonExclusiveGroups.PISTON_BLOCK_GROUP, id => new Piston(id)}, + {CommonExclusiveGroups.SERVO_BLOCK_GROUP, id => new Servo(id)}, + {CommonExclusiveGroups.SPAWNPOINT_BLOCK_GROUP, id => new SpawnPoint(id)}, + {CommonExclusiveGroups.BUILDINGSPAWN_BLOCK_GROUP, id => new SpawnPoint(id)}, + {CommonExclusiveGroups.SIMPLESFX_BLOCK_GROUP, id => new SfxBlock(id)}, + {CommonExclusiveGroups.LOOPEDSFX_BLOCK_GROUP, id => new SfxBlock(id)}, + {CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP, id => new DampedSpring(id)}, + {CommonExclusiveGroups.TEXT_BLOCK_GROUP, id => new TextBlock(id)}, + {CommonExclusiveGroups.TIMER_BLOCK_GROUP, id => new Timer(id)} + };/*.SelectMany(kv => kv.Value.Select(v => (Key: v, Value: kv.Key))) + .ToDictionary(kv => kv.Key, kv => kv.Value);*/ - /// - /// 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 + private static Block New(EGID egid) { - 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 => ((uint)g).ToString()).Aggregate((a, b) => a + ", " + b)} instead of {(uint)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; + return GroupToConstructor.ContainsKey(egid.groupID) + ? GroupToConstructor[egid.groupID](egid) + : new Block(egid); } public Block(EGID id) { - Id = id; - var type = GetType(); - if (typeToGroup.TryGetValue(type, out var groups)) - { - if (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); - } - else if (type != typeof(Block) && !typeof(CustomBlock).IsAssignableFrom(type)) - Logging.LogWarning($"Unknown block type! Add {type} to the dictionary."); + Id = id; //TODO: Check if block type is correct } /// @@ -224,7 +124,9 @@ namespace TechbloxModdingAPI /// public Block(uint id) { - Id = BlockEngine.FindBlockEGID(id) ?? throw new BlockTypeException("Could not find the appropriate group for the block. The block probably doesn't exist or hasn't been submitted."); + Id = BlockEngine.FindBlockEGID(id) + ?? throw new BlockTypeException("Could not find the appropriate group for the block." + + " The block probably doesn't exist or hasn't been submitted."); } /// @@ -471,9 +373,9 @@ namespace TechbloxModdingAPI /// Creates a copy of the block in the game with the same properties, stats and wires. /// /// - public T Copy() where T : Block + public Block Copy() { - var block = PlaceNew(Type, Position); + var block = PlaceNew(Type, Position); block.Rotation = Rotation; block.Color = Color; block.Material = Material; @@ -535,38 +437,5 @@ namespace TechbloxModdingAPI GameEngineManager.AddGameEngine(BlockCloneEngine); Wire.signalEngine = SignalEngine; // requires same functionality, no need to duplicate the engine } - - /// - /// Convert the block to a specialised block class. - /// - /// The block. - /// The specialised block type. - public T Specialise() where T : Block - { - // What have I gotten myself into? - // 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 - - //Lets improve that using delegates - var block = New(Id.entityID, Id.groupID); - if (this.InitData.Valid) - { - block.InitData = this.InitData; - Placed += block.OnPlacedInit; //Reset InitData of new object - } - - return block; - } - -#if DEBUG - public static EntitiesDB entitiesDB - { - get - { - return BlockEngine.GetEntitiesDB(); - } - } -#endif } } \ No newline at end of file diff --git a/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs b/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs index 2274600..f794b4d 100644 --- a/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs +++ b/TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs @@ -313,7 +313,7 @@ namespace TechbloxModdingAPI.Tests .Action((float x, float y, float z) => { Logging.CommandLog("Block placed: " + - Block.PlaceNew((BlockIDs) 500, new float3(0, 0, 0))); + Block.PlaceNew((BlockIDs) 500, new float3(0, 0, 0))); }).Build(); GameClient.SetDebugInfo("InstalledMods", InstalledMods);