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

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.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)

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

View file

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

View file

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

View file

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

View file

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