@@ -1,6 +1,7 @@ | |||
using System; | |||
using System.Reflection; | |||
using System.Threading.Tasks; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection.Emit; | |||
using Svelto.ECS; | |||
using Svelto.ECS.EntityStructs; | |||
@@ -54,23 +55,13 @@ namespace GamecraftModdingAPI | |||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, | |||
int uscale = 1, float3 scale = default, Player player = null) | |||
{ | |||
if (PlacementEngine.IsInGame && GameState.IsBuildMode()) | |||
{ | |||
return new Block(PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation)); | |||
} | |||
return null; | |||
return PlaceNew<Block>(block, position, rotation, color, darkness, uscale, scale, 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. | |||
/// <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> | |||
/// <param name="block">The block's type</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="player">The player who placed the block</param> | |||
/// <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, | |||
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()) | |||
{ | |||
try | |||
{ | |||
var ret = new Block(PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation)); | |||
await AsyncUtils.WaitForSubmission(); | |||
return ret; | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.MetaDebugLog(e); | |||
} | |||
var egid = PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation, out var initializer); | |||
var bl = New<T>(egid.entityID, egid.groupID); | |||
bl.InitData.Group = BlockEngine.InitGroup(initializer); | |||
Placed += bl.OnPlacedInit; | |||
return bl; | |||
} | |||
return null; | |||
@@ -109,7 +95,7 @@ namespace GamecraftModdingAPI | |||
/// <returns>The block object</returns> | |||
public static Block GetLastPlacedBlock() | |||
{ | |||
return new Block(BlockIdentifiers.LatestBlockID); | |||
return New<Block>(BlockIdentifiers.LatestBlockID); | |||
} | |||
/// <summary> | |||
@@ -130,9 +116,91 @@ namespace GamecraftModdingAPI | |||
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) | |||
{ | |||
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> | |||
@@ -147,16 +215,18 @@ namespace GamecraftModdingAPI | |||
public EGID Id { get; } | |||
internal BlockEngine.BlockInitData InitData; | |||
/// <summary> | |||
/// 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. | |||
/// </summary> | |||
public float3 Position | |||
{ | |||
get => Exists ? MovementEngine.GetPosition(Id) : float3.zero; | |||
get => MovementEngine.GetPosition(Id, InitData); | |||
set | |||
{ | |||
if (Exists) MovementEngine.MoveBlock(Id, value); | |||
MovementEngine.MoveBlock(Id, InitData, value); | |||
} | |||
} | |||
@@ -165,10 +235,10 @@ namespace GamecraftModdingAPI | |||
/// </summary> | |||
public float3 Rotation | |||
{ | |||
get => Exists ? RotationEngine.GetRotation(Id) : float3.zero; | |||
get => RotationEngine.GetRotation(Id, InitData); | |||
set | |||
{ | |||
if (Exists) RotationEngine.RotateBlock(Id, value); | |||
RotationEngine.RotateBlock(Id, InitData, value); | |||
} | |||
} | |||
@@ -178,12 +248,11 @@ namespace GamecraftModdingAPI | |||
/// </summary> | |||
public float3 Scale | |||
{ | |||
get => BlockEngine.GetBlockInfo<ScalingEntityStruct>(Id).scale; | |||
get => BlockEngine.GetBlockInfo(this, (ScalingEntityStruct st) => st.scale); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ScalingEntityStruct st, float3 val) => st.scale = val, value); | |||
if (!Exists) return; //UpdateCollision needs the block to exist | |||
ref var scaling = ref BlockEngine.GetBlockInfo<ScalingEntityStruct>(Id); | |||
scaling.scale = value; | |||
ScalingEngine.UpdateCollision(Id); | |||
} | |||
} | |||
@@ -194,11 +263,11 @@ namespace GamecraftModdingAPI | |||
/// </summary> | |||
public int UniformScale | |||
{ | |||
get => BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(Id).scaleFactor; | |||
get => BlockEngine.GetBlockInfo(this, (UniformBlockScaleEntityStruct st) => st.scaleFactor); | |||
set | |||
{ | |||
ref var scaleStruct = ref BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(Id); | |||
scaleStruct.scaleFactor = value; | |||
BlockEngine.SetBlockInfo(this, (ref UniformBlockScaleEntityStruct st, int val) => st.scaleFactor = val, | |||
value); | |||
Scale = new float3(value, value, value); | |||
} | |||
} | |||
@@ -210,8 +279,7 @@ namespace GamecraftModdingAPI | |||
{ | |||
get | |||
{ | |||
var id = (BlockIDs) BlockEngine.GetBlockInfo<DBEntityStruct>(Id, out var exists).DBID; | |||
return exists ? id : BlockIDs.Invalid; | |||
return BlockEngine.GetBlockInfo(this, (DBEntityStruct st) => (BlockIDs) st.DBID, BlockIDs.Invalid); | |||
} | |||
} | |||
@@ -222,17 +290,19 @@ namespace GamecraftModdingAPI | |||
{ | |||
get | |||
{ | |||
byte index = BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id, out var exists).indexInPalette; | |||
if (!exists) index = byte.MaxValue; | |||
byte index = BlockEngine.GetBlockInfo(this, (ColourParameterEntityStruct st) => st.indexInPalette, | |||
byte.MaxValue); | |||
return new BlockColor(index); | |||
} | |||
set | |||
{ | |||
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id); | |||
color.indexInPalette = (byte)(value.Color + value.Darkness * 10); | |||
color.overridePaletteColour = false; | |||
color.needsUpdate = true; | |||
BlockEngine.SetBlockColorFromPalette(ref color); | |||
BlockEngine.SetBlockInfo(this, (ref ColourParameterEntityStruct color, BlockColor val) => | |||
{ | |||
color.indexInPalette = (byte) (val.Color + val.Darkness * 10); | |||
color.overridePaletteColour = false; | |||
color.needsUpdate = true; | |||
BlockEngine.SetBlockColorFromPalette(ref color); | |||
}, value); | |||
} | |||
} | |||
@@ -241,32 +311,37 @@ namespace GamecraftModdingAPI | |||
/// </summary> | |||
public float4 CustomColor | |||
{ | |||
get => BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id).overriddenColour; | |||
get => BlockEngine.GetBlockInfo(this, (ColourParameterEntityStruct st) => st.overriddenColour); | |||
set | |||
{ | |||
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id); | |||
color.overriddenColour = value; | |||
color.overridePaletteColour = true; | |||
color.needsUpdate = true; | |||
BlockEngine.SetBlockInfo(this, (ref ColourParameterEntityStruct color, float4 val) => | |||
{ | |||
color.overriddenColour = val; | |||
color.overridePaletteColour = true; | |||
color.needsUpdate = true; | |||
}, value); | |||
} | |||
} | |||
/// <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. | |||
/// </summary> | |||
public string Label | |||
{ | |||
get => BlockEngine.GetBlockInfo<TextLabelEntityViewStruct>(Id).textLabelComponent?.text; | |||
get => BlockEngine.GetBlockInfo(this, (TextLabelEntityViewStruct st) => st.textLabelComponent?.text); | |||
set | |||
{ | |||
ref var text = ref BlockEngine.GetBlockInfo<TextLabelEntityViewStruct>(Id); | |||
if (text.textLabelComponent != null) text.textLabelComponent.text = value; | |||
BlockEngine.SetBlockInfo(this, (ref TextLabelEntityViewStruct text, string val) => | |||
{ | |||
if (text.textLabelComponent != null) text.textLabelComponent.text = val; | |||
}, value); | |||
} | |||
} | |||
/// <summary> | |||
/// 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> | |||
public bool Exists => BlockEngine.BlockExists(Id); | |||
@@ -282,14 +357,21 @@ namespace GamecraftModdingAPI | |||
public bool Remove() => RemovalEngine.RemoveBlock(Id); | |||
/// <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. | |||
/// </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() | |||
{ | |||
uint id = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(Id, out var exists).machineRigidBodyId; | |||
return exists ? new SimBody(id) : null; | |||
return BlockEngine.GetBlockInfo(this, | |||
(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() | |||
@@ -344,12 +426,11 @@ namespace GamecraftModdingAPI | |||
// 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 | |||
ConstructorInfo ctor = typeof(T).GetConstructor(types: new System.Type[] { typeof(EGID) }); | |||
if (ctor == null) | |||
{ | |||
throw new BlockSpecializationException("Specialized block constructor does not accept an EGID"); | |||
} | |||
return (T)ctor.Invoke(new object[] { Id }); | |||
//Lets improve that using delegates | |||
var block = New<T>(Id.entityID, Id.groupID); | |||
block.InitData = this.InitData; | |||
return block; | |||
} | |||
#if DEBUG | |||
@@ -1,3 +1,4 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Gamecraft.Wires; | |||
@@ -10,14 +11,13 @@ using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Engines; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
/// <summary> | |||
/// Engine for executing general block actions | |||
/// </summary> | |||
public class BlockEngine : IApiEngine | |||
public partial class BlockEngine : IApiEngine | |||
{ | |||
public string Name { get; } = "GamecraftModdingAPIBlockGameEngine"; | |||
@@ -25,8 +25,6 @@ namespace GamecraftModdingAPI.Blocks | |||
public bool isRemovable => false; | |||
internal bool Synced = true; | |||
public void Dispose() | |||
{ | |||
} | |||
@@ -61,66 +59,55 @@ namespace GamecraftModdingAPI.Blocks | |||
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 | |||
{ | |||
if (!Synced) | |||
{ | |||
Sync(); | |||
Synced = true; | |||
} | |||
if (entitiesDB.Exists<T>(blockID)) | |||
return ref entitiesDB.QueryEntity<T>(blockID); | |||
T[] structHolder = new T[1]; //Create something that can be referenced | |||
return ref structHolder[0]; //Gets a default value automatically | |||
} | |||
/// <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> | |||
/// <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 | |||
public U GetBlockInfo<T, U>(Block block, Func<T, U> getter, | |||
U def = default) 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(); | |||
Synced = true; | |||
var initializer = new EntityComponentInitializer(block.Id, block.InitData.Group); | |||
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) | |||
{ | |||
Sync(); | |||
Synced = true; | |||
} | |||
return entitiesDB.Exists<DBEntityStruct>(id); | |||
return entitiesDB.Exists<DBEntityStruct>(blockID); | |||
} | |||
public bool GetBlockInfoExists<T>(EGID blockID) where T : struct, IEntityComponent | |||
public bool GetBlockInfoExists<T>(Block block) where T : struct, IEntityComponent | |||
{ | |||
if (!Synced) | |||
{ | |||
Sync(); | |||
Synced = true; | |||
} | |||
return entitiesDB.Exists<T>(blockID); | |||
if (entitiesDB.Exists<T>(block.Id)) | |||
return true; | |||
if (block.InitData.Group == null) | |||
return false; | |||
var init = new EntityComponentInitializer(block.Id, block.InitData.Group); | |||
return init.Has<T>(); | |||
} | |||
public SimBody[] GetSimBodiesFromID(byte id) | |||
@@ -183,17 +170,6 @@ namespace GamecraftModdingAPI.Blocks | |||
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 | |||
public EntitiesDB GetEntitiesDB() | |||
{ | |||
@@ -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(); | |||
} | |||
} | |||
} |
@@ -1,9 +1,11 @@ | |||
using System; | |||
using GamecraftModdingAPI.Engines; | |||
using GamecraftModdingAPI.Utility; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Engines; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class BlockEventsEngine : IReactionaryEngine<DBEntityStruct> | |||
@@ -16,6 +18,7 @@ namespace GamecraftModdingAPI.Blocks | |||
} | |||
public EntitiesDB entitiesDB { get; set; } | |||
public void Dispose() | |||
{ | |||
} | |||
@@ -23,14 +26,21 @@ namespace GamecraftModdingAPI.Blocks | |||
public string Name { get; } = "GamecraftModdingAPIBlockEventsEngine"; | |||
public bool isRemovable { get; } = false; | |||
private bool shouldAddRemove; | |||
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) | |||
{ | |||
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 BlockIDs Type; | |||
private Block block; | |||
public Block Block => block ?? (block = new Block(ID)); | |||
} | |||
} |
@@ -6,7 +6,7 @@ namespace GamecraftModdingAPI.Blocks | |||
public enum BlockIDs : ushort | |||
{ | |||
/// <summary> | |||
/// A custom value for the API. Doesn't exist for Gamecraft. | |||
/// Called "nothing" in Gamecraft. (DBID.NOTHING) | |||
/// </summary> | |||
Invalid = ushort.MaxValue, | |||
AluminiumCube = 0, | |||
@@ -22,20 +22,19 @@ namespace GamecraftModdingAPI.Blocks | |||
} | |||
[APITestCase(TestType.EditMode)] | |||
public static void TestSync() | |||
public static void TestInitProperty() | |||
{ | |||
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; | |||
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)] | |||
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 | |||
Assert.Errorless(() => { textBlock = newBlock.Specialise<TextBlock>(); }, "Block.Specialize<TextBlock>() raised an exception: ", "Block.Specialize<TextBlock>() completed without issue."); | |||
if (!Assert.NotNull(textBlock, "Block.Specialize<TextBlock>() returned null, possibly because it failed silently.", "Specialized TextBlock is not null.")) return; | |||
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.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.TextBlockId, "TextBlock.TextBlockId is null, possibly because it failed silently.", "TextBlock.TextBlockId is not null.")) return; | |||
} | |||
@@ -12,79 +12,64 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class ConsoleBlock : Block | |||
{ | |||
public static ConsoleBlock PlaceNew(float3 position, | |||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, | |||
int uscale = 1, float3 scale = default, Player player = null) | |||
public ConsoleBlock(EGID id): base(id) | |||
{ | |||
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) | |||
{ | |||
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)) | |||
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 | |||
/// <summary> | |||
/// Setting a nonexistent command will crash the game when switching to simulation | |||
/// </summary> | |||
public string Command | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).commandName; | |||
return BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.commandName); | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).commandName.Set(value); | |||
BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.commandName.Set(val), | |||
value); | |||
} | |||
} | |||
public string Arg1 | |||
{ | |||
get => BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg1; | |||
get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg1); | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg1.Set(value); | |||
BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg1.Set(val), | |||
value); | |||
} | |||
} | |||
public string Arg2 | |||
{ | |||
get => BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg2; | |||
get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg2); | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg2.Set(value); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg2.Set(val), | |||
value); | |||
} | |||
} | |||
public string Arg3 | |||
{ | |||
get => BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg3; | |||
get => BlockEngine.GetBlockInfo(this, (ConsoleBlockEntityStruct st) => st.arg3); | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<ConsoleBlockEntityStruct>(Id).arg3.Set(value); | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ConsoleBlockEntityStruct st, string val) => st.arg3.Set(val), | |||
value); | |||
} | |||
} | |||
} | |||
} |
@@ -11,43 +11,12 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
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) | |||
{ | |||
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)) | |||
{ | |||
if (!BlockEngine.GetBlockInfoExists<MotorReadOnlyStruct>(this.Id)) | |||
{ | |||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); | |||
} | |||
} | |||
// custom motor properties | |||
@@ -59,13 +28,12 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id).maxVelocity; | |||
return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.maxVelocity); | |||
} | |||
set | |||
{ | |||
ref MotorReadOnlyStruct motor = ref BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id); | |||
motor.maxVelocity = value; | |||
BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, float val) => st.maxVelocity = val, value); | |||
} | |||
} | |||
@@ -76,13 +44,12 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id).maxForce; | |||
return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.maxForce); | |||
} | |||
set | |||
{ | |||
ref MotorReadOnlyStruct motor = ref BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id); | |||
motor.maxForce = value; | |||
BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, float val) => st.maxForce = val, value); | |||
} | |||
} | |||
@@ -93,13 +60,12 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id).reverse; | |||
return BlockEngine.GetBlockInfo(this, (MotorReadOnlyStruct st) => st.reverse); | |||
} | |||
set | |||
{ | |||
ref MotorReadOnlyStruct motor = ref BlockEngine.GetBlockInfo<MotorReadOnlyStruct>(Id); | |||
motor.reverse = value; | |||
BlockEngine.SetBlockInfo(this, (ref MotorReadOnlyStruct st, bool val) => st.reverse = val, value); | |||
} | |||
} | |||
} | |||
@@ -35,8 +35,17 @@ namespace GamecraftModdingAPI.Blocks | |||
// 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 GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID); | |||
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID); | |||
@@ -56,8 +65,14 @@ namespace GamecraftModdingAPI.Blocks | |||
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); | |||
return posStruct.position; | |||
} | |||
@@ -8,27 +8,22 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
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)) | |||
{ | |||
if (!BlockEngine.GetBlockInfoExists<ObjectIdEntityStruct>(Id)) | |||
{ | |||
throw new BlockTypeException($"Block is not a {GetType().Name} block"); | |||
} | |||
} | |||
public char Identifier | |||
{ | |||
get => (char) (BlockEngine.GetBlockInfo<ObjectIdEntityStruct>(Id).objectId + 'A'); | |||
get => (char) BlockEngine.GetBlockInfo(this, (ObjectIdEntityStruct st) => st.objectId + 'A'); | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<ObjectIdEntityStruct>(Id).objectId = (byte) (value - 'A'); | |||
Label = value + ""; //The label isn't updated automatically | |||
BlockEngine.SetBlockInfo(this, (ref ObjectIdEntityStruct st, char val) => | |||
{ | |||
st.objectId = (byte) (val - 'A'); | |||
Label = val + ""; //The label isn't updated automatically | |||
}, value); | |||
} | |||
} | |||
@@ -37,7 +32,7 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public byte SimID | |||
{ | |||
get => BlockEngine.GetBlockInfo<ObjectIdEntityStruct>(Id).simObjectId; | |||
get => BlockEngine.GetBlockInfo(this, (ObjectIdEntityStruct st) => st.simObjectId); | |||
} | |||
/// <summary> | |||
@@ -11,43 +11,12 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class Piston : Block | |||
{ | |||
/// <summary> | |||
/// 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) | |||
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)) | |||
{ | |||
if (!BlockEngine.GetBlockInfoExists<PistonReadOnlyStruct>(this.Id)) | |||
{ | |||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); | |||
} | |||
} | |||
// custom piston properties | |||
@@ -56,13 +25,13 @@ namespace GamecraftModdingAPI.Blocks | |||
/// The piston's max extension distance. | |||
/// </summary> | |||
public float MaximumExtension | |||
{ | |||
get => BlockEngine.GetBlockInfo<PistonReadOnlyStruct>(Id).maxDeviation; | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.maxDeviation); | |||
set | |||
{ | |||
ref PistonReadOnlyStruct piston = ref BlockEngine.GetBlockInfo<PistonReadOnlyStruct>(Id); | |||
piston.maxDeviation = value; | |||
BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.maxDeviation = val, | |||
value); | |||
} | |||
} | |||
@@ -70,13 +39,12 @@ namespace GamecraftModdingAPI.Blocks | |||
/// The piston's max extension force. | |||
/// </summary> | |||
public float MaximumForce | |||
{ | |||
get => BlockEngine.GetBlockInfo<PistonReadOnlyStruct>(Id).maxForce; | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.maxForce); | |||
set | |||
{ | |||
ref PistonReadOnlyStruct piston = ref BlockEngine.GetBlockInfo<PistonReadOnlyStruct>(Id); | |||
piston.maxForce = value; | |||
BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.maxForce = val, value); | |||
} | |||
} | |||
} | |||
@@ -40,15 +40,16 @@ namespace GamecraftModdingAPI.Blocks | |||
private static BlockEntityFactory _blockEntityFactory; //Injected from PlaceBlockEngine | |||
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 | |||
if (darkness > 9) | |||
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); | |||
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) | |||
throw new Exception("The factory is null."); | |||
@@ -106,8 +107,7 @@ namespace GamecraftModdingAPI.Blocks | |||
ref PickedBlockExtraDataStruct pickedBlock = ref entitiesDB.QueryEntity<PickedBlockExtraDataStruct>(playerEGID); | |||
pickedBlock.placedBlockEntityID = structInitializer.EGID; | |||
pickedBlock.placedBlockWasAPickedBlock = false; | |||
Block.BlockEngine.Synced = false; // Block entities will need to be submitted before properties can be used | |||
return structInitializer.EGID; | |||
return structInitializer; | |||
} | |||
public string Name { get; } = "GamecraftModdingAPIPlacementGameEngine"; | |||
@@ -35,23 +35,32 @@ namespace GamecraftModdingAPI.Blocks | |||
// 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 GridRotationStruct gridStruct = ref this.entitiesDB.QueryEntity<GridRotationStruct>(blockID); | |||
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID); | |||
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID); | |||
// main (persistent) position | |||
Quaternion newRotation = (Quaternion)rotStruct.rotation; | |||
newRotation.eulerAngles += vector; | |||
rotStruct.rotation = (quaternion)newRotation; | |||
Quaternion newRotation = rotStruct.rotation; | |||
newRotation.eulerAngles = vector; | |||
rotStruct.rotation = newRotation; | |||
// placement grid rotation | |||
Quaternion newGridRotation = (Quaternion)gridStruct.rotation; | |||
newGridRotation.eulerAngles += vector; | |||
gridStruct.rotation = (quaternion)newGridRotation; | |||
Quaternion newGridRotation = gridStruct.rotation; | |||
newGridRotation.eulerAngles = vector; | |||
gridStruct.rotation = newGridRotation; | |||
// rendered position | |||
Quaternion newTransRotation = (Quaternion)rotStruct.rotation; | |||
newTransRotation.eulerAngles += vector; | |||
Quaternion newTransRotation = rotStruct.rotation; | |||
newTransRotation.eulerAngles = vector; | |||
transStruct.rotation = newTransRotation; | |||
// collision position | |||
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); | |||
return ((Quaternion) rotStruct.rotation).eulerAngles; | |||
} | |||
@@ -11,43 +11,12 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
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) | |||
{ | |||
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)) | |||
{ | |||
if (!BlockEngine.GetBlockInfoExists<ServoReadOnlyStruct>(this.Id)) | |||
{ | |||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); | |||
} | |||
} | |||
// custom servo properties | |||
@@ -56,13 +25,12 @@ namespace GamecraftModdingAPI.Blocks | |||
/// The servo's minimum angle. | |||
/// </summary> | |||
public float MinimumAngle | |||
{ | |||
get => BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id).minDeviation; | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.minDeviation); | |||
set | |||
{ | |||
ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id); | |||
servo.minDeviation = value; | |||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.minDeviation = val, value); | |||
} | |||
} | |||
@@ -71,13 +39,12 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public float MaximumAngle | |||
{ | |||
get => BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id).maxDeviation; | |||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.maxDeviation); | |||
set | |||
{ | |||
ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id); | |||
servo.maxDeviation = value; | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.maxDeviation = val, value); | |||
} | |||
} | |||
/// <summary> | |||
@@ -85,13 +52,12 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public float MaximumForce | |||
{ | |||
get => BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id).maxForce; | |||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.maxForce); | |||
set | |||
{ | |||
ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id); | |||
servo.maxForce = value; | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.maxForce = val, value); | |||
} | |||
} | |||
/// <summary> | |||
@@ -99,13 +65,12 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public bool Reverse | |||
{ | |||
get => BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id).reverse; | |||
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.reverse); | |||
set | |||
{ | |||
ref ServoReadOnlyStruct servo = ref BlockEngine.GetBlockInfo<ServoReadOnlyStruct>(Id); | |||
servo.reverse = value; | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, bool val) => st.reverse = val, value); | |||
} | |||
} | |||
} | |||
} |
@@ -14,28 +14,9 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
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) | |||
{ | |||
if (!BlockEngine.GetBlockInfoExists<BlockPortsStruct>(this.Id)) | |||
if (!BlockEngine.GetBlockInfoExists<BlockPortsStruct>(this)) | |||
{ | |||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); | |||
} | |||
@@ -43,17 +24,12 @@ namespace GamecraftModdingAPI.Blocks | |||
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"); | |||
} | |||
} | |||
protected ref BlockPortsStruct GetBlockPortsStruct() | |||
{ | |||
return ref BlockEngine.GetBlockInfo<BlockPortsStruct>(Id); | |||
} | |||
/// <summary> | |||
/// Generates the input port identifiers. | |||
/// </summary> | |||
@@ -72,16 +48,6 @@ namespace GamecraftModdingAPI.Blocks | |||
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> | |||
/// Gets the connected wire. | |||
/// </summary> | |||
@@ -108,16 +74,16 @@ namespace GamecraftModdingAPI.Blocks | |||
/// The input port count. | |||
/// </summary> | |||
public uint InputCount | |||
{ | |||
get => GetBlockPortsStruct().inputCount; | |||
} | |||
{ | |||
get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.inputCount); | |||
} | |||
/// <summary> | |||
/// The output port count. | |||
/// </summary> | |||
public uint OutputCount | |||
{ | |||
get => GetBlockPortsStruct().outputCount; | |||
get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.outputCount); | |||
} | |||
} | |||
} |
@@ -13,43 +13,12 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class SpawnPoint : Block | |||
{ | |||
/// <summary> | |||
/// 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) | |||
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)) | |||
{ | |||
if (!BlockEngine.GetBlockInfoExists<SpawnPointStatsEntityStruct>(this.Id)) | |||
{ | |||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); | |||
} | |||
} | |||
// custom spawn point properties | |||
@@ -59,16 +28,12 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public uint Lives | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id).lives; | |||
} | |||
get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.lives); | |||
set | |||
{ | |||
ref SpawnPointStatsEntityStruct spses = ref BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id); | |||
spses.lives = value; | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, uint val) => st.lives = val, value); | |||
} | |||
} | |||
/// <summary> | |||
@@ -76,16 +41,12 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public bool Damageable | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id).canTakeDamage; | |||
} | |||
get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.canTakeDamage); | |||
set | |||
{ | |||
ref SpawnPointStatsEntityStruct spses = ref BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id); | |||
spses.canTakeDamage = value; | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, bool val) => st.canTakeDamage = val, value); | |||
} | |||
} | |||
/// <summary> | |||
@@ -93,16 +54,12 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public bool GameOverEnabled | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id).gameOverScreen; | |||
} | |||
get => BlockEngine.GetBlockInfo(this, (SpawnPointStatsEntityStruct st) => st.gameOverScreen); | |||
set | |||
{ | |||
ref SpawnPointStatsEntityStruct spses = ref BlockEngine.GetBlockInfo<SpawnPointStatsEntityStruct>(Id); | |||
spses.gameOverScreen = value; | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref SpawnPointStatsEntityStruct st, bool val) => st.gameOverScreen = val, value); | |||
} | |||
} | |||
/// <summary> | |||
@@ -110,16 +67,12 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public byte Team | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<SpawnPointIdsEntityStruct>(Id).teamId; | |||
} | |||
get => BlockEngine.GetBlockInfo(this, (SpawnPointIdsEntityStruct st) => st.teamId); | |||
set | |||
{ | |||
ref SpawnPointIdsEntityStruct spses = ref BlockEngine.GetBlockInfo<SpawnPointIdsEntityStruct>(Id); | |||
spses.teamId = value; | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref SpawnPointIdsEntityStruct st, byte val) => st.teamId = val, value); | |||
} | |||
} | |||
} | |||
} |
@@ -12,35 +12,12 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
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) | |||
{ | |||
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)) | |||
{ | |||
if (!BlockEngine.GetBlockInfoExists<TextBlockDataStruct>(this.Id)) | |||
{ | |||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); | |||
} | |||
} | |||
// custom text block properties | |||
@@ -50,35 +27,34 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public string Text | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<TextBlockDataStruct>(Id).textCurrent; | |||
} | |||
set | |||
{ | |||
ref TextBlockDataStruct tbds = ref BlockEngine.GetBlockInfo<TextBlockDataStruct>(Id); | |||
tbds.textCurrent.Set(value); | |||
tbds.textStored.Set(value); | |||
BlockEngine.GetBlockInfo<TextBlockNetworkDataStruct>(Id).newTextBlockStringContent.Set(value); | |||
} | |||
} | |||
get => BlockEngine.GetBlockInfo(this, (TextBlockDataStruct st) => st.textCurrent); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) => | |||
{ | |||
tbds.textCurrent.Set(val); | |||
tbds.textStored.Set(val); | |||
}, value); | |||
BlockEngine.SetBlockInfo(this, | |||
(ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockStringContent.Set(val), value); | |||
} | |||
} | |||
/// <summary> | |||
/// The text block's current text block ID (used in ChangeTextBlockCommand). | |||
/// The text block's current text block ID (used in ChangeTextBlockCommand). | |||
/// </summary> | |||
public string TextBlockId | |||
public string TextBlockId | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<TextBlockDataStruct>(Id).textBlockID; | |||
} | |||
set | |||
{ | |||
BlockEngine.GetBlockInfo<TextBlockDataStruct>(Id).textBlockID.Set(value); | |||
BlockEngine.GetBlockInfo<TextBlockNetworkDataStruct>(Id).newTextBlockID.Set(value); | |||
} | |||
get => BlockEngine.GetBlockInfo(this, (TextBlockDataStruct st) => st.textBlockID); | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) => | |||
tbds.textBlockID.Set(val), value); | |||
BlockEngine.SetBlockInfo(this, | |||
(ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockID.Set(val), value); | |||
} | |||
} | |||
} | |||
} |
@@ -13,37 +13,12 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
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) | |||
{ | |||
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)) | |||
{ | |||
if (!BlockEngine.GetBlockInfoExists<TimerBlockDataStruct>(this.Id)) | |||
{ | |||
throw new BlockTypeException($"Block is not a {this.GetType().Name} block"); | |||
} | |||
} | |||
// custom timer properties | |||
@@ -53,16 +28,13 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public float Start | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id).startTime; | |||
} | |||
get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.startTime); | |||
set | |||
{ | |||
ref TimerBlockDataStruct tbds = ref BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id); | |||
tbds.startTime = value; | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, float val) => tbds.startTime = val, | |||
value); | |||
} | |||
} | |||
/// <summary> | |||
@@ -70,16 +42,13 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public float End | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id).endTime; | |||
} | |||
get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.endTime); | |||
set | |||
{ | |||
ref TimerBlockDataStruct tbds = ref BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id); | |||
tbds.endTime = value; | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, float val) => tbds.endTime = val, | |||
value); | |||
} | |||
} | |||
/// <summary> | |||
@@ -87,16 +56,13 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public bool DisplayMilliseconds | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id).outputFormatHasMS; | |||
} | |||
get => BlockEngine.GetBlockInfo(this, (TimerBlockDataStruct st) => st.outputFormatHasMS); | |||
set | |||
{ | |||
ref TimerBlockDataStruct tbds = ref BlockEngine.GetBlockInfo<TimerBlockDataStruct>(Id); | |||
tbds.outputFormatHasMS = value; | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref TimerBlockDataStruct tbds, bool val) => tbds.outputFormatHasMS = val, | |||
value); | |||
} | |||
} | |||
/// <summary> | |||
@@ -104,16 +70,13 @@ namespace GamecraftModdingAPI.Blocks | |||
/// </summary> | |||
public int CurrentTime | |||
{ | |||
get | |||
{ | |||
return BlockEngine.GetBlockInfo<TimerBlockLabelCacheEntityStruct>(Id).timeLastRenderFrameMS; | |||
} | |||
get => BlockEngine.GetBlockInfo(this, (TimerBlockLabelCacheEntityStruct st) => st.timeLastRenderFrameMS); | |||
set | |||
{ | |||
ref TimerBlockLabelCacheEntityStruct tblces = ref BlockEngine.GetBlockInfo<TimerBlockLabelCacheEntityStruct>(Id); | |||
tblces.timeLastRenderFrameMS = value; | |||
} | |||
set | |||
{ | |||
BlockEngine.SetBlockInfo(this, (ref TimerBlockLabelCacheEntityStruct tbds, int val) => tbds.timeLastRenderFrameMS = val, | |||
value); | |||
} | |||
} | |||
} | |||
} |
@@ -9,7 +9,7 @@ using RobocraftX.Physics; | |||
namespace GamecraftModdingAPI | |||
{ | |||
/// <summary> | |||
/// A rigid body (like a cluster of connected blocks) during simulation. | |||
/// A rigid body (like a chunk of connected blocks) during simulation. | |||
/// </summary> | |||
public class SimBody : IEquatable<SimBody>, IEquatable<EGID> | |||
{ | |||
@@ -83,32 +83,12 @@ namespace GamecraftModdingAPI.Tests | |||
{ | |||
if (err == null) err = $"{nameof(T)} '{obj1}' is not 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 | |||
Log(PASS + success); | |||
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; | |||
} | |||
else | |||
@@ -232,6 +232,28 @@ namespace GamecraftModdingAPI.Tests | |||
} | |||
}).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); | |||
Block.Placed += (sender, args) => | |||
Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID); | |||
@@ -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(); | |||
} | |||
} | |||
} |