Merge branch 'delegating' into preview

This commit is contained in:
Norbi Peti 2020-07-21 00:24:50 +02:00
commit c5e9599c46
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
22 changed files with 464 additions and 596 deletions

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Reflection; using System.Collections.Generic;
using System.Threading.Tasks; using System.Linq;
using System.Reflection.Emit;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.EntityStructs; using Svelto.ECS.EntityStructs;
@ -54,23 +55,13 @@ namespace GamecraftModdingAPI
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
int uscale = 1, float3 scale = default, Player player = null) int uscale = 1, float3 scale = default, Player player = null)
{ {
if (PlacementEngine.IsInGame && GameState.IsBuildMode()) return PlaceNew<Block>(block, position, rotation, color, darkness, uscale, scale, player);
{
return new Block(PlacementEngine.PlaceBlock(block, color, darkness,
position, uscale, scale, player, rotation));
}
return null;
} }
/// <summary> /// <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 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. /// 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 placed block will be a complete block with a placement grid and collision which will be saved along with the game.
/// <para></para>
/// <para>This method waits for the block to be constructed in the game which may take a significant amount of time.
/// Only use this to place a single block.
/// For placing multiple blocks, use PlaceNew() then AsyncUtils.WaitForSubmission() when done with placing blocks.</para>
/// </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="color">The block's color</param>
@ -81,23 +72,18 @@ namespace GamecraftModdingAPI
/// <param name="scale">The block's non-uniform scale - 0 means <paramref name="uscale"/> is used</param> /// <param name="scale">The block's non-uniform scale - 0 means <paramref name="uscale"/> is used</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 async Task<Block> PlaceNewAsync(BlockIDs block, float3 position, public static T PlaceNew<T>(BlockIDs block, float3 position,
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
int uscale = 1, float3 scale = default, Player player = null) int uscale = 1, float3 scale = default, Player player = null) where T : Block
{ {
if (PlacementEngine.IsInGame && GameState.IsBuildMode()) if (PlacementEngine.IsInGame && GameState.IsBuildMode())
{ {
try var egid = PlacementEngine.PlaceBlock(block, color, darkness,
{ position, uscale, scale, player, rotation, out var initializer);
var ret = new Block(PlacementEngine.PlaceBlock(block, color, darkness, var bl = New<T>(egid.entityID, egid.groupID);
position, uscale, scale, player, rotation)); bl.InitData.Group = BlockEngine.InitGroup(initializer);
await AsyncUtils.WaitForSubmission(); Placed += bl.OnPlacedInit;
return ret; return bl;
}
catch (Exception e)
{
Logging.MetaDebugLog(e);
}
} }
return null; return null;
@ -109,7 +95,7 @@ namespace GamecraftModdingAPI
/// <returns>The block object</returns> /// <returns>The block object</returns>
public static Block GetLastPlacedBlock() public static Block GetLastPlacedBlock()
{ {
return new Block(BlockIdentifiers.LatestBlockID); return New<Block>(BlockIdentifiers.LatestBlockID);
} }
/// <summary> /// <summary>
@ -130,9 +116,91 @@ namespace GamecraftModdingAPI
remove => BlockEventsEngine.Removed -= value; remove => BlockEventsEngine.Removed -= value;
} }
private static Dictionary<Type, Func<EGID, Block>> initializers = new Dictionary<Type, Func<EGID, Block>>();
private static Dictionary<Type, ExclusiveGroupStruct[]> typeToGroup =
new Dictionary<Type, ExclusiveGroupStruct[]>
{
{typeof(ConsoleBlock), new[] {CommonExclusiveGroups.BUILD_CONSOLE_BLOCK_GROUP}},
{typeof(Motor), new[] {CommonExclusiveGroups.BUILD_MOTOR_BLOCK_GROUP}},
{typeof(Piston), new[] {CommonExclusiveGroups.BUILD_PISTON_BLOCK_GROUP}},
{typeof(Servo), new[] {CommonExclusiveGroups.BUILD_SERVO_BLOCK_GROUP}},
{
typeof(SpawnPoint),
new[]
{
CommonExclusiveGroups.BUILD_SPAWNPOINT_BLOCK_GROUP,
CommonExclusiveGroups.BUILD_BUILDINGSPAWN_BLOCK_GROUP
}
},
{typeof(TextBlock), new[] {CommonExclusiveGroups.BUILD_TEXT_BLOCK_GROUP}},
{typeof(Timer), new[] {CommonExclusiveGroups.BUILD_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
{
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 => g.ToString()).Aggregate((a, b) => a + ", " + b)} instead of {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;
if (typeToGroup.TryGetValue(GetType(), out var groups) && 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);
} }
/// <summary> /// <summary>
@ -147,16 +215,18 @@ namespace GamecraftModdingAPI
public EGID Id { get; } public EGID Id { get; }
internal BlockEngine.BlockInitData InitData;
/// <summary> /// <summary>
/// The block's current position or zero if the block no longer exists. /// The block's current position or zero if the block no longer exists.
/// A block is 0.2 wide by default in terms of position. /// A block is 0.2 wide by default in terms of position.
/// </summary> /// </summary>
public float3 Position public float3 Position
{ {
get => Exists ? MovementEngine.GetPosition(Id) : float3.zero; get => MovementEngine.GetPosition(Id, InitData);
set set
{ {
if (Exists) MovementEngine.MoveBlock(Id, value); MovementEngine.MoveBlock(Id, InitData, value);
} }
} }
@ -165,10 +235,10 @@ namespace GamecraftModdingAPI
/// </summary> /// </summary>
public float3 Rotation public float3 Rotation
{ {
get => Exists ? RotationEngine.GetRotation(Id) : float3.zero; get => RotationEngine.GetRotation(Id, InitData);
set set
{ {
if (Exists) RotationEngine.RotateBlock(Id, value); RotationEngine.RotateBlock(Id, InitData, value);
} }
} }
@ -178,12 +248,11 @@ namespace GamecraftModdingAPI
/// </summary> /// </summary>
public float3 Scale public float3 Scale
{ {
get => BlockEngine.GetBlockInfo<ScalingEntityStruct>(Id).scale; get => BlockEngine.GetBlockInfo(this, (ScalingEntityStruct st) => st.scale);
set set
{ {
BlockEngine.SetBlockInfo(this, (ref ScalingEntityStruct st, float3 val) => st.scale = val, value);
if (!Exists) return; //UpdateCollision needs the block to exist if (!Exists) return; //UpdateCollision needs the block to exist
ref var scaling = ref BlockEngine.GetBlockInfo<ScalingEntityStruct>(Id);
scaling.scale = value;
ScalingEngine.UpdateCollision(Id); ScalingEngine.UpdateCollision(Id);
} }
} }
@ -194,11 +263,11 @@ namespace GamecraftModdingAPI
/// </summary> /// </summary>
public int UniformScale public int UniformScale
{ {
get => BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(Id).scaleFactor; get => BlockEngine.GetBlockInfo(this, (UniformBlockScaleEntityStruct st) => st.scaleFactor);
set set
{ {
ref var scaleStruct = ref BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(Id); BlockEngine.SetBlockInfo(this, (ref UniformBlockScaleEntityStruct st, int val) => st.scaleFactor = val,
scaleStruct.scaleFactor = value; value);
Scale = new float3(value, value, value); Scale = new float3(value, value, value);
} }
} }
@ -210,8 +279,7 @@ namespace GamecraftModdingAPI
{ {
get get
{ {
var id = (BlockIDs) BlockEngine.GetBlockInfo<DBEntityStruct>(Id, out var exists).DBID; return BlockEngine.GetBlockInfo(this, (DBEntityStruct st) => (BlockIDs) st.DBID, BlockIDs.Invalid);
return exists ? id : BlockIDs.Invalid;
} }
} }
@ -222,17 +290,19 @@ namespace GamecraftModdingAPI
{ {
get get
{ {
byte index = BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id, out var exists).indexInPalette; byte index = BlockEngine.GetBlockInfo(this, (ColourParameterEntityStruct st) => st.indexInPalette,
if (!exists) index = byte.MaxValue; byte.MaxValue);
return new BlockColor(index); return new BlockColor(index);
} }
set set
{ {
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id); BlockEngine.SetBlockInfo(this, (ref ColourParameterEntityStruct color, BlockColor val) =>
color.indexInPalette = (byte)(value.Color + value.Darkness * 10); {
color.overridePaletteColour = false; color.indexInPalette = (byte) (val.Color + val.Darkness * 10);
color.needsUpdate = true; color.overridePaletteColour = false;
BlockEngine.SetBlockColorFromPalette(ref color); color.needsUpdate = true;
BlockEngine.SetBlockColorFromPalette(ref color);
}, value);
} }
} }
@ -241,32 +311,37 @@ namespace GamecraftModdingAPI
/// </summary> /// </summary>
public float4 CustomColor public float4 CustomColor
{ {
get => BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id).overriddenColour; get => BlockEngine.GetBlockInfo(this, (ColourParameterEntityStruct st) => st.overriddenColour);
set set
{ {
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id); BlockEngine.SetBlockInfo(this, (ref ColourParameterEntityStruct color, float4 val) =>
color.overriddenColour = value; {
color.overridePaletteColour = true; color.overriddenColour = val;
color.needsUpdate = true; color.overridePaletteColour = true;
color.needsUpdate = true;
}, value);
} }
} }
/// <summary> /// <summary>
/// The short text displayed on the block if applicable, or null. /// The text displayed on the block if applicable, or null.
/// Setting it is temporary to the session, it won't be saved. /// Setting it is temporary to the session, it won't be saved.
/// </summary> /// </summary>
public string Label public string Label
{ {
get => BlockEngine.GetBlockInfo<TextLabelEntityViewStruct>(Id).textLabelComponent?.text; get => BlockEngine.GetBlockInfo(this, (TextLabelEntityViewStruct st) => st.textLabelComponent?.text);
set set
{ {
ref var text = ref BlockEngine.GetBlockInfo<TextLabelEntityViewStruct>(Id); BlockEngine.SetBlockInfo(this, (ref TextLabelEntityViewStruct text, string val) =>
if (text.textLabelComponent != null) text.textLabelComponent.text = value; {
if (text.textLabelComponent != null) text.textLabelComponent.text = val;
}, value);
} }
} }
/// <summary> /// <summary>
/// Whether the block exists. The other properties will return a default value if the block doesn't exist. /// Whether the block exists. The other properties will return a default value if the block doesn't exist.
/// If the block was just placed, then this will also return false but the properties will work correctly.
/// </summary> /// </summary>
public bool Exists => BlockEngine.BlockExists(Id); public bool Exists => BlockEngine.BlockExists(Id);
@ -282,14 +357,21 @@ namespace GamecraftModdingAPI
public bool Remove() => RemovalEngine.RemoveBlock(Id); public bool Remove() => RemovalEngine.RemoveBlock(Id);
/// <summary> /// <summary>
/// Returns the rigid body of the cluster of blocks this one belongs to during simulation. /// Returns the rigid body of the chunk of blocks this one belongs to during simulation.
/// Can be used to apply forces or move the block around while the simulation is running. /// Can be used to apply forces or move the block around while the simulation is running.
/// </summary> /// </summary>
/// <returns>The SimBody of the cluster or null if the block doesn't exist.</returns> /// <returns>The SimBody of the chunk or null if the block doesn't exist.</returns>
public SimBody GetSimBody() public SimBody GetSimBody()
{ {
uint id = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(Id, out var exists).machineRigidBodyId; return BlockEngine.GetBlockInfo(this,
return exists ? new SimBody(id) : null; (GridConnectionsEntityStruct st) => new SimBody(st.machineRigidBodyId));
}
private void OnPlacedInit(object sender, BlockPlacedRemovedEventArgs e)
{ //Member method instead of lambda to avoid constantly creating delegates
if (e.ID != Id) return;
Placed -= OnPlacedInit; //And we can reference it
InitData = default; //Remove initializer as it's no longer valid - if the block gets removed it shouldn't be used again
} }
public override string ToString() public override string ToString()
@ -344,12 +426,11 @@ namespace GamecraftModdingAPI
// C# can't cast to a child of Block unless the object was originally that child type // 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 // 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 // So thanks to Microsoft, we've got this horrible implementation using reflection
ConstructorInfo ctor = typeof(T).GetConstructor(types: new System.Type[] { typeof(EGID) });
if (ctor == null) //Lets improve that using delegates
{ var block = New<T>(Id.entityID, Id.groupID);
throw new BlockSpecializationException("Specialized block constructor does not accept an EGID"); block.InitData = this.InitData;
} return block;
return (T)ctor.Invoke(new object[] { Id });
} }
#if DEBUG #if DEBUG

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Gamecraft.Wires; using Gamecraft.Wires;
@ -10,14 +11,13 @@ using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using GamecraftModdingAPI.Engines; using GamecraftModdingAPI.Engines;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Blocks namespace GamecraftModdingAPI.Blocks
{ {
/// <summary> /// <summary>
/// Engine for executing general block actions /// Engine for executing general block actions
/// </summary> /// </summary>
public class BlockEngine : IApiEngine public partial class BlockEngine : IApiEngine
{ {
public string Name { get; } = "GamecraftModdingAPIBlockGameEngine"; public string Name { get; } = "GamecraftModdingAPIBlockGameEngine";
@ -25,8 +25,6 @@ namespace GamecraftModdingAPI.Blocks
public bool isRemovable => false; public bool isRemovable => false;
internal bool Synced = true;
public void Dispose() public void Dispose()
{ {
} }
@ -61,66 +59,55 @@ namespace GamecraftModdingAPI.Blocks
color.paletteColour = paletteEntry.Colour; color.paletteColour = paletteEntry.Colour;
} }
/// <summary>
/// Get a struct of a block. Can be used to set properties.
/// Returns a default value if not found.
/// </summary>
/// <param name="blockID">The block's ID</param>
/// <typeparam name="T">The struct to query</typeparam>
/// <returns>An editable reference to the struct</returns>
public ref T GetBlockInfo<T>(EGID blockID) where T : struct, IEntityComponent public ref T GetBlockInfo<T>(EGID blockID) where T : struct, IEntityComponent
{ {
if (!Synced)
{
Sync();
Synced = true;
}
if (entitiesDB.Exists<T>(blockID)) if (entitiesDB.Exists<T>(blockID))
return ref entitiesDB.QueryEntity<T>(blockID); return ref entitiesDB.QueryEntity<T>(blockID);
T[] structHolder = new T[1]; //Create something that can be referenced T[] structHolder = new T[1]; //Create something that can be referenced
return ref structHolder[0]; //Gets a default value automatically return ref structHolder[0]; //Gets a default value automatically
} }
/// <summary> public U GetBlockInfo<T, U>(Block block, Func<T, U> getter,
/// Get a struct of a block. Can be used to set properties. U def = default) where T : struct, IEntityComponent
/// Returns a default value if not found.
/// </summary>
/// <param name="blockID">The block's ID</param>
/// <param name="exists">Whether the specified struct exists for the block</param>
/// <typeparam name="T">The struct to query</typeparam>
/// <returns>An editable reference to the struct</returns>
public ref T GetBlockInfo<T>(EGID blockID, out bool exists) where T : struct, IEntityComponent
{ {
if (!Synced) if (entitiesDB.Exists<T>(block.Id))
return getter(entitiesDB.QueryEntity<T>(block.Id));
if (block.InitData.Group == null) return def;
var initializer = new EntityComponentInitializer(block.Id, block.InitData.Group);
if (initializer.Has<T>())
return getter(initializer.Get<T>());
return def;
}
public delegate void Setter<T, U>(ref T component, U value) where T : struct, IEntityComponent;
public void SetBlockInfo<T, U>(Block block, Setter<T, U> setter, U value) where T : struct, IEntityComponent
{
if (entitiesDB.Exists<T>(block.Id))
setter(ref entitiesDB.QueryEntity<T>(block.Id), value);
else if (block.InitData.Group != null)
{ {
Sync(); var initializer = new EntityComponentInitializer(block.Id, block.InitData.Group);
Synced = true; T component = initializer.Has<T>() ? initializer.Get<T>() : default;
ref T structRef = ref component;
setter(ref structRef, value);
initializer.Init(structRef);
} }
exists = entitiesDB.Exists<T>(blockID);
if (exists)
return ref entitiesDB.QueryEntity<T>(blockID);
T[] structHolder = new T[1];
return ref structHolder[0];
} }
public bool BlockExists(EGID id) public bool BlockExists(EGID blockID)
{ {
if (!Synced) return entitiesDB.Exists<DBEntityStruct>(blockID);
{
Sync();
Synced = true;
}
return entitiesDB.Exists<DBEntityStruct>(id);
} }
public bool GetBlockInfoExists<T>(EGID blockID) where T : struct, IEntityComponent public bool GetBlockInfoExists<T>(Block block) where T : struct, IEntityComponent
{ {
if (!Synced) if (entitiesDB.Exists<T>(block.Id))
{ return true;
Sync(); if (block.InitData.Group == null)
Synced = true; return false;
} var init = new EntityComponentInitializer(block.Id, block.InitData.Group);
return entitiesDB.Exists<T>(blockID); return init.Has<T>();
} }
public SimBody[] GetSimBodiesFromID(byte id) public SimBody[] GetSimBodiesFromID(byte id)
@ -183,17 +170,6 @@ namespace GamecraftModdingAPI.Blocks
return null; return null;
} }
/// <summary>
/// Synchronize newly created entity components with entities DB.
/// This forces a partial game tick, so it may be slow.
/// This also has the potential to make Gamecraft unstable.
/// Use this sparingly.
/// </summary>
private static void Sync()
{
DeterministicStepCompositionRootPatch.SubmitEntitiesNow();
}
#if DEBUG #if DEBUG
public EntitiesDB GetEntitiesDB() public EntitiesDB GetEntitiesDB()
{ {

View file

@ -0,0 +1,52 @@
using System;
using System.Linq.Expressions;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.Internal;
namespace GamecraftModdingAPI.Blocks
{
public partial class BlockEngine
{
/// <summary>
/// Holds information needed to construct a component initializer
/// </summary>
internal struct BlockInitData
{
public FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> Group;
}
internal delegate FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> GetInitGroup(
EntityComponentInitializer initializer);
/// <summary>
/// Accesses the group field of the initializer
/// </summary>
internal GetInitGroup InitGroup = CreateAccessor<GetInitGroup>("_group");
//https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection
internal static TDelegate CreateAccessor<TDelegate>(string memberName) where TDelegate : Delegate
{
var invokeMethod = typeof(TDelegate).GetMethod("Invoke");
if (invokeMethod == null)
throw new InvalidOperationException($"{typeof(TDelegate)} signature could not be determined.");
var delegateParameters = invokeMethod.GetParameters();
if (delegateParameters.Length != 1)
throw new InvalidOperationException("Delegate must have a single parameter.");
var paramType = delegateParameters[0].ParameterType;
var objParam = Expression.Parameter(paramType, "obj");
var memberExpr = Expression.PropertyOrField(objParam, memberName);
Expression returnExpr = memberExpr;
if (invokeMethod.ReturnType != memberExpr.Type)
returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType);
var lambda =
Expression.Lambda<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] {objParam});
return lambda.Compile();
}
}
}

