Create Block class with existing functionality

Placement, movement, rotation, removal
Block looked at (in Player class), connected blocks
This commit is contained in:
Norbi Peti 2020-05-13 14:02:36 +02:00 committed by NGnius (Graham)
parent 5168bfbad7
commit ff57a16565
14 changed files with 218 additions and 268 deletions

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -57,30 +57,24 @@ namespace GamecraftModdingAPI.Blocks
ref LocalTransformEntityStruct transStruct = ref this.entitiesDB.QueryEntity<LocalTransformEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP); 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); ref UECSPhysicsEntityStruct phyStruct = ref this.entitiesDB.QueryEntity<UECSPhysicsEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
// main (persistent) position // main (persistent) position
posStruct.position += vector; posStruct.position = vector;
// placement grid position // placement grid position
gridStruct.position += vector; gridStruct.position = vector;
// rendered position // rendered position
transStruct.position += vector; transStruct.position = vector;
// collision position // collision position
FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Translation FullGameFields._physicsWorld.EntityManager.SetComponentData(phyStruct.uecsEntity, new Translation
{ {
Value = posStruct.position Value = posStruct.position
}); });
entitiesDB.QueryEntity<GridConnectionsEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP).isProcessed = false;
return posStruct.position; return posStruct.position;
} }
public float3 MoveConnectedBlocks(uint blockID, float3 vector) public float3 GetPosition(uint blockID)
{ {
Stack<uint> cubeStack = new Stack<uint>(); ref PositionEntityStruct posStruct = ref this.entitiesDB.QueryEntity<PositionEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
FasterList<uint> cubesToMove = new FasterList<uint>(); return posStruct.position;
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;
} }
} }
} }

View file

@ -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);
}
}
}

View file

@ -22,6 +22,7 @@ using uREPL;
using GamecraftModdingAPI.Utility; using GamecraftModdingAPI.Utility;
using GamecraftModdingAPI.Engines; using GamecraftModdingAPI.Engines;
using GamecraftModdingAPI.Players;
namespace GamecraftModdingAPI.Blocks namespace GamecraftModdingAPI.Blocks
{ {
@ -46,11 +47,12 @@ namespace GamecraftModdingAPI.Blocks
private static BlockEntityFactory _blockEntityFactory; //Injected from PlaceBlockEngine private static BlockEntityFactory _blockEntityFactory; //Injected from PlaceBlockEngine
public EGID PlaceBlock(BlockIDs block, BlockColors color, byte darkness, float3 position, int uscale, public EGID PlaceBlock(BlockIDs block, BlockColors color, byte darkness, float3 position, int uscale,
float3 scale, 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 { //It appears that only the non-uniform scale has any visible effect, but if that's not given here it will be set to the uniform one
if (darkness > 9) if (darkness > 9)
throw new Exception("That is too dark. Make sure to use 0-9 as darkness. (0 is default.)"); throw new Exception("That is too dark. Make sure to use 0-9 as darkness. (0 is default.)");
return BuildBlock((ushort) block, (byte) (color + darkness * 10), position, uscale, scale, rotation, 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) private EGID BuildBlock(ushort block, byte color, float3 position, int uscale, float3 scale, float3 rot, uint playerId)

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -72,14 +72,15 @@ namespace GamecraftModdingAPI.Blocks
{ {
Value = rotStruct.rotation Value = rotStruct.rotation
}); });
entitiesDB.QueryEntity<GridConnectionsEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP).isProcessed = false;
return ((Quaternion)rotStruct.rotation).eulerAngles; return ((Quaternion)rotStruct.rotation).eulerAngles;
} }
public float3 RotateConnectedBlocks(uint blockID, Vector3 vector) public float3 GetRotation(uint blockID)
{ {
// TODO: Implement and figure out the math ref RotationEntityStruct rotStruct = ref entitiesDB.QueryEntity<RotationEntityStruct>(blockID, CommonExclusiveGroups.OWNED_BLOCKS_GROUP);
throw new NotImplementedException(); return ((Quaternion) rotStruct.rotation).eulerAngles;
} }
} }
} }

