diff --git a/GamecraftModdingAPI/Block.cs b/GamecraftModdingAPI/Block.cs index 271ca1c..4768a57 100644 --- a/GamecraftModdingAPI/Block.cs +++ b/GamecraftModdingAPI/Block.cs @@ -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(); /// /// 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. - /// - /// This method causes a sync which may have a performance impact. Use the async version if possible. /// /// The block's type /// The block's color @@ -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"); - } - } } /// @@ -264,6 +247,17 @@ namespace GamecraftModdingAPI /// True if the block exists and could be removed. public bool Remove() => RemovalEngine.RemoveBlock(Id); + /// + /// 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. + /// + /// + public SimBody ToSimBody() + { + uint id = BlockEngine.GetBlockInfo(Id).machineRigidBodyId; + return new SimBody(id); + } + public override string ToString() { return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Rotation)}: {Rotation}"; diff --git a/GamecraftModdingAPI/Blocks/ConsoleBlock.cs b/GamecraftModdingAPI/Blocks/ConsoleBlock.cs index da9c895..173b3f6 100644 --- a/GamecraftModdingAPI/Blocks/ConsoleBlock.cs +++ b/GamecraftModdingAPI/Blocks/ConsoleBlock.cs @@ -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) diff --git a/GamecraftModdingAPI/Blocks/Motor.cs b/GamecraftModdingAPI/Blocks/Motor.cs index 8cafccb..c900f10 100644 --- a/GamecraftModdingAPI/Blocks/Motor.cs +++ b/GamecraftModdingAPI/Blocks/Motor.cs @@ -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; diff --git a/GamecraftModdingAPI/Blocks/Piston.cs b/GamecraftModdingAPI/Blocks/Piston.cs index 2f7064c..00ad273 100644 --- a/GamecraftModdingAPI/Blocks/Piston.cs +++ b/GamecraftModdingAPI/Blocks/Piston.cs @@ -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; diff --git a/GamecraftModdingAPI/Blocks/Servo.cs b/GamecraftModdingAPI/Blocks/Servo.cs index dd2cc52..9a66e3b 100644 --- a/GamecraftModdingAPI/Blocks/Servo.cs +++ b/GamecraftModdingAPI/Blocks/Servo.cs @@ -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; diff --git a/GamecraftModdingAPI/Blocks/SignalingBlock.cs b/GamecraftModdingAPI/Blocks/SignalingBlock.cs index 9c2f457..f8006f6 100644 --- a/GamecraftModdingAPI/Blocks/SignalingBlock.cs +++ b/GamecraftModdingAPI/Blocks/SignalingBlock.cs @@ -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; diff --git a/GamecraftModdingAPI/Blocks/SpawnPoint.cs b/GamecraftModdingAPI/Blocks/SpawnPoint.cs index b43dffb..bb3e7f4 100644 --- a/GamecraftModdingAPI/Blocks/SpawnPoint.cs +++ b/GamecraftModdingAPI/Blocks/SpawnPoint.cs @@ -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; diff --git a/GamecraftModdingAPI/Blocks/TextBlock.cs b/GamecraftModdingAPI/Blocks/TextBlock.cs index f2993fb..92f8116 100644 --- a/GamecraftModdingAPI/Blocks/TextBlock.cs +++ b/GamecraftModdingAPI/Blocks/TextBlock.cs @@ -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; diff --git a/GamecraftModdingAPI/Blocks/Timer.cs b/GamecraftModdingAPI/Blocks/Timer.cs index a53adf1..d26620e 100644 --- a/GamecraftModdingAPI/Blocks/Timer.cs +++ b/GamecraftModdingAPI/Blocks/Timer.cs @@ -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; diff --git a/GamecraftModdingAPI/Player.cs b/GamecraftModdingAPI/Player.cs index 965ba45..0b7a134 100644 --- a/GamecraftModdingAPI/Player.cs +++ b/GamecraftModdingAPI/Player.cs @@ -1,6 +1,7 @@ using System; using Unity.Mathematics; +using RobocraftX.Common; using GamecraftModdingAPI.Players; @@ -221,13 +222,27 @@ namespace GamecraftModdingAPI /// /// Returns the block the player is currently looking at. /// - /// The player's ID - /// The entities DB /// The maximum distance from the player (default is the player's building reach) - /// The block's EGID or null if not found + /// The block or null if not found 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; + } + + /// + /// Returns the rigid body the player is currently looking at during simulation. + /// + /// The maximum distance from the player (default is the player's building reach) + /// The block or null if not found + 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 diff --git a/GamecraftModdingAPI/Players/PlayerEngine.cs b/GamecraftModdingAPI/Players/PlayerEngine.cs index f8e53f0..d2c8fa3 100644 --- a/GamecraftModdingAPI/Players/PlayerEngine.cs +++ b/GamecraftModdingAPI/Players/PlayerEngine.cs @@ -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( 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; + 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; } diff --git a/GamecraftModdingAPI/SimBody.cs b/GamecraftModdingAPI/SimBody.cs new file mode 100644 index 0000000..e6d1c68 --- /dev/null +++ b/GamecraftModdingAPI/SimBody.cs @@ -0,0 +1,88 @@ +using RobocraftX.Common; +using RobocraftX.Physics; +using Svelto.ECS; +using Unity.Mathematics; +using Unity.Physics; +using UnityEngine; + +namespace GamecraftModdingAPI +{ + /// + /// A rigid body (like a cluster of connected blocks) during simulation. + /// + 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); + } + + /// + /// Whether the body can be moved or static + /// + public bool Static + { + get => Block.BlockEngine.GetBlockInfo(Id).isStatic; + set => Block.BlockEngine.GetBlockInfo(Id).isStatic = value; + } + + private ref RigidBodyEntityStruct GetStruct() + { + return ref Block.BlockEngine.GetBlockInfo(Id); + } + } +} \ No newline at end of file diff --git a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs index 4c9105c..b662dd7 100644 --- a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs +++ b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs @@ -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 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() { } diff --git a/GamecraftModdingAPI/Utility/DebugInterfaceEngine.cs b/GamecraftModdingAPI/Utility/DebugInterfaceEngine.cs index b8b0222..c604c34 100644 --- a/GamecraftModdingAPI/Utility/DebugInterfaceEngine.cs +++ b/GamecraftModdingAPI/Utility/DebugInterfaceEngine.cs @@ -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); } } } diff --git a/GamecraftModdingAPI/Utility/Logging.cs b/GamecraftModdingAPI/Utility/Logging.cs index 20c17cf..80ef323 100644 --- a/GamecraftModdingAPI/Utility/Logging.cs +++ b/GamecraftModdingAPI/Utility/Logging.cs @@ -83,7 +83,7 @@ namespace GamecraftModdingAPI.Utility } /// - /// Write an exception to Gamecraft's log + /// Write an exception to Gamecraft's log and to the screen and exit game /// /// The exception to log /// The extra data to pass to the ILogger. @@ -95,7 +95,7 @@ namespace GamecraftModdingAPI.Utility } /// - /// Write an exception message to Gamecraft's log + /// Write an exception message to Gamecraft's log and to the screen and exit game /// /// The object to log /// The exception to log