View file

@ -1,9 +1,11 @@
using System; using System;
using GamecraftModdingAPI.Engines;
using GamecraftModdingAPI.Utility;
using RobocraftX.Common; using RobocraftX.Common;
using Svelto.ECS; using Svelto.ECS;
using GamecraftModdingAPI.Engines;
using GamecraftModdingAPI.Utility;
namespace GamecraftModdingAPI.Blocks namespace GamecraftModdingAPI.Blocks
{ {
public class BlockEventsEngine : IReactionaryEngine<DBEntityStruct> public class BlockEventsEngine : IReactionaryEngine<DBEntityStruct>
@ -16,6 +18,7 @@ namespace GamecraftModdingAPI.Blocks
} }
public EntitiesDB entitiesDB { get; set; } public EntitiesDB entitiesDB { get; set; }
public void Dispose() public void Dispose()
{ {
} }
@ -23,14 +26,21 @@ namespace GamecraftModdingAPI.Blocks
public string Name { get; } = "GamecraftModdingAPIBlockEventsEngine"; public string Name { get; } = "GamecraftModdingAPIBlockEventsEngine";
public bool isRemovable { get; } = false; public bool isRemovable { get; } = false;
private bool shouldAddRemove;
public void Add(ref DBEntityStruct entityComponent, EGID egid) public void Add(ref DBEntityStruct entityComponent, EGID egid)
{ {
ExceptionUtil.InvokeEvent(Placed, this, new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID}); if (!(shouldAddRemove = !shouldAddRemove))
return;
ExceptionUtil.InvokeEvent(Placed, this,
new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID});
} }
public void Remove(ref DBEntityStruct entityComponent, EGID egid) public void Remove(ref DBEntityStruct entityComponent, EGID egid)
{ {
ExceptionUtil.InvokeEvent(Removed, this, new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID}); if (!(shouldAddRemove = !shouldAddRemove))
return;
ExceptionUtil.InvokeEvent(Removed, this,
new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID});
} }
} }
@ -38,5 +48,8 @@ namespace GamecraftModdingAPI.Blocks
{ {
public EGID ID; public EGID ID;
public BlockIDs Type; public BlockIDs Type;
private Block block;
public Block Block => block ?? (block = new Block(ID));
} }
} }

