Browse Source

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
tags/v2.0.0
NorbiPeti 3 years ago
parent
commit
3432a1ae33
Signed by: NorbiPeti <szatmari.norbert.peter@gmail.com> GPG Key ID: DBA4C4549A927E56
2 changed files with 33 additions and 164 deletions
  1. +32
    -163
      TechbloxModdingAPI/Block.cs
  2. +1
    -1
      TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs

+ 32
- 163
TechbloxModdingAPI/Block.cs 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}}
};

/// <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
{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);*/

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
}
}

+ 1
- 1
TechbloxModdingAPI/Tests/TechbloxModdingAPIPluginTest.cs 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);


Loading…
Cancel
Save