Placement, movement, rotation, removal Block looked at (in Player class), connected blockstags/v1.0.0
@@ -0,0 +1,115 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using GamecraftModdingAPI.Blocks; | |||
using GamecraftModdingAPI.Utility; | |||
using RobocraftX.Common; | |||
using Svelto.ECS; | |||
using Unity.Mathematics; | |||
namespace GamecraftModdingAPI | |||
{ | |||
public class Block | |||
{ | |||
private static readonly PlacementEngine PlacementEngine = new PlacementEngine(); | |||
private static readonly MovementEngine MovementEngine = new MovementEngine(); | |||
private static readonly RotationEngine RotationEngine = new RotationEngine(); | |||
private static readonly RemovalEngine RemovalEngine = new RemovalEngine(); | |||
private 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="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()) | |||
{ | |||
try | |||
{ | |||
return new Block(PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation)); | |||
} | |||
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); | |||
} | |||
public Block(EGID id) | |||
{ | |||
Id = id; | |||
} | |||
public Block(uint id) | |||
{ | |||
Id = new EGID(id, CommonExclusiveGroups.OWNED_BLOCKS_GROUP); | |||
} | |||
public EGID Id { get; } | |||
/// <summary> | |||
/// The block's current position. | |||
/// </summary> | |||
public float3 Position | |||
{ | |||
get => MovementEngine.GetPosition(Id.entityID); | |||
set => MovementEngine.MoveBlock(Id.entityID, value); | |||
} | |||
/// <summary> | |||
/// Returns an array of blocks that are connected to this one. | |||
/// </summary> | |||
public Block[] ConnectedCubes => BlockEngine.GetConnectedBlocks(Id.entityID); | |||
/// <summary> | |||
/// The block's current rotation in degrees. | |||
/// </summary> | |||
public float3 Rotation | |||
{ | |||
get => RotationEngine.GetRotation(Id.entityID); | |||
set => RotationEngine.RotateBlock(Id.entityID, value); | |||
} | |||
/// <summary> | |||
/// Removes this block. | |||
/// </summary> | |||
/// <returns>True if the block exists and could be removed.</returns> | |||
public bool Remove() | |||
{ | |||
return RemovalEngine.RemoveBlock(Id); | |||
} | |||
public static void Init() | |||
{ | |||
GameEngineManager.AddGameEngine(PlacementEngine); | |||
GameEngineManager.AddGameEngine(MovementEngine); | |||
GameEngineManager.AddGameEngine(RotationEngine); | |||
GameEngineManager.AddGameEngine(RemovalEngine); | |||
GameEngineManager.AddGameEngine(BlockEngine); | |||
} | |||
} | |||
} |
@@ -0,0 +1,39 @@ | |||
using System.Collections.Generic; | |||
using GamecraftModdingAPI.Engines; | |||
using RobocraftX.Blocks; | |||
using RobocraftX.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using Svelto.ECS.EntityStructs; | |||
using Unity.Mathematics; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class BlockEngine : IApiEngine | |||
{ | |||
public string Name { get; } = "GamecraftModdingAPIBlockGameEngine"; | |||
public EntitiesDB entitiesDB { set; private get; } | |||
public bool isRemovable => false; | |||
public void Dispose() | |||
{ | |||
} | |||
public void Ready() | |||
{ | |||
} | |||
public Block[] GetConnectedBlocks(uint blockID) | |||
{ | |||
Stack<uint> cubeStack = new Stack<uint>(); | |||
FasterList<uint> cubesToProcess = new FasterList<uint>(); | |||
ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubesToProcess, (in GridConnectionsEntityStruct g) => { return false; }); | |||
var ret = new Block[cubesToProcess.count]; | |||
for (int i = 0; i < cubesToProcess.count; i++) | |||
ret[i] = new Block(cubesToProcess[i]); | |||
return ret; | |||
} | |||
} | |||
} |
@@ -1,31 +0,0 @@ | |||
using RobocraftX.Blocks.Ghost; | |||
using RobocraftX.Character.Camera; | |||
using RobocraftX.Character.Factories; | |||
using Svelto.ECS; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class BlockUtility | |||
{ | |||
/// <summary> | |||
/// Returns the block the player is currently looking at. | |||
/// </summary> | |||
/// <param name="playerId">The player's ID</param> | |||
/// <param name="entitiesDB">The entities DB</param> | |||
/// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param> | |||
/// <returns>The block's EGID or null if not found</returns> | |||
public static EGID? GetBlockLookedAt(uint playerId, EntitiesDB entitiesDB, float maxDistance = -1f) | |||
{ | |||
if (!entitiesDB.TryQueryMappedEntities<CharacterCameraRayCastEntityStruct>( | |||
CameraExclusiveGroups.CameraGroup, out var mapper)) | |||
return null; | |||
mapper.TryGetEntity(playerId, out CharacterCameraRayCastEntityStruct rayCast); | |||
float distance = maxDistance < 0 | |||
? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast) | |||
: maxDistance; | |||
if (rayCast.hit && rayCast.distance <= distance) | |||
return rayCast.hitEgid; | |||
return null; | |||
} | |||
} | |||
} |
@@ -1,61 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Unity.Mathematics; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
/// <summary> | |||
/// Common block movement operations. | |||
/// The functionality of this class only works in build mode. | |||
/// </summary> | |||
public static class Movement | |||
{ | |||
private static MovementEngine movementEngine = new MovementEngine(); | |||
/// <summary> | |||
/// Move a single block by a specific (x,y,z) amount (offset). | |||
/// The moved block will remain connected to the blocks it was touching before it was moved. | |||
/// The block's placement grid and collision box are also moved. | |||
/// </summary> | |||
/// <param name="id">The block's id</param> | |||
/// <param name="vector">The movement amount (x,y,z)</param> | |||
/// <returns>Whether the operation was successful</returns> | |||
public static bool MoveBlock(uint id, float3 vector) | |||
{ | |||
if (movementEngine.IsInGame && GamecraftModdingAPI.Utility.GameState.IsBuildMode()) | |||
{ | |||
movementEngine.MoveBlock(id, vector); | |||
return true; | |||
} | |||
return false; | |||
} | |||
/// <summary> | |||
/// Move all connected blocks by a specific (x,y,z) amount (offset). | |||
/// The moved blocks will remain connected to the block they're touching. | |||
/// All of the block's placement grids and collision boxes are also moved. | |||
/// This is equivalent to calling MoveBlock() for every connected block. | |||
/// </summary> | |||
/// <param name="id">The starting block's id</param> | |||
/// <param name="vector">The movement amount (x,y,z)</param> | |||
/// <returns>Whether the operation was successful</returns> | |||
public static bool MoveConnectedBlocks(uint id, float3 vector) | |||
{ | |||
if (movementEngine.IsInGame && GamecraftModdingAPI.Utility.GameState.IsBuildMode()) | |||
{ | |||
movementEngine.MoveConnectedBlocks(id, vector); | |||
return true; | |||
} | |||
return false; | |||
} | |||
public static void Init() | |||
{ | |||
GamecraftModdingAPI.Utility.GameEngineManager.AddGameEngine(movementEngine); | |||
} | |||
} | |||
} |
@@ -57,30 +57,24 @@ namespace GamecraftModdingAPI.Blocks | |||
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP); | |||
ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP); | |||
// main (persistent) position | |||
posStruct.position += vector; | |||
posStruct.position = vector; | |||
// placement grid position | |||
gridStruct.position += vector; | |||
gridStruct.position = vector; | |||
// rendered position | |||
transStruct.position += vector; | |||
transStruct.position = vector; | |||
// collision position | |||
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Translation | |||
{ | |||
Value = posStruct.position | |||
}); | |||
entitiesDB.QueryEntity<GridConnectionsEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP).isProcessed = false; | |||
return posStruct.position; | |||
} | |||
public float3 MoveConnectedBlocks(uint blockID, float3 vector) | |||
public float3 GetPosition(uint blockID) | |||
{ | |||
Stack<uint> cubeStack = new Stack<uint>(); | |||
FasterList<uint> cubesToMove = new FasterList<uint>(); | |||
ConnectedCubesUtility.TreeTraversal.GetConnectedCubes(entitiesDB, blockID, cubeStack, cubesToMove, (in GridConnectionsEntityStruct g) => { return false; }); | |||
for (int i = 0; i < cubesToMove.count; i++) | |||
{ | |||
MoveBlock(cubesToMove[i], vector); | |||
entitiesDB.QueryEntity<GridConnectionsEntityStruct>(cubesToMove[i], CommonExclusiveGroups.OWNED_BLOCKS_GROUP).isProcessed = false; | |||
} | |||
return this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP).position; | |||
ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP); | |||
return posStruct.position; | |||
} | |||
} | |||
} |
@@ -1,55 +0,0 @@ | |||
using System; | |||
using Unity.Mathematics; | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
/// <summary> | |||
/// Common block placement operations. | |||
/// The functionality in this class is for build mode. | |||
/// </summary> | |||
public static class Placement | |||
{ | |||
private static PlacementEngine placementEngine = new PlacementEngine(); | |||
/// <summary> | |||
/// Place a new block at the given position. If scaled, position means the center of the block. The default block size is 0.2 in terms of position. | |||
/// Place blocks next to each other to connect them. | |||
/// The placed block will be a complete block with a placement grid and collision which will be saved along with the game. | |||
/// </summary> | |||
/// <param name="block">The block's type</param> | |||
/// <param name="color">The block's color</param> | |||
/// <param name="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="playerId">The player who placed the block</param> | |||
/// <returns>The placed block's ID or null if failed</returns> | |||
public static EGID? PlaceBlock(BlockIDs block, float3 position, | |||
float3 rotation = default, BlockColors color = BlockColors.Default, byte darkness = 0, | |||
int uscale = 1, float3 scale = default, uint playerId = 0) | |||
{ | |||
if (placementEngine.IsInGame && GameState.IsBuildMode()) | |||
{ | |||
try | |||
{ | |||
return placementEngine.PlaceBlock(block, color, darkness, position, uscale, scale, playerId, rotation); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.MetaDebugLog(e); | |||
} | |||
} | |||
return null; | |||
} | |||
public static void Init() | |||
{ | |||
GameEngineManager.AddGameEngine(placementEngine); | |||
} | |||
} | |||
} |
@@ -22,6 +22,7 @@ using uREPL; | |||
using GamecraftModdingAPI.Utility; | |||
using GamecraftModdingAPI.Engines; | |||
using GamecraftModdingAPI.Players; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
@@ -46,11 +47,12 @@ 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, uint playerId, float3 rotation) | |||
float3 scale, Player player, float3 rotation) | |||
{ //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, playerId); | |||
return BuildBlock((ushort) block, (byte) (color + darkness * 10), position, uscale, scale, rotation, | |||
(player ?? new Player(PlayerType.Local)).Id); | |||
} | |||
private EGID BuildBlock(ushort block, byte color, float3 position, int uscale, float3 scale, float3 rot, uint playerId) | |||
@@ -1,28 +0,0 @@ | |||
using Svelto.ECS; | |||
using GamecraftModdingAPI.Utility; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
public class Removal | |||
{ | |||
private static RemovalEngine _removalEngine = new RemovalEngine(); | |||
/// <summary> | |||
/// Removes the block with the given ID. Returns false if the block doesn't exist or the game isn't in build mode. | |||
/// </summary> | |||
/// <param name="targetBlock">The block to remove</param> | |||
/// <returns>Whether the block was successfully removed</returns> | |||
public static bool RemoveBlock(EGID targetBlock) | |||
{ | |||
if (GameState.IsBuildMode()) | |||
return _removalEngine.RemoveBlock(targetBlock); | |||
return false; | |||
} | |||
public static void Init() | |||
{ | |||
GameEngineManager.AddGameEngine(_removalEngine); | |||
} | |||
} | |||
} |
@@ -1,59 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using Unity.Mathematics; | |||
namespace GamecraftModdingAPI.Blocks | |||
{ | |||
/// <summary> | |||
/// Common block rotation operations. | |||
/// The functionality in this class is not completely implemented and will only work in build mode. | |||
/// </summary> | |||
public static class Rotation | |||
{ | |||
private static RotationEngine rotationEngine = new RotationEngine(); | |||
/// <summary> | |||
/// Rotate a single block by a specific amount in degrees. | |||
/// This not destroy inter-block connections, so neighbouring blocks will remain attached despite appearances. | |||
/// The cube placement grid and collision are also rotated. | |||
/// </summary> | |||
/// <param name="id">The block's id</param> | |||
/// <param name="vector">The rotation amount around the x,y,z-axis</param> | |||
/// <returns>Whether the operation was successful</returns> | |||
public static bool RotateBlock(uint id, float3 vector) | |||
{ | |||
if (rotationEngine.IsInGame && GamecraftModdingAPI.Utility.GameState.IsBuildMode()) | |||
{ | |||
rotationEngine.RotateBlock(id, vector); | |||
return true; | |||
} | |||
return false; | |||
} | |||
/// <summary> | |||
/// Rotate all connected blocks by a specific amount in degrees. | |||
/// This does not do anything because it has not been implemented. | |||
/// </summary> | |||
/// <param name="id">The starting block's id</param> | |||
/// <param name="vector">The rotation around the x,y,z-axis</param> | |||
/// <returns>Whether the operation was successful</returns> | |||
public static bool RotateConnectedBlocks(uint id, float3 vector) | |||
{ | |||
if (rotationEngine.IsInGame && GamecraftModdingAPI.Utility.GameState.IsBuildMode()) | |||
{ | |||
rotationEngine.RotateConnectedBlocks(id, vector); | |||
return true; | |||
} | |||
return false; | |||
} | |||
public static void Init() | |||
{ | |||
GamecraftModdingAPI.Utility.GameEngineManager.AddGameEngine(rotationEngine); | |||
} | |||
} | |||
} |
@@ -72,14 +72,15 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
Value = rotStruct.rotation | |||
}); | |||
entitiesDB.QueryEntity<GridConnectionsEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP).isProcessed = false; | |||
return ((Quaternion)rotStruct.rotation).eulerAngles; | |||
} | |||
public float3 RotateConnectedBlocks(uint blockID, Vector3 vector) | |||
public float3 GetRotation(uint blockID) | |||
{ | |||
// TODO: Implement and figure out the math | |||
throw new NotImplementedException(); | |||
ref RotationEntityStruct rotStruct = ref entitiesDB.QueryEntity<RotationEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP); | |||
return ((Quaternion) rotStruct.rotation).eulerAngles; | |||
} | |||
} | |||
} |
@@ -9,7 +9,9 @@ using HarmonyLib; | |||
using GamecraftModdingAPI.Utility; | |||
using GamecraftModdingAPI.Events; | |||
using GamecraftModdingAPI.Players; | |||
using GamecraftModdingAPI.Tasks; | |||
using uREPL; | |||
namespace GamecraftModdingAPI | |||
{ | |||
@@ -61,18 +63,16 @@ namespace GamecraftModdingAPI | |||
EventManager.AddEventEmitter(GameHostTransitionDeterministicGroupEnginePatch.simEngine); | |||
// init block implementors | |||
Logging.MetaDebugLog($"Initializing Blocks"); | |||
Blocks.Movement.Init(); | |||
Blocks.Rotation.Init(); | |||
Blocks.Signals.Init(); | |||
Blocks.Placement.Init(); | |||
Blocks.Tweakable.Init(); | |||
Blocks.Removal.Init(); | |||
// init inventory | |||
Inventory.Hotbar.Init(); | |||
// init input | |||
Input.FakeInput.Init(); | |||
// init object-oriented classes | |||
Player.Init(); | |||
Block.Init(); | |||
RuntimeCommands.Register("getblock", () => new Player(PlayerType.Local).GetBlockLookedAt()); | |||
Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} initialized"); | |||
} | |||
@@ -218,6 +218,18 @@ namespace GamecraftModdingAPI | |||
} | |||
playerEngine.SetLocation(Id, location, exitSeat: exitSeat); | |||
} | |||
/// <summary> | |||
/// Returns the block the player is currently looking at. | |||
/// </summary> | |||
/// <param name="playerId">The player's ID</param> | |||
/// <param name="entitiesDB">The entities DB</param> | |||
/// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param> | |||
/// <returns>The block's EGID or null if not found</returns> | |||
public Block GetBlockLookedAt(float maxDistance = -1f) | |||
{ | |||
return playerEngine.GetBlockLookedAt(Id, maxDistance); | |||
} | |||
// internal methods | |||
@@ -11,6 +11,9 @@ using Unity.Mathematics; | |||
using Unity.Physics; | |||
using GamecraftModdingAPI.Engines; | |||
using RobocraftX.Blocks.Ghost; | |||
using RobocraftX.Character.Camera; | |||
using RobocraftX.Character.Factories; | |||
namespace GamecraftModdingAPI.Players | |||
{ | |||
@@ -234,5 +237,23 @@ namespace GamecraftModdingAPI.Players | |||
s = default; | |||
return false; | |||
} | |||
public Block GetBlockLookedAt(uint playerId, float maxDistance = -1f) | |||
{ | |||
if (!entitiesDB.TryQueryMappedEntities<CharacterCameraRayCastEntityStruct>( | |||
CameraExclusiveGroups.CameraGroup, out var mapper)) | |||
return null; | |||
mapper.TryGetEntity(playerId, out CharacterCameraRayCastEntityStruct rayCast); | |||
float distance = maxDistance < 0 | |||
? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast) | |||
: maxDistance; | |||
if (rayCast.hit && rayCast.distance <= distance) | |||
{ | |||
Console.WriteLine("Hit block: " + rayCast.hitEgid); | |||
return new Block(rayCast.hitEgid); | |||
} | |||
return null; | |||
} | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
using System; | |||
using System.Linq; | |||
using System.Reflection; | |||
using HarmonyLib; | |||
@@ -94,22 +95,21 @@ namespace GamecraftModdingAPI.Tests | |||
.Build(); | |||
CommandBuilder.Builder() | |||
.Name("MoveLastBlock") | |||
.Description("Move the most-recently-placed block, and any connected blocks by the given offset") | |||
.Action((float x, float y, float z) => { | |||
bool success = GamecraftModdingAPI.Blocks.Movement.MoveConnectedBlocks( | |||
GamecraftModdingAPI.Blocks.BlockIdentifiers.LatestBlockID, | |||
new Unity.Mathematics.float3(x, y, z)); | |||
if (!success) | |||
{ | |||
GamecraftModdingAPI.Utility.Logging.CommandLogError("Blocks can only be moved in Build mode!"); | |||
} | |||
}).Build(); | |||
.Name("MoveLastBlock") | |||
.Description("Move the most-recently-placed block, and any connected blocks by the given offset") | |||
.Action((float x, float y, float z) => | |||
{ | |||
if (GameState.IsBuildMode()) | |||
foreach (var block in Block.GetLastPlacedBlock().ConnectedCubes) | |||
block.Position += new Unity.Mathematics.float3(x, y, z); | |||
else | |||
GamecraftModdingAPI.Utility.Logging.CommandLogError("Blocks can only be moved in Build mode!"); | |||
}).Build(); | |||
CommandBuilder.Builder() | |||
.Name("PlaceAluminium") | |||
.Description("Place a block of aluminium at the given coordinates") | |||
.Action((float x, float y, float z) => { Blocks.Placement.PlaceBlock(Blocks.BlockIDs.AluminiumCube, new Unity.Mathematics.float3(x, y, z)); }) | |||
.Action((float x, float y, float z) => { Block.PlaceNew(Blocks.BlockIDs.AluminiumCube, new Unity.Mathematics.float3(x, y, z)); }) | |||
.Build(); | |||
System.Random random = new System.Random(); // for command below | |||