From c4be97e0de0db736d3193d68fe6a299519f39eaf Mon Sep 17 00:00:00 2001 From: Minimata Date: Sun, 8 Feb 2026 15:16:01 +0100 Subject: [PATCH] added forge addon --- Movement tests.csproj | 281 +++++----- addons/forge/Forge.props | 8 + addons/forge/ForgePluginLoader.cs | 109 ++++ addons/forge/ForgePluginLoader.cs.uid | 1 + addons/forge/LICENSE | 21 + addons/forge/README.md | 58 ++ addons/forge/core/EffectApplier.cs | 312 +++++++++++ addons/forge/core/EffectApplier.cs.uid | 1 + addons/forge/core/ForgeBootstrap.cs | 14 + addons/forge/core/ForgeBootstrap.cs.uid | 1 + addons/forge/core/ForgeCurve.cs | 21 + addons/forge/core/ForgeCurve.cs.uid | 1 + addons/forge/core/ForgeData.cs | 13 + addons/forge/core/ForgeData.cs.uid | 1 + addons/forge/core/ForgeManagers.cs | 30 + addons/forge/core/ForgeManagers.cs.uid | 1 + addons/forge/core/ForgeRandom.cs | 98 ++++ addons/forge/core/ForgeRandom.cs.uid | 1 + addons/forge/core/forge_data.tres | 7 + addons/forge/editor/AssetRepairTool.cs | 210 +++++++ addons/forge/editor/AssetRepairTool.cs.uid | 1 + addons/forge/editor/EditorUtils.cs | 62 +++ addons/forge/editor/EditorUtils.cs.uid | 1 + addons/forge/editor/ForgeEditor.tscn | 14 + .../attributes/AttributeEditorPlugin.cs | 34 ++ .../attributes/AttributeEditorPlugin.cs.uid | 1 + .../attributes/AttributeEditorProperty.cs | 101 ++++ .../attributes/AttributeEditorProperty.cs.uid | 1 + .../AttributeSetClassEditorProperty.cs | 74 +++ .../AttributeSetClassEditorProperty.cs.uid | 1 + .../attributes/AttributeSetInspectorPlugin.cs | 43 ++ .../AttributeSetInspectorPlugin.cs.uid | 1 + .../AttributeSetValuesEditorProperty.cs | 171 ++++++ .../AttributeSetValuesEditorProperty.cs.uid | 1 + .../editor/attributes/AttributeValues.cs | 29 + .../editor/attributes/AttributeValues.cs.uid | 1 + .../editor/cues/CueHandlerInspectorPlugin.cs | 53 ++ .../cues/CueHandlerInspectorPlugin.cs.uid | 1 + .../forge/editor/cues/CueKeyEditorProperty.cs | 106 ++++ .../editor/cues/CueKeyEditorProperty.cs.uid | 1 + addons/forge/editor/cues/CueKeysEditor.cs.uid | 1 + .../editor/tags/TagContainerEditorProperty.cs | 160 ++++++ .../tags/TagContainerEditorProperty.cs.uid | 1 + .../tags/TagContainerInspectorPlugin.cs | 35 ++ .../tags/TagContainerInspectorPlugin.cs.uid | 1 + addons/forge/editor/tags/TagEditorProperty.cs | 149 +++++ .../editor/tags/TagEditorProperty.cs.uid | 1 + .../forge/editor/tags/TagInspectorPlugin.cs | 35 ++ .../editor/tags/TagInspectorPlugin.cs.uid | 1 + addons/forge/editor/tags/TagsEditor.cs | 192 +++++++ addons/forge/editor/tags/TagsEditor.cs.uid | 1 + addons/forge/editor/tags/TagsEditor.tscn | 33 ++ addons/forge/icons/Component.svg | 37 ++ addons/forge/icons/Component.svg.import | 43 ++ addons/forge/icons/ability.svg | 34 ++ addons/forge/icons/ability.svg.import | 43 ++ addons/forge/icons/attributes.svg | 69 +++ addons/forge/icons/attributes.svg.import | 43 ++ addons/forge/icons/calculator.svg | 34 ++ addons/forge/icons/calculator.svg.import | 43 ++ addons/forge/icons/cue_data.svg | 39 ++ addons/forge/icons/cue_data.svg.import | 43 ++ addons/forge/icons/cue_handler.svg | 39 ++ addons/forge/icons/cue_handler.svg.import | 43 ++ addons/forge/icons/effect.svg | 44 ++ addons/forge/icons/effect.svg.import | 43 ++ addons/forge/icons/effect_area_2d.svg | 44 ++ addons/forge/icons/effect_area_2d.svg.import | 43 ++ addons/forge/icons/effect_area_3d.svg | 44 ++ addons/forge/icons/effect_area_3d.svg.import | 43 ++ addons/forge/icons/effect_data.svg | 44 ++ addons/forge/icons/effect_data.svg.import | 43 ++ addons/forge/icons/effect_ray_cast_2d.svg | 42 ++ .../forge/icons/effect_ray_cast_2d.svg.import | 43 ++ addons/forge/icons/effect_ray_cast_3d.svg | 42 ++ .../forge/icons/effect_ray_cast_3d.svg.import | 43 ++ addons/forge/icons/effect_shape_cast_2d.svg | 37 ++ .../icons/effect_shape_cast_2d.svg.import | 43 ++ addons/forge/icons/effect_shape_cast_3d.svg | 37 ++ .../icons/effect_shape_cast_3d.svg.import | 43 ++ addons/forge/icons/execution.svg | 42 ++ addons/forge/icons/execution.svg.import | 43 ++ addons/forge/icons/forge_entity.svg | 39 ++ addons/forge/icons/forge_entity.svg.import | 43 ++ addons/forge/icons/modifier.svg | 57 ++ addons/forge/icons/modifier.svg.import | 43 ++ addons/forge/icons/modifier_magnitude.svg | 43 ++ .../forge/icons/modifier_magnitude.svg.import | 43 ++ addons/forge/icons/query_expression.svg | 43 ++ .../forge/icons/query_expression.svg.import | 43 ++ addons/forge/icons/scalable_float.svg | 52 ++ addons/forge/icons/scalable_float.svg.import | 43 ++ addons/forge/icons/scalable_int.svg | 52 ++ addons/forge/icons/scalable_int.svg.import | 43 ++ addons/forge/icons/tag.svg | 45 ++ addons/forge/icons/tag.svg.import | 43 ++ addons/forge/icons/tags_container.svg | 53 ++ addons/forge/icons/tags_container.svg.import | 43 ++ addons/forge/nodes/EffectArea2D.cs | 81 +++ addons/forge/nodes/EffectArea2D.cs.uid | 1 + addons/forge/nodes/EffectArea3D.cs | 81 +++ addons/forge/nodes/EffectArea3D.cs.uid | 1 + addons/forge/nodes/EffectRayCast2D.cs | 84 +++ addons/forge/nodes/EffectRayCast2D.cs.uid | 1 + addons/forge/nodes/EffectRayCast3D.cs | 84 +++ addons/forge/nodes/EffectRayCast3D.cs.uid | 1 + addons/forge/nodes/EffectShapeCast2D.cs | 99 ++++ addons/forge/nodes/EffectShapeCast2D.cs.uid | 1 + addons/forge/nodes/EffectShapeCast3D.cs | 99 ++++ addons/forge/nodes/EffectShapeCast3D.cs.uid | 1 + addons/forge/nodes/EffectTriggerMode.cs | 21 + addons/forge/nodes/EffectTriggerMode.cs.uid | 1 + addons/forge/nodes/ForgeAttributeSet.cs | 86 +++ addons/forge/nodes/ForgeAttributeSet.cs.uid | 1 + addons/forge/nodes/ForgeCueHandler.cs | 121 +++++ addons/forge/nodes/ForgeCueHandler.cs.uid | 1 + addons/forge/nodes/ForgeEffect.cs | 14 + addons/forge/nodes/ForgeEffect.cs.uid | 1 + addons/forge/nodes/ForgeEntity.cs | 67 +++ addons/forge/nodes/ForgeEntity.cs.uid | 1 + addons/forge/plugin.cfg | 7 + addons/forge/resources/ForgeCue.cs | 60 ++ addons/forge/resources/ForgeCue.cs.uid | 1 + addons/forge/resources/ForgeEffectData.cs | 514 ++++++++++++++++++ addons/forge/resources/ForgeEffectData.cs.uid | 1 + addons/forge/resources/ForgeModifier.cs | 230 ++++++++ addons/forge/resources/ForgeModifier.cs.uid | 1 + .../forge/resources/ForgeQueryExpression.cs | 126 +++++ .../resources/ForgeQueryExpression.cs.uid | 1 + addons/forge/resources/ForgeTag.cs | 21 + addons/forge/resources/ForgeTag.cs.uid | 1 + addons/forge/resources/ForgeTagContainer.cs | 42 ++ .../forge/resources/ForgeTagContainer.cs.uid | 1 + .../abilities/ForgeAbilityBehavior.cs | 14 + .../abilities/ForgeAbilityBehavior.cs.uid | 1 + .../resources/abilities/ForgeAbilityData.cs | 180 ++++++ .../abilities/ForgeAbilityData.cs.uid | 1 + .../resources/abilities/TriggerSource.cs | 26 + .../resources/abilities/TriggerSource.cs.uid | 1 + .../calculators/ForgeCustomCalculator.cs | 14 + .../calculators/ForgeCustomCalculator.cs.uid | 1 + .../calculators/ForgeCustomExecution.cs | 14 + .../calculators/ForgeCustomExecution.cs.uid | 1 + .../components/ChanceToApplyEffect.cs | 21 + .../components/ChanceToApplyEffect.cs.uid | 1 + .../components/ForgeEffectComponent.cs | 14 + .../components/ForgeEffectComponent.cs.uid | 1 + .../components/ForgeGrantAbilityConfig.cs | 51 ++ .../components/ForgeGrantAbilityConfig.cs.uid | 1 + .../GameplayEffectComponents.cs.uid | 1 + .../resources/components/GrantAbility.cs | 28 + .../resources/components/GrantAbility.cs.uid | 1 + .../resources/components/ModifierTags.cs | 21 + .../resources/components/ModifierTags.cs.uid | 1 + .../components/TargetTagRequirements.cs | 84 +++ .../components/TargetTagRequirements.cs.uid | 1 + .../magnitudes/ForgeModifierMagnitude.cs | 212 ++++++++ .../magnitudes/ForgeModifierMagnitude.cs.uid | 1 + .../magnitudes/ForgeScalableFloat.cs | 34 ++ .../magnitudes/ForgeScalableFloat.cs.uid | 1 + .../resources/magnitudes/ForgeScalableInt.cs | 34 ++ .../magnitudes/ForgeScalableInt.cs.uid | 1 + project.godot | 3 +- 163 files changed, 6975 insertions(+), 141 deletions(-) create mode 100644 addons/forge/Forge.props create mode 100644 addons/forge/ForgePluginLoader.cs create mode 100644 addons/forge/ForgePluginLoader.cs.uid create mode 100644 addons/forge/LICENSE create mode 100644 addons/forge/README.md create mode 100644 addons/forge/core/EffectApplier.cs create mode 100644 addons/forge/core/EffectApplier.cs.uid create mode 100644 addons/forge/core/ForgeBootstrap.cs create mode 100644 addons/forge/core/ForgeBootstrap.cs.uid create mode 100644 addons/forge/core/ForgeCurve.cs create mode 100644 addons/forge/core/ForgeCurve.cs.uid create mode 100644 addons/forge/core/ForgeData.cs create mode 100644 addons/forge/core/ForgeData.cs.uid create mode 100644 addons/forge/core/ForgeManagers.cs create mode 100644 addons/forge/core/ForgeManagers.cs.uid create mode 100644 addons/forge/core/ForgeRandom.cs create mode 100644 addons/forge/core/ForgeRandom.cs.uid create mode 100644 addons/forge/core/forge_data.tres create mode 100644 addons/forge/editor/AssetRepairTool.cs create mode 100644 addons/forge/editor/AssetRepairTool.cs.uid create mode 100644 addons/forge/editor/EditorUtils.cs create mode 100644 addons/forge/editor/EditorUtils.cs.uid create mode 100644 addons/forge/editor/ForgeEditor.tscn create mode 100644 addons/forge/editor/attributes/AttributeEditorPlugin.cs create mode 100644 addons/forge/editor/attributes/AttributeEditorPlugin.cs.uid create mode 100644 addons/forge/editor/attributes/AttributeEditorProperty.cs create mode 100644 addons/forge/editor/attributes/AttributeEditorProperty.cs.uid create mode 100644 addons/forge/editor/attributes/AttributeSetClassEditorProperty.cs create mode 100644 addons/forge/editor/attributes/AttributeSetClassEditorProperty.cs.uid create mode 100644 addons/forge/editor/attributes/AttributeSetInspectorPlugin.cs create mode 100644 addons/forge/editor/attributes/AttributeSetInspectorPlugin.cs.uid create mode 100644 addons/forge/editor/attributes/AttributeSetValuesEditorProperty.cs create mode 100644 addons/forge/editor/attributes/AttributeSetValuesEditorProperty.cs.uid create mode 100644 addons/forge/editor/attributes/AttributeValues.cs create mode 100644 addons/forge/editor/attributes/AttributeValues.cs.uid create mode 100644 addons/forge/editor/cues/CueHandlerInspectorPlugin.cs create mode 100644 addons/forge/editor/cues/CueHandlerInspectorPlugin.cs.uid create mode 100644 addons/forge/editor/cues/CueKeyEditorProperty.cs create mode 100644 addons/forge/editor/cues/CueKeyEditorProperty.cs.uid create mode 100644 addons/forge/editor/cues/CueKeysEditor.cs.uid create mode 100644 addons/forge/editor/tags/TagContainerEditorProperty.cs create mode 100644 addons/forge/editor/tags/TagContainerEditorProperty.cs.uid create mode 100644 addons/forge/editor/tags/TagContainerInspectorPlugin.cs create mode 100644 addons/forge/editor/tags/TagContainerInspectorPlugin.cs.uid create mode 100644 addons/forge/editor/tags/TagEditorProperty.cs create mode 100644 addons/forge/editor/tags/TagEditorProperty.cs.uid create mode 100644 addons/forge/editor/tags/TagInspectorPlugin.cs create mode 100644 addons/forge/editor/tags/TagInspectorPlugin.cs.uid create mode 100644 addons/forge/editor/tags/TagsEditor.cs create mode 100644 addons/forge/editor/tags/TagsEditor.cs.uid create mode 100644 addons/forge/editor/tags/TagsEditor.tscn create mode 100644 addons/forge/icons/Component.svg create mode 100644 addons/forge/icons/Component.svg.import create mode 100644 addons/forge/icons/ability.svg create mode 100644 addons/forge/icons/ability.svg.import create mode 100644 addons/forge/icons/attributes.svg create mode 100644 addons/forge/icons/attributes.svg.import create mode 100644 addons/forge/icons/calculator.svg create mode 100644 addons/forge/icons/calculator.svg.import create mode 100644 addons/forge/icons/cue_data.svg create mode 100644 addons/forge/icons/cue_data.svg.import create mode 100644 addons/forge/icons/cue_handler.svg create mode 100644 addons/forge/icons/cue_handler.svg.import create mode 100644 addons/forge/icons/effect.svg create mode 100644 addons/forge/icons/effect.svg.import create mode 100644 addons/forge/icons/effect_area_2d.svg create mode 100644 addons/forge/icons/effect_area_2d.svg.import create mode 100644 addons/forge/icons/effect_area_3d.svg create mode 100644 addons/forge/icons/effect_area_3d.svg.import create mode 100644 addons/forge/icons/effect_data.svg create mode 100644 addons/forge/icons/effect_data.svg.import create mode 100644 addons/forge/icons/effect_ray_cast_2d.svg create mode 100644 addons/forge/icons/effect_ray_cast_2d.svg.import create mode 100644 addons/forge/icons/effect_ray_cast_3d.svg create mode 100644 addons/forge/icons/effect_ray_cast_3d.svg.import create mode 100644 addons/forge/icons/effect_shape_cast_2d.svg create mode 100644 addons/forge/icons/effect_shape_cast_2d.svg.import create mode 100644 addons/forge/icons/effect_shape_cast_3d.svg create mode 100644 addons/forge/icons/effect_shape_cast_3d.svg.import create mode 100644 addons/forge/icons/execution.svg create mode 100644 addons/forge/icons/execution.svg.import create mode 100644 addons/forge/icons/forge_entity.svg create mode 100644 addons/forge/icons/forge_entity.svg.import create mode 100644 addons/forge/icons/modifier.svg create mode 100644 addons/forge/icons/modifier.svg.import create mode 100644 addons/forge/icons/modifier_magnitude.svg create mode 100644 addons/forge/icons/modifier_magnitude.svg.import create mode 100644 addons/forge/icons/query_expression.svg create mode 100644 addons/forge/icons/query_expression.svg.import create mode 100644 addons/forge/icons/scalable_float.svg create mode 100644 addons/forge/icons/scalable_float.svg.import create mode 100644 addons/forge/icons/scalable_int.svg create mode 100644 addons/forge/icons/scalable_int.svg.import create mode 100644 addons/forge/icons/tag.svg create mode 100644 addons/forge/icons/tag.svg.import create mode 100644 addons/forge/icons/tags_container.svg create mode 100644 addons/forge/icons/tags_container.svg.import create mode 100644 addons/forge/nodes/EffectArea2D.cs create mode 100644 addons/forge/nodes/EffectArea2D.cs.uid create mode 100644 addons/forge/nodes/EffectArea3D.cs create mode 100644 addons/forge/nodes/EffectArea3D.cs.uid create mode 100644 addons/forge/nodes/EffectRayCast2D.cs create mode 100644 addons/forge/nodes/EffectRayCast2D.cs.uid create mode 100644 addons/forge/nodes/EffectRayCast3D.cs create mode 100644 addons/forge/nodes/EffectRayCast3D.cs.uid create mode 100644 addons/forge/nodes/EffectShapeCast2D.cs create mode 100644 addons/forge/nodes/EffectShapeCast2D.cs.uid create mode 100644 addons/forge/nodes/EffectShapeCast3D.cs create mode 100644 addons/forge/nodes/EffectShapeCast3D.cs.uid create mode 100644 addons/forge/nodes/EffectTriggerMode.cs create mode 100644 addons/forge/nodes/EffectTriggerMode.cs.uid create mode 100644 addons/forge/nodes/ForgeAttributeSet.cs create mode 100644 addons/forge/nodes/ForgeAttributeSet.cs.uid create mode 100644 addons/forge/nodes/ForgeCueHandler.cs create mode 100644 addons/forge/nodes/ForgeCueHandler.cs.uid create mode 100644 addons/forge/nodes/ForgeEffect.cs create mode 100644 addons/forge/nodes/ForgeEffect.cs.uid create mode 100644 addons/forge/nodes/ForgeEntity.cs create mode 100644 addons/forge/nodes/ForgeEntity.cs.uid create mode 100644 addons/forge/plugin.cfg create mode 100644 addons/forge/resources/ForgeCue.cs create mode 100644 addons/forge/resources/ForgeCue.cs.uid create mode 100644 addons/forge/resources/ForgeEffectData.cs create mode 100644 addons/forge/resources/ForgeEffectData.cs.uid create mode 100644 addons/forge/resources/ForgeModifier.cs create mode 100644 addons/forge/resources/ForgeModifier.cs.uid create mode 100644 addons/forge/resources/ForgeQueryExpression.cs create mode 100644 addons/forge/resources/ForgeQueryExpression.cs.uid create mode 100644 addons/forge/resources/ForgeTag.cs create mode 100644 addons/forge/resources/ForgeTag.cs.uid create mode 100644 addons/forge/resources/ForgeTagContainer.cs create mode 100644 addons/forge/resources/ForgeTagContainer.cs.uid create mode 100644 addons/forge/resources/abilities/ForgeAbilityBehavior.cs create mode 100644 addons/forge/resources/abilities/ForgeAbilityBehavior.cs.uid create mode 100644 addons/forge/resources/abilities/ForgeAbilityData.cs create mode 100644 addons/forge/resources/abilities/ForgeAbilityData.cs.uid create mode 100644 addons/forge/resources/abilities/TriggerSource.cs create mode 100644 addons/forge/resources/abilities/TriggerSource.cs.uid create mode 100644 addons/forge/resources/calculators/ForgeCustomCalculator.cs create mode 100644 addons/forge/resources/calculators/ForgeCustomCalculator.cs.uid create mode 100644 addons/forge/resources/calculators/ForgeCustomExecution.cs create mode 100644 addons/forge/resources/calculators/ForgeCustomExecution.cs.uid create mode 100644 addons/forge/resources/components/ChanceToApplyEffect.cs create mode 100644 addons/forge/resources/components/ChanceToApplyEffect.cs.uid create mode 100644 addons/forge/resources/components/ForgeEffectComponent.cs create mode 100644 addons/forge/resources/components/ForgeEffectComponent.cs.uid create mode 100644 addons/forge/resources/components/ForgeGrantAbilityConfig.cs create mode 100644 addons/forge/resources/components/ForgeGrantAbilityConfig.cs.uid create mode 100644 addons/forge/resources/components/GameplayEffectComponents.cs.uid create mode 100644 addons/forge/resources/components/GrantAbility.cs create mode 100644 addons/forge/resources/components/GrantAbility.cs.uid create mode 100644 addons/forge/resources/components/ModifierTags.cs create mode 100644 addons/forge/resources/components/ModifierTags.cs.uid create mode 100644 addons/forge/resources/components/TargetTagRequirements.cs create mode 100644 addons/forge/resources/components/TargetTagRequirements.cs.uid create mode 100644 addons/forge/resources/magnitudes/ForgeModifierMagnitude.cs create mode 100644 addons/forge/resources/magnitudes/ForgeModifierMagnitude.cs.uid create mode 100644 addons/forge/resources/magnitudes/ForgeScalableFloat.cs create mode 100644 addons/forge/resources/magnitudes/ForgeScalableFloat.cs.uid create mode 100644 addons/forge/resources/magnitudes/ForgeScalableInt.cs create mode 100644 addons/forge/resources/magnitudes/ForgeScalableInt.cs.uid diff --git a/Movement tests.csproj b/Movement tests.csproj index 618b1cab..6c0c2ec2 100644 --- a/Movement tests.csproj +++ b/Movement tests.csproj @@ -1,142 +1,143 @@ - - net9.0 - true - Movementtests - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - none - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + net9.0 + true + Movementtests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/addons/forge/Forge.props b/addons/forge/Forge.props new file mode 100644 index 00000000..6ed69133 --- /dev/null +++ b/addons/forge/Forge.props @@ -0,0 +1,8 @@ + + + enable + + + + + diff --git a/addons/forge/ForgePluginLoader.cs b/addons/forge/ForgePluginLoader.cs new file mode 100644 index 00000000..a25e602a --- /dev/null +++ b/addons/forge/ForgePluginLoader.cs @@ -0,0 +1,109 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using System.Diagnostics; +using Gamesmiths.Forge.Godot.Editor; +using Gamesmiths.Forge.Godot.Editor.Attributes; +using Gamesmiths.Forge.Godot.Editor.Cues; +using Gamesmiths.Forge.Godot.Editor.Tags; +using Godot; + +namespace Gamesmiths.Forge.Godot; + +[Tool] +public partial class ForgePluginLoader : EditorPlugin +{ + private const string AutoloadPath = "uid://ba8fquhtwu5mu"; + private const string PluginScenePath = "uid://pjscvogl6jak"; + + private EditorDock? _editorDock; + private PanelContainer? _dockedScene; + private TagContainerInspectorPlugin? _tagContainerInspectorPlugin; + private TagInspectorPlugin? _tagInspectorPlugin; + private AttributeSetInspectorPlugin? _attributeSetInspectorPlugin; + private CueHandlerInspectorPlugin? _cueHandlerInspectorPlugin; + private AttributeEditorPlugin? _attributeEditorPlugin; + + public override void _EnterTree() + { + PackedScene pluginScene = ResourceLoader.Load(PluginScenePath); + + _editorDock = new EditorDock + { + Title = "Forge", + DockIcon = GD.Load("uid://cu6ncpuumjo20"), + DefaultSlot = EditorDock.DockSlot.RightUl, + }; + + _dockedScene = (PanelContainer)pluginScene.Instantiate(); + _dockedScene.GetNode("%Tags").IsPluginInstance = true; + + _editorDock.AddChild(_dockedScene); + AddDock(_editorDock); + + _tagContainerInspectorPlugin = new TagContainerInspectorPlugin(); + AddInspectorPlugin(_tagContainerInspectorPlugin); + _tagInspectorPlugin = new TagInspectorPlugin(); + AddInspectorPlugin(_tagInspectorPlugin); + _attributeSetInspectorPlugin = new AttributeSetInspectorPlugin(); + AddInspectorPlugin(_attributeSetInspectorPlugin); + _cueHandlerInspectorPlugin = new CueHandlerInspectorPlugin(); + AddInspectorPlugin(_cueHandlerInspectorPlugin); + _attributeEditorPlugin = new AttributeEditorPlugin(); + AddInspectorPlugin(_attributeEditorPlugin); + + AddToolMenuItem("Repair assets tags", new Callable(this, MethodName.CallAssetRepairTool)); + } + + public override void _ExitTree() + { + Debug.Assert(_editorDock is not null, $"{nameof(_editorDock)} should have been initialized on _Ready()."); + Debug.Assert(_dockedScene is not null, $"{nameof(_dockedScene)} should have been initialized on _Ready()."); + + RemoveDock(_editorDock); + _editorDock.QueueFree(); + _dockedScene.Free(); + + RemoveInspectorPlugin(_tagContainerInspectorPlugin); + RemoveInspectorPlugin(_tagInspectorPlugin); + RemoveInspectorPlugin(_attributeSetInspectorPlugin); + RemoveInspectorPlugin(_cueHandlerInspectorPlugin); + RemoveInspectorPlugin(_attributeEditorPlugin); + + RemoveToolMenuItem("Repair assets tags"); + } + + public override void _EnablePlugin() + { + base._EnablePlugin(); + + var config = ProjectSettings.LoadResourcePack(AutoloadPath); + + if (config) + { + GD.PrintErr("Failed to load script at res://addons/forge/core/ForgeBootstrap.cs"); + return; + } + + if (!ProjectSettings.HasSetting("autoload/Forge Bootstrap")) + { + ProjectSettings.SetSetting("autoload/Forge Bootstrap", AutoloadPath); + ProjectSettings.Save(); + } + } + + public override void _DisablePlugin() + { + if (ProjectSettings.HasSetting("autoload/Forge Bootstrap")) + { + ProjectSettings.Clear("autoload/Forge Bootstrap"); + ProjectSettings.Save(); + } + } + + private static void CallAssetRepairTool() + { + AssetRepairTool.RepairAllAssetsTags(); + } +} +#endif diff --git a/addons/forge/ForgePluginLoader.cs.uid b/addons/forge/ForgePluginLoader.cs.uid new file mode 100644 index 00000000..eed76d43 --- /dev/null +++ b/addons/forge/ForgePluginLoader.cs.uid @@ -0,0 +1 @@ +uid://686m2ah4as6w diff --git a/addons/forge/LICENSE b/addons/forge/LICENSE new file mode 100644 index 00000000..7e1635df --- /dev/null +++ b/addons/forge/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Gamesmiths Guild + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/addons/forge/README.md b/addons/forge/README.md new file mode 100644 index 00000000..2dacbfc3 --- /dev/null +++ b/addons/forge/README.md @@ -0,0 +1,58 @@ +# Forge for Godot + +Forge for Godot is an Unreal GAS-like gameplay framework for the Godot Engine. + +It integrates the [Forge Gameplay System](https://github.com/gamesmiths-guild/forge) into Godot, providing a robust, data-driven foundation for gameplay features such as attributes, effects, gameplay tags, abilities, events, and cues, fully aligned with Godot’s node, resource, and editor workflows. + +This plugin enables you to: + +- Use **ForgeEntity** nodes or implement `IForgeEntity` to integrate core Forge systems like attributes, effects, abilities, events and tags. +- Define attributes, effects, abilities, cues, and tags directly in the Godot editor. +- Apply and manage gameplay effects with area or raycasting nodes. +- Create hierarchical gameplay tags using the built-in Tags Editor. +- Trigger visual and audio feedback with the Cues system. +- Create player skills, attacks, or behaviors, with support for custom logic, costs, cooldowns, and triggers. + +## Features + +- **Effects System**: Comprehensive effect application and management, including stacking, periodic, instant, and infinite effects. +- **Attributes System**: Attribute management, supporting sets, modifiers, and configuration. +- **Tags System**: Full hierarchical tag system with Godot editor integration. +- **Abilities System**: Feature-complete ability system, supporting grant/removal, custom behaviors, triggers, cooldowns, and costs. +- **Events System**: Gameplay event bus supporting event-driven logic, subscriptions, and triggers. +- **Cues System**: Visual/audio feedback layer; decouples presentation from game logic. +- **Editor Extensions**: Custom inspector elements and tag editor with Godot integration. +- **Custom Nodes**: Includes nodes like `ForgeEntity`, `ForgeAttributeSet`, `EffectArea2D`, and more. + +## Installation + +### Requirements + +- Godot 4.6 or later with .NET support. +- .NET SDK 8.0 or later. + +### Steps + +1. Install the plugin via the Godot Asset Library or manually by copying the `addons` folder. + - [Godot Asset Library](https://godotengine.org/asset-library/asset/4239) + - [Manual installation guide](https://docs.godotengine.org/en/stable/tutorials/plugins/editor/installing_plugins.html) +2. Add the following line in your `.csproj` file (before the closing `` tag). The `.csproj` file can be created through Godot by navigating to `Project > Tools > C# > Create C# solution`: + ```xml + + ``` +3. Back in the Godot editor, build your project by clicking `Build` in the top-right corner of the script editor. +4. Enable **Forge Gameplay System** in `Project > Project Settings > Plugins`. + +## Getting Started + +- See the [Quick Start Guide](https://github.com/gamesmiths-guild/forge-godot/blob/main/docs/quick-start.md) for a basic setup. +- Explore [sample scenes](https://github.com/gamesmiths-guild/forge-godot/tree/main/examples) by cloning the full repo. + +## Documentation + +Full documentation, examples, and advanced usage are available in the [Forge for Godot GitHub repository](https://github.com/gamesmiths-guild/forge-godot). +For technical details about core systems, see the [Forge Gameplay System documentation](https://github.com/gamesmiths-guild/forge/blob/main/docs/README.md). + +## License + +This plugin is licensed under the same terms as the core [Forge Gameplay System](https://github.com/gamesmiths-guild/forge). diff --git a/addons/forge/core/EffectApplier.cs b/addons/forge/core/EffectApplier.cs new file mode 100644 index 00000000..c24bb7db --- /dev/null +++ b/addons/forge/core/EffectApplier.cs @@ -0,0 +1,312 @@ +// Copyright © Gamesmiths Guild. + +using System.Collections.Generic; +using Gamesmiths.Forge.Core; +using Gamesmiths.Forge.Effects; +using Gamesmiths.Forge.Godot.Nodes; +using Godot; + +namespace Gamesmiths.Forge.Godot.Core; + +internal sealed class EffectApplier +{ + private record struct EffectKey(EffectData EffectData, EffectOwnership EffectOwnership, int Level); + + private readonly List _effects = []; + + private readonly Dictionary> _effectInstances = []; + private readonly Dictionary _effectsCache = []; + + public EffectApplier(Node node) + { + foreach (Node child in node.GetChildren()) + { + if (child is ForgeEffect effectNode && effectNode.EffectData is not null) + { + _effects.Add(effectNode.EffectData.GetEffectData()); + } + } + } + + public void ApplyEffects( + Node node, + IForgeEntity? effectOwner, + IForgeEntity? effectSource, + int level = 1) + { + if (node is IForgeEntity forgeEntity) + { + ApplyEffects(forgeEntity, effectOwner, effectSource, level); + return; + } + + foreach (Node? child in node.GetChildren()) + { + if (child is IForgeEntity forgeEntityChild) + { + ApplyEffects(forgeEntityChild, effectOwner, effectSource, level); + return; + } + } + } + + public void ApplyEffects( + Node node, + TData contextData, + IForgeEntity? effectOwner, + IForgeEntity? effectSource, + int level = 1) + { + if (node is IForgeEntity forgeEntity) + { + ApplyEffects(forgeEntity, contextData, effectOwner, effectSource, level); + return; + } + + foreach (Node? child in node.GetChildren()) + { + if (child is IForgeEntity forgeEntityChild) + { + ApplyEffects(forgeEntityChild, contextData, effectOwner, effectSource, level); + return; + } + } + } + + public void AddEffects( + Node node, + IForgeEntity? effectOwner, + IForgeEntity? effectSource, + int level) + { + if (node is IForgeEntity forgeEntity) + { + AddEffects(forgeEntity, effectOwner, effectSource, level); + return; + } + + foreach (Node? child in node.GetChildren()) + { + if (child is IForgeEntity forgeEntityChild) + { + AddEffects(forgeEntityChild, effectOwner, effectSource, level); + return; + } + } + } + + public void AddEffects( + Node node, + TData contextData, + IForgeEntity? effectOwner, + IForgeEntity? effectSource, + int level) + { + if (node is IForgeEntity forgeEntity) + { + AddEffects(forgeEntity, contextData, effectOwner, effectSource, level); + return; + } + + foreach (Node? child in node.GetChildren()) + { + if (child is IForgeEntity forgeEntityChild) + { + AddEffects(forgeEntityChild, contextData, effectOwner, effectSource, level); + return; + } + } + } + + public void RemoveEffects(Node node) + { + if (node is IForgeEntity forgeEntity) + { + RemoveEffects(forgeEntity); + return; + } + + foreach (Node? child in node.GetChildren()) + { + if (child is IForgeEntity forgeEntityChild) + { + RemoveEffects(forgeEntityChild); + return; + } + } + } + + private void ApplyEffects( + IForgeEntity forgeEntity, + IForgeEntity? effectOwner, + IForgeEntity? effectSource, + int level) + { + var effectOwnership = new EffectOwnership(effectOwner, effectSource); + + foreach (EffectData effectData in _effects) + { + var key = new EffectKey(effectData, effectOwnership, level); + + if (_effectsCache.TryGetValue(key, out Effect? cachedEffect)) + { + forgeEntity.EffectsManager.ApplyEffect(cachedEffect); + continue; + } + + var effect = new Effect( + effectData, + new EffectOwnership(effectOwner, effectSource), + level); + + _effectsCache[key] = effect; + + forgeEntity.EffectsManager.ApplyEffect(effect); + } + } + + private void ApplyEffects( + IForgeEntity forgeEntity, + TData contextData, + IForgeEntity? effectOwner, + IForgeEntity? effectSource, + int level) + { + var effectOwnership = new EffectOwnership(effectOwner, effectSource); + + foreach (EffectData effectData in _effects) + { + var key = new EffectKey(effectData, effectOwnership, level); + + if (_effectsCache.TryGetValue(key, out Effect? cachedEffect)) + { + forgeEntity.EffectsManager.ApplyEffect(cachedEffect); + continue; + } + + var effect = new Effect( + effectData, + new EffectOwnership(effectOwner, effectSource), + level); + + _effectsCache[key] = effect; + + forgeEntity.EffectsManager.ApplyEffect(effect, contextData); + } + } + + private void AddEffects( + IForgeEntity forgeEntity, + IForgeEntity? + effectOwner, + IForgeEntity? effectSource, + int level) + { + var instanceEffects = new List(); + if (!_effectInstances.TryAdd(forgeEntity, instanceEffects)) + { + instanceEffects = _effectInstances[forgeEntity]; + } + + var effectOwnership = new EffectOwnership(effectOwner, effectSource); + + foreach (EffectData effectData in _effects) + { + var key = new EffectKey(effectData, effectOwnership, level); + + ActiveEffectHandle? handle; + if (_effectsCache.TryGetValue(key, out Effect? cachedEffect)) + { + handle = forgeEntity.EffectsManager.ApplyEffect(cachedEffect); + + if (handle is null) + { + continue; + } + + instanceEffects.Add(handle); + + continue; + } + + var effect = new Effect( + effectData, + new EffectOwnership(effectOwner, effectSource), + level); + + handle = forgeEntity.EffectsManager.ApplyEffect(effect); + + if (handle is null) + { + continue; + } + + instanceEffects.Add(handle); + } + } + + private void AddEffects( + IForgeEntity forgeEntity, + TData contextData, + IForgeEntity? effectOwner, + IForgeEntity? effectSource, + int level) + { + var instanceEffects = new List(); + if (!_effectInstances.TryAdd(forgeEntity, instanceEffects)) + { + instanceEffects = _effectInstances[forgeEntity]; + } + + var effectOwnership = new EffectOwnership(effectOwner, effectSource); + + foreach (EffectData effectData in _effects) + { + var key = new EffectKey(effectData, effectOwnership, level); + + ActiveEffectHandle? handle; + if (_effectsCache.TryGetValue(key, out Effect? cachedEffect)) + { + handle = forgeEntity.EffectsManager.ApplyEffect(cachedEffect); + + if (handle is null) + { + continue; + } + + instanceEffects.Add(handle); + + continue; + } + + var effect = new Effect( + effectData, + new EffectOwnership(effectOwner, effectSource), + level); + + handle = forgeEntity.EffectsManager.ApplyEffect(effect, contextData); + + if (handle is null) + { + continue; + } + + instanceEffects.Add(handle); + } + } + + private void RemoveEffects(IForgeEntity forgeEntity) + { + if (!_effectInstances.TryGetValue(forgeEntity, out List? value)) + { + return; + } + + foreach (ActiveEffectHandle handle in value) + { + forgeEntity.EffectsManager.RemoveEffect(handle); + } + + _effectInstances[forgeEntity] = []; + } +} diff --git a/addons/forge/core/EffectApplier.cs.uid b/addons/forge/core/EffectApplier.cs.uid new file mode 100644 index 00000000..53a44f26 --- /dev/null +++ b/addons/forge/core/EffectApplier.cs.uid @@ -0,0 +1 @@ +uid://bs52uo5esaiu2 diff --git a/addons/forge/core/ForgeBootstrap.cs b/addons/forge/core/ForgeBootstrap.cs new file mode 100644 index 00000000..eb2a52c7 --- /dev/null +++ b/addons/forge/core/ForgeBootstrap.cs @@ -0,0 +1,14 @@ +// Copyright © Gamesmiths Guild. + +using Godot; + +namespace Gamesmiths.Forge.Godot.Core; + +public partial class ForgeBootstrap : Node +{ + public override void _Ready() + { + ForgeData pluginData = ResourceLoader.Load("uid://8j4xg16o3qnl"); + _ = new ForgeManagers(pluginData); + } +} diff --git a/addons/forge/core/ForgeBootstrap.cs.uid b/addons/forge/core/ForgeBootstrap.cs.uid new file mode 100644 index 00000000..7b9668ce --- /dev/null +++ b/addons/forge/core/ForgeBootstrap.cs.uid @@ -0,0 +1 @@ +uid://ba8fquhtwu5mu diff --git a/addons/forge/core/ForgeCurve.cs b/addons/forge/core/ForgeCurve.cs new file mode 100644 index 00000000..cf18d287 --- /dev/null +++ b/addons/forge/core/ForgeCurve.cs @@ -0,0 +1,21 @@ +// Copyright © Gamesmiths Guild. + +using Gamesmiths.Forge.Core; +using Godot; + +namespace Gamesmiths.Forge.Godot.Core; + +public readonly struct ForgeCurve(Curve? curve) : ICurve +{ + private readonly Curve? _curve = curve; + + public float Evaluate(float value) + { + if (_curve is null) + { + return 1; + } + + return _curve.Sample(value); + } +} diff --git a/addons/forge/core/ForgeCurve.cs.uid b/addons/forge/core/ForgeCurve.cs.uid new file mode 100644 index 00000000..85910f89 --- /dev/null +++ b/addons/forge/core/ForgeCurve.cs.uid @@ -0,0 +1 @@ +uid://cei2cgvy84iy6 diff --git a/addons/forge/core/ForgeData.cs b/addons/forge/core/ForgeData.cs new file mode 100644 index 00000000..7b8261db --- /dev/null +++ b/addons/forge/core/ForgeData.cs @@ -0,0 +1,13 @@ +// Copyright © Gamesmiths Guild. + +using Godot; +using Godot.Collections; + +namespace Gamesmiths.Forge.Godot.Core; + +[Tool] +public partial class ForgeData : Resource +{ + [Export] + public Array RegisteredTags { get; set; } = []; +} diff --git a/addons/forge/core/ForgeData.cs.uid b/addons/forge/core/ForgeData.cs.uid new file mode 100644 index 00000000..1c5e412c --- /dev/null +++ b/addons/forge/core/ForgeData.cs.uid @@ -0,0 +1 @@ +uid://bq4vlbfx00hea diff --git a/addons/forge/core/ForgeManagers.cs b/addons/forge/core/ForgeManagers.cs new file mode 100644 index 00000000..c13cf167 --- /dev/null +++ b/addons/forge/core/ForgeManagers.cs @@ -0,0 +1,30 @@ +// Copyright © Gamesmiths Guild. + +using Gamesmiths.Forge.Core; +using Gamesmiths.Forge.Cues; +using Gamesmiths.Forge.Tags; + +namespace Gamesmiths.Forge.Godot.Core; + +public class ForgeManagers +{ + public static ForgeManagers Instance { get; private set; } = null!; + + public TagsManager TagsManager { get; private set; } + + public CuesManager CuesManager { get; private set; } + + public ForgeManagers(ForgeData pluginData) + { + Instance = this; + +#if DEBUG + Validation.Enabled = true; +#else + Validation.Enabled = false; +#endif + + TagsManager = new TagsManager([.. pluginData.RegisteredTags]); + CuesManager = new CuesManager(); + } +} diff --git a/addons/forge/core/ForgeManagers.cs.uid b/addons/forge/core/ForgeManagers.cs.uid new file mode 100644 index 00000000..305cabac --- /dev/null +++ b/addons/forge/core/ForgeManagers.cs.uid @@ -0,0 +1 @@ +uid://djeiinm1gclh4 diff --git a/addons/forge/core/ForgeRandom.cs b/addons/forge/core/ForgeRandom.cs new file mode 100644 index 00000000..08ee3d6d --- /dev/null +++ b/addons/forge/core/ForgeRandom.cs @@ -0,0 +1,98 @@ +// Copyright © Gamesmiths Guild. + +using System; +using Gamesmiths.Forge.Core; +using Godot; + +namespace Gamesmiths.Forge.Godot.Core; + +public class ForgeRandom : IRandom, IDisposable +{ + private readonly RandomNumberGenerator _randomNumberGenerator; + + public ForgeRandom() + { + _randomNumberGenerator = new RandomNumberGenerator(); + _randomNumberGenerator.Randomize(); + } + + public void NextBytes(byte[] buffer) + { + for (var i = 0; i < buffer.Length; i++) + { + buffer[i] = (byte)_randomNumberGenerator.RandiRange(0, 255); + } + } + + public void NextBytes(Span buffer) + { + for (var i = 0; i < buffer.Length; i++) + { + buffer[i] = (byte)_randomNumberGenerator.RandiRange(0, 255); + } + } + + public double NextDouble() + { + return _randomNumberGenerator.Randf(); + } + + public int NextInt() + { + return (int)_randomNumberGenerator.Randi(); + } + + public int NextInt(int maxValue) + { + return _randomNumberGenerator.RandiRange(0, maxValue - 1); + } + + public int NextInt(int minValue, int maxValue) + { + return _randomNumberGenerator.RandiRange(minValue, maxValue - 1); + } + + public long NextInt64() + { + unchecked + { + var high = _randomNumberGenerator.Randi(); + var low = _randomNumberGenerator.Randi(); + return ((long)high << 32) | low; + } + } + + public long NextInt64(long maxValue) + { + return NextInt64(0, maxValue); + } + + public long NextInt64(long minValue, long maxValue) + { + if (minValue >= maxValue) + { + throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be less than maxValue."); + } + + var range = (ulong)(maxValue - minValue); + var rand = (ulong)NextInt64(); + + return (long)(rand % range) + minValue; + } + + public float NextSingle() + { + return _randomNumberGenerator.Randf(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + _randomNumberGenerator.Dispose(); + } +} diff --git a/addons/forge/core/ForgeRandom.cs.uid b/addons/forge/core/ForgeRandom.cs.uid new file mode 100644 index 00000000..59d70c12 --- /dev/null +++ b/addons/forge/core/ForgeRandom.cs.uid @@ -0,0 +1 @@ +uid://dap6x2ddf6baj diff --git a/addons/forge/core/forge_data.tres b/addons/forge/core/forge_data.tres new file mode 100644 index 00000000..7671d561 --- /dev/null +++ b/addons/forge/core/forge_data.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" load_steps=2 format=3 uid="uid://8j4xg16o3qnl"] + +[ext_resource type="Script" uid="uid://bq4vlbfx00hea" path="res://addons/forge/core/ForgeData.cs" id="1_x0pne"] + +[resource] +script = ExtResource("1_x0pne") +RegisteredTags = Array[String](["effect.fire", "effect.wet", "cue.floating.text", "cue.vfx.fire", "cue.vfx.wet", "cue.vfx.regen", "cooldown.enemy.attack", "set_by_caller.damage", "event.damage", "cooldown", "cooldown.skill.projectile", "cooldown.skill.shield", "cooldown.skill.dash", "movement.block", "immunity.damage", "effect.mana_shield", "cue.vfx.shield", "event.damage.taken", "event.damage.dealt", "event", "set_by_caller", "trait.flammable", "trait.healable", "trait.damageable", "trait.wettable", "cue.vfx.reflect", "cue.vfx", "cooldown.skill", "cooldown.skill.reflect"]) diff --git a/addons/forge/editor/AssetRepairTool.cs b/addons/forge/editor/AssetRepairTool.cs new file mode 100644 index 00000000..46682f3d --- /dev/null +++ b/addons/forge/editor/AssetRepairTool.cs @@ -0,0 +1,210 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using System; +using System.Collections.Generic; +using System.Linq; +using Gamesmiths.Forge.Godot.Core; +using Gamesmiths.Forge.Godot.Resources; +using Gamesmiths.Forge.Tags; +using Godot; +using Godot.Collections; + +namespace Gamesmiths.Forge.Godot.Editor; + +[Tool] +public partial class AssetRepairTool : EditorPlugin +{ + public static void RepairAllAssetsTags() + { + ForgeData pluginData = ResourceLoader.Load("uid://8j4xg16o3qnl"); + var tagsManager = new TagsManager([.. pluginData.RegisteredTags]); + + List scenes = GetScenePaths("res://"); + GD.Print($"Found {scenes.Count} scene(s) to process."); + + var openedScenes = EditorInterface.Singleton.GetOpenScenes(); + + foreach (var originalScenePath in scenes) + { + // For some weird reason scenes from the GetScenePath are coming with 3 slashes instead of just two. + var scenePath = originalScenePath.Replace("res:///", "res://"); + + GD.Print($"Processing scene: {scenePath}."); + PackedScene? packedScene = ResourceLoader.Load(scenePath); + + if (packedScene is null) + { + GD.PrintErr($"Failed to load scene: {scenePath}."); + continue; + } + + Node sceneInstance = packedScene.Instantiate(); + var modified = ProcessNode(sceneInstance, tagsManager); + + if (!modified) + { + GD.Print($"No changes needed for {scenePath}."); + continue; + } + + // 'sceneInstance' is the modified scene instance in memory, need to save to disk and reload if needed. + var newScene = new PackedScene(); + Error error = newScene.Pack(sceneInstance); + if (error != Error.Ok) + { + GD.PrintErr($"Failed to pack scene: {error}."); + continue; + } + + error = ResourceSaver.Save(newScene, scenePath); + if (error != Error.Ok) + { + GD.PrintErr($"Failed to save scene: {error}."); + continue; + } + + if (openedScenes.Contains(scenePath)) + { + GD.Print($"Scene was opened, reloading background scene: {scenePath}."); + EditorInterface.Singleton.ReloadSceneFromPath(scenePath); + } + } + } + + /// + /// Recursively get scene files from a folder. + /// + /// Current path iteration. + /// List of scenes found. + private static List GetScenePaths(string basePath) + { + var scenePaths = new List(); + var dir = DirAccess.Open(basePath); + + if (dir is null) + { + GD.PrintErr($"Failed to open directory: {basePath}"); + return scenePaths; + } + + // Start listing directory entries; skip navigational and hidden files. + dir.ListDirBegin(); + while (true) + { + var fileName = dir.GetNext(); + if (string.IsNullOrEmpty(fileName)) + { + break; + } + + var filePath = $"{basePath}/{fileName}"; + if (dir.CurrentIsDir()) + { + // Recursively scan subdirectories. + scenePaths.AddRange(GetScenePaths(filePath)); + } + else if (fileName.EndsWith(".tscn", StringComparison.InvariantCultureIgnoreCase) + || fileName.EndsWith(".scn", StringComparison.InvariantCultureIgnoreCase)) + { + scenePaths.Add(filePath); + } + } + + dir.ListDirEnd(); + return scenePaths; + } + + /// + /// Recursively process nodes; returns true if any ForgeEntity was modified. + /// + /// Current node iteration. + /// The tags manager used to validate tags. + /// if any ForgeEntity was modified. + private static bool ProcessNode(Node node, TagsManager tagsManager) + { + var modified = ValidateNode(node, tagsManager); + + foreach (Node child in node.GetChildren()) + { + modified |= ProcessNode(child, tagsManager); + } + + return modified; + } + + private static bool ValidateNode(Node node, TagsManager tagsManager) + { + var modified = false; + foreach (Dictionary propertyInfo in node.GetPropertyList()) + { + if (!propertyInfo.TryGetValue("class_name", out Variant className)) + { + continue; + } + + if (className.AsString() != "TagContainer") + { + continue; + } + + if (!propertyInfo.TryGetValue("name", out Variant nameObj)) + { + continue; + } + + var propertyName = nameObj.AsString(); + Variant value = node.Get(propertyName); + + if (value.VariantType != Variant.Type.Object) + { + continue; + } + + if (value.As() is ForgeTagContainer tagContainer) + { + modified |= ValidateTagContainerProperty(tagContainer, node.Name, tagsManager); + } + } + + return modified; + } + + private static bool ValidateTagContainerProperty( + ForgeTagContainer container, + string nodeName, + TagsManager tagsManager) + { + if (container.ContainerTags is null) + { + return false; + } + + Array originalTags = container.ContainerTags; + var newTags = new Array(); + var modified = false; + + foreach (var tag in originalTags) + { + try + { + Tag.RequestTag(tagsManager, tag); + newTags.Add(tag); + } + catch (TagNotRegisteredException) + { + GD.PrintRich( + $"[color=LIGHT_STEEL_BLUE][RepairTool] Removing invalid tag [{tag}] from node {nodeName}."); + modified = true; + } + } + + if (modified) + { + container.ContainerTags = newTags; + } + + return modified; + } +} +#endif diff --git a/addons/forge/editor/AssetRepairTool.cs.uid b/addons/forge/editor/AssetRepairTool.cs.uid new file mode 100644 index 00000000..90b58b38 --- /dev/null +++ b/addons/forge/editor/AssetRepairTool.cs.uid @@ -0,0 +1 @@ +uid://1runivyr5don diff --git a/addons/forge/editor/EditorUtils.cs b/addons/forge/editor/EditorUtils.cs new file mode 100644 index 00000000..94cef20d --- /dev/null +++ b/addons/forge/editor/EditorUtils.cs @@ -0,0 +1,62 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Gamesmiths.Forge.Attributes; + +namespace Gamesmiths.Forge.Godot.Editor; + +internal static class EditorUtils +{ + /// + /// Uses reflection to gather all classes inheriting from AttributeSet and their fields of type Attribute. + /// + /// An array with the available attributes. + public static string[] GetAttributeSetOptions() + { + var options = new List(); + + // Get all types in the current assembly + Type[] allTypes = Assembly.GetExecutingAssembly().GetTypes(); + + // Find all types that subclass AttributeSet + foreach (Type attributeSetType in allTypes.Where(x => x.IsSubclassOf(typeof(AttributeSet)))) + { + options.Add(attributeSetType.Name); + } + + return [.. options]; + } + + /// + /// Uses reflection to gather all classes inheriting from AttributeSet and their fields of type Attribute. + /// + /// The attribute set used to search for the attributes. + /// An array with the available attributes. + public static string[] GetAttributeOptions(string? attributeSet) + { + if (string.IsNullOrEmpty(attributeSet)) + { + return []; + } + + var asm = Assembly.GetExecutingAssembly(); + Type? type = Array.Find( + asm.GetTypes(), + x => x.IsSubclassOf(typeof(AttributeSet)) && x.Name == attributeSet); + + if (type is null) + { + return []; + } + + IEnumerable properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.PropertyType == typeof(EntityAttribute)); + + return [.. properties.Select(x => $"{x.Name}")]; + } +} +#endif diff --git a/addons/forge/editor/EditorUtils.cs.uid b/addons/forge/editor/EditorUtils.cs.uid new file mode 100644 index 00000000..da206c48 --- /dev/null +++ b/addons/forge/editor/EditorUtils.cs.uid @@ -0,0 +1 @@ +uid://dcvmf0r1f43m6 diff --git a/addons/forge/editor/ForgeEditor.tscn b/addons/forge/editor/ForgeEditor.tscn new file mode 100644 index 00000000..d0cf8f15 --- /dev/null +++ b/addons/forge/editor/ForgeEditor.tscn @@ -0,0 +1,14 @@ +[gd_scene format=3 uid="uid://pjscvogl6jak"] + +[ext_resource type="PackedScene" uid="uid://c17f812by5x23" path="res://addons/forge/editor/tags/TagsEditor.tscn" id="1_bxwfw"] + +[node name="Forge" type="PanelContainer" unique_id=249446352] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Tags" parent="." unique_id=654228508 instance=ExtResource("1_bxwfw")] +unique_name_in_owner = true +layout_mode = 2 diff --git a/addons/forge/editor/attributes/AttributeEditorPlugin.cs b/addons/forge/editor/attributes/AttributeEditorPlugin.cs new file mode 100644 index 00000000..92c32e19 --- /dev/null +++ b/addons/forge/editor/attributes/AttributeEditorPlugin.cs @@ -0,0 +1,34 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using Godot; + +namespace Gamesmiths.Forge.Godot.Editor.Attributes; + +[Tool] +public partial class AttributeEditorPlugin : EditorInspectorPlugin +{ + public override bool _CanHandle(GodotObject @object) + { + return @object is Resources.ForgeModifier || @object is Resources.ForgeCue; + } + + public override bool _ParseProperty( + GodotObject @object, + Variant.Type type, + string name, + PropertyHint hintType, + string hintString, + PropertyUsageFlags usageFlags, + bool wide) + { + if (name == "Attribute" || name == "CapturedAttribute" || name == "MagnitudeAttribute") + { + AddPropertyEditor(name, new AttributeEditorProperty()); + return true; + } + + return false; + } +} +#endif diff --git a/addons/forge/editor/attributes/AttributeEditorPlugin.cs.uid b/addons/forge/editor/attributes/AttributeEditorPlugin.cs.uid new file mode 100644 index 00000000..20330ba5 --- /dev/null +++ b/addons/forge/editor/attributes/AttributeEditorPlugin.cs.uid @@ -0,0 +1 @@ +uid://bl2w0vp6b5p8k diff --git a/addons/forge/editor/attributes/AttributeEditorProperty.cs b/addons/forge/editor/attributes/AttributeEditorProperty.cs new file mode 100644 index 00000000..dbdf791f --- /dev/null +++ b/addons/forge/editor/attributes/AttributeEditorProperty.cs @@ -0,0 +1,101 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using Godot; + +namespace Gamesmiths.Forge.Godot.Editor.Attributes; + +[Tool] +public partial class AttributeEditorProperty : EditorProperty +{ + private const int ButtonSize = 26; + private const int PopupSize = 300; + + private Label _label = null!; + + public override void _Ready() + { + Texture2D dropdownIcon = EditorInterface.Singleton + .GetEditorTheme() + .GetIcon("GuiDropdown", "EditorIcons"); + + var hbox = new HBoxContainer(); + _label = new Label { Text = "None", SizeFlagsHorizontal = SizeFlags.ExpandFill }; + var button = new Button { Icon = dropdownIcon, CustomMinimumSize = new Vector2(ButtonSize, 0) }; + + hbox.AddChild(_label); + hbox.AddChild(button); + AddChild(hbox); + + var popup = new Popup { Size = new Vector2I(PopupSize, PopupSize) }; + var tree = new Tree + { + HideRoot = true, + AnchorRight = 1, + AnchorBottom = 1, + }; + popup.AddChild(tree); + + var bg = new StyleBoxFlat + { + BgColor = EditorInterface.Singleton + .GetEditorTheme() + .GetColor("dark_color_2", "Editor"), + }; + tree.AddThemeStyleboxOverride("panel", bg); + + AddChild(popup); + + BuildAttributeTree(tree); + + button.Pressed += () => + { + Window win = GetWindow(); + popup.Position = (Vector2I)button.GlobalPosition + + win.Position + - new Vector2I(PopupSize - ButtonSize, -30); + popup.Popup(); + }; + + tree.ItemActivated += () => + { + TreeItem item = tree.GetSelected(); + if (item?.HasMeta("attribute_path") != true) + { + return; + } + + var fullPath = item.GetMeta("attribute_path").AsString(); + _label.Text = fullPath; + EmitChanged(GetEditedProperty(), fullPath); + popup.Hide(); + }; + } + + public override void _UpdateProperty() + { + var value = GetEditedObject().Get(GetEditedProperty()).AsString(); + _label.Text = string.IsNullOrEmpty(value) ? "None" : value; + } + + private static void BuildAttributeTree(Tree tree) + { + TreeItem root = tree.CreateItem(); + + foreach (var attributeSet in EditorUtils.GetAttributeSetOptions()) + { + TreeItem setItem = tree.CreateItem(root); + setItem.SetText(0, attributeSet); + setItem.Collapsed = true; + + foreach (var attribute in EditorUtils.GetAttributeOptions(attributeSet)) + { + TreeItem attributeItem = tree.CreateItem(setItem); + var attributePath = $"{attributeSet}.{attribute}"; + attributeItem.SetText(0, attribute); + attributeItem.SetMeta("attribute_path", attributePath); + } + } + } +} +#endif diff --git a/addons/forge/editor/attributes/AttributeEditorProperty.cs.uid b/addons/forge/editor/attributes/AttributeEditorProperty.cs.uid new file mode 100644 index 00000000..b4186ac3 --- /dev/null +++ b/addons/forge/editor/attributes/AttributeEditorProperty.cs.uid @@ -0,0 +1 @@ +uid://dvjqj637kfav diff --git a/addons/forge/editor/attributes/AttributeSetClassEditorProperty.cs b/addons/forge/editor/attributes/AttributeSetClassEditorProperty.cs new file mode 100644 index 00000000..fbda0d75 --- /dev/null +++ b/addons/forge/editor/attributes/AttributeSetClassEditorProperty.cs @@ -0,0 +1,74 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using System; +using System.Linq; +using System.Reflection; +using Gamesmiths.Forge.Attributes; +using Gamesmiths.Forge.Godot.Nodes; +using Godot; +using Godot.Collections; + +namespace Gamesmiths.Forge.Godot.Editor.Attributes; + +[Tool] +public partial class AttributeSetClassEditorProperty : EditorProperty +{ + private OptionButton _optionButton = null!; + + public override void _Ready() + { + _optionButton = new OptionButton(); + AddChild(_optionButton); + + _optionButton.AddItem("Select AttributeSet Class"); + foreach (var option in EditorUtils.GetAttributeSetOptions()) + { + _optionButton.AddItem(option); + } + + _optionButton.ItemSelected += x => + { + var className = _optionButton.GetItemText((int)x); + EmitChanged(GetEditedProperty(), className); + + GodotObject obj = GetEditedObject(); + if (obj is not null) + { + var dict = new Dictionary(); + + var assembly = Assembly.GetAssembly(typeof(ForgeAttributeSet)); + Type? targetType = System.Array.Find(assembly?.GetTypes() ?? [], x => x.Name == className); + if (targetType is not null) + { + System.Collections.Generic.IEnumerable attrProps = targetType + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.PropertyType == typeof(EntityAttribute)); + + foreach (PropertyInfo? pi in attrProps) + { + dict[pi.Name] = new AttributeValues(0, 0, int.MaxValue); + } + } + + EmitChanged("InitialAttributeValues", dict); + } + }; + } + + public override void _UpdateProperty() + { + GodotObject obj = GetEditedObject(); + StringName property = GetEditedProperty(); + var val = obj.Get(property).AsString(); + for (var i = 0; i < _optionButton.GetItemCount(); i++) + { + if (_optionButton.GetItemText(i) == val) + { + _optionButton.Selected = i; + break; + } + } + } +} +#endif diff --git a/addons/forge/editor/attributes/AttributeSetClassEditorProperty.cs.uid b/addons/forge/editor/attributes/AttributeSetClassEditorProperty.cs.uid new file mode 100644 index 00000000..7774a449 --- /dev/null +++ b/addons/forge/editor/attributes/AttributeSetClassEditorProperty.cs.uid @@ -0,0 +1 @@ +uid://bumuxlivyt66b diff --git a/addons/forge/editor/attributes/AttributeSetInspectorPlugin.cs b/addons/forge/editor/attributes/AttributeSetInspectorPlugin.cs new file mode 100644 index 00000000..5143465c --- /dev/null +++ b/addons/forge/editor/attributes/AttributeSetInspectorPlugin.cs @@ -0,0 +1,43 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using Gamesmiths.Forge.Godot.Nodes; +using Godot; + +namespace Gamesmiths.Forge.Godot.Editor.Attributes; + +[Tool] +public partial class AttributeSetInspectorPlugin : EditorInspectorPlugin +{ + private PackedScene? _inspectorScene; + + public override bool _CanHandle(GodotObject @object) + { + return @object is ForgeAttributeSet; + } + + public override bool _ParseProperty( + GodotObject @object, + Variant.Type type, + string name, + PropertyHint hintType, + string hintString, + PropertyUsageFlags usageFlags, + bool wide) + { + if (name == "AttributeSetClass") + { + AddPropertyEditor(name, new AttributeSetClassEditorProperty()); + return true; + } + + if (name == "InitialAttributeValues") + { + AddPropertyEditor(name, new AttributeSetValuesEditorProperty()); + return true; + } + + return false; + } +} +#endif diff --git a/addons/forge/editor/attributes/AttributeSetInspectorPlugin.cs.uid b/addons/forge/editor/attributes/AttributeSetInspectorPlugin.cs.uid new file mode 100644 index 00000000..49007f10 --- /dev/null +++ b/addons/forge/editor/attributes/AttributeSetInspectorPlugin.cs.uid @@ -0,0 +1 @@ +uid://t3gpjlcyqor diff --git a/addons/forge/editor/attributes/AttributeSetValuesEditorProperty.cs b/addons/forge/editor/attributes/AttributeSetValuesEditorProperty.cs new file mode 100644 index 00000000..f8a61ec9 --- /dev/null +++ b/addons/forge/editor/attributes/AttributeSetValuesEditorProperty.cs @@ -0,0 +1,171 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using Gamesmiths.Forge.Attributes; +using Gamesmiths.Forge.Godot.Nodes; +using Godot; +using Godot.Collections; + +namespace Gamesmiths.Forge.Godot.Editor.Attributes; + +[Tool] +public partial class AttributeSetValuesEditorProperty : EditorProperty +{ + public override void _Ready() + { + var attributesRoot = new VBoxContainer { Name = "AttributesRoot" }; + AddChild(attributesRoot); + SetBottomEditor(attributesRoot); + } + + public override void _UpdateProperty() + { + VBoxContainer attributesRoot = GetNodeOrNull("AttributesRoot"); + + if (attributesRoot is null) + { + return; + } + + FreeAllChildren(attributesRoot); + + if (GetEditedObject() is not ForgeAttributeSet obj + || string.IsNullOrEmpty(obj.AttributeSetClass) + || obj.InitialAttributeValues is null) + { + return; + } + + var className = obj.AttributeSetClass; + var assembly = Assembly.GetAssembly(typeof(ForgeAttributeSet)); + Type? targetType = System.Array.Find(assembly?.GetTypes() ?? [], x => x.Name == className); + + if (targetType is null) + { + return; + } + + System.Collections.Generic.IEnumerable attributeProperties = targetType + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.PropertyType == typeof(EntityAttribute)); + + foreach (var attributeName in attributeProperties.Select(x => x.Name)) + { + var groupVBox = new VBoxContainer(); + + groupVBox.AddChild(AttributeHeader(attributeName)); + + AttributeValues value = obj.InitialAttributeValues.TryGetValue(attributeName, out AttributeValues? v) + ? v + : new AttributeValues(0, 0, int.MaxValue); + + SpinBox spinDefault = CreateSpinBox(value.Min, value.Max, value.Default); + SpinBox spinMin = CreateSpinBox(int.MinValue, value.Max, value.Min); + SpinBox spinMax = CreateSpinBox(value.Min, int.MaxValue, value.Max); + + groupVBox.AddChild(AttributeFieldRow("Default", spinDefault)); + groupVBox.AddChild(AttributeFieldRow("Min", spinMin)); + groupVBox.AddChild(AttributeFieldRow("Max", spinMax)); + + spinDefault.ValueChanged += x => + { + UpdateAndEmit(obj, attributeName, (int)x, (int)spinMin.Value, (int)spinMax.Value); + }; + + spinMin.ValueChanged += x => + { + spinDefault.MinValue = x; + spinMax.MinValue = x; + UpdateAndEmit(obj, attributeName, (int)spinDefault.Value, (int)x, (int)spinMax.Value); + }; + + spinMax.ValueChanged += x => + { + spinDefault.MaxValue = x; + spinMin.MaxValue = x; + UpdateAndEmit(obj, attributeName, (int)spinDefault.Value, (int)spinMin.Value, (int)x); + }; + + attributesRoot.AddChild(groupVBox); + } + } + + private static PanelContainer AttributeHeader(string text) + { + var headerPanel = new PanelContainer + { + CustomMinimumSize = new Vector2(0, 28), + }; + + var style = new StyleBoxFlat + { + BgColor = new Color(0.16f, 0.17f, 0.20f), + }; + + headerPanel.AddThemeStyleboxOverride("panel", style); + + var label = new Label + { + Text = text, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + SizeFlagsHorizontal = (SizeFlags)(int)SizeFlags.ExpandFill, + CustomMinimumSize = new Vector2(0, 22), + AutowrapMode = TextServer.AutowrapMode.Off, + }; + + headerPanel.AddChild(label); + return headerPanel; + } + + private static HBoxContainer AttributeFieldRow(string label, SpinBox spinBox) + { + var hbox = new HBoxContainer(); + + hbox.AddChild(new Label + { + Text = label, + CustomMinimumSize = new Vector2(80, 0), + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }); + + hbox.AddChild(spinBox); + return hbox; + } + + private static SpinBox CreateSpinBox(int min, int max, int value) + { + return new SpinBox + { + MinValue = min, + MaxValue = max, + Value = value, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + } + + private static void FreeAllChildren(Node node) + { + for (var i = node.GetChildCount() - 1; i >= 0; i--) + { + node.GetChild(i).QueueFree(); + } + } + + private void UpdateAndEmit(ForgeAttributeSet obj, string name, int def, int min, int max) + { + Debug.Assert(obj.InitialAttributeValues is not null, "InitialAttributeValues should not be null here."); + + var dict = new Dictionary(obj.InitialAttributeValues) + { + [name] = new AttributeValues(def, min, max), + }; + + EmitChanged(nameof(ForgeAttributeSet.InitialAttributeValues), dict); + } +} +#endif diff --git a/addons/forge/editor/attributes/AttributeSetValuesEditorProperty.cs.uid b/addons/forge/editor/attributes/AttributeSetValuesEditorProperty.cs.uid new file mode 100644 index 00000000..b38d71d8 --- /dev/null +++ b/addons/forge/editor/attributes/AttributeSetValuesEditorProperty.cs.uid @@ -0,0 +1 @@ +uid://cdj20gbpxkda1 diff --git a/addons/forge/editor/attributes/AttributeValues.cs b/addons/forge/editor/attributes/AttributeValues.cs new file mode 100644 index 00000000..6944827a --- /dev/null +++ b/addons/forge/editor/attributes/AttributeValues.cs @@ -0,0 +1,29 @@ +// Copyright © Gamesmiths Guild. + +using Godot; + +namespace Gamesmiths.Forge.Godot.Editor.Attributes; + +[Tool] +public partial class AttributeValues : Resource +{ + [Export] + public int Default { get; set; } + + [Export] + public int Min { get; set; } + + [Export] + public int Max { get; set; } + + public AttributeValues() + { + } + + public AttributeValues(int @default, int min, int max) + { + Default = @default; + Min = min; + Max = max; + } +} diff --git a/addons/forge/editor/attributes/AttributeValues.cs.uid b/addons/forge/editor/attributes/AttributeValues.cs.uid new file mode 100644 index 00000000..7a4fc1c5 --- /dev/null +++ b/addons/forge/editor/attributes/AttributeValues.cs.uid @@ -0,0 +1 @@ +uid://ccovd5i0wr3kk diff --git a/addons/forge/editor/cues/CueHandlerInspectorPlugin.cs b/addons/forge/editor/cues/CueHandlerInspectorPlugin.cs new file mode 100644 index 00000000..246e2f65 --- /dev/null +++ b/addons/forge/editor/cues/CueHandlerInspectorPlugin.cs @@ -0,0 +1,53 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using System; +using Gamesmiths.Forge.Godot.Nodes; +using Godot; + +namespace Gamesmiths.Forge.Godot.Editor.Cues; + +[Tool] +public partial class CueHandlerInspectorPlugin : EditorInspectorPlugin +{ + public override bool _CanHandle(GodotObject @object) + { + // Find out if its an implementation of CueHandler without having to add [Tool] attribute to them. + if (@object?.GetScript().As() is CSharpScript script) + { + StringName className = script.GetGlobalName(); + + Type baseType = typeof(ForgeCueHandler); + System.Reflection.Assembly assembly = baseType.Assembly; + + Type? implementationType = + Array.Find(assembly.GetTypes(), x => + x.Name == className && + baseType.IsAssignableFrom(x)); + + return implementationType is not null; + } + + return false; + } + + public override bool _ParseProperty( + GodotObject @object, + Variant.Type type, + string name, + PropertyHint hintType, + string hintString, + PropertyUsageFlags usageFlags, + bool wide) + { + if (name == "CueTag") + { + var cueKeyEditorProperty = new CueKeyEditorProperty(); + AddPropertyEditor(name, cueKeyEditorProperty); + return true; + } + + return base._ParseProperty(@object, type, name, hintType, hintString, usageFlags, wide); + } +} +#endif diff --git a/addons/forge/editor/cues/CueHandlerInspectorPlugin.cs.uid b/addons/forge/editor/cues/CueHandlerInspectorPlugin.cs.uid new file mode 100644 index 00000000..4e32dad0 --- /dev/null +++ b/addons/forge/editor/cues/CueHandlerInspectorPlugin.cs.uid @@ -0,0 +1 @@ +uid://dattkelp87mhv diff --git a/addons/forge/editor/cues/CueKeyEditorProperty.cs b/addons/forge/editor/cues/CueKeyEditorProperty.cs new file mode 100644 index 00000000..8681f14c --- /dev/null +++ b/addons/forge/editor/cues/CueKeyEditorProperty.cs @@ -0,0 +1,106 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using System.Collections.Generic; +using Gamesmiths.Forge.Godot.Core; +using Gamesmiths.Forge.Tags; +using Godot; + +namespace Gamesmiths.Forge.Godot.Editor.Cues; + +[Tool] +public partial class CueKeyEditorProperty : EditorProperty +{ + private const int ButtonSize = 26; + private const int PopupSize = 300; + + private Label _label = null!; + + public override void _Ready() + { + Texture2D dropdownIcon = EditorInterface.Singleton + .GetEditorTheme() + .GetIcon("GuiDropdown", "EditorIcons"); + + var hbox = new HBoxContainer(); + _label = new Label { Text = "None", SizeFlagsHorizontal = SizeFlags.ExpandFill }; + var button = new Button { Icon = dropdownIcon, CustomMinimumSize = new Vector2(ButtonSize, 0) }; + + hbox.AddChild(_label); + hbox.AddChild(button); + AddChild(hbox); + + var popup = new Popup { Size = new Vector2I(PopupSize, PopupSize) }; + var tree = new Tree + { + HideRoot = true, + AnchorRight = 1, + AnchorBottom = 1, + }; + popup.AddChild(tree); + + var backgroundStyle = new StyleBoxFlat + { + BgColor = EditorInterface.Singleton.GetEditorTheme().GetColor("base_color", "Editor"), + }; + tree.AddThemeStyleboxOverride("panel", backgroundStyle); + + AddChild(popup); + + ForgeData pluginData = ResourceLoader.Load("uid://8j4xg16o3qnl"); + var tagsManager = new TagsManager([.. pluginData.RegisteredTags]); + TreeItem root = tree.CreateItem(); + BuildTreeRecursively(tree, root, tagsManager.RootNode); + + button.Pressed += () => + { + Window win = GetWindow(); + popup.Position = (Vector2I)button.GlobalPosition + + win.Position + - new Vector2I(PopupSize - ButtonSize, -30); + popup.Popup(); + }; + + tree.ItemActivated += () => + { + TreeItem item = tree.GetSelected(); + if (item is null) + { + return; + } + + // Build full path from root. + var segments = new List(); + TreeItem current = item; + while (current.GetParent() is not null) + { + segments.Insert(0, current.GetText(0)); + current = current.GetParent(); + } + + var fullPath = string.Join(".", segments); + + _label.Text = fullPath; + EmitChanged(GetEditedProperty(), fullPath); + popup.Hide(); + }; + } + + public override void _UpdateProperty() + { + var property = GetEditedObject().Get(GetEditedProperty()).AsString(); + _label.Text = string.IsNullOrEmpty(property) ? "None" : property; + } + + private static void BuildTreeRecursively(Tree tree, TreeItem currentTreeItem, TagNode currentNode) + { + foreach (TagNode childTagNode in currentNode.ChildTags) + { + TreeItem childTreeNode = tree.CreateItem(currentTreeItem); + childTreeNode.SetText(0, childTagNode.TagKey); + childTreeNode.Collapsed = true; + BuildTreeRecursively(tree, childTreeNode, childTagNode); + } + } +} +#endif diff --git a/addons/forge/editor/cues/CueKeyEditorProperty.cs.uid b/addons/forge/editor/cues/CueKeyEditorProperty.cs.uid new file mode 100644 index 00000000..cfe15a10 --- /dev/null +++ b/addons/forge/editor/cues/CueKeyEditorProperty.cs.uid @@ -0,0 +1 @@ +uid://csmr2puffid4k diff --git a/addons/forge/editor/cues/CueKeysEditor.cs.uid b/addons/forge/editor/cues/CueKeysEditor.cs.uid new file mode 100644 index 00000000..10774bc0 --- /dev/null +++ b/addons/forge/editor/cues/CueKeysEditor.cs.uid @@ -0,0 +1 @@ +uid://dnsy7p8h1ujjv diff --git a/addons/forge/editor/tags/TagContainerEditorProperty.cs b/addons/forge/editor/tags/TagContainerEditorProperty.cs new file mode 100644 index 00000000..e2ab253f --- /dev/null +++ b/addons/forge/editor/tags/TagContainerEditorProperty.cs @@ -0,0 +1,160 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using System.Collections.Generic; +using Gamesmiths.Forge.Godot.Core; +using Gamesmiths.Forge.Tags; +using Godot; + +using GodotStringArray = Godot.Collections.Array; + +namespace Gamesmiths.Forge.Godot.Editor.Tags; + +[Tool] +public partial class TagContainerEditorProperty : EditorProperty +{ + private readonly Dictionary _treeItemToNode = []; + + private VBoxContainer _root = null!; + private Button _containerButton = null!; + private ScrollContainer _scroll = null!; + private Tree _tree = null!; + + private Texture2D _checkedIcon = null!; + private Texture2D _uncheckedIcon = null!; + + private GodotStringArray _currentValue = []; + + public override void _Ready() + { + _root = new VBoxContainer + { + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + + _containerButton = new Button + { + ToggleMode = true, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + _containerButton.Toggled += OnToggled; + + _scroll = new ScrollContainer + { + Visible = false, + CustomMinimumSize = new Vector2(0, 220), + SizeFlagsHorizontal = SizeFlags.ExpandFill, + SizeFlagsVertical = SizeFlags.ExpandFill, + }; + + _tree = new Tree + { + HideRoot = true, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + SizeFlagsVertical = SizeFlags.ExpandFill, + }; + + _scroll.AddChild(_tree); + + _root.AddChild(_containerButton); + _root.AddChild(_scroll); + + AddChild(_root); + SetBottomEditor(_root); + + _checkedIcon = EditorInterface.Singleton + .GetEditorTheme() + .GetIcon("GuiChecked", "EditorIcons"); + + _uncheckedIcon = EditorInterface.Singleton + .GetEditorTheme() + .GetIcon("GuiUnchecked", "EditorIcons"); + + _tree.ButtonClicked += OnTreeButtonClicked; + } + + public override void _UpdateProperty() + { + GodotObject obj = GetEditedObject(); + string propertyName = GetEditedProperty(); + + _currentValue = + obj.Get(propertyName).AsGodotArray() ?? []; + + RebuildTree(); + } + + private void RebuildTree() + { + _tree.Clear(); + _treeItemToNode.Clear(); + + _containerButton.Text = + $"Container (size: {_currentValue.Count})"; + + TreeItem root = _tree.CreateItem(); + + ForgeData forgePluginData = + ResourceLoader.Load("uid://8j4xg16o3qnl"); + + var tagsManager = + new TagsManager([.. forgePluginData.RegisteredTags]); + + BuildTreeRecursive(root, tagsManager.RootNode); + + UpdateMinimumSize(); + NotifyPropertyListChanged(); + } + + private void BuildTreeRecursive(TreeItem parent, TagNode node) + { + foreach (TagNode child in node.ChildTags) + { + TreeItem item = _tree.CreateItem(parent); + item.SetText(0, child.TagKey); + + var checkedState = + _currentValue.Contains(child.CompleteTagKey); + + item.AddButton( + 0, + checkedState ? _checkedIcon : _uncheckedIcon); + + _treeItemToNode[item] = child; + BuildTreeRecursive(item, child); + } + } + + private void OnTreeButtonClicked( + TreeItem item, + long column, + long id, + long mouseButtonIndex) + { + if (mouseButtonIndex != 1 || id != 0) + { + return; + } + + string tag = _treeItemToNode[item].CompleteTagKey; + + var newValue = new GodotStringArray(); + newValue.AddRange(_currentValue); + + if (!newValue.Remove(tag)) + { + newValue.Add(tag); + } + + EmitChanged(GetEditedProperty(), newValue); + } + + private void OnToggled(bool toggled) + { + _scroll.Visible = toggled; + + UpdateMinimumSize(); + NotifyPropertyListChanged(); + } +} +#endif diff --git a/addons/forge/editor/tags/TagContainerEditorProperty.cs.uid b/addons/forge/editor/tags/TagContainerEditorProperty.cs.uid new file mode 100644 index 00000000..425a9c28 --- /dev/null +++ b/addons/forge/editor/tags/TagContainerEditorProperty.cs.uid @@ -0,0 +1 @@ +uid://dppi5lmv8q5ti diff --git a/addons/forge/editor/tags/TagContainerInspectorPlugin.cs b/addons/forge/editor/tags/TagContainerInspectorPlugin.cs new file mode 100644 index 00000000..42a2c831 --- /dev/null +++ b/addons/forge/editor/tags/TagContainerInspectorPlugin.cs @@ -0,0 +1,35 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using Gamesmiths.Forge.Godot.Resources; +using Godot; + +namespace Gamesmiths.Forge.Godot.Editor.Tags; + +public partial class TagContainerInspectorPlugin : EditorInspectorPlugin +{ + public override bool _CanHandle(GodotObject @object) + { + return @object is ForgeTagContainer; + } + + public override bool _ParseProperty( + GodotObject @object, + Variant.Type type, + string name, + PropertyHint hintType, + string hintString, + PropertyUsageFlags usageFlags, + bool wide) + { + if (name != "ContainerTags") + { + return false; + } + + var prop = new TagContainerEditorProperty(); + AddPropertyEditor(name, prop); + return true; + } +} +#endif diff --git a/addons/forge/editor/tags/TagContainerInspectorPlugin.cs.uid b/addons/forge/editor/tags/TagContainerInspectorPlugin.cs.uid new file mode 100644 index 00000000..f69a0aff --- /dev/null +++ b/addons/forge/editor/tags/TagContainerInspectorPlugin.cs.uid @@ -0,0 +1 @@ +uid://8g56j8vs35mn diff --git a/addons/forge/editor/tags/TagEditorProperty.cs b/addons/forge/editor/tags/TagEditorProperty.cs new file mode 100644 index 00000000..05edd2de --- /dev/null +++ b/addons/forge/editor/tags/TagEditorProperty.cs @@ -0,0 +1,149 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using System.Collections.Generic; +using Gamesmiths.Forge.Godot.Core; +using Gamesmiths.Forge.Tags; +using Godot; + +namespace Gamesmiths.Forge.Godot.Editor.Tags; + +[Tool] +public partial class TagEditorProperty : EditorProperty +{ + private readonly Dictionary _treeItemToNode = []; + + private VBoxContainer _root = null!; + private Button _containerButton = null!; + private ScrollContainer _scroll = null!; + private Tree _tree = null!; + + private Texture2D _checkedIcon = null!; + private Texture2D _uncheckedIcon = null!; + + private string _currentValue = string.Empty; + + public override void _Ready() + { + _root = new VBoxContainer + { + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + + _containerButton = new Button + { + ToggleMode = true, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + _containerButton.Toggled += OnToggled; + + _scroll = new ScrollContainer + { + Visible = false, + CustomMinimumSize = new Vector2(0, 220), + SizeFlagsHorizontal = SizeFlags.ExpandFill, + SizeFlagsVertical = SizeFlags.ExpandFill, + }; + + _tree = new Tree + { + HideRoot = true, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + SizeFlagsVertical = SizeFlags.ExpandFill, + }; + + _scroll.AddChild(_tree); + + _root.AddChild(_containerButton); + _root.AddChild(_scroll); + + AddChild(_root); + SetBottomEditor(_root); + + _checkedIcon = EditorInterface.Singleton + .GetEditorTheme() + .GetIcon("GuiRadioChecked", "EditorIcons"); + + _uncheckedIcon = EditorInterface.Singleton + .GetEditorTheme() + .GetIcon("GuiRadioUnchecked", "EditorIcons"); + + _tree.ButtonClicked += OnTreeButtonClicked; + } + + public override void _UpdateProperty() + { + GodotObject obj = GetEditedObject(); + string propertyName = GetEditedProperty(); + + _currentValue = obj.Get(propertyName).AsString(); + RebuildTree(); + } + + private void RebuildTree() + { + _tree.Clear(); + _treeItemToNode.Clear(); + + _containerButton.Text = + string.IsNullOrEmpty(_currentValue) ? "(none)" : _currentValue; + + TreeItem root = _tree.CreateItem(); + + ForgeData forgePluginData = + ResourceLoader.Load("uid://8j4xg16o3qnl"); + + var tagsManager = + new TagsManager([.. forgePluginData.RegisteredTags]); + + BuildTreeRecursive(root, tagsManager.RootNode); + + UpdateMinimumSize(); + NotifyPropertyListChanged(); + } + + private void BuildTreeRecursive(TreeItem parent, TagNode node) + { + foreach (TagNode child in node.ChildTags) + { + TreeItem item = _tree.CreateItem(parent); + item.SetText(0, child.TagKey); + + var selected = _currentValue == child.CompleteTagKey; + item.AddButton(0, selected ? _checkedIcon : _uncheckedIcon); + + _treeItemToNode[item] = child; + BuildTreeRecursive(item, child); + } + } + + private void OnTreeButtonClicked( + TreeItem item, + long column, + long id, + long mouseButtonIndex) + { + if (mouseButtonIndex != 1 || id != 0) + { + return; + } + + string newValue = _treeItemToNode[item].CompleteTagKey; + + if (newValue == _currentValue) + { + newValue = string.Empty; + } + + EmitChanged(GetEditedProperty(), newValue); + } + + private void OnToggled(bool toggled) + { + _scroll.Visible = toggled; + + UpdateMinimumSize(); + NotifyPropertyListChanged(); + } +} +#endif diff --git a/addons/forge/editor/tags/TagEditorProperty.cs.uid b/addons/forge/editor/tags/TagEditorProperty.cs.uid new file mode 100644 index 00000000..62837f96 --- /dev/null +++ b/addons/forge/editor/tags/TagEditorProperty.cs.uid @@ -0,0 +1 @@ +uid://bc4vhfbuyp7xd diff --git a/addons/forge/editor/tags/TagInspectorPlugin.cs b/addons/forge/editor/tags/TagInspectorPlugin.cs new file mode 100644 index 00000000..a4007849 --- /dev/null +++ b/addons/forge/editor/tags/TagInspectorPlugin.cs @@ -0,0 +1,35 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using Gamesmiths.Forge.Godot.Resources; +using Godot; + +namespace Gamesmiths.Forge.Godot.Editor.Tags; + +public partial class TagInspectorPlugin : EditorInspectorPlugin +{ + public override bool _CanHandle(GodotObject @object) + { + return @object is ForgeTag; + } + + public override bool _ParseProperty( + GodotObject @object, + Variant.Type type, + string name, + PropertyHint hintType, + string hintString, + PropertyUsageFlags usageFlags, + bool wide) + { + if (name != "Tag") + { + return false; + } + + var prop = new TagEditorProperty(); + AddPropertyEditor(name, prop); + return true; + } +} +#endif diff --git a/addons/forge/editor/tags/TagInspectorPlugin.cs.uid b/addons/forge/editor/tags/TagInspectorPlugin.cs.uid new file mode 100644 index 00000000..3daba19c --- /dev/null +++ b/addons/forge/editor/tags/TagInspectorPlugin.cs.uid @@ -0,0 +1 @@ +uid://cx3reriadfsnh diff --git a/addons/forge/editor/tags/TagsEditor.cs b/addons/forge/editor/tags/TagsEditor.cs new file mode 100644 index 00000000..e14a9f57 --- /dev/null +++ b/addons/forge/editor/tags/TagsEditor.cs @@ -0,0 +1,192 @@ +// Copyright © Gamesmiths Guild. + +#if TOOLS +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Gamesmiths.Forge.Godot.Core; +using Gamesmiths.Forge.Tags; +using Godot; + +namespace Gamesmiths.Forge.Godot.Editor.Tags; + +[Tool] +public partial class TagsEditor : VBoxContainer, ISerializationListener +{ + private readonly Dictionary _treeItemToNode = []; + + private TagsManager _tagsManager = null!; + + private ForgeData? _forgePluginData; + + private Tree? _tree; + private LineEdit? _tagNameTextField; + private Button? _addTagButton; + + private Texture2D? _addIcon; + private Texture2D? _removeIcon; + + public bool IsPluginInstance { get; set; } + + public override void _Ready() + { + base._Ready(); + + if (!IsPluginInstance) + { + return; + } + + _forgePluginData = ResourceLoader.Load("uid://8j4xg16o3qnl"); + _tagsManager = new TagsManager([.. _forgePluginData.RegisteredTags]); + + _addIcon = EditorInterface.Singleton.GetEditorTheme().GetIcon("Add", "EditorIcons"); + _removeIcon = EditorInterface.Singleton.GetEditorTheme().GetIcon("Remove", "EditorIcons"); + + _tree = GetNode("%Tree"); + _tagNameTextField = GetNode("%TagNameField"); + _addTagButton = GetNode