From b41886864455c5cff4263c07190962b5dbde5b3d Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Wed, 22 Jan 2025 21:27:10 +0100 Subject: [PATCH] Include DuplicateFix code from @art0007i https://github.com/art0007i/DuplicateFix Co-authored-by: art0007i --- .../CommunityBugFixCollection.csproj | 2 +- CommunityBugFixCollection/Contributors.cs | 2 + .../DuplicateAndMoveMultipleGrabbedItems.cs | 240 ++++++++++++++++++ 3 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 CommunityBugFixCollection/DuplicateAndMoveMultipleGrabbedItems.cs diff --git a/CommunityBugFixCollection/CommunityBugFixCollection.csproj b/CommunityBugFixCollection/CommunityBugFixCollection.csproj index b067fac..2ce7bc0 100644 --- a/CommunityBugFixCollection/CommunityBugFixCollection.csproj +++ b/CommunityBugFixCollection/CommunityBugFixCollection.csproj @@ -10,7 +10,7 @@ True CommunityBugFixCollection Component Selector Additions - Banane9; Nytra + Banane9; Nytra; art0007i 0.7.0-beta This MonkeyLoader mod for Resonite overhauls the Component Selector / Protoflux Node Selector to have a search, as well as favorites and recents categories. README.md diff --git a/CommunityBugFixCollection/Contributors.cs b/CommunityBugFixCollection/Contributors.cs index 949e3e1..f4fad1f 100644 --- a/CommunityBugFixCollection/Contributors.cs +++ b/CommunityBugFixCollection/Contributors.cs @@ -7,6 +7,8 @@ namespace CommunityBugFixCollection { internal static class Contributors { + public static string[] Art0007i { get; } = ["art0007i"]; + public static string[] Banane9 { get; } = ["Banane9"]; public static string[] Nytra { get; } = ["Nytra"]; diff --git a/CommunityBugFixCollection/DuplicateAndMoveMultipleGrabbedItems.cs b/CommunityBugFixCollection/DuplicateAndMoveMultipleGrabbedItems.cs new file mode 100644 index 0000000..b721e67 --- /dev/null +++ b/CommunityBugFixCollection/DuplicateAndMoveMultipleGrabbedItems.cs @@ -0,0 +1,240 @@ +using Elements.Core; +using FrooxEngine; +using FrooxEngine.Undo; +using HarmonyLib; +using MonkeyLoader.Configuration; +using MonkeyLoader.Resonite; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CommunityBugFixCollection +{ + public static class DuplicateExtensions + { + // Literally just a copy paste of Slot.Duplicate but with a tiny snippet added to the middle + // would probably do this using a transpiler but it would require some kind of additional function argument to whether it should create undo steps + // or I could check stack traces to see if it's called from within my code but that's a little jank + + public static Slot UndoableChildrenDuplicate(this Slot toDuplicate, Slot duplicateRoot = null, bool keepGlobalTransform = true, DuplicationSettings settings = null) + { + if (toDuplicate.IsRootSlot) + { + throw new Exception("Cannot duplicate root slot"); + } + if (duplicateRoot == null) + { + duplicateRoot = toDuplicate.Parent ?? toDuplicate.World.RootSlot; + } + else if (duplicateRoot.IsChildOf(toDuplicate)) + { + throw new Exception("Target for the duplicate hierarchy cannot be within the hierarchy of the source"); + } + //InternalReferences internalReferences = new InternalReferences(); + var internalReferences = typeof(Worker).GetNestedType("InternalReferences", AccessTools.all).GetConstructor(new Type[] { }).Invoke(null); + HashSet hashSet = Pool.BorrowHashSet(); + HashSet hashSet2 = Pool.BorrowHashSet(); + List postDuplication = Pool.BorrowList(); + toDuplicate.ForeachComponentInChildren(delegate (IDuplicationHandler h) + { + h.OnBeforeDuplicate(toDuplicate, out var onDuplicated); + if (onDuplicated != null) + { + postDuplication.Add(onDuplicated); + } + }, includeLocal: false, cacheItems: true); + toDuplicate.GenerateHierarchy(hashSet2); + DuplicateFix.Debug("traverse1"); + Traverse.Create(toDuplicate).Method("CollectInternalReferences", toDuplicate, internalReferences, hashSet, hashSet2).GetValue(); + DuplicateFix.Debug("traverse2"); + Slot slot = (Slot)typeof(Slot).GetMethod("InternalDuplicate", AccessTools.all).Invoke(toDuplicate, new object[] { duplicateRoot, internalReferences, hashSet, settings }); + if (keepGlobalTransform) + { + slot.CopyTransform(toDuplicate); + } + DuplicateFix.Debug("traverse3"); + Traverse.Create(internalReferences).Method("TransferReferences", false).GetValue(); + List list = Pool.BorrowList(); + slot.GetComponentsInChildren(list); + // arti stuff begin + foreach (var child in slot.Children) + { + child.CreateSpawnUndoPoint(); + } + // arti stuff end + var runDuplicateMethod = typeof(Component).GetMethod("RunDuplicate", AccessTools.all); + foreach (Component item in list) + { + runDuplicateMethod.Invoke(item, null); + } + Pool.Return(ref list); + Pool.Return(ref hashSet); + + DuplicateFix.Debug("traverse4"); + Traverse.Create(internalReferences).Method("Dispose").GetValue(); + foreach (Action item2 in postDuplication) + { + item2(); + } + Pool.Return(ref postDuplication); + return slot; + } + } + + [HarmonyPatch] + [HarmonyPatchCategory(nameof(DuplicateAndMoveMultipleGrabbedItems))] + internal sealed class DuplicateAndMoveMultipleGrabbedItems : ResoniteMonkey + { + public override IEnumerable Authors => Contributors.Art0007i; + public override bool CanBeDisabled => true; + + [HarmonyPatch(typeof(InteractionHandler), "DuplicateGrabbed", new Type[] { })] + private class DuplicateFixPatch + { + public static bool Prefix(InteractionHandler __instance) + { + if (!config.GetValue(KEY_ENABLED)) return true; + + Slot tempHolder = null; + Slot dupeHolder = null; + __instance.World.BeginUndoBatch("Undo.DuplicateGrabbed".AsLocaleKey()); + try + { + tempHolder = __instance.Grabber.Slot.AddSlot("Holder"); + foreach (IGrabbable grabbedObject in __instance.Grabber.GrabbedObjects) + { + if (__instance.Grabber.GrabbableGetComponentInParents(grabbedObject.Slot, null, excludeDisabled: true) != null) + { + grabbedObject.Slot.SetParent(tempHolder, false); + continue; + } + } + + // by the time the duplication is done the children will have already escaped using their Grabbable.OnDuplicate function + // so I just copied the entire duplication sequence and made it create undo steps at the correct time.. kinda jank but works + /*dupeHolder = __instance.Grabber.HolderSlot.Duplicate(); + + foreach (var child in dupeHolder.Children) + { + child.CreateSpawnUndoPoint(); + }*/ + dupeHolder = __instance.Grabber.HolderSlot.UndoableChildrenDuplicate(); + + dupeHolder.GetComponentsInChildren().ForEach(delegate (IGrabbable g) + { + if (g.IsGrabbed) + { + g.Release(g.Grabber); + } + }); + tempHolder.Destroy(__instance.Grabber.HolderSlot, false); + } + catch (Exception ex) + { + __instance.Debug.Error("Exception duplicating items!\n" + ex); + } + if (dupeHolder != null && !dupeHolder.IsRemoved) dupeHolder.Destroy(false); + if (tempHolder != null && !tempHolder.IsRemoved) tempHolder.Destroy(false); + + __instance.World.EndUndoBatch(); + + return false; + } + } + + [HarmonyPatch(typeof(Userspace), nameof(Userspace.Paste))] + private class UserspacePastePatch + { + public static bool Prefix(Userspace __instance, Job __result, SavedGraph data, Slot source, float3 userspacePos, floatQ userspaceRot, float3 userspaceScale, World targetWorld) + { + if (!config.GetValue(KEY_ENABLED)) return true; + + Job task = new Job(); + World world = targetWorld ?? Engine.Current.WorldManager.FocusedWorld; + world.RunSynchronously(delegate + { + Slot slot = null; + if (world.CanSpawnObjects()) + { + float3 globalPosition = WorldManager.TransferPoint(userspacePos, Userspace.Current.World, world); + floatQ globalRotation = WorldManager.TransferRotation(userspaceRot, Userspace.Current.World, world); + float3 globalScale = WorldManager.TransferScale(userspaceScale, Userspace.Current.World, world); + if (source?.World == world && !source.IsDestroyed) + { + source.ActiveSelf = true; + source.GlobalPosition = globalPosition; + source.GlobalRotation = globalRotation; + source.GlobalScale = globalScale; + task.SetResultAndFinish(source); + } + else + { + slot = world.AddSlot("Paste"); + slot.LoadObject(data.Root); + slot.GlobalPosition = globalPosition; + slot.GlobalRotation = globalRotation; + slot.GlobalScale = globalScale; + Traverse.Create(slot).Method("RunOnPaste").GetValue(); + if (slot.Name == "Holder") + { + slot.Destroy(slot.Parent, false); + } + } + } + if (source != null && !source.IsDestroyed) + { + source.World.RunSynchronously(source.Destroy); + } + task.SetResultAndFinish(slot); + }); + __result = task; + return false; + } + } + + [HarmonyPatch(typeof(Grabber), nameof(Grabber.OnFocusChanged))] + private class UserspaceTransferPatch + { + public static bool Prefix(Grabber __instance, World.WorldFocus focus) + { + if (!config.GetValue(KEY_ENABLED)) return true; + + if (!__instance.World.CanTransferObjectsOut()) + { + return false; + } + __instance.BeforeUserspaceTransfer?.Invoke(); + Traverse.Create(__instance).Method("CleanupGrabbed").GetValue(); + if (focus != 0 || !__instance.IsHoldingObjects) + { + return false; + } + foreach (IGrabbable grabbedObject in __instance.GrabbedObjects) + { + if (grabbedObject.Slot.GetComponentInChildren((IItemPermissions p) => !p.CanSave) != null) + { + return false; + } + } + float3 a = __instance.HolderSlot.LocalPosition; + float3 b = float3.Zero; + if (a != b) + { + float3 b2 = __instance.HolderSlot.LocalPosition; + __instance.HolderSlot.LocalPosition = float3.Zero; + foreach (Slot child in __instance.HolderSlot.Children) + { + a = child.LocalPosition; + child.LocalPosition = a + b2; + } + } + + if (Userspace.TryTransferToUserspaceGrabber(__instance.HolderSlot, __instance.LinkingKey)) + { + __instance.DestroyGrabbed(); + } + return false; + } + } + } +} \ No newline at end of file