NorbiPeti
f403feb298
Removed BlockIdentifiers.OWNED_BLOCKS as the original got replaced with an array Added the correct group for each supported functional block Removed EntityFactory property from IEntitySerializer as it is provided on deserialization
363 lines
No EOL
13 KiB
C#
363 lines
No EOL
13 KiB
C#
using System;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
|
|
using Svelto.ECS;
|
|
using Svelto.ECS.EntityStructs;
|
|
using RobocraftX.Common;
|
|
using RobocraftX.Blocks;
|
|
using Unity.Mathematics;
|
|
using Unity.Entities;
|
|
using Gamecraft.Blocks.GUI;
|
|
|
|
using GamecraftModdingAPI.Blocks;
|
|
using GamecraftModdingAPI.Utility;
|
|
|
|
namespace GamecraftModdingAPI
|
|
{
|
|
/// <summary>
|
|
/// A single (perhaps scaled) block. Properties may return default values if the block is removed and then setting them is ignored.
|
|
/// For specific block type operations, use the specialised block classes in the GamecraftModdingAPI.Blocks namespace.
|
|
/// </summary>
|
|
public class Block : IEquatable<Block>, IEquatable<EGID>
|
|
{
|
|
protected static readonly PlacementEngine PlacementEngine = new PlacementEngine();
|
|
protected static readonly MovementEngine MovementEngine = new MovementEngine();
|
|
protected static readonly RotationEngine RotationEngine = new RotationEngine();
|
|
protected static readonly RemovalEngine RemovalEngine = new RemovalEngine();
|
|
protected static readonly SignalEngine SignalEngine = new SignalEngine();
|
|
protected static readonly BlockEventsEngine BlockEventsEngine = new BlockEventsEngine();
|
|
protected static readonly ScalingEngine ScalingEngine = new ScalingEngine();
|
|
|
|
protected internal static readonly BlockEngine BlockEngine = new BlockEngine();
|
|
|
|
/// <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>When placing multiple blocks, do not access properties immediately after creation as this
|
|
/// triggers a sync each time which can affect performance and may cause issues with the game.
|
|
/// You may either use AsyncUtils.WaitForSubmission() after placing all of the blocks
|
|
/// or simply access the block properties which will trigger the synchronization the first time a property is used.</para>
|
|
/// </summary>
|
|
/// <param name="block">The block's type</param>
|
|
/// <param name="color">The block's color</param>
|
|
/// <param name="darkness">The block color's darkness (0-9) - 0 is default color</param>
|
|
/// <param name="position">The block's position in the grid - default block size is 0.2</param>
|
|
/// <param name="rotation">The block's rotation in degrees</param>
|
|
/// <param name="uscale">The block's uniform scale - default scale is 1 (with 0.2 width)</param>
|
|
/// <param name="scale">The block's non-uniform scale - 0 means <paramref name="uscale"/> is used</param>
|
|
/// <param name="player">The player who placed the block</param>
|
|
/// <returns>The placed block or null if failed</returns>
|
|
public static Block PlaceNew(BlockIDs block, float3 position,
|
|
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;
|
|
}
|
|
|
|
/// <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>
|
|
/// <param name="darkness">The block color's darkness (0-9) - 0 is default color</param>
|
|
/// <param name="position">The block's position in the grid - default block size is 0.2</param>
|
|
/// <param name="rotation">The block's rotation in degrees</param>
|
|
/// <param name="uscale">The block's uniform scale - default scale is 1 (with 0.2 width)</param>
|
|
/// <param name="scale">The block's non-uniform scale - 0 means <paramref name="uscale"/> is used</param>
|
|
/// <param name="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,
|
|
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0,
|
|
int uscale = 1, float3 scale = default, Player player = null)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the most recently placed block.
|
|
/// </summary>
|
|
/// <returns>The block object</returns>
|
|
public static Block GetLastPlacedBlock()
|
|
{
|
|
return new Block(BlockIdentifiers.LatestBlockID);
|
|
}
|
|
|
|
/// <summary>
|
|
/// An event that fires each time a block is placed.
|
|
/// </summary>
|
|
public static event EventHandler<BlockPlacedRemovedEventArgs> Placed
|
|
{
|
|
add => BlockEventsEngine.Placed += value;
|
|
remove => BlockEventsEngine.Placed -= value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// An event that fires each time a block is removed.
|
|
/// </summary>
|
|
public static event EventHandler<BlockPlacedRemovedEventArgs> Removed
|
|
{
|
|
add => BlockEventsEngine.Removed += value;
|
|
remove => BlockEventsEngine.Removed -= value;
|
|
}
|
|
|
|
public Block(EGID id)
|
|
{
|
|
Id = id;
|
|
}
|
|
|
|
public Block(uint id) : this(new EGID(id, CommonExclusiveGroups.BUILD_STANDARD_BLOCK_GROUP))
|
|
{ //TODO: Figure out the block group based on the id
|
|
}
|
|
|
|
public EGID Id { get; }
|
|
|
|
/// <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;
|
|
set
|
|
{
|
|
if (Exists) MovementEngine.MoveBlock(Id, value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The block's current rotation in degrees or zero if the block doesn't exist.
|
|
/// </summary>
|
|
public float3 Rotation
|
|
{
|
|
get => Exists ? RotationEngine.GetRotation(Id) : float3.zero;
|
|
set
|
|
{
|
|
if (Exists) RotationEngine.RotateBlock(Id, value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The block's non-uniform scale or zero if the block's invalid. Independent of the uniform scaling.
|
|
/// The default scale of 1 means 0.2 in terms of position.
|
|
/// </summary>
|
|
public float3 Scale
|
|
{
|
|
get => BlockEngine.GetBlockInfo<ScalingEntityStruct>(Id).scale;
|
|
set
|
|
{
|
|
if (!Exists) return; //UpdateCollision needs the block to exist
|
|
ref var scaling = ref BlockEngine.GetBlockInfo<ScalingEntityStruct>(Id);
|
|
scaling.scale = value;
|
|
ScalingEngine.UpdateCollision(Id);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The block's uniform scale or zero if the block's invalid. Also sets the non-uniform scale.
|
|
/// The default scale of 1 means 0.2 in terms of position.
|
|
/// </summary>
|
|
public int UniformScale
|
|
{
|
|
get => BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(Id).scaleFactor;
|
|
set
|
|
{
|
|
ref var scaleStruct = ref BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(Id);
|
|
scaleStruct.scaleFactor = value;
|
|
Scale = new float3(value, value, value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The block's type (ID). Returns BlockIDs.Invalid if the block doesn't exist anymore.
|
|
/// </summary>
|
|
public BlockIDs Type
|
|
{
|
|
get
|
|
{
|
|
var id = (BlockIDs) BlockEngine.GetBlockInfo<DBEntityStruct>(Id, out var exists).DBID;
|
|
return exists ? id : BlockIDs.Invalid;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The block's color. Returns BlockColors.Default if the block no longer exists.
|
|
/// </summary>
|
|
public BlockColor Color
|
|
{
|
|
get
|
|
{
|
|
byte index = BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id, out var exists).indexInPalette;
|
|
if (!exists) index = 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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The block's exact color. Gets reset to the palette color (Color property) after reentering the game.
|
|
/// </summary>
|
|
public float4 CustomColor
|
|
{
|
|
get => BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id).overriddenColour;
|
|
set
|
|
{
|
|
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(Id);
|
|
color.overriddenColour = value;
|
|
color.overridePaletteColour = true;
|
|
color.needsUpdate = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The short 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;
|
|
set
|
|
{
|
|
ref var text = ref BlockEngine.GetBlockInfo<TextLabelEntityViewStruct>(Id);
|
|
if (text.textLabelComponent != null) text.textLabelComponent.text = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the block exists. The other properties will return a default value if the block doesn't exist.
|
|
/// </summary>
|
|
public bool Exists => BlockEngine.BlockExists(Id);
|
|
|
|
/// <summary>
|
|
/// Returns an array of blocks that are connected to this one. Returns an empty array if the block doesn't exist.
|
|
/// </summary>
|
|
public Block[] GetConnectedCubes() => BlockEngine.GetConnectedBlocks(Id);
|
|
|
|
/// <summary>
|
|
/// Removes this block.
|
|
/// </summary>
|
|
/// <returns>True if the block exists and could be removed.</returns>
|
|
public bool Remove() => RemovalEngine.RemoveBlock(Id);
|
|
|
|
/// <summary>
|
|
/// Returns the rigid body of the cluster 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>
|
|
public SimBody GetSimBody()
|
|
{
|
|
uint id = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(Id, out var exists).machineRigidBodyId;
|
|
return exists ? new SimBody(id) : null;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Type)}: {Type}, {nameof(Color)}: {Color}, {nameof(Exists)}: {Exists}";
|
|
}
|
|
|
|
public bool Equals(Block other)
|
|
{
|
|
if (ReferenceEquals(null, other)) return false;
|
|
if (ReferenceEquals(this, other)) return true;
|
|
return Id.Equals(other.Id);
|
|
}
|
|
|
|
public bool Equals(EGID other)
|
|
{
|
|
return Id.Equals(other);
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
if (ReferenceEquals(null, obj)) return false;
|
|
if (ReferenceEquals(this, obj)) return true;
|
|
if (obj.GetType() != this.GetType()) return false;
|
|
return Equals((Block) obj);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return Id.GetHashCode();
|
|
}
|
|
|
|
public static void Init()
|
|
{
|
|
GameEngineManager.AddGameEngine(PlacementEngine);
|
|
GameEngineManager.AddGameEngine(MovementEngine);
|
|
GameEngineManager.AddGameEngine(RotationEngine);
|
|
GameEngineManager.AddGameEngine(RemovalEngine);
|
|
GameEngineManager.AddGameEngine(BlockEngine);
|
|
GameEngineManager.AddGameEngine(BlockEventsEngine);
|
|
GameEngineManager.AddGameEngine(ScalingEngine);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert the block to a specialised block class.
|
|
/// </summary>
|
|
/// <returns>The block.</returns>
|
|
/// <typeparam name="T">The specialised block type.</typeparam>
|
|
public T Specialise<T>() where T : Block
|
|
{
|
|
// What have I gotten myself into?
|
|
// C# can't cast to a child of Block unless the object was originally that child type
|
|
// And C# doesn't let me make implicit cast operators for child types
|
|
// So thanks to Microsoft, we've got this horrible implementation using reflection
|
|
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 });
|
|
}
|
|
|
|
#if DEBUG
|
|
public static EntitiesDB entitiesDB
|
|
{
|
|
get
|
|
{
|
|
return BlockEngine.GetEntitiesDB();
|
|
}
|
|
}
|
|
#endif
|
|
internal static void Setup(World physicsWorld)
|
|
{
|
|
ScalingEngine.Setup(physicsWorld.EntityManager);
|
|
}
|
|
}
|
|
} |