View file

@ -6,7 +6,7 @@ namespace GamecraftModdingAPI.Blocks
public enum BlockIDs : ushort public enum BlockIDs : ushort
{ {
/// <summary> /// <summary>
/// A custom value for the API. Doesn't exist for Gamecraft. /// Called "nothing" in Gamecraft. (DBID.NOTHING)
/// </summary> /// </summary>
Invalid = ushort.MaxValue, Invalid = ushort.MaxValue,
AluminiumCube = 0, AluminiumCube = 0,

View file

@ -22,20 +22,19 @@ namespace GamecraftModdingAPI.Blocks
} }
[APITestCase(TestType.EditMode)] [APITestCase(TestType.EditMode)]
public static void TestSync() public static void TestInitProperty()
{ {
Block newBlock = Block.PlaceNew(BlockIDs.AluminiumCube, Unity.Mathematics.float3.zero + 2); Block newBlock = Block.PlaceNew(BlockIDs.AluminiumCube, Unity.Mathematics.float3.zero + 2);
if (!Assert.CloseTo(newBlock.Position, (Unity.Mathematics.float3.zero + 2), $"Newly placed block at {newBlock.Position} is expected at {Unity.Mathematics.float3.zero + 2}.", "Newly placed block position matches.")) return; if (!Assert.CloseTo(newBlock.Position, (Unity.Mathematics.float3.zero + 2), $"Newly placed block at {newBlock.Position} is expected at {Unity.Mathematics.float3.zero + 2}.", "Newly placed block position matches.")) return;
Assert.Equal(newBlock.Exists, true, "Newly placed block does not exist, possibly because Sync() skipped/missed/failed.", "Newly placed block exists, Sync() successful."); //Assert.Equal(newBlock.Exists, true, "Newly placed block does not exist, possibly because Sync() skipped/missed/failed.", "Newly placed block exists, Sync() successful.");
} }
[APITestCase(TestType.EditMode)] [APITestCase(TestType.EditMode)]
public static void TestTextBlock() public static void TestTextBlock()
{ {
Block newBlock = Block.PlaceNew(BlockIDs.TextBlock, Unity.Mathematics.float3.zero + 1);
TextBlock textBlock = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler TextBlock textBlock = null; // Note: the assignment operation is a lambda, which slightly confuses the compiler
Assert.Errorless(() => { textBlock = newBlock.Specialise<TextBlock>(); }, "Block.Specialize<TextBlock>() raised an exception: ", "Block.Specialize<TextBlock>() completed without issue."); Assert.Errorless(() => { textBlock = Block.PlaceNew<TextBlock>(BlockIDs.TextBlock, Unity.Mathematics.float3.zero + 1); }, "Block.PlaceNew<TextBlock>() raised an exception: ", "Block.PlaceNew<TextBlock>() completed without issue.");
if (!Assert.NotNull(textBlock, "Block.Specialize<TextBlock>() returned null, possibly because it failed silently.", "Specialized TextBlock is not null.")) return; if (!Assert.NotNull(textBlock, "Block.PlaceNew<TextBlock>() returned null, possibly because it failed silently.", "Specialized TextBlock is not null.")) return;
if (!Assert.NotNull(textBlock.Text, "TextBlock.Text is null, possibly because it failed silently.", "TextBlock.Text is not null.")) return; if (!Assert.NotNull(textBlock.Text, "TextBlock.Text is null, possibly because it failed silently.", "TextBlock.Text is not null.")) return;
if (!Assert.NotNull(textBlock.TextBlockId, "TextBlock.TextBlockId is null, possibly because it failed silently.", "TextBlock.TextBlockId is not null.")) return; if (!Assert.NotNull(textBlock.TextBlockId, "TextBlock.TextBlockId is null, possibly because it failed silently.", "TextBlock.TextBlockId is not null.")) return;
} }

