From d744aaab79ae1c9a02a84784d4358ef51720d1ee Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Thu, 12 Nov 2020 02:39:58 +0100 Subject: [PATCH] Add ability to create & move block groups & other stuff Added a way to store block groups as blueprints Blocks can be added/removed from block groups, although it doesn't work well atm Added some patches to the test class in an attempt to debug an unrelated issue Added a command to test placing a block group Added a SelectedBlueprint property to the Player class --- GamecraftModdingAPI/Block.cs | 28 +++- GamecraftModdingAPI/BlockGroup.cs | 153 ++++++++++++++++-- .../Blocks/BlockEventsEngine.cs | 11 +- GamecraftModdingAPI/Blocks/BlueprintEngine.cs | 49 ++++-- GamecraftModdingAPI/Blocks/PlacementEngine.cs | 8 +- GamecraftModdingAPI/Blueprint.cs | 16 +- GamecraftModdingAPI/Player.cs | 13 +- .../Tests/GamecraftModdingAPIPluginTest.cs | 101 ++++++++++++ 8 files changed, 332 insertions(+), 47 deletions(-) diff --git a/GamecraftModdingAPI/Block.cs b/GamecraftModdingAPI/Block.cs index 862db3f..33a1623 100644 --- a/GamecraftModdingAPI/Block.cs +++ b/GamecraftModdingAPI/Block.cs @@ -240,6 +240,8 @@ namespace GamecraftModdingAPI set { MovementEngine.MoveBlock(Id, InitData, value); + if (blockGroup != null) + blockGroup.PosAndRotCalculated = false; } } @@ -252,6 +254,8 @@ namespace GamecraftModdingAPI set { RotationEngine.RotateBlock(Id, InitData, value); + if (blockGroup != null) + blockGroup.PosAndRotCalculated = false; } } @@ -354,15 +358,31 @@ namespace GamecraftModdingAPI } } + private BlockGroup blockGroup; /// /// Returns the block group this block is a part of. Block groups can be placed using blueprints. - /// Returns null if not part of a group. + /// Returns null if not part of a group.
+ /// Setting the group after the block has been initialized will not update everything properly. + /// You should only set this property on blocks newly placed by your code. ///
public BlockGroup BlockGroup { - get => BlockEngine.GetBlockInfo(this, - (BlockGroupEntityComponent bgec) => - bgec.currentBlockGroup == -1 ? null : new BlockGroup(bgec.currentBlockGroup, this)); + get + { + if (blockGroup != null) return blockGroup; + return blockGroup = BlockEngine.GetBlockInfo(this, + (BlockGroupEntityComponent bgec) => + bgec.currentBlockGroup == -1 ? null : new BlockGroup(bgec.currentBlockGroup, this)); + } + set + { + blockGroup?.RemoveInternal(this); + BlockEngine.SetBlockInfo(this, + (ref BlockGroupEntityComponent bgec, BlockGroup val) => bgec.currentBlockGroup = val?.Id ?? -1, + value); + value?.AddInternal(this); + blockGroup = value; + } } /// diff --git a/GamecraftModdingAPI/BlockGroup.cs b/GamecraftModdingAPI/BlockGroup.cs index 8d9e761..d660236 100644 --- a/GamecraftModdingAPI/BlockGroup.cs +++ b/GamecraftModdingAPI/BlockGroup.cs @@ -1,4 +1,8 @@ -using Gamecraft.Blocks.BlockGroups; +using System; +using System.Collections; +using System.Collections.Generic; + +using Gamecraft.Blocks.BlockGroups; using Unity.Mathematics; using UnityEngine; @@ -10,11 +14,14 @@ namespace GamecraftModdingAPI /// /// A group of blocks that can be selected together. The placed version of blueprints. /// - public class BlockGroup + public class BlockGroup : ICollection { internal static BlueprintEngine _engine = new BlueprintEngine(); public int Id { get; } private readonly Block sourceBlock; + private readonly List blocks; + private float3 position, rotation; + internal bool PosAndRotCalculated; internal BlockGroup(int id, Block block) { @@ -22,41 +29,155 @@ namespace GamecraftModdingAPI throw new BlockException("Cannot create a block group for blocks without a group!"); Id = id; sourceBlock = block; + blocks = new List(GetBlocks()); } /// - /// The position of the block group. Calculated when GetBlocks() is used. + /// The position of the block group (center). Recalculated if blocks have been added/removed since the last query. /// - public float3 Position { get; private set; } - + public float3 Position + { + get + { + if (!PosAndRotCalculated) + Refresh(); + return position; + } + set + { + var diff = value - position; + foreach (var block in blocks) + block.Position += diff; + if (!PosAndRotCalculated) //The condition can only be true if a block has been added/removed manually + Refresh(); //So the blocks array is up to date + else + position += diff; + } + } + /// - /// The rotation of the block group. Calculated when GetBlocks() is used. + /// The rotation of the block group. Recalculated if blocks have been added/removed since the last query. /// - public float3 Rotation { get; private set; } + public float3 Rotation + { + get + { + if (!PosAndRotCalculated) + Refresh(); + return rotation; + } + set + { + var diff = value - rotation; + var qdiff = Quaternion.Euler(diff); + foreach (var block in blocks) + { + block.Rotation += diff; + block.Position = qdiff * block.Position; + } + if (!PosAndRotCalculated) + Refresh(); + else + rotation += diff; + } + } + + /*/// + /// Removes all of the blocks in this group from the world. + /// + public void RemoveBlocks() + { + _engine.RemoveBlockGroup(Id); - TODO: Causes a hard crash + }*/ + + /// + /// Creates a new block group consisting of a single block. + /// You can add more blocks using the Add() method or by setting the BlockGroup property of the blocks.
+ /// Note that only newly placed blocks should be added to groups. + ///
+ /// The block to add + /// A new block group containing the given block + public static BlockGroup Create(Block block) + { + return new BlockGroup(_engine.CreateBlockGroup(default, default), block); + } /// /// Collects each block that is a part of this group. Also sets the position and rotation. /// /// An array of blocks - public Block[] GetBlocks() + private Block[] GetBlocks() { + if (!sourceBlock.Exists) return new[] {sourceBlock}; //The block must exist to get the others var ret = _engine.GetBlocksFromGroup(sourceBlock.Id, out var pos, out var rot); - Position = pos; - Rotation = ((Quaternion) rot).eulerAngles; + position = pos; + rotation = ((Quaternion) rot).eulerAngles; + PosAndRotCalculated = true; return ret; } - /// - /// Removes all of the blocks in this group from the world. - /// - public void Remove() + private void Refresh() { - _engine.RemoveBlockGroup(Id); + blocks.Clear(); + blocks.AddRange(GetBlocks()); } - public static void Init() + internal static void Init() { GameEngineManager.AddGameEngine(_engine); } + + public IEnumerator GetEnumerator() => blocks.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => blocks.GetEnumerator(); + + /// + /// Adds a block to the group. You should only add newly placed blocks + /// so that the game initializes the group membership properly. + /// + /// + /// + public void Add(Block item) + { + if (item == null) throw new NullReferenceException("Cannot add null to a block group"); + item.BlockGroup = this; //Calls AddInternal + } + + internal void AddInternal(Block item) => blocks.Add(item); + + /// + /// Removes all blocks from this group. + /// You should not remove blocks that have been initialized, only those that you placed recently. + /// + public void Clear() + { + while (blocks.Count > 0) + Remove(blocks[blocks.Count - 1]); + } + + public bool Contains(Block item) => blocks.Contains(item); + public void CopyTo(Block[] array, int arrayIndex) => blocks.CopyTo(array, arrayIndex); + + /// + /// Removes a block from this group. + /// You should not remove blocks that have been initialized, only those that you placed recently. + /// + /// + /// + /// + public bool Remove(Block item) + { + if (item == null) throw new NullReferenceException("Cannot remove null from a block group"); + bool ret = item.BlockGroup == this; + if (ret) + item.BlockGroup = null; //Calls RemoveInternal + return ret; + } + + internal void RemoveInternal(Block item) => blocks.Remove(item); + + public int Count => blocks.Count; + public bool IsReadOnly { get; } = false; + + public Block this[int index] => blocks[index]; //Setting is not supported, since the order doesn't matter } } \ No newline at end of file diff --git a/GamecraftModdingAPI/Blocks/BlockEventsEngine.cs b/GamecraftModdingAPI/Blocks/BlockEventsEngine.cs index 1e5ce21..d1c2639 100644 --- a/GamecraftModdingAPI/Blocks/BlockEventsEngine.cs +++ b/GamecraftModdingAPI/Blocks/BlockEventsEngine.cs @@ -5,10 +5,11 @@ using Svelto.ECS; using GamecraftModdingAPI.Engines; using GamecraftModdingAPI.Utility; +using RobocraftX.Blocks; namespace GamecraftModdingAPI.Blocks { - public class BlockEventsEngine : IReactionaryEngine + public class BlockEventsEngine : IReactionaryEngine { public event EventHandler Placed; public event EventHandler Removed; @@ -27,20 +28,20 @@ namespace GamecraftModdingAPI.Blocks public bool isRemovable { get; } = false; private bool shouldAddRemove; - public void Add(ref DBEntityStruct entityComponent, EGID egid) + public void Add(ref BlockTagEntityStruct entityComponent, EGID egid) { if (!(shouldAddRemove = !shouldAddRemove)) return; ExceptionUtil.InvokeEvent(Placed, this, - new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID}); + new BlockPlacedRemovedEventArgs {ID = egid}); } - public void Remove(ref DBEntityStruct entityComponent, EGID egid) + public void Remove(ref BlockTagEntityStruct entityComponent, EGID egid) { if (!(shouldAddRemove = !shouldAddRemove)) return; ExceptionUtil.InvokeEvent(Removed, this, - new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID}); + new BlockPlacedRemovedEventArgs {ID = egid}); } } diff --git a/GamecraftModdingAPI/Blocks/BlueprintEngine.cs b/GamecraftModdingAPI/Blocks/BlueprintEngine.cs index 4104ae6..e46ad50 100644 --- a/GamecraftModdingAPI/Blocks/BlueprintEngine.cs +++ b/GamecraftModdingAPI/Blocks/BlueprintEngine.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; using Gamecraft.Blocks.BlockGroups; using Gamecraft.GUI.Blueprints; @@ -21,7 +22,7 @@ using Allocator = Svelto.Common.Allocator; namespace GamecraftModdingAPI.Blocks { - public class BlueprintEngine : IApiEngine + public class BlueprintEngine : IFactoryEngine { private readonly MethodInfo getBlocksFromGroup = AccessTools.Method("RobocraftX.CR.MachineEditing.PlaceBlockUtility:GetBlocksSharingBlockgroup"); @@ -77,12 +78,29 @@ namespace GamecraftModdingAPI.Blocks connectionFactory, default).Complete(); } + public int CreateBlockGroup(float3 position, quaternion rotation) + { + int nextFilterId = BlockGroupUtility.NextFilterId; + Factory.BuildEntity((uint) nextFilterId, + BlockGroupExclusiveGroups.BlockGroupEntityGroup).Init(new BlockGroupTransformEntityComponent + { + blockGroupGridRotation = rotation, + blockGroupGridPosition = position + }); + return nextFilterId; + } + public void SelectBlueprint(uint resourceID) { - BlueprintUtil.SelectBlueprint(null, new BlueprintInventoryItemEntityStruct + if (resourceID == uint.MaxValue) + BlueprintUtil.UnselectBlueprint(entitiesDB); + else { - blueprintResourceId = resourceID, - }); + BlueprintUtil.SelectBlueprint(entitiesDB, new BlueprintInventoryItemEntityStruct + { + blueprintResourceId = resourceID, + }); + } } public uint CreateBlueprint() @@ -91,28 +109,27 @@ namespace GamecraftModdingAPI.Blocks return index; } - public void ReplaceBlueprint(uint playerID, uint blueprintID, Block[] selected, float3 pos, quaternion rot) + public void ReplaceBlueprint(uint playerID, uint blueprintID, ICollection selected, float3 pos, quaternion rot) { - var blockIDs = new EGID[selected.Length]; - for (var i = 0; i < selected.Length; i++) + var blockIDs = new EGID[selected.Count]; + using (var enumerator = selected.GetEnumerator()) { - var block = selected[i]; - blockIDs[i] = block.Id; + for (var i = 0; enumerator.MoveNext(); i++) + { + var block = enumerator.Current; + blockIDs[i] = block.Id; + } } var serializationData = clipboardManager.GetSerializationData(blueprintID); SelectionSerializationUtility.ClearClipboard(playerID, entitiesDB, entityFunctions, serializationData.blueprintData); - if (selected.Length == 0) + if (selected.Count == 0) return; //ref BlockGroupTransformEntityComponent groupTransform = ref EntityNativeDBExtensions.QueryEntity(entitiesDb, (uint) local1.currentBlockGroup, BlockGroupExclusiveGroups.BlockGroupEntityGroup); //ref ColliderAabb collider = ref EntityNativeDBExtensions.QueryEntity(entitiesDB, (uint) groupID, BlockGroupExclusiveGroups.BlockGroupEntityGroup); //float3 bottomOffset = PlaceBlockUtility.GetBottomOffset(collider); //var rootPosition = math.mul(groupTransform.blockGroupGridRotation, bottomOffset) + groupTransform.blockGroupGridPosition; //var rootRotation = groupTransform.blockGroupGridRotation; - if (math.all(pos == default)) - pos = selected[0].Position; - if (math.all(rot.value == default)) - rot = Quaternion.Euler(selected[0].Rotation); clipboardManager.SetGhostSerialized(blueprintID, false); SelectionSerializationUtility.CopySelectionToClipboard(playerID, entitiesDB, @@ -189,7 +206,7 @@ namespace GamecraftModdingAPI.Blocks } public string Name { get; } = "GamecraftModdingAPIBlueprintGameEngine"; - public bool isRemovable { get; } + public bool isRemovable { get; } = false; [HarmonyPatch] private static class RemoveEnginePatch @@ -225,5 +242,7 @@ namespace GamecraftModdingAPI.Blocks return AccessTools.GetDeclaredConstructors(AccessTools.TypeByName("RobocraftX.CR.MachineEditing.SelectBlockEngine"))[0]; } } + + public IEntityFactory Factory { get; set; } } } \ No newline at end of file diff --git a/GamecraftModdingAPI/Blocks/PlacementEngine.cs b/GamecraftModdingAPI/Blocks/PlacementEngine.cs index cf9a80f..5357e7f 100644 --- a/GamecraftModdingAPI/Blocks/PlacementEngine.cs +++ b/GamecraftModdingAPI/Blocks/PlacementEngine.cs @@ -53,15 +53,15 @@ namespace GamecraftModdingAPI.Blocks private EntityComponentInitializer BuildBlock(ushort block, byte color, float3 position, int uscale, float3 scale, float3 rot, uint playerId) { if (_blockEntityFactory == null) - throw new Exception("The factory is null."); + throw new BlockException("The factory is null."); if (uscale < 1) - throw new Exception("Scale needs to be at least 1"); + throw new BlockException("Scale needs to be at least 1"); if (scale.x < 4e-5) scale.x = uscale; if (scale.y < 4e-5) scale.y = uscale; if (scale.z < 4e-5) scale.z = uscale; uint dbid = block; - if (!PrefabsID.DBIDMAP.ContainsKey(dbid)) - throw new Exception("Block with ID " + dbid + " not found!"); + if (!PrefabsID.HasPrefabRegistered(dbid, 0)) + throw new BlockException("Block with ID " + dbid + " not found!"); //RobocraftX.CR.MachineEditing.PlaceBlockEngine ScalingEntityStruct scaling = new ScalingEntityStruct {scale = scale}; Quaternion rotQ = Quaternion.Euler(rot); diff --git a/GamecraftModdingAPI/Blueprint.cs b/GamecraftModdingAPI/Blueprint.cs index 26876f6..458c56c 100644 --- a/GamecraftModdingAPI/Blueprint.cs +++ b/GamecraftModdingAPI/Blueprint.cs @@ -1,5 +1,6 @@ using System; using Unity.Mathematics; +using UnityEngine; namespace GamecraftModdingAPI { @@ -35,16 +36,27 @@ namespace GamecraftModdingAPI /// /// Set the blocks that the blueprint contains. + /// Use the BlockGroup overload for automatically calculated position and rotation. /// /// The array of blocks to use /// The anchor position of the blueprint - /// The rotation of the blueprint - public void SetStoredBlocks(Block[] blocks, float3 position = default, float3 rotation = default) + /// The base rotation of the blueprint + public void StoreBlocks(Block[] blocks, float3 position, float3 rotation) { BlockGroup._engine.ReplaceBlueprint(Player.LocalPlayer.Id, Id, blocks, position, quaternion.Euler(rotation)); } + /// + /// Store the blocks from the given group in the blueprint with correct position and rotation for the blueprint. + /// + /// The block group to store + public void StoreBlocks(BlockGroup group) + { + BlockGroup._engine.ReplaceBlueprint(Player.LocalPlayer.Id, Id, group, group.Position, + Quaternion.Euler(group.Rotation)); + } + /// /// Places the blocks the blueprint contains at the specified position and rotation. /// diff --git a/GamecraftModdingAPI/Player.cs b/GamecraftModdingAPI/Player.cs index a4b7064..14892a3 100644 --- a/GamecraftModdingAPI/Player.cs +++ b/GamecraftModdingAPI/Player.cs @@ -1,5 +1,5 @@ using System; - +using Gamecraft.GUI.Blueprints; using Unity.Mathematics; using RobocraftX.Common; using RobocraftX.Common.Players; @@ -343,6 +343,17 @@ namespace GamecraftModdingAPI } } + /// + /// The player's selected blueprint in their hand. Set to null to clear. Dispose after usage. + /// + public Blueprint SelectedBlueprint + { + get => playerEngine.GetPlayerStruct(Id, out BlueprintInventoryItemEntityStruct biies) + ? new Blueprint(biies.blueprintResourceId) + : null; + set => BlockGroup._engine.SelectBlueprint(value?.Id ?? uint.MaxValue); + } + // object methods /// diff --git a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs index f0334aa..fb806ea 100644 --- a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs +++ b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs @@ -1,24 +1,29 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Reflection.Emit; using System.Text; using HarmonyLib; using IllusionInjector; // test +using GPUInstancer; using Svelto.ECS; using RobocraftX.Blocks; using RobocraftX.Common; using RobocraftX.SimulationModeState; using RobocraftX.FrontEnd; using Unity.Mathematics; +using UnityEngine; using GamecraftModdingAPI.Commands; using GamecraftModdingAPI.Events; using GamecraftModdingAPI.Utility; using GamecraftModdingAPI.Blocks; using GamecraftModdingAPI.Players; +using EventType = GamecraftModdingAPI.Events.EventType; namespace GamecraftModdingAPI.Tests { @@ -269,6 +274,20 @@ namespace GamecraftModdingAPI.Tests Logging.CommandLog("Health set to: " + val); }).Build(); + CommandBuilder.Builder("placeBlockGroup", "Places some blocks in a group") + .Action((float x, float y, float z) => + { + var pos = new float3(x, y, z); + var group = BlockGroup.Create(Block.PlaceNew(BlockIDs.AluminiumCube, pos, + color: BlockColors.Aqua)); + Block.PlaceNew(BlockIDs.AluminiumCube, pos += new float3(1, 0, 0), color: BlockColors.Blue) + .BlockGroup = group; + Block.PlaceNew(BlockIDs.AluminiumCube, pos += new float3(1, 0, 0), color: BlockColors.Green) + .BlockGroup = group; + Block.PlaceNew(BlockIDs.AluminiumCube, pos += new float3(1, 0, 0), color: BlockColors.Lime) + .BlockGroup = group; + }).Build(); + GameClient.SetDebugInfo("InstalledMods", InstalledMods); Block.Placed += (sender, args) => Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID); @@ -391,6 +410,88 @@ namespace GamecraftModdingAPI.Tests return ((Action) MinimumSpecsCheck.CheckRequirementsMet).Method; } } + + [HarmonyPatch] + public class BugHuntPatch + { + public static MethodInfo method = + SymbolExtensions.GetMethodInfo(str => Console.WriteLine(str)); + + public static IEnumerable Transpiler(IEnumerable instructions) + { + int i = 0; + foreach (var instruction in instructions) + { + i++; + yield return instruction; //Return the instruction first + //stloc, dup, callvirt + if (instruction.opcode.Name.ToLower().StartsWith("stloc") + || instruction.opcode == OpCodes.Dup + || instruction.opcode == OpCodes.Callvirt) + { + yield return new CodeInstruction(OpCodes.Ldstr, + "Just ran the " + i + ". instruction ending with " + instruction.opcode.Name); + yield return new CodeInstruction(OpCodes.Call, method); + } + } + } + + public static MethodInfo TargetMethod() + { + return AccessTools.Method("RobocraftX.CR.MachineEditing.BoxSelect.CopySelectionEngine:GenerateThumbnail"); + } + } + + [HarmonyPatch] + public class BugHuntPatch2 + { + public static void Prefix(int width, float fieldOfView, Vector3 cameraDirection, Vector3 lightDirection) + { + Console.WriteLine("TakeThumbnail invoked with parameters: " + width + ", " + fieldOfView + ", " + + cameraDirection + ", " + lightDirection); + + GPUInstancerManager manager = GPUInstancerAPI.GetActiveManagers().Find(m => m is GPUInstancerPrefabManager); + Bounds instancesBounds = manager.ComputeInstancesBounds(2); + Console.WriteLine("Bounds: " + instancesBounds); + Console.WriteLine("Size: " + instancesBounds.size); + Console.WriteLine("Size.x < 0: " + (instancesBounds.size.x < 0)); + } + + public static void Postfix(Texture2D __result) + { + Console.WriteLine("TakeThumbnail returned: " + (__result == null ? null : __result.name)); + } + + private delegate Texture2D TakeThumbnailDel(int width, float fieldOfView, Vector3 cameraDirection, + Vector3 lightDirection); + + public static MethodInfo TargetMethod() + { + return ((TakeThumbnailDel) ThumbnailUtility.TakeThumbnail).Method; + } + } + + [HarmonyPatch] + public class BugHuntPatch3 + { + public static void Prefix(int width, int filterLayerMask, GPUInstancerManager manager, + Vector3 cameraPosition, Quaternion cameraRotation, float cameraFov, Vector3 lightDirection, + int cullingLayer) + { + Console.WriteLine("Inner TakeThumbnail invoked with parameters: " + width + ", " + filterLayerMask + + ", " + (manager != null ? manager.name : null) + ", " + cameraPosition + ", " + + cameraRotation + ", " + cameraFov + ", " + lightDirection + ", " + cullingLayer); + } + + private delegate Texture2D TakeThumbnailDel(int width, int filterLayerMask, GPUInstancerManager manager, + Vector3 cameraPosition, Quaternion cameraRotation, float cameraFov, Vector3 lightDirection, + int cullingLayer); + + public static MethodInfo TargetMethod() + { + return ((TakeThumbnailDel) ThumbnailUtility.TakeThumbnail).Method; + } + } } #endif }