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