View file

@ -12,79 +12,64 @@ namespace GamecraftModdingAPI.Blocks
{ {
public class ConsoleBlock : Block public class ConsoleBlock : Block
{ {
public static ConsoleBlock PlaceNew(float3 position, public ConsoleBlock(EGID id): base(id)
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
int uscale = 1, float3 scale = default, Player player = null)
{ {
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
{
EGID id = PlacementEngine.PlaceBlock(BlockIDs.ConsoleBlock, color, darkness,
position, uscale, scale, player, rotation);
return new ConsoleBlock(id);
}
return null;
} }
public ConsoleBlock(EGID id): base(id) public ConsoleBlock(uint id): base(new EGID(id, CommonExclusiveGroups.BUILD_CONSOLE_BLOCK_GROUP))
{ {
if (!BlockEngine.GetBlockInfoExists<ConsoleBlockEntityStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
}
public ConsoleBlock(uint id): base(new EGID(id, CommonExclusiveGroups.BUILD_CONSOLE_BLOCK_GROUP))
{
if (!BlockEngine.GetBlockInfoExists<ConsoleBlockEntityStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
// custom console block properties // custom console block properties
/// <summary>
/// Setting a nonexistent command will crash the game when switching to simulation
/// </summary>
public string Command public string Command
{ {
get get
{ {
return BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).commandName; return BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.commandName);
} }
set set
{ {
BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).commandName.Set(value); BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.commandName.Set(val),
value);
} }
} }
public string Arg1 public string Arg1
{ {
get => BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg1; get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg1);
set set
{ {
BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg1.Set(value); BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg1.Set(val),
value);
} }
} }
public string Arg2 public string Arg2
{ {
get => BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg2; get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg2);
set set
{ {
BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg2.Set(value); BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg2.Set(val),
} value);
}
} }
public string Arg3 public string Arg3
{ {
get => BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg3; get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg3);
set set
{ {
BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg3.Set(value); BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg3.Set(val),
} value);
}
} }
} }
} }

View file

@ -11,43 +11,12 @@ namespace GamecraftModdingAPI.Blocks
{ {
public class Motor : Block public class Motor : Block
{ {
/// <summary>
/// Places a new motor.
/// Any valid motor type is accepted.
/// This re-implements Block.PlaceNew(...)
/// </summary>
public static new Motor PlaceNew(BlockIDs block, float3 position,
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
int uscale = 1, float3 scale = default, Player player = null)
{
if (!(block == BlockIDs.MotorS || block == BlockIDs.MotorM))
{
throw new BlockTypeException($"Block is not a {typeof(Motor).Name} block");
}
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
{
EGID id = PlacementEngine.PlaceBlock(block, color, darkness,
position, uscale, scale, player, rotation);
return new Motor(id);
}
return null;
}
public Motor(EGID id) : base(id) public Motor(EGID id) : base(id)
{ {
if (!BlockEngine.GetBlockInfoExists<MotorReadOnlyStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
public Motor(uint id): base(new EGID(id, CommonExclusiveGroups.BUILD_MOTOR_BLOCK_GROUP)) public Motor(uint id): base(new EGID(id, CommonExclusiveGroups.BUILD_MOTOR_BLOCK_GROUP))
{ {
if (!BlockEngine.GetBlockInfoExists<MotorReadOnlyStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
// custom motor properties // custom motor properties
@ -59,13 +28,12 @@ namespace GamecraftModdingAPI.Blocks
{ {
get get
{ {
return BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id).maxVelocity; return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.maxVelocity);
} }
set set
{ {
ref MotorReadOnlyStruct motor = ref BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id); BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, float val) => st.maxVelocity = val, value);
motor.maxVelocity = value;
} }
} }
@ -76,13 +44,12 @@ namespace GamecraftModdingAPI.Blocks
{ {
get get
{ {
return BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id).maxForce; return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.maxForce);
} }
set set
{ {
ref MotorReadOnlyStruct motor = ref BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id); BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, float val) => st.maxForce = val, value);
motor.maxForce = value;
} }
} }
@ -93,13 +60,12 @@ namespace GamecraftModdingAPI.Blocks
{ {
get get
{ {
return BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id).reverse; return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.reverse);
} }
set set
{ {
ref MotorReadOnlyStruct motor = ref BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id); BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, bool val) => st.reverse = val, value);
motor.reverse = value;
} }
} }
} }

