195 lines
4.8 KiB
C#
195 lines
4.8 KiB
C#
|
|
#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<Node>();
|
||
|
|
var duplicateNodes = new List<Node>();
|
||
|
|
|
||
|
|
NodesWalker.Get().Iterate( originalRoot, n => originalNodes.Add( n ), false );
|
||
|
|
NodesWalker.Get().Iterate( duplicateRoot, n => duplicateNodes.Add( n ), false );
|
||
|
|
|
||
|
|
var originalToDuplicate = new Dictionary<Node, Node>();
|
||
|
|
|
||
|
|
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<Node, Node> 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<Node, Node> 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<Node, Node> 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<Node, Node> map )
|
||
|
|
{
|
||
|
|
if ( map.TryGetValue( originalTarget, out var mapped ) )
|
||
|
|
{
|
||
|
|
return mapped;
|
||
|
|
}
|
||
|
|
|
||
|
|
return originalTarget;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif
|