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:
parent
7a53e1d32f
commit
3432a1ae33
2 changed files with 32 additions and 163 deletions
|
@ -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 placed block will be a complete block with a placement grid and collision which will be saved along with the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="block">The block's type</param>
|
/// <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="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="autoWire">Whether the block should be auto-wired (if functional)</param>
|
||||||
/// <param name="player">The player who placed the block</param>
|
/// <param name="player">The player who placed the block</param>
|
||||||
/// <returns>The placed block or null if failed</returns>
|
/// <returns>The placed block or null if failed</returns>
|
||||||
public static Block PlaceNew(BlockIDs block, float3 position, bool autoWire = false, Player player = null)
|
public static Block PlaceNew(BlockIDs block, float3 position, bool autoWire = false, Player player = null)
|
||||||
{
|
|
||||||
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())
|
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
|
||||||
{
|
{
|
||||||
var initializer = PlacementEngine.PlaceBlock(block, position, player, autoWire);
|
var initializer = PlacementEngine.PlaceBlock(block, position, player, autoWire);
|
||||||
var egid = initializer.EGID;
|
var egid = initializer.EGID;
|
||||||
var bl = New<T>(egid.entityID, egid.groupID);
|
var bl = New(egid);
|
||||||
bl.InitData = initializer;
|
bl.InitData = initializer;
|
||||||
Placed += bl.OnPlacedInit;
|
Placed += bl.OnPlacedInit;
|
||||||
return bl;
|
return bl;
|
||||||
|
@ -89,10 +61,11 @@ namespace TechbloxModdingAPI
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the most recently placed block.
|
/// Returns the most recently placed block.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The block object</returns>
|
/// <returns>The block object or null if doesn't exist</returns>
|
||||||
public static Block GetLastPlacedBlock()
|
public static Block GetLastPlacedBlock()
|
||||||
{
|
{
|
||||||
return New<Block>(BlockIdentifiers.LatestBlockID);
|
EGID? egid = BlockEngine.FindBlockEGID(BlockIdentifiers.LatestBlockID);
|
||||||
|
return egid.HasValue ? New(egid.Value) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -113,108 +86,35 @@ namespace TechbloxModdingAPI
|
||||||
remove => BlockEventsEngine.Removed -= value;
|
remove => BlockEventsEngine.Removed -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<Type, Func<EGID, Block>> initializers = new Dictionary<Type, Func<EGID, Block>>();
|
private static readonly Dictionary<ExclusiveBuildGroup, Func<EGID, Block>> GroupToConstructor =
|
||||||
|
new Dictionary<ExclusiveBuildGroup, Func<EGID, Block>>
|
||||||
private static Dictionary<Type, ExclusiveBuildGroup[]> typeToGroup =
|
|
||||||
new Dictionary<Type, ExclusiveBuildGroup[]>
|
|
||||||
{
|
{
|
||||||
{typeof(LogicGate), new [] {CommonExclusiveGroups.LOGIC_BLOCK_GROUP}},
|
{CommonExclusiveGroups.LOGIC_BLOCK_GROUP, id => new LogicGate(id)},
|
||||||
{typeof(Motor), new[] {CommonExclusiveGroups.MOTOR_BLOCK_GROUP}},
|
{CommonExclusiveGroups.MOTOR_BLOCK_GROUP, id => new Motor(id)},
|
||||||
{typeof(MusicBlock), new[] {CommonExclusiveGroups.MUSIC_BLOCK_GROUP}},
|
{CommonExclusiveGroups.MUSIC_BLOCK_GROUP, id => new MusicBlock(id)},
|
||||||
{typeof(ObjectIdentifier), new[]{CommonExclusiveGroups.OBJID_BLOCK_GROUP}},
|
{CommonExclusiveGroups.OBJID_BLOCK_GROUP, id => new ObjectIdentifier(id)},
|
||||||
{typeof(Piston), new[] {CommonExclusiveGroups.PISTON_BLOCK_GROUP}},
|
{CommonExclusiveGroups.PISTON_BLOCK_GROUP, id => new Piston(id)},
|
||||||
{typeof(Servo), new[] {CommonExclusiveGroups.SERVO_BLOCK_GROUP}},
|
{CommonExclusiveGroups.SERVO_BLOCK_GROUP, id => new Servo(id)},
|
||||||
{
|
{CommonExclusiveGroups.SPAWNPOINT_BLOCK_GROUP, id => new SpawnPoint(id)},
|
||||||
typeof(SpawnPoint),
|
{CommonExclusiveGroups.BUILDINGSPAWN_BLOCK_GROUP, id => new SpawnPoint(id)},
|
||||||
new[]
|
{CommonExclusiveGroups.SIMPLESFX_BLOCK_GROUP, id => new SfxBlock(id)},
|
||||||
{
|
{CommonExclusiveGroups.LOOPEDSFX_BLOCK_GROUP, id => new SfxBlock(id)},
|
||||||
CommonExclusiveGroups.SPAWNPOINT_BLOCK_GROUP,
|
{CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP, id => new DampedSpring(id)},
|
||||||
CommonExclusiveGroups.BUILDINGSPAWN_BLOCK_GROUP
|
{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);*/
|
||||||
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}}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
private static Block New(EGID egid)
|
||||||
/// 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
|
|
||||||
{
|
{
|
||||||
var type = typeof(T);
|
return GroupToConstructor.ContainsKey(egid.groupID)
|
||||||
EGID egid;
|
? GroupToConstructor[egid.groupID](egid)
|
||||||
if (!group.HasValue)
|
: new Block(egid);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Block(EGID id)
|
public Block(EGID id)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id; //TODO: Check if block type is correct
|
||||||
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.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -224,7 +124,9 @@ namespace TechbloxModdingAPI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Block(uint id)
|
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>
|
/// <summary>
|
||||||
|
@ -471,9 +373,9 @@ namespace TechbloxModdingAPI
|
||||||
/// Creates a copy of the block in the game with the same properties, stats and wires.
|
/// Creates a copy of the block in the game with the same properties, stats and wires.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <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.Rotation = Rotation;
|
||||||
block.Color = Color;
|
block.Color = Color;
|
||||||
block.Material = Material;
|
block.Material = Material;
|
||||||
|
@ -535,38 +437,5 @@ namespace TechbloxModdingAPI
|
||||||
GameEngineManager.AddGameEngine(BlockCloneEngine);
|
GameEngineManager.AddGameEngine(BlockCloneEngine);
|
||||||
Wire.signalEngine = SignalEngine; // requires same functionality, no need to duplicate the engine
|
Wire.signalEngine = SignalEngine; // requires same functionality, no need to duplicate the engine
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -313,7 +313,7 @@ namespace TechbloxModdingAPI.Tests
|
||||||
.Action((float x, float y, float z) =>
|
.Action((float x, float y, float z) =>
|
||||||
{
|
{
|
||||||
Logging.CommandLog("Block placed: " +
|
Logging.CommandLog("Block placed: " +
|
||||||
Block.PlaceNew<TestBlock>((BlockIDs) 500, new float3(0, 0, 0)));
|
Block.PlaceNew((BlockIDs) 500, new float3(0, 0, 0)));
|
||||||
}).Build();
|
}).Build();
|
||||||
|
|
||||||
GameClient.SetDebugInfo("InstalledMods", InstalledMods);
|
GameClient.SetDebugInfo("InstalledMods", InstalledMods);
|
||||||
|
|
Loading…
Reference in a new issue