View file

@ -35,8 +35,17 @@ namespace GamecraftModdingAPI.Blocks
// implementations for Movement static class // implementations for Movement static class
public float3 MoveBlock(EGID blockID, float3 vector) internal float3 MoveBlock(EGID blockID, BlockEngine.BlockInitData data, float3 vector)
{ {
if (!entitiesDB.Exists<PositionEntityStruct>(blockID))
{
if (data.Group == null) return float3.zero;
var init = new EntityComponentInitializer(blockID, data.Group);
init.Init(new PositionEntityStruct {position = vector});
init.Init(new GridRotationStruct {position = vector});
init.Init(new LocalTransformEntityStruct {position = vector});
return vector;
}
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID); ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID);
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID); ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID);
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID); ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID);
@ -56,8 +65,14 @@ namespace GamecraftModdingAPI.Blocks
return posStruct.position; return posStruct.position;
} }
public float3 GetPosition(EGID blockID) internal float3 GetPosition(EGID blockID, BlockEngine.BlockInitData data)
{ {
if (!entitiesDB.Exists<PositionEntityStruct>(blockID))
{
if (data.Group == null) return float3.zero;
var init = new EntityComponentInitializer(blockID, data.Group);
return init.Has<PositionEntityStruct>() ? init.Get<PositionEntityStruct>().position : float3.zero;
}
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID); ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID);
return posStruct.position; return posStruct.position;
} }

View file

@ -8,27 +8,22 @@ namespace GamecraftModdingAPI.Blocks
{ {
public ObjectIdentifier(EGID id) : base(id) public ObjectIdentifier(EGID id) : base(id)
{ {
if (!BlockEngine.GetBlockInfoExists<ObjectIdEntityStruct>(Id))
{
throw new BlockTypeException($"Block is not a {GetType().Name} block");
}
} }
public ObjectIdentifier(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_OBJID_BLOCK_GROUP)) public ObjectIdentifier(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_OBJID_BLOCK_GROUP))
{ {
if (!BlockEngine.GetBlockInfoExists<ObjectIdEntityStruct>(Id))
{
throw new BlockTypeException($"Block is not a {GetType().Name} block");
}
} }
public char Identifier public char Identifier
{ {
get => (char) (BlockEngine.GetBlockInfo<ObjectIdEntityStruct>(Id).objectId + 'A'); get => (char) BlockEngine.GetBlockInfo(this, (ObjectIdEntityStruct st) => st.objectId + 'A');
set set
{ {
BlockEngine.GetBlockInfo<ObjectIdEntityStruct>(Id).objectId = (byte) (value - 'A'); BlockEngine.SetBlockInfo(this, (ref ObjectIdEntityStruct st, char val) =>
Label = value + ""; //The label isn't updated automatically {
st.objectId = (byte) (val - 'A');
Label = val + ""; //The label isn't updated automatically
}, value);
} }
} }
@ -37,7 +32,7 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public byte SimID public byte SimID
{ {
get => BlockEngine.GetBlockInfo<ObjectIdEntityStruct>(Id).simObjectId; get => BlockEngine.GetBlockInfo(this, (ObjectIdEntityStruct st) => st.simObjectId);
} }
/// <summary> /// <summary>

View file