View file

@ -9,7 +9,9 @@ using HarmonyLib;
using GamecraftModdingAPI.Utility; using GamecraftModdingAPI.Utility;
using GamecraftModdingAPI.Events; using GamecraftModdingAPI.Events;
using GamecraftModdingAPI.Players;
using GamecraftModdingAPI.Tasks; using GamecraftModdingAPI.Tasks;
using uREPL;
namespace GamecraftModdingAPI namespace GamecraftModdingAPI
{ {
@ -61,18 +63,16 @@ namespace GamecraftModdingAPI
EventManager.AddEventEmitter(GameHostTransitionDeterministicGroupEnginePatch.simEngine); EventManager.AddEventEmitter(GameHostTransitionDeterministicGroupEnginePatch.simEngine);
// init block implementors // init block implementors
Logging.MetaDebugLog($"Initializing Blocks"); Logging.MetaDebugLog($"Initializing Blocks");
Blocks.Movement.Init();
Blocks.Rotation.Init();
Blocks.Signals.Init(); Blocks.Signals.Init();
Blocks.Placement.Init();
Blocks.Tweakable.Init(); Blocks.Tweakable.Init();
Blocks.Removal.Init();
// init inventory // init inventory
Inventory.Hotbar.Init(); Inventory.Hotbar.Init();
// init input // init input
Input.FakeInput.Init(); Input.FakeInput.Init();
// init object-oriented classes // init object-oriented classes
Player.Init(); Player.Init();
Block.Init();
RuntimeCommands.Register("getblock", () => new Player(PlayerType.Local).GetBlockLookedAt());
Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} initialized"); Logging.MetaLog($"{currentAssembly.GetName().Name} v{currentAssembly.GetName().Version} initialized");
} }

View file

@ -218,6 +218,18 @@ namespace GamecraftModdingAPI
} }
playerEngine.SetLocation(Id, location, exitSeat: exitSeat); 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 // internal methods

View file

@ -11,6 +11,9 @@ using Unity.Mathematics;
using Unity.Physics; using Unity.Physics;
using GamecraftModdingAPI.Engines; using GamecraftModdingAPI.Engines;
using RobocraftX.Blocks.Ghost;
using RobocraftX.Character.Camera;
using RobocraftX.Character.Factories;
namespace GamecraftModdingAPI.Players namespace GamecraftModdingAPI.Players
{ {
@ -234,5 +237,23 @@ namespace GamecraftModdingAPI.Players
s = default; s = default;
return false; 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;
}
} }
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using System.Reflection; using System.Reflection;
using HarmonyLib; using HarmonyLib;
@ -94,22 +95,21 @@ namespace GamecraftModdingAPI.Tests
.Build(); .Build();
CommandBuilder.Builder() CommandBuilder.Builder()
.Name("MoveLastBlock") .Name("MoveLastBlock")
.Description("Move the most-recently-placed block, and any connected blocks by the given offset") .Description("Move the most-recently-placed block, and any connected blocks by the given offset")
.Action((float x, float y, float z) => { .Action((float x, float y, float z) =>
bool success = GamecraftModdingAPI.Blocks.Movement.MoveConnectedBlocks( {
GamecraftModdingAPI.Blocks.BlockIdentifiers.LatestBlockID, if (GameState.IsBuildMode())
new Unity.Mathematics.float3(x, y, z)); foreach (var block in Block.GetLastPlacedBlock().ConnectedCubes)
if (!success) block.Position += new Unity.Mathematics.float3(x, y, z);
{ else
GamecraftModdingAPI.Utility.Logging.CommandLogError("Blocks can only be moved in Build mode!"); GamecraftModdingAPI.Utility.Logging.CommandLogError("Blocks can only be moved in Build mode!");
} }).Build();
}).Build();
CommandBuilder.Builder() CommandBuilder.Builder()
.Name("PlaceAluminium") .Name("PlaceAluminium")
.Description("Place a block of aluminium at the given coordinates") .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(); .Build();
System.Random random = new System.Random(); // for command below System.Random random = new System.Random(); // for command below