Replicated the weapon flying tick setup using resources
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 26s
Create tag and build when new code gets to main / Export (push) Successful in 5m42s

This commit is contained in:
2026-04-07 16:32:26 +02:00
parent cc7cb90041
commit 1d856fd937
145 changed files with 12943 additions and 109 deletions

View File

@@ -0,0 +1,279 @@
// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using System.Collections.Generic;
using Gamesmiths.Forge.Godot.Resources.Statescript;
using Godot;
namespace Gamesmiths.Forge.Godot.Editor.Statescript;
/// <summary>
/// Base class for custom node property editors. Implementations override the default input-property / output-variable
/// sections rendered by <see cref="StatescriptGraphNode"/> for specific node types. Analogous to Godot's
/// <c>EditorInspectorPlugin</c> pattern.
/// </summary>
/// <remarks>
/// <para>
/// If a <see cref="CustomNodeEditor"/> is registered for a node's <c>RuntimeTypeName</c>, its
/// <see cref="BuildPropertySections"/> method is called instead of the default property rendering. The base class
/// provides helper methods that mirror the default behavior so that custom editors can reuse them selectively.
/// </para>
/// <para>
/// Because this class extends <see cref="RefCounted"/>, signal handlers defined on subclasses can be connected
/// directly to Godot signals (e.g. <c>dropdown.ItemSelected += OnItemSelected</c>) without needing wrapper nodes
/// or workarounds for serialization.
/// </para>
/// </remarks>
[Tool]
internal abstract partial class CustomNodeEditor : RefCounted
{
private StatescriptGraphNode? _graphNode;
private StatescriptGraph? _graph;
private StatescriptNode? _nodeResource;
private Dictionary<PropertySlotKey, NodeEditorProperty>? _activeResolverEditors;
/// <summary>
/// Gets the runtime type name this editor handles (e.g.,
/// <c>"Gamesmiths.Forge.Statescript.Nodes.Action.SetVariableNode"</c>).
/// </summary>
public abstract string HandledRuntimeTypeName { get; }
/// <summary>
/// Builds the custom input-property and output-variable sections for the node.
/// </summary>
/// <param name="typeInfo">Discovered metadata about the node type.</param>
public abstract void BuildPropertySections(StatescriptNodeDiscovery.NodeTypeInfo typeInfo);
/// <summary>
/// Gets the input property section color.
/// </summary>
protected static Color InputPropertyColor { get; } = new(0x61afefff);
/// <summary>
/// Gets the output variable section color.
/// </summary>
protected static Color OutputVariableColor { get; } = new(0xe5c07bff);
/// <summary>
/// Gets the active resolver editors dictionary.
/// </summary>
protected Dictionary<PropertySlotKey, NodeEditorProperty> ActiveResolverEditors => _activeResolverEditors!;
/// <summary>
/// Gets the owning graph resource.
/// </summary>
protected StatescriptGraph Graph => _graph!;
/// <summary>
/// Gets the node resource.
/// </summary>
protected StatescriptNode NodeResource => _nodeResource!;
/// <summary>
/// Gets the undo/redo manager, if available.
/// </summary>
protected EditorUndoRedoManager? UndoRedo => _graphNode?.GetUndoRedo();
/// <summary>
/// Stores references needed by helper methods. Called once after the instance is created.
/// </summary>
/// <param name="graphNode">The graph node this editor is bound to.</param>
/// <param name="graph">The graph resource this node belongs to.</param>
/// <param name="nodeResource">The node resource being edited.</param>
/// <param name="activeResolverEditors">A dictionary of active resolver editors.</param>
internal void Bind(
StatescriptGraphNode graphNode,
StatescriptGraph graph,
StatescriptNode nodeResource,
Dictionary<PropertySlotKey, NodeEditorProperty> activeResolverEditors)
{
_graphNode = graphNode;
_graph = graph;
_nodeResource = nodeResource;
_activeResolverEditors = activeResolverEditors;
}
/// <summary>
/// Clears all references stored by <see cref="Bind"/>. Called before the owning graph node is freed or serialized
/// to prevent accessing disposed objects.
/// </summary>
internal virtual void Unbind()
{
_graphNode = null;
_graph = null;
_nodeResource = null;
_activeResolverEditors = null;
}
/// <summary>
/// Clears all children from a container control.
/// </summary>
/// <param name="container">The container control to clear.</param>
protected static void ClearContainer(Control container)
{
foreach (Node child in container.GetChildren())
{
container.RemoveChild(child);
child.Free();
}
}
/// <summary>
/// Adds a foldable section divider to the graph node.
/// </summary>
/// <param name="sectionTitle">Title displayed on the divider.</param>
/// <param name="color">Color of the divider.</param>
/// <param name="foldKey">Key used to persist the fold state.</param>
/// <param name="folded">Initial fold state.</param>
protected FoldableContainer AddPropertySectionDivider(
string sectionTitle,
Color color,
string foldKey,
bool folded)
{
return _graphNode!.AddPropertySectionDividerInternal(sectionTitle, color, foldKey, folded);
}
/// <summary>
/// Renders a standard input-property row (resolver dropdown + editor UI).
/// </summary>
/// <param name="propInfo">Metadata about the input property.</param>
/// <param name="index">Index of the input property.</param>
/// <param name="container">Container to add the input property row to.</param>
protected void AddInputPropertyRow(
StatescriptNodeDiscovery.InputPropertyInfo propInfo,
int index,
Control container)
{
_graphNode!.AddInputPropertyRowInternal(propInfo, index, container);
}
/// <summary>
/// Renders a standard output-variable row (variable dropdown).
/// </summary>
/// <param name="varInfo">Metadata about the output variable.</param>
/// <param name="index">Index of the output variable.</param>
/// <param name="container">Container to add the output variable row to.</param>
protected void AddOutputVariableRow(
StatescriptNodeDiscovery.OutputVariableInfo varInfo,
int index,
FoldableContainer container)
{
_graphNode!.AddOutputVariableRowInternal(varInfo, index, container);
}
/// <summary>
/// Gets the persisted fold state for a given key.
/// </summary>
/// <param name="key">The key used to persist the fold state.</param>
protected bool GetFoldState(string key)
{
return _graphNode!.GetFoldStateInternal(key);
}
/// <summary>
/// Finds an existing property binding by direction and index.
/// </summary>
/// <param name="direction">The direction of the property (input or output).</param>
/// <param name="propertyIndex">The index of the property.</param>
protected StatescriptNodeProperty? FindBinding(
StatescriptPropertyDirection direction,
int propertyIndex)
{
return _graphNode!.FindBindingInternal(direction, propertyIndex);
}
/// <summary>
/// Ensures a property binding exists for the given direction and index, creating one if needed.
/// </summary>
/// <param name="direction">The direction of the property (input or output).</param>
/// <param name="propertyIndex">The index of the property.</param>
protected StatescriptNodeProperty EnsureBinding(
StatescriptPropertyDirection direction,
int propertyIndex)
{
return _graphNode!.EnsureBindingInternal(direction, propertyIndex);
}
/// <summary>
/// Removes a property binding by direction and index.
/// </summary>
/// <param name="direction">The direction of the property (input or output).</param>
/// <param name="propertyIndex">The index of the property.</param>
protected void RemoveBinding(
StatescriptPropertyDirection direction,
int propertyIndex)
{
_graphNode!.RemoveBindingInternal(direction, propertyIndex);
}
/// <summary>
/// Shows a resolver editor inside the given container.
/// </summary>
/// <param name="factory">A factory function to create the resolver editor.</param>
/// <param name="existingBinding">The existing binding, if any.</param>
/// <param name="expectedType">The expected type for the resolver editor.</param>
/// <param name="container">The container to add the resolver editor to.</param>
/// <param name="direction">The direction of the property (input or output).</param>
/// <param name="propertyIndex">The index of the property.</param>
/// <param name="isArray">Whether the input expects an array of values.</param>
protected void ShowResolverEditorUI(
Func<NodeEditorProperty> factory,
StatescriptNodeProperty? existingBinding,
Type expectedType,
VBoxContainer container,
StatescriptPropertyDirection direction,
int propertyIndex,
bool isArray = false)
{
_graphNode!.ShowResolverEditorUIInternal(
factory,
existingBinding,
expectedType,
container,
direction,
propertyIndex,
isArray);
}
/// <summary>
/// Requests the owning graph node to recalculate its size.
/// </summary>
protected void ResetSize()
{
_graphNode!.ResetSize();
}
/// <summary>
/// Raises the <see cref="StatescriptGraphNode.PropertyBindingChanged"/> event.
/// </summary>
protected void RaisePropertyBindingChanged()
{
_graphNode!.RaisePropertyBindingChangedInternal();
}
/// <summary>
/// Records an undo/redo action for changing a resolver binding, then rebuilds the node.
/// </summary>
/// <param name="direction">The direction of the property.</param>
/// <param name="propertyIndex">The index of the property.</param>
/// <param name="oldResolver">The previous resolver resource.</param>
/// <param name="newResolver">The new resolver resource.</param>
/// <param name="actionName">The name for the undo/redo action.</param>
protected void RecordResolverBindingChange(
StatescriptPropertyDirection direction,
int propertyIndex,
StatescriptResolverResource? oldResolver,
StatescriptResolverResource? newResolver,
string actionName = "Change Node Property")
{
_graphNode!.RecordResolverBindingChangeInternal(
direction,
propertyIndex,
oldResolver,
newResolver,
actionName);
}
}
#endif