@ -11,43 +11,12 @@ namespace GamecraftModdingAPI.Blocks
{ {
public class Piston : Block public class Piston : Block
{ {
/// <summary> public Piston(EGID id) : base(id)
/// Places a new piston.
/// Any valid piston type is accepted.
/// This re-implements Block.PlaceNew(...)
/// </summary>
public static new Piston PlaceNew(BlockIDs block, float3 position,
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
int uscale = 1, float3 scale = default, Player player = null)
{ {
if (!(block == BlockIDs.ServoPiston || block == BlockIDs.StepperPiston || block == BlockIDs.PneumaticPiston))
{
throw new BlockTypeException($"Block is not a {typeof(Piston).Name} block");
}
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
{
EGID id = PlacementEngine.PlaceBlock(block, color, darkness,
position, uscale, scale, player, rotation);
return new Piston(id);
}
return null;
}
public Piston(EGID id) : base(id)
{
if (!BlockEngine.GetBlockInfoExists<PistonReadOnlyStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
public Piston(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_PISTON_BLOCK_GROUP)) public Piston(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_PISTON_BLOCK_GROUP))
{ {
if (!BlockEngine.GetBlockInfoExists<PistonReadOnlyStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
// custom piston properties // custom piston properties
@ -56,13 +25,13 @@ namespace GamecraftModdingAPI.Blocks
/// The piston's max extension distance. /// The piston's max extension distance.
/// </summary> /// </summary>
public float MaximumExtension public float MaximumExtension
{ {
get => BlockEngine.GetBlockInfo<PistonReadOnlyStruct>(Id).maxDeviation; get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.maxDeviation);
set set
{ {
ref PistonReadOnlyStruct piston = ref BlockEngine.GetBlockInfo<PistonReadOnlyStruct>(Id); BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.maxDeviation = val,
piston.maxDeviation = value; value);
} }
} }
@ -70,13 +39,12 @@ namespace GamecraftModdingAPI.Blocks
/// The piston's max extension force. /// The piston's max extension force.
/// </summary> /// </summary>
public float MaximumForce public float MaximumForce
{ {
get => BlockEngine.GetBlockInfo<PistonReadOnlyStruct>(Id).maxForce; get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.maxForce);
set set
{ {
ref PistonReadOnlyStruct piston = ref BlockEngine.GetBlockInfo<PistonReadOnlyStruct>(Id); BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.maxForce = val, value);
piston.maxForce = value;
} }
} }
} }

View file

@ -40,15 +40,16 @@ namespace GamecraftModdingAPI.Blocks
private static BlockEntityFactory _blockEntityFactory; //Injected from PlaceBlockEngine private static BlockEntityFactory _blockEntityFactory; //Injected from PlaceBlockEngine
public EGID PlaceBlock(BlockIDs block, BlockColors color, byte darkness, float3 position, int uscale, public EGID PlaceBlock(BlockIDs block, BlockColors color, byte darkness, float3 position, int uscale,
float3 scale, Player player, float3 rotation) float3 scale, Player player, float3 rotation, out EntityComponentInitializer initializer)
{ //It appears that only the non-uniform scale has any visible effect, but if that's not given here it will be set to the uniform one { //It appears that only the non-uniform scale has any visible effect, but if that's not given here it will be set to the uniform one
if (darkness > 9) if (darkness > 9)
throw new Exception("That is too dark. Make sure to use 0-9 as darkness. (0 is default.)"); throw new Exception("That is too dark. Make sure to use 0-9 as darkness. (0 is default.)");
return BuildBlock((ushort) block, (byte) (color + darkness * 10), position, uscale, scale, rotation, initializer = BuildBlock((ushort) block, (byte) (color + darkness * 10), position, uscale, scale, rotation,
(player ?? new Player(PlayerType.Local)).Id); (player ?? new Player(PlayerType.Local)).Id);
return initializer.EGID;
} }
private EGID BuildBlock(ushort block, byte color, float3 position, int uscale, float3 scale, float3 rot, uint playerId) private EntityComponentInitializer BuildBlock(ushort block, byte color, float3 position, int uscale, float3 scale, float3 rot, uint playerId)
{ {
if (_blockEntityFactory == null) if (_blockEntityFactory == null)
throw new Exception("The factory is null."); throw new Exception("The factory is null.");
@ -106,8 +107,7 @@ namespace GamecraftModdingAPI.Blocks
ref PickedBlockExtraDataStruct pickedBlock = ref entitiesDB.QueryEntity<PickedBlockExtraDataStruct>(playerEGID); ref PickedBlockExtraDataStruct pickedBlock = ref entitiesDB.QueryEntity<PickedBlockExtraDataStruct>(playerEGID);
pickedBlock.placedBlockEntityID = structInitializer.EGID; pickedBlock.placedBlockEntityID = structInitializer.EGID;
pickedBlock.placedBlockWasAPickedBlock = false; pickedBlock.placedBlockWasAPickedBlock = false;
Block.BlockEngine.Synced = false; // Block entities will need to be submitted before properties can be used return structInitializer;
return structInitializer.EGID;
} }
public string Name { get; } = "GamecraftModdingAPIPlacementGameEngine"; public string Name { get; } = "GamecraftModdingAPIPlacementGameEngine";

View file

@ -35,23 +35,32 @@ namespace GamecraftModdingAPI.Blocks
// implementations for Rotation static class // implementations for Rotation static class
public float3 RotateBlock(EGID blockID, Vector3 vector) internal float3 RotateBlock(EGID blockID, BlockEngine.BlockInitData data, Vector3 vector)
{ {
if (!entitiesDB.Exists<RotationEntityStruct>(blockID))
{
if (data.Group == null) return float3.zero;
var init = new EntityComponentInitializer(blockID, data.Group);
init.Init(new RotationEntityStruct {rotation = new Quaternion {eulerAngles = vector}});
init.Init(new GridRotationStruct {rotation = new Quaternion {eulerAngles = vector}});
init.Init(new LocalTransformEntityStruct {rotation = new Quaternion {eulerAngles = vector}});
return vector;
}
ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntity<RotationEntityStruct>(blockID); ref RotationEntityStruct rotStruct = ref this.entitiesDB.QueryEntity<RotationEntityStruct>(blockID);
ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID); ref GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID);
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID); ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID);
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID); ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID);
// main (persistent) position // main (persistent) position
Quaternion newRotation = (Quaternion)rotStruct.rotation; Quaternion newRotation = rotStruct.rotation;
newRotation.eulerAngles += vector; newRotation.eulerAngles = vector;
rotStruct.rotation = (quaternion)newRotation; rotStruct.rotation = newRotation;
// placement grid rotation // placement grid rotation
Quaternion newGridRotation = (Quaternion)gridStruct.rotation; Quaternion newGridRotation = gridStruct.rotation;
newGridRotation.eulerAngles += vector; newGridRotation.eulerAngles = vector;
gridStruct.rotation = (quaternion)newGridRotation; gridStruct.rotation = newGridRotation;
// rendered position // rendered position
Quaternion newTransRotation = (Quaternion)rotStruct.rotation; Quaternion newTransRotation = rotStruct.rotation;
newTransRotation.eulerAngles += vector; newTransRotation.eulerAngles = vector;
transStruct.rotation = newTransRotation; transStruct.rotation = newTransRotation;
// collision position // collision position
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Unity.Transforms.Rotation FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Unity.Transforms.Rotation
@ -63,8 +72,17 @@ namespace GamecraftModdingAPI.Blocks
} }
public float3 GetRotation(EGID blockID) internal float3 GetRotation(EGID blockID, BlockEngine.BlockInitData data)
{ {
if (!entitiesDB.Exists<RotationEntityStruct>(blockID))
{
if (data.Group == null) return float3.zero;
var init = new EntityComponentInitializer(blockID, data.Group);
return init.Has<RotationEntityStruct>()
? (float3) ((Quaternion) init.Get<RotationEntityStruct>().rotation).eulerAngles
: float3.zero;
}
ref RotationEntityStruct rotStruct = ref entitiesDB.QueryEntity<RotationEntityStruct>(blockID); ref RotationEntityStruct rotStruct = ref entitiesDB.QueryEntity<RotationEntityStruct>(blockID);
return ((Quaternion) rotStruct.rotation).eulerAngles; return ((Quaternion) rotStruct.rotation).eulerAngles;
} }

View file

@ -11,43 +11,12 @@ namespace GamecraftModdingAPI.Blocks
{ {
public class Servo : Block public class Servo : Block
{ {
/// <summary>
/// Places a new servo.
/// Any valid servo type is accepted.
/// This re-implements Block.PlaceNew(...)
/// </summary>
public static new Servo PlaceNew(BlockIDs block, float3 position,
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
int uscale = 1, float3 scale = default, Player player = null)
{
if (!(block == BlockIDs.ServoAxle || block == BlockIDs.ServoHinge || block == BlockIDs.ServoPiston))
{
throw new BlockTypeException($"Block is not a {nameof(Servo)} block");
}
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
{
EGID id = PlacementEngine.PlaceBlock(block, color, darkness,
position, uscale, scale, player, rotation);
return new Servo(id);
}
return null;
}
public Servo(EGID id) : base(id) public Servo(EGID id) : base(id)
{ {
if (!BlockEngine.GetBlockInfoExists<ServoReadOnlyStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
public Servo(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_SERVO_BLOCK_GROUP)) public Servo(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_SERVO_BLOCK_GROUP))
{ {
if (!BlockEngine.GetBlockInfoExists<ServoReadOnlyStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
// custom servo properties // custom servo properties
@ -56,13 +25,12 @@ namespace GamecraftModdingAPI.Blocks
/// The servo's minimum angle. /// The servo's minimum angle.
/// </summary> /// </summary>
public float MinimumAngle public float MinimumAngle
{ {
get => BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id).minDeviation; get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.minDeviation);
set set
{ {
ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id); BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.minDeviation = val, value);
servo.minDeviation = value;
} }
} }
@ -71,13 +39,12 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public float MaximumAngle public float MaximumAngle
{ {
get => BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id).maxDeviation; get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.maxDeviation);
set set
{ {
ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id); BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.maxDeviation = val, value);
servo.maxDeviation = value; }
}
} }
/// <summary> /// <summary>
@ -85,13 +52,12 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public float MaximumForce public float MaximumForce
{ {
get => BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id).maxForce; get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.maxForce);
set set
{ {
ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id); BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.maxForce = val, value);
servo.maxForce = value; }
}
} }
/// <summary> /// <summary>
@ -99,13 +65,12 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public bool Reverse public bool Reverse
{ {
get => BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id).reverse; get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.reverse);
set set
{ {
ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id); BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, bool val) => st.reverse = val, value);
servo.reverse = value; }
}
} }
} }
} }

View file

