diff --git a/RokojoriPlugin.cs b/RokojoriPlugin.cs index 6277768..fd2c673 100644 --- a/RokojoriPlugin.cs +++ b/RokojoriPlugin.cs @@ -91,12 +91,12 @@ public partial class RokojoriPlugin: EditorPlugin // return _instance; // } - public override void _EnablePlugin() + public void EnablePlugin() { this.LogInfo(); AddAutoloadSingleton( RokojoriRootAutoLoad, RokojoriRootAutoLoadPath ); - + EnsureHiddenProjectPath( RokojoriProjectInternalPath ); EnsureHiddenProjectPath( RokojoriSettingsPath ); EnsureHiddenProjectPath( RokojoriCachePath ); @@ -147,7 +147,7 @@ public partial class RokojoriPlugin: EditorPlugin } - public override void _DisablePlugin() + public void DisablePlugin() { this.LogInfo(); RemoveAutoloadSingleton( RokojoriRootAutoLoad ); diff --git a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCamera.cs b/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCamera.cs index 646e26f..6a01446 100644 --- a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCamera.cs +++ b/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCamera.cs @@ -63,11 +63,16 @@ namespace Rokojori // return; // } - if ( target == null ) + if ( target == null || ! IsInstanceValid( target ) ) { return; } + if ( lockTarget != null && ! IsInstanceValid( lockTarget ) ) + { + lockTarget = null; + } + // var tl = TimeLineManager.Ensure( timeLine ); // var delta = tl.delta; diff --git a/Runtime/Interactions/LockOn/LockOnPointer.cs b/Runtime/Interactions/LockOn/LockOnPointer.cs index c423147..de4005b 100644 --- a/Runtime/Interactions/LockOn/LockOnPointer.cs +++ b/Runtime/Interactions/LockOn/LockOnPointer.cs @@ -65,6 +65,18 @@ namespace Rokojori return; } + if ( pointable != null && ! IsInstanceValid( pointable ) ) + { + pointable = null; + + if ( engageSensor.isActive && engageSensor is ActivationModeSensor ams ) + { + ams.Release(); + } + + onLockReleased?.Trigger(); + } + UpdateClosestCandidate(); var wasLocked = pointable != null; diff --git a/Tools/Editor/NodeDuplicator.cs b/Tools/Editor/NodeDuplicator.cs new file mode 100644 index 0000000..9933c8b --- /dev/null +++ b/Tools/Editor/NodeDuplicator.cs @@ -0,0 +1,194 @@ +#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 diff --git a/Tools/Editor/NodeDuplicator.cs.uid b/Tools/Editor/NodeDuplicator.cs.uid new file mode 100644 index 0000000..b89d435 --- /dev/null +++ b/Tools/Editor/NodeDuplicator.cs.uid @@ -0,0 +1 @@ +uid://bigysvcanctvn diff --git a/Tools/Editor/RJ_EditorBridge.cs b/Tools/Editor/RJ_EditorBridge.cs new file mode 100644 index 0000000..3f84b18 --- /dev/null +++ b/Tools/Editor/RJ_EditorBridge.cs @@ -0,0 +1,24 @@ +#if TOOLS + +using Godot; + +namespace Rokojori.Tools +{ + using Rokojori; + + [Tool] + public partial class RJ_EditorBridge : RefCounted + { + public void LogHello( Node node ) + { + node.LogInfo( "Hello" ); + } + + public void DuplicateDeep( Node node ) + { + NodeDuplicator.DuplicateDeep( node ); + } + } +} + +#endif diff --git a/Tools/Editor/RJ_EditorBridge.cs.uid b/Tools/Editor/RJ_EditorBridge.cs.uid new file mode 100644 index 0000000..efac7d4 --- /dev/null +++ b/Tools/Editor/RJ_EditorBridge.cs.uid @@ -0,0 +1 @@ +uid://b83jwjb1m4uae diff --git a/Tools/Editor/RJ_NodeContextMenu.gd b/Tools/Editor/RJ_NodeContextMenu.gd new file mode 100644 index 0000000..1c8066b --- /dev/null +++ b/Tools/Editor/RJ_NodeContextMenu.gd @@ -0,0 +1,21 @@ +@tool +extends EditorContextMenuPlugin + +const EditorBridgeScript = preload( "res://addons/rokojori_action_library/Tools/Editor/RJ_EditorBridge.cs" ) + +var bridge = EditorBridgeScript.new() + + +func _popup_menu( paths:PackedStringArray ) -> void: + add_context_menu_item( "Log Hello", _on_log_hello ) + add_context_menu_item( "Duplicate Deep", _on_duplicate_deep ) + + +func _on_log_hello( nodes:Array ) -> void: + for node in nodes: + bridge.LogHello( node ) + + +func _on_duplicate_deep( nodes:Array ) -> void: + for node in nodes: + bridge.DuplicateDeep( node ) diff --git a/Tools/Editor/RJ_NodeContextMenu.gd.uid b/Tools/Editor/RJ_NodeContextMenu.gd.uid new file mode 100644 index 0000000..4fab145 --- /dev/null +++ b/Tools/Editor/RJ_NodeContextMenu.gd.uid @@ -0,0 +1 @@ +uid://bgopgmdisn1df diff --git a/plugin.cfg b/plugin.cfg index 246b0a9..6104805 100644 --- a/plugin.cfg +++ b/plugin.cfg @@ -4,4 +4,4 @@ name="Rokojori Action Library" description="Library for actions, assets and effects" author="Rokojori" version="0.6" -script="RokojoriPlugin.cs" +script="plugin.gd" diff --git a/plugin.gd b/plugin.gd new file mode 100644 index 0000000..b207d19 --- /dev/null +++ b/plugin.gd @@ -0,0 +1,41 @@ +@tool +extends EditorPlugin + +const RokojoriPluginScript = preload( "res://addons/rokojori_action_library/RokojoriPlugin.cs" ) +const NodeContextMenuScript = preload( "res://addons/rokojori_action_library/Tools/Editor/RJ_NodeContextMenu.gd" ) + +var csPlugin +var nodeContextMenu + + +func _ensure_cs_plugin() -> void: + if csPlugin == null: + csPlugin = RokojoriPluginScript.new() + add_child( csPlugin ) + + +func _enter_tree() -> void: + _ensure_cs_plugin() + + nodeContextMenu = NodeContextMenuScript.new() + add_context_menu_plugin( EditorContextMenuPlugin.CONTEXT_SLOT_SCENE_TREE, nodeContextMenu ) + + +func _exit_tree() -> void: + if nodeContextMenu: + remove_context_menu_plugin( nodeContextMenu ) + nodeContextMenu = null + + if csPlugin: + csPlugin.queue_free() + csPlugin = null + + +func _enable_plugin() -> void: + _ensure_cs_plugin() + csPlugin.EnablePlugin() + + +func _disable_plugin() -> void: + if csPlugin: + csPlugin.DisablePlugin() diff --git a/plugin.gd.uid b/plugin.gd.uid new file mode 100644 index 0000000..e98c441 --- /dev/null +++ b/plugin.gd.uid @@ -0,0 +1 @@ +uid://c3fvpug0mdv5l