#if TOOLS using Godot; using System.Collections.Generic; namespace Rokojori.Tools { using Rokojori; // Like Node.Duplicate(), but [Export] Node/array/list members that point outside the // duplicated subtree (parent, siblings, cousins, ...) keep pointing at the original // external target instead of being nulled out. Members pointing inside the subtree are // remapped to the corresponding duplicated node, same as native duplicate already does. public static class NodeDuplicator { public static Node DuplicateDeep( Node original ) { if ( original == null ) { return null; } var parent = original.GetParent(); if ( parent == null ) { original.LogError( "Duplicate Deep needs a parent node, can't duplicate the scene root." ); return null; } var duplicate = original.Duplicate( (int) Node.DuplicateFlags.Default ); parent.AddChild( duplicate ); parent.MoveChild( duplicate, original.GetIndex() + 1 ); SetOwnerRecursively( duplicate, original.Owner ); RemapExternalReferences( original, duplicate ); duplicate.LogInfo( "Duplicated", original.Name, "as", duplicate.Name ); return duplicate; } static void SetOwnerRecursively( Node node, Node sceneOwner ) { if ( sceneOwner == null ) { return; } node.Owner = sceneOwner; foreach ( Node child in node.GetChildren() ) { SetOwnerRecursively( child, sceneOwner ); } } static void RemapExternalReferences( Node originalRoot, Node duplicateRoot ) { var originalNodes = new List(); var duplicateNodes = new List(); NodesWalker.Get().Iterate( originalRoot, n => originalNodes.Add( n ), false ); NodesWalker.Get().Iterate( duplicateRoot, n => duplicateNodes.Add( n ), false ); var originalToDuplicate = new Dictionary(); for ( var i = 0; i < originalNodes.Count && i < duplicateNodes.Count; i++ ) { originalToDuplicate[ originalNodes[ i ] ] = duplicateNodes[ i ]; } for ( var i = 0; i < originalNodes.Count; i++ ) { RemapNodeExports( originalNodes[ i ], duplicateNodes[ i ], originalToDuplicate ); } } static void RemapNodeExports( Node original, Node duplicate, Dictionary map ) { var properties = original.GetPropertyList(); foreach ( var propertyInfo in properties ) { var dict = (Godot.Collections.Dictionary) propertyInfo; if ( ! IsStored( dict ) ) { continue; } var name = dict[ "name" ].AsString(); if ( string.IsNullOrEmpty( name ) ) { continue; } var type = (Variant.Type) dict[ "type" ].AsInt32(); if ( type == Variant.Type.Object ) { RemapSingleProperty( original, duplicate, name, map ); } else if ( type == Variant.Type.Array ) { RemapArrayProperty( original, duplicate, name, map ); } } } static bool IsStored( Godot.Collections.Dictionary propertyInfo ) { if ( ! propertyInfo.ContainsKey( "usage" ) ) { return false; } var usage = propertyInfo[ "usage" ].AsInt64(); return ( usage & (long) PropertyUsageFlags.Storage ) != 0; } static void RemapSingleProperty( Node original, Node duplicate, string name, Dictionary map ) { var value = original.Get( name ); if ( value.VariantType != Variant.Type.Object ) { return; } if ( value.AsGodotObject() is not Node targetNode ) { return; } var remapped = Remap( targetNode, map ); if ( remapped != targetNode ) { duplicate.Set( name, remapped ); } } static void RemapArrayProperty( Node original, Node duplicate, string name, Dictionary map ) { var value = original.Get( name ); if ( value.VariantType != Variant.Type.Array ) { return; } var array = value.AsGodotArray(); var changed = false; for ( var i = 0; i < array.Count; i++ ) { var element = array[ i ]; if ( element.VariantType != Variant.Type.Object || element.AsGodotObject() is not Node elementNode ) { continue; } var remapped = Remap( elementNode, map ); if ( remapped != elementNode ) { array[ i ] = remapped; changed = true; } } if ( changed ) { duplicate.Set( name, array ); } } static Node Remap( Node originalTarget, Dictionary map ) { if ( map.TryGetValue( originalTarget, out var mapped ) ) { return mapped; } return originalTarget; } } } #endif