@@ -23,15 +23,14 @@ namespace GamecraftModdingAPI | |||
protected static readonly MovementEngine MovementEngine = new MovementEngine(); | |||
protected static readonly RotationEngine RotationEngine = new RotationEngine(); | |||
protected static readonly RemovalEngine RemovalEngine = new RemovalEngine(); | |||
protected static readonly BlockEngine BlockEngine = new BlockEngine(); | |||
protected static readonly SignalEngine SignalEngine = new SignalEngine(); | |||
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>This method causes a sync which may have a performance impact. Use the async version if possible.</para> | |||
/// </summary> | |||
/// <param name="block">The block's type</param> | |||
/// <param name="color">The block's color</param> | |||
@@ -48,15 +47,8 @@ namespace GamecraftModdingAPI | |||
{ | |||
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 new Block(PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation)); | |||
} | |||
return null; | |||
@@ -122,17 +114,8 @@ namespace GamecraftModdingAPI | |||
} | |||
} | |||
public Block(uint id) | |||
public Block(uint id) : this(new EGID(id, CommonExclusiveGroups.OWNED_BLOCKS_GROUP)) | |||
{ | |||
Id = new EGID(id, CommonExclusiveGroups.OWNED_BLOCKS_GROUP); | |||
if (!BlockEngine.BlockExists(Id)) | |||
{ | |||
Sync(); | |||
if (!BlockEngine.BlockExists(Id)) | |||
{ | |||
throw new BlockDoesNotExistException($"Block {Id.entityID} must be placed using PlaceNew(...) since it does not exist yet"); | |||
} | |||
} | |||
} | |||
/// <summary> | |||
@@ -264,6 +247,17 @@ namespace GamecraftModdingAPI | |||
/// <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></returns> | |||
public SimBody ToSimBody() | |||
{ | |||
uint id = BlockEngine.GetBlockInfo<GridConnectionsEntityStruct>(Id).machineRigidBodyId; | |||
return new SimBody(id); | |||
} | |||
public override string ToString() | |||
{ | |||
return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}"; | |||
@@ -15,21 +15,14 @@ namespace GamecraftModdingAPI.Blocks | |||
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 | |||
{ | |||
EGID id = PlacementEngine.PlaceBlock(BlockIDs.ConsoleBlock, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new ConsoleBlock(id); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.MetaDebugLog(e); | |||
} | |||
} | |||
if (PlacementEngine.IsInGame && GameState.IsBuildMode()) | |||
{ | |||
EGID id = PlacementEngine.PlaceBlock(BlockIDs.ConsoleBlock, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new ConsoleBlock(id); | |||
} | |||
return null; | |||
return null; | |||
} | |||
public ConsoleBlock(EGID id): base(id) | |||
@@ -25,16 +25,9 @@ namespace GamecraftModdingAPI.Blocks | |||
} | |||
if (PlacementEngine.IsInGame && GameState.IsBuildMode()) | |||
{ | |||
try | |||
{ | |||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new Motor(id); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.MetaDebugLog(e); | |||
} | |||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new Motor(id); | |||
} | |||
return null; | |||
@@ -25,16 +25,9 @@ namespace GamecraftModdingAPI.Blocks | |||
} | |||
if (PlacementEngine.IsInGame && GameState.IsBuildMode()) | |||
{ | |||
try | |||
{ | |||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new Piston(id); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.MetaDebugLog(e); | |||
} | |||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new Piston(id); | |||
} | |||
return null; | |||
@@ -21,20 +21,13 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
if (!(block == BlockIDs.ServoAxle || block == BlockIDs.ServoHinge || block == BlockIDs.ServoPiston)) | |||
{ | |||
throw new BlockTypeException($"Block is not a {typeof(Servo).Name} block"); | |||
throw new BlockTypeException($"Block is not a {nameof(Servo)} block"); | |||
} | |||
if (PlacementEngine.IsInGame && GameState.IsBuildMode()) | |||
{ | |||
try | |||
{ | |||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new Servo(id); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.MetaDebugLog(e); | |||
} | |||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new Servo(id); | |||
} | |||
return null; | |||
@@ -25,16 +25,9 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
if (PlacementEngine.IsInGame && GameState.IsBuildMode()) | |||
{ | |||
try | |||
{ | |||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new SignalingBlock(id); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.MetaDebugLog(e); | |||
} | |||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new SignalingBlock(id); | |||
} | |||
return null; | |||
@@ -23,20 +23,13 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
if (!(block == BlockIDs.LargeSpawn || block == BlockIDs.SmallSpawn || block == BlockIDs.MediumSpawn || block == BlockIDs.PlayerSpawn)) | |||
{ | |||
throw new BlockTypeException($"Block is not a {typeof(SpawnPoint).Name} block"); | |||
throw new BlockTypeException($"Block is not a {nameof(SpawnPoint)} block"); | |||
} | |||
if (PlacementEngine.IsInGame && GameState.IsBuildMode()) | |||
{ | |||
try | |||
{ | |||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
EGID id = PlacementEngine.PlaceBlock(block, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new SpawnPoint(id); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.MetaDebugLog(e); | |||
} | |||
} | |||
return null; | |||
@@ -18,16 +18,9 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
if (PlacementEngine.IsInGame && GameState.IsBuildMode()) | |||
{ | |||
try | |||
{ | |||
EGID id = PlacementEngine.PlaceBlock(BlockIDs.TextBlock, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new TextBlock(id); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.MetaDebugLog(e); | |||
} | |||
EGID id = PlacementEngine.PlaceBlock(BlockIDs.TextBlock, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new TextBlock(id); | |||
} | |||
return null; | |||
@@ -21,17 +21,9 @@ namespace GamecraftModdingAPI.Blocks | |||
{ | |||
if (PlacementEngine.IsInGame && GameState.IsBuildMode()) | |||
{ | |||
try | |||
{ | |||
EGID id = PlacementEngine.PlaceBlock(BlockIDs.Timer, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new Timer(id); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.MetaDebugLog(e); | |||
} | |||
EGID id = PlacementEngine.PlaceBlock(BlockIDs.Timer, color, darkness, | |||
position, uscale, scale, player, rotation); | |||
return new Timer(id); | |||
} | |||
return null; | |||
@@ -1,6 +1,7 @@ | |||
using System; | |||
using Unity.Mathematics; | |||
using RobocraftX.Common; | |||
using GamecraftModdingAPI.Players; | |||
@@ -221,13 +222,27 @@ namespace GamecraftModdingAPI | |||
/// <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> | |||
/// <returns>The block or null if not found</returns> | |||
public Block GetBlockLookedAt(float maxDistance = -1f) | |||
{ | |||
return playerEngine.GetBlockLookedAt(Id, maxDistance); | |||
var egid = playerEngine.GetThingLookedAt(Id, maxDistance); | |||
return egid.HasValue && egid.Value.groupID == CommonExclusiveGroups.OWNED_BLOCKS_GROUP | |||
? new Block(egid.Value) | |||
: null; | |||
} | |||
/// <summary> | |||
/// Returns the rigid body the player is currently looking at during simulation. | |||
/// </summary> | |||
/// <param name="maxDistance">The maximum distance from the player (default is the player's building reach)</param> | |||
/// <returns>The block or null if not found</returns> | |||
public SimBody GetSimBodyLookedAt(float maxDistance = -1f) | |||
{ | |||
var egid = playerEngine.GetThingLookedAt(Id, maxDistance); | |||
return egid.HasValue && egid.Value.groupID == CommonExclusiveGroups.SIMULATION_BODIES_GROUP | |||
? new SimBody(egid.Value) | |||
: null; | |||
} | |||
// internal methods | |||
@@ -238,7 +238,7 @@ namespace GamecraftModdingAPI.Players | |||
return false; | |||
} | |||
public Block GetBlockLookedAt(uint playerId, float maxDistance = -1f) | |||
public EGID? GetThingLookedAt(uint playerId, float maxDistance = -1f) | |||
{ | |||
if (!entitiesDB.TryQueryMappedEntities<CharacterCameraRayCastEntityStruct>( | |||
CameraExclusiveGroups.CameraGroup, out var mapper)) | |||
@@ -248,7 +248,22 @@ namespace GamecraftModdingAPI.Players | |||
? GhostBlockUtils.GetBuildInteractionDistance(entitiesDB, rayCast) | |||
: maxDistance; | |||
if (rayCast.hit && rayCast.distance <= distance) | |||
return new Block(rayCast.hitEgid); | |||
return rayCast.hitEgid; | |||
/*if (rayCast.hit) | |||
{ | |||
*Logging.MetaDebugLog("RayCast EGID: " + rayCast.hitEgid); | |||
var d = AccessTools.Field(typeof(ExclusiveGroup), "_knownGroups").GetValue(null) as | |||
Dictionary<string, ExclusiveGroupStruct>; | |||
foreach (var groupStruct in d) | |||
{ | |||
if (groupStruct.Value == rayCast.hitEgid.groupID) | |||
{ | |||
Logging.MetaDebugLog("Group name: " + groupStruct.Key); | |||
break; //SIMULATION_BODIES_GROUP | |||
} | |||
}* | |||
//Logging.MetaDebugLog("Position: " + Block.GetBlockPositionTest(rayCast.hitEgid)); | |||
}*/ | |||
return null; | |||
} | |||
@@ -0,0 +1,88 @@ | |||
using RobocraftX.Common; | |||
using RobocraftX.Physics; | |||
using Svelto.ECS; | |||
using Unity.Mathematics; | |||
using Unity.Physics; | |||
using UnityEngine; | |||
namespace GamecraftModdingAPI | |||
{ | |||
/// <summary> | |||
/// A rigid body (like a cluster of connected blocks) during simulation. | |||
/// </summary> | |||
public class SimBody | |||
{ | |||
public EGID Id { get; } | |||
public SimBody(EGID id) | |||
{ | |||
Id = id; | |||
} | |||
public SimBody(uint id) : this(new EGID(id, CommonExclusiveGroups.SIMULATION_BODIES_GROUP)) | |||
{ | |||
} | |||
public float3 Position | |||
{ | |||
get => GetStruct().position; | |||
set => GetStruct().position = value; | |||
} | |||
public float3 Velocity | |||
{ | |||
get => GetStruct().velocity; | |||
set => GetStruct().velocity = value; | |||
} | |||
public float3 AngularVelocity | |||
{ | |||
get => GetStruct().angularVelocity; | |||
set => GetStruct().angularVelocity = value; | |||
} | |||
public float3 DeltaVelocity | |||
{ | |||
get => GetStruct().deltaVelocity; | |||
set => GetStruct().deltaVelocity = value; | |||
} | |||
public float3 DeltaAngularVelocity | |||
{ | |||
get => GetStruct().deltaAngularVelocity; | |||
set => GetStruct().deltaAngularVelocity = value; | |||
} | |||
public float3 Rotation | |||
{ | |||
get => ((Quaternion) GetStruct().rotation).eulerAngles; | |||
set | |||
{ | |||
ref var str = ref GetStruct(); | |||
Quaternion quaternion = str.rotation; | |||
quaternion.eulerAngles = value; | |||
str.rotation = quaternion; | |||
} | |||
} | |||
public float Mass | |||
{ | |||
get => math.rcp(GetStruct().physicsMass.InverseMass); | |||
set => GetStruct().physicsMass.InverseMass = math.rcp(value); | |||
} | |||
/// <summary> | |||
/// Whether the body can be moved or static | |||
/// </summary> | |||
public bool Static | |||
{ | |||
get => Block.BlockEngine.GetBlockInfo<MassEntityStruct>(Id).isStatic; | |||
set => Block.BlockEngine.GetBlockInfo<MassEntityStruct>(Id).isStatic = value; | |||
} | |||
private ref RigidBodyEntityStruct GetStruct() | |||
{ | |||
return ref Block.BlockEngine.GetBlockInfo<RigidBodyEntityStruct>(Id); | |||
} | |||
} | |||
} |
@@ -1,8 +1,11 @@ | |||
using System; | |||
using System.Diagnostics; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Text; | |||
using HarmonyLib; | |||
using IllusionInjector; | |||
// test | |||
using Svelto.ECS; | |||
using RobocraftX.Blocks; | |||
@@ -143,15 +146,46 @@ namespace GamecraftModdingAPI.Tests | |||
CommandBuilder.Builder() | |||
.Name("PlaceAluminium") | |||
.Description("Place a block of aluminium at the given coordinates") | |||
.Action(async (float x, float y, float z) => | |||
.Action((float x, float y, float z) => | |||
{ | |||
var block = await Block.PlaceNewAsync(BlockIDs.AluminiumCube, new float3(x, y, z)); | |||
Logging.MetaDebugLog("Block placed with type: " + block.Type); | |||
var block = Block.PlaceNew(BlockIDs.AluminiumCube, new float3(x, y, z)); | |||
Logging.CommandLog("Block placed with type: " + block.Type); | |||
}) | |||
.Build(); | |||
CommandBuilder.Builder("getBlock") | |||
.Action(() => uREPL.Log.Output(new Player(Players.PlayerType.Local).GetBlockLookedAt()+"")).Build(); | |||
CommandBuilder.Builder() | |||
.Name("PlaceAluminiumLots") | |||
.Description("Place a lot of blocks of aluminium at the given coordinates") | |||
.Action((float x, float y, float z) => | |||
{ | |||
Logging.CommandLog("Starting..."); | |||
var sw = Stopwatch.StartNew(); | |||
for (int i = 0; i < 100; i++) | |||
for (int j = 0; j < 100; j++) | |||
Block.PlaceNew(BlockIDs.AluminiumCube, new float3(x + i, y, z + j)); | |||
//Block.Sync(); | |||
sw.Stop(); | |||
Logging.CommandLog("Finished in " + sw.ElapsedMilliseconds + "ms"); | |||
}) | |||
.Build(); | |||
//With Sync(): 1135ms | |||
//Without Sync(): 134ms | |||
//Async: 348 794ms, doesn't freeze game | |||
//Without Sync() but wait for submission: 530ms | |||
//With Sync() at the end: 380ms | |||
Block b = null; | |||
CommandBuilder.Builder("moveBlockInSim", "Run in build mode first, then in sim while looking at a block to move it up") | |||
.Action(() => | |||
{ | |||
if (b == null) | |||
{ | |||
b = new Player(PlayerType.Local).GetBlockLookedAt(); | |||
Logging.CommandLog("Block saved: " + b); | |||
} | |||
else | |||
Logging.CommandLog("Block moved to: " + (b.ToSimBody().Position += new float3(0, 2, 0))); | |||
}).Build(); | |||
CommandBuilder.Builder("Error", "Throw an error to make sure SimpleCustomCommandEngine's wrapper catches it.") | |||
.Action(() => { throw new Exception("Error Command always throws an error"); }) | |||
@@ -176,6 +210,7 @@ namespace GamecraftModdingAPI.Tests | |||
}).Build(); | |||
GameClient.SetDebugInfo("lookedAt", LookedAt); | |||
GameClient.SetDebugInfo("InstalledMods", InstalledMods); | |||
/* | |||
CommandManager.AddCommand(new SimpleCustomCommandEngine<float>((float d) => { UnityEngine.Camera.main.fieldOfView = d; }, | |||
@@ -237,10 +272,35 @@ namespace GamecraftModdingAPI.Tests | |||
private string LookedAt() | |||
{ | |||
if (player == null) | |||
player = new Player(Players.PlayerType.Local); | |||
Block block = player.GetBlockLookedAt(); | |||
if (block == null) return "Block: none"; | |||
return "Block: " + block.Type + "\nColor: " + block.Color + "\n" + "At: " + block.Position; | |||
player = new Player(PlayerType.Local); | |||
if (GameState.IsBuildMode()) | |||
{ | |||
Block block = player.GetBlockLookedAt(); | |||
if (block == null) return "Block: none"; | |||
return "Block: " + block.Type + "\nColor: " + block.Color + "\n" + "At: " + block.Position; | |||
} | |||
if (GameState.IsSimulationMode()) | |||
{ | |||
SimBody body = player.GetSimBodyLookedAt(); | |||
if (body == null) return "Body: none"; | |||
return "Body: " + (body.Static ? "static" : "non-static") | |||
+ "\nAt: " + body.Position + " - rotated: " + body.Rotation | |||
+ "\nWith mass: " + body.Mass | |||
+ "\nVelocity: " + body.Velocity + " - angular: " + body.AngularVelocity | |||
+ "\nDelta velocity: " + body.DeltaVelocity + " - angular: " + body.DeltaAngularVelocity; | |||
} | |||
return "Switching modes..."; | |||
} | |||
private string modsString; | |||
private string InstalledMods() | |||
{ | |||
if (modsString != null) return modsString; | |||
StringBuilder sb = new StringBuilder("Installed mods:"); | |||
foreach (var plugin in PluginManager.Plugins) | |||
sb.Append("\n" + plugin); | |||
return modsString = sb.ToString(); | |||
} | |||
public void OnFixedUpdate() { } | |||
@@ -52,7 +52,7 @@ namespace GamecraftModdingAPI.Utility | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogException(e, "Failed to inject AddInfo method for the debug display!"); | |||
Logging.LogWarning("Failed to inject AddInfo method for the debug display!\n" + e); | |||
} | |||
return list; | |||
@@ -68,7 +68,7 @@ namespace GamecraftModdingAPI.Utility | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogException(e, "Unable to get info for " + info.Key); | |||
Logging.LogWarning("Unable to get info for " + info.Key + "\n" + e); | |||
} | |||
} | |||
} | |||
@@ -83,7 +83,7 @@ namespace GamecraftModdingAPI.Utility | |||
} | |||
/// <summary> | |||
/// Write an exception to Gamecraft's log | |||
/// Write an exception to Gamecraft's log and to the screen and exit game | |||
/// </summary> | |||
/// <param name="e">The exception to log</param> | |||
/// <param name="extraData">The extra data to pass to the ILogger. | |||
@@ -95,7 +95,7 @@ namespace GamecraftModdingAPI.Utility | |||
} | |||
/// <summary> | |||
/// Write an exception message to Gamecraft's log | |||
/// Write an exception message to Gamecraft's log and to the screen and exit game | |||
/// </summary> | |||
/// <param name="obj">The object to log</param> | |||
/// <param name="e">The exception to log</param> | |||