Return block objects based on the group, not a type param

Replaced typeToGroup with GroupToConstructor
The block object's type is determined by the exclusive group instead of a type parameter
Removed the Specialise() method, the API should always return specialised objects

This fixes the not supported exception but not the game crash that follows
This commit is contained in:
Norbi Peti 2021-05-12 02:33:01 +02:00
parent 7a53e1d32f
commit 3432a1ae33
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
2 changed files with 32 additions and 163 deletions

View file

@ -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.
/// </summary>
/// <param name="block">The block's type</param>
/// <param name="color">The block's color</param>
/// <param name="material">The block's material</param>
/// <param name="position">The block's position - default block size is 0.2</param>
/// <param name="rotation">The block's rotation in degrees</param>
/// <param name="uscale">The block's uniform scale - default scale is 1 (with 0.2 width)</param>
/// <param name="scale">The block's non-uniform scale - 0 means <paramref name="uscale"/> is used</param>
/// <param name="isFlipped">Whether the block should be flipped</param>
/// <param name="autoWire">Whether the block should be auto-wired (if functional)</param>
/// <param name="player">The player who placed the block</param>
/// <returns>The placed block or null if failed</returns>
public static Block PlaceNew(BlockIDs block, float3 position, bool autoWire = false, Player player = null)
{
return PlaceNew<Block>(block, position, autoWire, player);
}
/// <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 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.
/// </summary>
/// <param name="block">The block's type</param>
/// <param name="color">The block's color</param>
/// <param name="material">The block's materialr</param>
/// <param name="position">The block's position - default block size is 0.2</param>
/// <param name="rotation">The block's rotation in degrees</param>
/// <param name="uscale">The block's uniform scale - default scale is 1 (with 0.2 width)</param>
/// <param name="scale">The block's non-uniform scale - 0 means <paramref name="uscale"/> is used</param>
/// <param name="isFlipped">Whether the block should be flipped</param>
/// <param name="autoWire">Whether the block should be auto-wired (if functional)</param>
/// <param name="player">The player who placed the block</param>
/// <returns>The placed block or null if failed</returns>
public static T PlaceNew<T>(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<T>(egid.entityID, egid.groupID);
var bl = New(egid);
bl.InitData = initializer;
Placed += bl.OnPlacedInit;
return bl;
@ -89,10 +61,11 @@ namespace TechbloxModdingAPI
/// <summary>
/// Returns the most recently placed block.
/// </summary>
/// <returns>The block object</returns>
/// <returns>The block object or null if doesn't exist</returns>
public static Block GetLastPlacedBlock()
{
return New<Block>(BlockIdentifiers.LatestBlockID);
EGID? egid = BlockEngine.FindBlockEGID(BlockIdentifiers.LatestBlockID);
return egid.HasValue ? New(egid.Value) : null;
}
/// <summary>
@ -113,108 +86,35 @@ namespace TechbloxModdingAPI
remove => BlockEventsEngine.Removed -= value;
}
private static Dictionary<Type, Func<EGID, Block>> initializers = new Dictionary<Type, Func<EGID, Block>>();
private static Dictionary<Type, ExclusiveBuildGroup[]> typeToGroup =
new Dictionary<Type, ExclusiveBuildGroup[]>
private static readonly Dictionary<ExclusiveBuildGroup, Func<EGID, Block>> GroupToConstructor =
new Dictionary<ExclusiveBuildGroup, Func<EGID, Block>>
{
{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);*/
/// <summary>
/// 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.
/// </summary>
/// <param name="id">The block ID</param>
/// <param name="group">The block group</param>
/// <typeparam name="T">The block's type or Block itself</typeparam>
/// <returns>An instance of the provided type</returns>
/// <exception cref="BlockTypeException">The block group doesn't match or cannot be found</exception>
/// <exception cref="MissingMethodException">The block class doesn't have the needed constructor</exception>
private static T New<T>(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<EGID, T>) dynamic.CreateDelegate(typeof(Func<EGID, T>));
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
}
/// <summary>
@ -224,7 +124,9 @@ namespace TechbloxModdingAPI
/// </summary>
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.");
}
/// <summary>
@ -471,9 +373,9 @@ namespace TechbloxModdingAPI
/// Creates a copy of the block in the game with the same properties, stats and wires.
/// </summary>
/// <returns></returns>
public T Copy<T>() where T : Block
public Block Copy()
{
var block = PlaceNew<T>(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
}
/// <summary>
/// Convert the block to a specialised block class.
/// </summary>
/// <returns>The block.</returns>
/// <typeparam name="T">The specialised block type.</typeparam>
public T Specialise<T>() 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<T>(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
}
}

View file

@ -313,7 +313,7 @@ namespace TechbloxModdingAPI.Tests
.Action((float x, float y, float z) =>
{
Logging.CommandLog("Block placed: " +
Block.PlaceNew<TestBlock>((BlockIDs) 500, new float3(0, 0, 0)));
Block.PlaceNew((BlockIDs) 500, new float3(0, 0, 0)));
}).Build();
GameClient.SetDebugInfo("InstalledMods", InstalledMods);