@ -14,28 +14,9 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public class SignalingBlock : Block public class SignalingBlock : Block
{ {
/// <summary>
/// Places a new signaling block.
/// Any valid functional block type with IO ports will work.
/// This re-implements Block.PlaceNew(...)
/// </summary>
public static new SignalingBlock PlaceNew(BlockIDs block, float3 position,
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
int uscale = 1, float3 scale = default, Player player = null)
{
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
{
EGID id = PlacementEngine.PlaceBlock(block, color, darkness,
position, uscale, scale, player, rotation);
return new SignalingBlock(id);
}
return null;
}
public SignalingBlock(EGID id) : base(id) public SignalingBlock(EGID id) : base(id)
{ {
if (!BlockEngine.GetBlockInfoExists<BlockPortsStruct>(this.Id)) if (!BlockEngine.GetBlockInfoExists<BlockPortsStruct>(this))
{ {
throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
} }
@ -43,17 +24,12 @@ namespace GamecraftModdingAPI.Blocks
public SignalingBlock(uint id) : base(id) public SignalingBlock(uint id) : base(id)
{ {
if (!BlockEngine.GetBlockInfoExists<BlockPortsStruct>(this.Id)) if (!BlockEngine.GetBlockInfoExists<BlockPortsStruct>(this))
{ {
throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
} }
} }
protected ref BlockPortsStruct GetBlockPortsStruct()
{
return ref BlockEngine.GetBlockInfo<BlockPortsStruct>(Id);
}
/// <summary> /// <summary>
/// Generates the input port identifiers. /// Generates the input port identifiers.
/// </summary> /// </summary>
@ -72,16 +48,6 @@ namespace GamecraftModdingAPI.Blocks
return SignalEngine.GetSignalOutputs(Id); return SignalEngine.GetSignalOutputs(Id);
} }
/// <summary>
/// Gets the port struct.
/// </summary>
/// <returns>The port struct.</returns>
/// <param name="portId">Port identifier.</param>
protected ref PortEntityStruct GetPortStruct(EGID portId)
{
return ref BlockEngine.GetBlockInfo<PortEntityStruct>(portId);
}
/// <summary> /// <summary>
/// Gets the connected wire. /// Gets the connected wire.
/// </summary> /// </summary>
@ -108,16 +74,16 @@ namespace GamecraftModdingAPI.Blocks
/// The input port count. /// The input port count.
/// </summary> /// </summary>
public uint InputCount public uint InputCount
{ {
get => GetBlockPortsStruct().inputCount; get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.inputCount);
} }
/// <summary> /// <summary>
/// The output port count. /// The output port count.
/// </summary> /// </summary>
public uint OutputCount public uint OutputCount
{ {
get => GetBlockPortsStruct().outputCount; get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.outputCount);
} }
} }
} }

View file

@ -13,43 +13,12 @@ namespace GamecraftModdingAPI.Blocks
{ {
public class SpawnPoint : Block public class SpawnPoint : Block
{ {
/// <summary> public SpawnPoint(EGID id) : base(id)
/// Places a new spawn point.
/// Any valid spawn block type is accepted.
/// This re-implements Block.PlaceNew(...)
/// </summary>
public static new SpawnPoint PlaceNew(BlockIDs block, float3 position,
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
int uscale = 1, float3 scale = default, Player player = null)
{ {
if (!(block == BlockIDs.LargeSpawn || block == BlockIDs.SmallSpawn || block == BlockIDs.MediumSpawn || block == BlockIDs.PlayerSpawn))
{
throw new BlockTypeException($"Block is not a {nameof(SpawnPoint)} block");
}
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
{
EGID id = PlacementEngine.PlaceBlock(block, color, darkness,
position, uscale, scale, player, rotation);
return new SpawnPoint(id);
}
return null;
}
public SpawnPoint(EGID id) : base(id)
{
if (!BlockEngine.GetBlockInfoExists<SpawnPointStatsEntityStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
public SpawnPoint(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_SPAWNPOINT_BLOCK_GROUP)) public SpawnPoint(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_SPAWNPOINT_BLOCK_GROUP))
{ {
if (!BlockEngine.GetBlockInfoExists<SpawnPointStatsEntityStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
// custom spawn point properties // custom spawn point properties
@ -59,16 +28,12 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public uint Lives public uint Lives
{ {
get get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.lives);
{
return BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id).lives;
}
set set
{ {
ref SpawnPointStatsEntityStruct spses = ref BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id); BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, uint val) => st.lives = val, value);
spses.lives = value; }
}
} }
/// <summary> /// <summary>
@ -76,16 +41,12 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public bool Damageable public bool Damageable
{ {
get get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.canTakeDamage);
{
return BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id).canTakeDamage;
}
set set
{ {
ref SpawnPointStatsEntityStruct spses = ref BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id); BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, bool val) => st.canTakeDamage = val, value);
spses.canTakeDamage = value; }
}
} }
/// <summary> /// <summary>
@ -93,16 +54,12 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public bool GameOverEnabled public bool GameOverEnabled
{ {
get get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.gameOverScreen);
{
return BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id).gameOverScreen;
}
set set
{ {
ref SpawnPointStatsEntityStruct spses = ref BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id); BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, bool val) => st.gameOverScreen = val, value);
spses.gameOverScreen = value; }
}
} }
/// <summary> /// <summary>
@ -110,16 +67,12 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public byte Team public byte Team
{ {
get get => BlockEngine.GetBlockInfo(this, (SpawnPointIdsEntityStruct st) => st.teamId);
{
return BlockEngine.GetBlockInfo<SpawnPointIdsEntityStruct>(Id).teamId;
}
set set
{ {
ref SpawnPointIdsEntityStruct spses = ref BlockEngine.GetBlockInfo<SpawnPointIdsEntityStruct>(Id); BlockEngine.SetBlockInfo(this, (ref SpawnPointIdsEntityStruct st, byte val) => st.teamId = val, value);
spses.teamId = value; }
}
} }
} }
} }

View file

