NorbiPeti
e8515ef42b
Copying to Plugins folder on build Registering deterministic game engines automatically Each event handler is wrapped so if one fails it will still trigger the rest
434 lines
No EOL
15 KiB
C#
434 lines
No EOL
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
using DataLoader;
|
|
using Gamecraft.Blocks.BlockGroups;
|
|
using Svelto.ECS;
|
|
using Svelto.ECS.EntityStructs;
|
|
using RobocraftX.Common;
|
|
using RobocraftX.Blocks;
|
|
using Unity.Mathematics;
|
|
using Gamecraft.Blocks.GUI;
|
|
|
|
using TechbloxModdingAPI.Blocks;
|
|
using TechbloxModdingAPI.Blocks.Engines;
|
|
using TechbloxModdingAPI.Tests;
|
|
using TechbloxModdingAPI.Utility;
|
|
|
|
namespace TechbloxModdingAPI
|
|
{
|
|
/// <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 TechbloxModdingAPI.Blocks namespace.
|
|
/// </summary>
|
|
public class Block : EcsObjectBase, 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 static readonly BlockCloneEngine BlockCloneEngine = new BlockCloneEngine();
|
|
|
|
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.
|
|
/// </summary>
|
|
/// <param name="block">The block's type</param>
|
|
/// <param name="position">The block's position - default block size is 0.2</param>
|
|
/// <param name="autoWire">Whether the block should be auto-wired (if functional)</param>
|
|
/// <param name="player">The player who placed the block</param>
|
|
/// <returns>The placed block or null if failed</returns>
|
|
public static Block PlaceNew(BlockIDs block, float3 position, bool autoWire = false, Player player = null)
|
|
{
|
|
if (PlacementEngine.IsInGame && GameState.IsBuildMode())
|
|
{
|
|
var initializer = PlacementEngine.PlaceBlock(block, position, player, autoWire);
|
|
var egid = initializer.EGID;
|
|
var bl = New(egid);
|
|
bl.InitData = initializer;
|
|
Placed += bl.OnPlacedInit;
|
|
return bl;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the most recently placed block.
|
|
/// </summary>
|
|
/// <returns>The block object or null if doesn't exist</returns>
|
|
public static Block GetLastPlacedBlock()
|
|
{
|
|
EGID? egid = BlockEngine.FindBlockEGID(BlockIdentifiers.LatestBlockID);
|
|
return egid.HasValue ? New(egid.Value) : null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// An event that fires each time a block is placed.
|
|
/// </summary>
|
|
public static event EventHandler<BlockPlacedRemovedEventArgs> Placed
|
|
{
|
|
add => BlockEventsEngine.Placed += ExceptionUtil.WrapHandler(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 += ExceptionUtil.WrapHandler(value);
|
|
remove => BlockEventsEngine.Removed -= value;
|
|
}
|
|
|
|
internal static readonly Dictionary<ExclusiveBuildGroup, Func<EGID, Block>> GroupToConstructor =
|
|
new Dictionary<ExclusiveBuildGroup, Func<EGID, Block>>
|
|
{
|
|
{CommonExclusiveGroups.DAMPEDSPRING_BLOCK_GROUP, id => new DampedSpring(id)},
|
|
{CommonExclusiveGroups.ENGINE_BLOCK_BUILD_GROUP, id => new Engine(id)}
|
|
};
|
|
|
|
internal static Block New(EGID egid)
|
|
{
|
|
return GroupToConstructor.ContainsKey(egid.groupID)
|
|
? GroupToConstructor[egid.groupID](egid)
|
|
: new Block(egid);
|
|
}
|
|
|
|
public Block(EGID id)
|
|
{
|
|
Id = id; //TODO: Check if block type is correct
|
|
}
|
|
|
|
/// <summary>
|
|
/// This overload searches for the correct group the block is in.
|
|
/// It will throw an exception if the block doesn't exist.
|
|
/// Use the EGID constructor where possible or subclasses of Block as those specify the group.
|
|
/// </summary>
|
|
public Block(uint id)
|
|
{
|
|
Id = BlockEngine.FindBlockEGID(id)
|
|
?? throw new BlockTypeException("Could not find the appropriate group for the block." +
|
|
" The block probably doesn't exist or hasn't been submitted.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Places a new block in the world.
|
|
/// </summary>
|
|
/// <param name="type">The block's type</param>
|
|
/// <param name="position">The block's position (a block is 0.2 wide in terms of position)</param>
|
|
/// <param name="autoWire">Whether the block should be auto-wired (if functional)</param>
|
|
/// <param name="player">The player who placed the block</param>
|
|
public Block(BlockIDs type, float3 position, bool autoWire = false, Player player = null)
|
|
{
|
|
if (!PlacementEngine.IsInGame || !GameState.IsBuildMode())
|
|
throw new BlockException("Blocks can only be placed in build mode.");
|
|
var initializer = PlacementEngine.PlaceBlock(type, position, player, autoWire);
|
|
Id = initializer.EGID;
|
|
InitData = initializer;
|
|
Placed += OnPlacedInit;
|
|
}
|
|
|
|
public override EGID Id { get; }
|
|
|
|
private EGID copiedFrom;
|
|
|
|
/// <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 => MovementEngine.GetPosition(this);
|
|
set
|
|
{
|
|
MovementEngine.MoveBlock(this, value);
|
|
if (blockGroup != null)
|
|
blockGroup.PosAndRotCalculated = false;
|
|
BlockEngine.UpdateDisplayedBlock(Id);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The block's current rotation in degrees or zero if the block doesn't exist.
|
|
/// </summary>
|
|
public float3 Rotation
|
|
{
|
|
get => RotationEngine.GetRotation(this);
|
|
set
|
|
{
|
|
RotationEngine.RotateBlock(this, value);
|
|
if (blockGroup != null)
|
|
blockGroup.PosAndRotCalculated = false;
|
|
BlockEngine.UpdateDisplayedBlock(Id);
|
|
}
|
|
}
|
|
|
|
/// <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>(this).scale;
|
|
set
|
|
{
|
|
int uscale = UniformScale;
|
|
if (value.x < 4e-5) value.x = uscale;
|
|
if (value.y < 4e-5) value.y = uscale;
|
|
if (value.z < 4e-5) value.z = uscale;
|
|
BlockEngine.GetBlockInfo<ScalingEntityStruct>(this).scale = value;
|
|
//BlockEngine.GetBlockInfo<GridScaleStruct>(this).gridScale = value - (int3) value + 1;
|
|
if (!Exists) return; //UpdateCollision needs the block to exist
|
|
ScalingEngine.UpdateCollision(Id);
|
|
BlockEngine.UpdateDisplayedBlock(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>(this).scaleFactor;
|
|
set
|
|
{
|
|
if (value < 1) value = 1;
|
|
BlockEngine.GetBlockInfo<UniformBlockScaleEntityStruct>(this).scaleFactor = value;
|
|
Scale = new float3(value, value, value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whether the block is flipped.
|
|
*/
|
|
public bool Flipped
|
|
{
|
|
get => BlockEngine.GetBlockInfo<ScalingEntityStruct>(this).scale.x < 0;
|
|
set
|
|
{
|
|
ref var st = ref BlockEngine.GetBlockInfo<ScalingEntityStruct>(this);
|
|
st.scale.x = math.abs(st.scale.x) * (value ? -1 : 1);
|
|
BlockEngine.UpdatePrefab(this, (byte) Material, value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The block's type (ID). Returns BlockIDs.Invalid if the block doesn't exist anymore.
|
|
/// </summary>
|
|
public BlockIDs Type
|
|
{
|
|
get
|
|
{
|
|
var opt = BlockEngine.GetBlockInfoOptional<DBEntityStruct>(this);
|
|
return opt ? (BlockIDs) opt.Get().DBID : BlockIDs.Invalid;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The block's color. Returns BlockColors.Default if the block no longer exists.
|
|
/// </summary>
|
|
public BlockColor Color
|
|
{
|
|
get
|
|
{
|
|
var opt = BlockEngine.GetBlockInfoOptional<ColourParameterEntityStruct>(this);
|
|
return new BlockColor(opt ? opt.Get().indexInPalette : byte.MaxValue);
|
|
}
|
|
set
|
|
{
|
|
//TODO: Check if setting to 255 works
|
|
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(this);
|
|
color.indexInPalette = value.Index;
|
|
color.hasNetworkChange = true;
|
|
color.paletteColour = BlockEngine.ConvertBlockColor(color.indexInPalette);
|
|
}
|
|
}
|
|
|
|
/// <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>(this).paletteColour;
|
|
set
|
|
{
|
|
ref var color = ref BlockEngine.GetBlockInfo<ColourParameterEntityStruct>(this);
|
|
color.paletteColour = value;
|
|
color.hasNetworkChange = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The block's material.
|
|
*/
|
|
public BlockMaterial Material
|
|
{
|
|
get
|
|
{
|
|
var opt = BlockEngine.GetBlockInfoOptional<CubeMaterialStruct>(this);
|
|
return opt ? (BlockMaterial) opt.Get().materialId : BlockMaterial.Default;
|
|
}
|
|
set
|
|
{
|
|
if (!FullGameFields._dataDb.ContainsKey<MaterialPropertiesData>((byte) value))
|
|
throw new BlockException($"Block material {value} does not exist!");
|
|
BlockEngine.GetBlockInfo<CubeMaterialStruct>(this).materialId = (byte) value;
|
|
BlockEngine.UpdatePrefab(this, (byte) value, Flipped); //TODO: Test default
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The text displayed on the block if applicable, or null.
|
|
/// Setting it is temporary to the session, it won't be saved.
|
|
/// </summary>
|
|
[TestValue(null)]
|
|
public string Label
|
|
{
|
|
get => BlockEngine.GetBlockInfoViewComponent<TextLabelEntityViewStruct>(this).textLabelComponent?.text;
|
|
set
|
|
{
|
|
var comp = BlockEngine.GetBlockInfoViewComponent<TextLabelEntityViewStruct>(this).textLabelComponent;
|
|
if (comp != null) comp.text = value;
|
|
}
|
|
}
|
|
|
|
private BlockGroup blockGroup;
|
|
/// <summary>
|
|
/// Returns the block group this block is a part of. Block groups can also be placed using blueprints.
|
|
/// Returns null if not part of a group, although all blocks should have their own by default.<br />
|
|
/// Setting the group after the block has been initialized will not update everything properly,
|
|
/// so you can only set this property on blocks newly placed by your code.<br />
|
|
/// To set it for existing blocks, you can use the Copy() method and set the property on the resulting block
|
|
/// (and remove this block).
|
|
/// </summary>
|
|
public BlockGroup BlockGroup
|
|
{
|
|
get
|
|
{
|
|
if (blockGroup != null) return blockGroup;
|
|
var bgec = BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this);
|
|
return blockGroup = bgec.currentBlockGroup == -1 ? null : new BlockGroup(bgec.currentBlockGroup, this);
|
|
}
|
|
set
|
|
{
|
|
if (Exists)
|
|
{
|
|
Logging.LogWarning("Attempted to set group of existing block. This is not supported."
|
|
+ " Copy the block and set the group of the resulting block.");
|
|
return;
|
|
}
|
|
blockGroup?.RemoveInternal(this);
|
|
BlockEngine.GetBlockInfo<BlockGroupEntityComponent>(this).currentBlockGroup = (int?) value?.Id.entityID ?? -1;
|
|
value?.AddInternal(this);
|
|
blockGroup = 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);
|
|
|
|
/// <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 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 chunk or null if the block doesn't exist or not in simulation mode.</returns>
|
|
public SimBody GetSimBody()
|
|
{
|
|
var st = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(this);
|
|
return st.machineRigidBodyId != uint.MaxValue
|
|
? new SimBody(st.machineRigidBodyId, st.clusterId)
|
|
: null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a copy of the block in the game with the same properties, stats and wires.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Block Copy()
|
|
{
|
|
var block = PlaceNew(Type, Position);
|
|
block.Rotation = Rotation;
|
|
block.Color = Color;
|
|
block.Material = Material;
|
|
block.UniformScale = UniformScale;
|
|
block.Scale = Scale;
|
|
block.copiedFrom = Id;
|
|
return block;
|
|
}
|
|
|
|
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
|
|
if (copiedFrom != EGID.Empty)
|
|
BlockCloneEngine.CopyBlockStats(copiedFrom, Id);
|
|
}
|
|
|
|
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);
|
|
GameEngineManager.AddGameEngine(SignalEngine);
|
|
GameEngineManager.AddGameEngine(BlockCloneEngine);
|
|
Wire.signalEngine = SignalEngine; // requires same functionality, no need to duplicate the engine
|
|
}
|
|
}
|
|
} |