@ -12,35 +12,12 @@ namespace GamecraftModdingAPI.Blocks
{ {
public class TextBlock : Block public class TextBlock : Block
{ {
public static TextBlock PlaceNew(float3 position,
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
int uscale = 1, float3 scale = default, Player player = null)
{
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
{
EGID id = PlacementEngine.PlaceBlock(BlockIDs.TextBlock, color, darkness,
position, uscale, scale, player, rotation);
return new TextBlock(id);
}
return null;
}
public TextBlock(EGID id) : base(id) public TextBlock(EGID id) : base(id)
{ {
if (!BlockEngine.GetBlockInfoExists<TextBlockDataStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
public TextBlock(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_TEXT_BLOCK_GROUP)) public TextBlock(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_TEXT_BLOCK_GROUP))
{ {
if (!BlockEngine.GetBlockInfoExists<TextBlockDataStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
// custom text block properties // custom text block properties
@ -50,35 +27,34 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public string Text public string Text
{ {
get get => BlockEngine.GetBlockInfo(this, (TextBlockDataStruct st) => st.textCurrent);
{
return BlockEngine.GetBlockInfo<TextBlockDataStruct>(Id).textCurrent;
}
set set
{ {
ref TextBlockDataStruct tbds = ref BlockEngine.GetBlockInfo<TextBlockDataStruct>(Id); BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) =>
tbds.textCurrent.Set(value); {
tbds.textStored.Set(value); tbds.textCurrent.Set(val);
BlockEngine.GetBlockInfo<TextBlockNetworkDataStruct>(Id).newTextBlockStringContent.Set(value); tbds.textStored.Set(val);
} }, value);
} BlockEngine.SetBlockInfo(this,
(ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockStringContent.Set(val), value);
}
}
/// <summary> /// <summary>
/// The text block's current text block ID (used in ChangeTextBlockCommand). /// The text block's current text block ID (used in ChangeTextBlockCommand).
/// </summary> /// </summary>
public string TextBlockId public string TextBlockId
{ {
get get => BlockEngine.GetBlockInfo(this, (TextBlockDataStruct st) => st.textBlockID);
{
return BlockEngine.GetBlockInfo<TextBlockDataStruct>(Id).textBlockID;
}
set set
{ {
BlockEngine.GetBlockInfo<TextBlockDataStruct>(Id).textBlockID.Set(value); BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) =>
BlockEngine.GetBlockInfo<TextBlockNetworkDataStruct>(Id).newTextBlockID.Set(value); tbds.textBlockID.Set(val), value);
} BlockEngine.SetBlockInfo(this,
(ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockID.Set(val), value);
}
} }
} }
} }

View file

@ -13,37 +13,12 @@ namespace GamecraftModdingAPI.Blocks
{ {
public class Timer : Block public class Timer : Block
{ {
/// <summary>
/// Places a new timer block.
/// </summary>
public static Timer PlaceNew(float3 position,
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
int uscale = 1, float3 scale = default, Player player = null)
{
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
{
EGID id = PlacementEngine.PlaceBlock(BlockIDs.Timer, color, darkness,
position, uscale, scale, player, rotation);
return new Timer(id);
}
return null;
}
public Timer(EGID id) : base(id) public Timer(EGID id) : base(id)
{ {
if (!BlockEngine.GetBlockInfoExists<TimerBlockDataStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
public Timer(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_TIMER_BLOCK_GROUP)) public Timer(uint id) : base(new EGID(id, CommonExclusiveGroups.BUILD_TIMER_BLOCK_GROUP))
{ {
if (!BlockEngine.GetBlockInfoExists<TimerBlockDataStruct>(this.Id))
{
throw new BlockTypeException($"Block is not a {this.GetType().Name} block");
}
} }
// custom timer properties // custom timer properties
@ -53,16 +28,13 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public float Start public float Start
{ {
get get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.startTime);
{
return BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id).startTime;
}
set set
{ {
ref TimerBlockDataStruct tbds = ref BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id); BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, float val) => tbds.startTime = val,
tbds.startTime = value; value);
} }
} }
/// <summary> /// <summary>
@ -70,16 +42,13 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public float End public float End
{ {
get get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.endTime);
{
return BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id).endTime;
}
set set
{ {
ref TimerBlockDataStruct tbds = ref BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id); BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, float val) => tbds.endTime = val,
tbds.endTime = value; value);
} }
} }
/// <summary> /// <summary>
@ -87,16 +56,13 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public bool DisplayMilliseconds public bool DisplayMilliseconds
{ {
get get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.outputFormatHasMS);
{
return BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id).outputFormatHasMS;
}
set set
{ {
ref TimerBlockDataStruct tbds = ref BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id); BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, bool val) => tbds.outputFormatHasMS = val,
tbds.outputFormatHasMS = value; value);
} }
} }
/// <summary> /// <summary>
@ -104,16 +70,13 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public int CurrentTime public int CurrentTime
{ {
get get => BlockEngine.GetBlockInfo(this, (TimerBlockLabelCacheEntityStruct st) => st.timeLastRenderFrameMS);
{
return BlockEngine.GetBlockInfo<TimerBlockLabelCacheEntityStruct>(Id).timeLastRenderFrameMS;
}
set set
{ {
ref TimerBlockLabelCacheEntityStruct tblces = ref BlockEngine.GetBlockInfo<TimerBlockLabelCacheEntityStruct>(Id); BlockEngine.SetBlockInfo(this, (ref TimerBlockLabelCacheEntityStruct tbds, int val) => tbds.timeLastRenderFrameMS = val,
tblces.timeLastRenderFrameMS = value; value);
} }
} }
} }
} }

View file

@ -9,7 +9,7 @@ using RobocraftX.Physics;
namespace GamecraftModdingAPI namespace GamecraftModdingAPI
{ {
/// <summary> /// <summary>
/// A rigid body (like a cluster of connected blocks) during simulation. /// A rigid body (like a chunk of connected blocks) during simulation.
/// </summary> /// </summary>
public class SimBody : IEquatable<SimBody>, IEquatable<EGID> public class SimBody : IEquatable<SimBody>, IEquatable<EGID>
{ {

View file

@ -83,32 +83,12 @@ namespace GamecraftModdingAPI.Tests
{ {
if (err == null) err = $"{nameof(T)} '{obj1}' is not equal to '{obj2}'."; if (err == null) err = $"{nameof(T)} '{obj1}' is not equal to '{obj2}'.";
if (success == null) success = $"{nameof(T)} '{obj1}' is equal to '{obj2}'."; if (success == null) success = $"{nameof(T)} '{obj1}' is equal to '{obj2}'.";
if (obj1 == null && obj2 == null) if ((obj1 == null && obj2 == null)
|| (obj1 != null && obj2 != null && obj1.Equals(obj2) && obj2.Equals(obj1)))
{ {
// pass // pass
Log(PASS + success); Log(PASS + success);
TestRoot.TestsPassed = true; TestRoot.TestsPassed = true;
return true;
}
else if (!(obj1 == null && obj2 == null) && obj1.Equals(obj2) && obj2.Equals(obj1))
{
// pass
Log(PASS + success);
TestRoot.TestsPassed = true;
return true;
}
else if (obj1 != null && (obj1 != null && !obj1.Equals(obj2)))
{
// pass
Log(PASS + success);
TestRoot.TestsPassed = true;
return true;
}
else if (obj2 != null && !obj2.Equals(obj1))
{
// pass
Log(PASS + success);
TestRoot.TestsPassed = true;
return true; return true;
} }
else else

View file

@ -232,6 +232,28 @@ namespace GamecraftModdingAPI.Tests
} }
}).Build(); }).Build();
CommandBuilder.Builder()
.Name("PlaceConsole")
.Description("Place a bunch of console block with a given text - entering simulation with them crashes the game as the cmd doesn't exist")
.Action((float x, float y, float z) =>
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 100; j++)
{
var block = Block.PlaceNew<ConsoleBlock>(BlockIDs.ConsoleBlock,
new float3(x + i, y, z + j));
block.Command = "test_command";
}
}
sw.Stop();
Logging.CommandLog($"Blocks placed in {sw.ElapsedMilliseconds} ms");
})
.Build();
GameClient.SetDebugInfo("InstalledMods", InstalledMods); GameClient.SetDebugInfo("InstalledMods", InstalledMods);
Block.Placed += (sender, args) => Block.Placed += (sender, args) =>
Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID); Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID);

View file

@ -1,25 +0,0 @@
using System;
using RobocraftX.StateSync;
using Svelto.ECS;
using HarmonyLib;
namespace GamecraftModdingAPI.Utility
{
[HarmonyPatch(typeof(DeterministicStepCompositionRoot), "ResetWorld")]
public static class DeterministicStepCompositionRootPatch
{
private static SimpleEntitiesSubmissionScheduler engineRootScheduler;
public static void Postfix(SimpleEntitiesSubmissionScheduler scheduler)
{
engineRootScheduler = scheduler;
}
internal static void SubmitEntitiesNow()
{
if (engineRootScheduler != null)
engineRootScheduler.SubmitEntities();
}
}
}