commit 00f94483293fc074b369b118053374788682488e Author: Nuno "Omega Leo" Diogo Date: Fri May 8 18:21:51 2026 +0100 first commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4709183 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Godot 4+ specific ignores +.godot/ diff --git a/.idea/.idea.Tutorials/.idea/.gitignore b/.idea/.idea.Tutorials/.idea/.gitignore new file mode 100644 index 0000000..6807f31 --- /dev/null +++ b/.idea/.idea.Tutorials/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/contentModel.xml +/.idea.Tutorials.iml +/projectSettingsUpdater.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.Tutorials/.idea/encodings.xml b/.idea/.idea.Tutorials/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.Tutorials/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.Tutorials/.idea/indexLayout.xml b/.idea/.idea.Tutorials/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.Tutorials/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Ability.gd b/Ability.gd new file mode 100644 index 0000000..384104b --- /dev/null +++ b/Ability.gd @@ -0,0 +1,15 @@ +class_name Ability +extends Resource + +@export var name: String +@export var power: float +@export var throwable: PackedScene + +func _init() -> void: + # Initialize your ability here + var test: String = get_ability_name() + + # Initialize your packed scene here + +func get_ability_name() -> String: + return name diff --git a/Ability.gd.uid b/Ability.gd.uid new file mode 100644 index 0000000..29d09f3 --- /dev/null +++ b/Ability.gd.uid @@ -0,0 +1 @@ +uid://cnjayq2d3r2dq diff --git a/AbilityTest.gd b/AbilityTest.gd new file mode 100644 index 0000000..b69c330 --- /dev/null +++ b/AbilityTest.gd @@ -0,0 +1,16 @@ +extends Control + +@export var label: RichTextLabel +@export var ability: Ability +@export var empowered_ability: EmpoweredAbility + +func _enter_tree() -> void: + label.text = "-----Regular Ability-----\r\n" + label.text += "Name: " + ability.get_ability_name() + "\r\n" + label.text += "Power: " + str(ability.power) + "\r\n" + label.text += "\r\n" + + label.text += "-----Enhanced Ability-----\r\n" + label.text += "Name: " + empowered_ability.get_ability_name() + "\r\n" + label.text += "Power: " + str(empowered_ability.power) + "\r\n" + label.text += "Power (after multiplier): " + str(empowered_ability.get_power()) diff --git a/AbilityTest.gd.uid b/AbilityTest.gd.uid new file mode 100644 index 0000000..e5c91a9 --- /dev/null +++ b/AbilityTest.gd.uid @@ -0,0 +1 @@ +uid://sos2jppfpm05 diff --git a/EmpoweredAbility.gd b/EmpoweredAbility.gd new file mode 100644 index 0000000..1e967f5 --- /dev/null +++ b/EmpoweredAbility.gd @@ -0,0 +1,7 @@ +class_name EmpoweredAbility +extends Ability + +@export var power_multiplier: float + +func get_power() -> float: + return power * power_multiplier; diff --git a/EmpoweredAbility.gd.uid b/EmpoweredAbility.gd.uid new file mode 100644 index 0000000..9f2cae8 --- /dev/null +++ b/EmpoweredAbility.gd.uid @@ -0,0 +1 @@ +uid://xhcrf1lvfvc6 diff --git a/EnhancedNode.gd b/EnhancedNode.gd new file mode 100644 index 0000000..1a68975 --- /dev/null +++ b/EnhancedNode.gd @@ -0,0 +1,5 @@ +class_name EnhancedArea2D +extends Area2D + +@export var entity_name: String +signal TestSignal(input: String) diff --git a/EnhancedNode.gd.uid b/EnhancedNode.gd.uid new file mode 100644 index 0000000..213e336 --- /dev/null +++ b/EnhancedNode.gd.uid @@ -0,0 +1 @@ +uid://cyrakc1tyy74o diff --git a/TestBoxTest.cs b/TestBoxTest.cs new file mode 100644 index 0000000..55fb70e --- /dev/null +++ b/TestBoxTest.cs @@ -0,0 +1,10 @@ +using Godot; +using System; +using Tutorials.addons.custom_inspector_tools.InspectorPlugins; + +[Tool] +public partial class TestBoxTest : Node2D +{ + [TestBox("Field1", "This is a test box for Field1")] + [Export] public int Field1 = 42; +} diff --git a/TestBoxTest.cs.uid b/TestBoxTest.cs.uid new file mode 100644 index 0000000..6d9efcf --- /dev/null +++ b/TestBoxTest.cs.uid @@ -0,0 +1 @@ +uid://dkgneigm55ij7 diff --git a/Tutorials.csproj b/Tutorials.csproj new file mode 100644 index 0000000..63a009f --- /dev/null +++ b/Tutorials.csproj @@ -0,0 +1,7 @@ + + + net8.0 + net9.0 + true + + \ No newline at end of file diff --git a/Tutorials.sln b/Tutorials.sln new file mode 100644 index 0000000..ff52fab --- /dev/null +++ b/Tutorials.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tutorials", "Tutorials.csproj", "{7E42B83A-A25D-4FD2-A8FC-BC16565A2C33}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + ExportDebug|Any CPU = ExportDebug|Any CPU + ExportRelease|Any CPU = ExportRelease|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7E42B83A-A25D-4FD2-A8FC-BC16565A2C33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E42B83A-A25D-4FD2-A8FC-BC16565A2C33}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E42B83A-A25D-4FD2-A8FC-BC16565A2C33}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU + {7E42B83A-A25D-4FD2-A8FC-BC16565A2C33}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU + {7E42B83A-A25D-4FD2-A8FC-BC16565A2C33}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU + {7E42B83A-A25D-4FD2-A8FC-BC16565A2C33}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU + EndGlobalSection +EndGlobal diff --git a/addons/custom_inspector_tools/CustomInspectorPlugin.cs b/addons/custom_inspector_tools/CustomInspectorPlugin.cs new file mode 100644 index 0000000..4e4d416 --- /dev/null +++ b/addons/custom_inspector_tools/CustomInspectorPlugin.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using Godot; +using Tutorials.addons.custom_inspector_tools.InspectorPlugins; + +namespace Tutorials.addons.custom_inspector_tools; + +#if TOOLS +[Tool] +public partial class CustomInspectorPlugin : EditorPlugin +{ + private readonly List _customPlugins = new(); + private bool _pluginsBuilt = false; + private bool _inspectorPluginsAdded = false; + + private EditorSelection _selection; + private bool _refreshingInspector = false; + + public override void _EnterTree() + { + base._EnterTree(); + + if (!_pluginsBuilt) + { + BuildPlugins(); + _pluginsBuilt = true; + } + + _selection = EditorInterface.Singleton.GetSelection(); + _selection.SelectionChanged += OnSelectionChanged; + } + + private void OnSelectionChanged() + { + if (!_inspectorPluginsAdded) + { + return; + } + + + RefreshInspectorForCurrentSelection(); + } + + private void RefreshInspectorForCurrentSelection() + { + if (_refreshingInspector) + { + return; + } + + _refreshingInspector = true; + + try + { + GodotObject objToInspect = null; + + foreach (var node in _selection.GetSelectedNodes()) + { + if (node is not null) + { + objToInspect = node; + break; + } + } + + // Drop old inspector UI, then rebuild for the selected object. + EditorInterface.Singleton.InspectObject(null); + + if (objToInspect is not null) + { + EditorInterface.Singleton.InspectObject(objToInspect); + } + } + finally + { + _refreshingInspector = false; + } + } + + private void BuildPlugins() + { + _customPlugins.Clear(); + + // Add new plugins here as you create them. + _customPlugins.Add(new TestBoxInspectorPlugin()); + } + + public override void _EnablePlugin() + { + base._EnablePlugin(); + + try + { + if (_inspectorPluginsAdded) + return; + + foreach (var plugin in _customPlugins) + { + AddInspectorPlugin(plugin); + GD.Print($"Added inspector plugin: {plugin.GetType().Name}"); + } + + _inspectorPluginsAdded = true; + + RefreshInspectorForCurrentSelection(); + } + catch (Exception ex) + { + GD.PrintErr($"Error enabling CustomInspectorPlugin: {ex.Message}"); + } + } + + public override void _DisablePlugin() + { + try + { + RemoveAllInspectorPlugins(); + + EditorInterface.Singleton.InspectObject(null); + RefreshInspectorForCurrentSelection(); + } + catch (Exception ex) + { + GD.PrintErr($"Error disabling CustomInspectorPlugin: {ex.Message}"); + } + base._DisablePlugin(); + } + + private void RemoveAllInspectorPlugins() + { + if (!_inspectorPluginsAdded) + return; + + foreach (var plugin in _customPlugins) + { + RemoveInspectorPlugin(plugin); + GD.Print($"Removed inspector plugin: {plugin.GetType().Name}"); + } + + _inspectorPluginsAdded = false; + } +} +#endif \ No newline at end of file diff --git a/addons/custom_inspector_tools/CustomInspectorPlugin.cs.uid b/addons/custom_inspector_tools/CustomInspectorPlugin.cs.uid new file mode 100644 index 0000000..5fa1c06 --- /dev/null +++ b/addons/custom_inspector_tools/CustomInspectorPlugin.cs.uid @@ -0,0 +1 @@ +uid://dxmsbkgfqf1kh diff --git a/addons/custom_inspector_tools/InspectorPlugins/TestBoxAttribute.cs b/addons/custom_inspector_tools/InspectorPlugins/TestBoxAttribute.cs new file mode 100644 index 0000000..92f826d --- /dev/null +++ b/addons/custom_inspector_tools/InspectorPlugins/TestBoxAttribute.cs @@ -0,0 +1,16 @@ +using System; + +namespace Tutorials.addons.custom_inspector_tools.InspectorPlugins; + +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public class TestBoxAttribute : Attribute +{ + public string Identifier { get; } + public string Text { get; } + + public TestBoxAttribute(string identifier, string text) + { + Identifier = identifier; + Text = text; + } +} \ No newline at end of file diff --git a/addons/custom_inspector_tools/InspectorPlugins/TestBoxAttribute.cs.uid b/addons/custom_inspector_tools/InspectorPlugins/TestBoxAttribute.cs.uid new file mode 100644 index 0000000..5c27976 --- /dev/null +++ b/addons/custom_inspector_tools/InspectorPlugins/TestBoxAttribute.cs.uid @@ -0,0 +1 @@ +uid://c0rv1g5ikjgrm diff --git a/addons/custom_inspector_tools/InspectorPlugins/TestBoxInspectorPlugin.cs b/addons/custom_inspector_tools/InspectorPlugins/TestBoxInspectorPlugin.cs new file mode 100644 index 0000000..f26c507 --- /dev/null +++ b/addons/custom_inspector_tools/InspectorPlugins/TestBoxInspectorPlugin.cs @@ -0,0 +1,153 @@ +#if TOOLS +using System.Collections.Generic; +using System.Reflection; +using Godot; +using Microsoft.VisualBasic.CompilerServices; + +namespace Tutorials.addons.custom_inspector_tools.InspectorPlugins; + +[Tool] +public partial class TestBoxInspectorPlugin : EditorInspectorPlugin +{ + private const bool DEBUG_LOGS = true; + + // What object are we looking at? + private ulong _currentObjectId = 0; + + private readonly HashSet _identifiersInjectedForCurrentObject = new(); + + public override bool _CanHandle(GodotObject @object) + { + if (@object is null) + { + return false; + } + + var objectId = @object.GetInstanceId(); + if (DEBUG_LOGS) + { + GD.Print($"[TestBoxInspectorPlugin] _CanHandle called for object with ID {objectId}"); + } + + var type = @object.GetType(); + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic); + + foreach (var property in properties) + { + if (property.GetCustomAttribute() is not null) + { + if (DEBUG_LOGS) + { + GD.Print($"[TestBoxInspectorPlugin] Found property with TestBoxAttribute: {property.Name}"); + } + return true; + } + } + + foreach (var field in fields) + { + if (field.GetCustomAttribute() is not null) + { + if (DEBUG_LOGS) + { + GD.Print($"[TestBoxInspectorPlugin] Found field with TestBoxAttribute: {field.Name}"); + } + return true; + } + } + + return false; + } + + public override bool _ParseProperty(GodotObject @object, Variant.Type type, string name, PropertyHint hintType, string hintString, + PropertyUsageFlags usageFlags, bool wide) + { + if (@object is null) + { + return false; + } + + var objectId = @object.GetInstanceId(); + + if (_currentObjectId != objectId) + { + _currentObjectId = objectId; + _identifiersInjectedForCurrentObject.Clear(); + + if (DEBUG_LOGS) + { + GD.Print($"[TestBoxInspectorPlugin] New object selected with ID {_currentObjectId}. Cleared injected identifiers."); + } + } + + var objectType = @object.GetType(); + const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic; + + var property = objectType.GetProperty(name, flags); + var field = objectType.GetField(name, flags); + + if (property is null && field is null && name.Contains("_")) + { + // Godot mangles property names with underscores, so we need to check for that. + var unmangledName = name.Replace("_", ""); + property = objectType.GetProperty(unmangledName, flags); + field = objectType.GetField(unmangledName, flags); + } + + var member = (MemberInfo)property ?? field; + + if (member is null) + { + return false; + } + + var testBoxAttribute = member.GetCustomAttribute(); + if (testBoxAttribute is null) + { + return false; + } + + if (_identifiersInjectedForCurrentObject.Add(testBoxAttribute.Identifier)) + { + if (DEBUG_LOGS) + { + GD.Print($"[TestBoxInspectorPlugin] Injecting TestBox for identifier '{testBoxAttribute.Identifier}' on object ID {_currentObjectId}"); + } + + AddCustomControl(TestBoxControl.Create(testBoxAttribute.Text)); + } + else + { + if (DEBUG_LOGS) + { + GD.Print($"[TestBoxInspectorPlugin] Already injected TestBox for identifier '{testBoxAttribute.Identifier}' on object ID {_currentObjectId}. Skipping."); + } + } + + return false; + } +} + +[Tool] +public partial class TestBoxControl : PanelContainer +{ + public static TestBoxControl Create(string text) + { + var control = new TestBoxControl(); + control.SizeFlagsHorizontal = SizeFlags.ExpandFill; + + var label = new RichTextLabel() + { + Text = text, + FitContent = true, + ScrollActive = false, + BbcodeEnabled = true, + SizeFlagsHorizontal = SizeFlags.ExpandFill + }; + control.AddChild(label); + return control; + } +} + +#endif \ No newline at end of file diff --git a/addons/custom_inspector_tools/InspectorPlugins/TestBoxInspectorPlugin.cs.uid b/addons/custom_inspector_tools/InspectorPlugins/TestBoxInspectorPlugin.cs.uid new file mode 100644 index 0000000..2597233 --- /dev/null +++ b/addons/custom_inspector_tools/InspectorPlugins/TestBoxInspectorPlugin.cs.uid @@ -0,0 +1 @@ +uid://t0xwv6w6ue8v diff --git a/addons/custom_inspector_tools/plugin.cfg b/addons/custom_inspector_tools/plugin.cfg new file mode 100644 index 0000000..0ccf71a --- /dev/null +++ b/addons/custom_inspector_tools/plugin.cfg @@ -0,0 +1,6 @@ +[plugin] +name="Custom Inspector Tools" +description="Custom Inspector Tools" +author="omegaleo" +version="1.0" +script="CustomInspectorPlugin.cs" \ No newline at end of file diff --git a/control.tscn b/control.tscn new file mode 100644 index 0000000..d54fc83 --- /dev/null +++ b/control.tscn @@ -0,0 +1,36 @@ +[gd_scene format=3 uid="uid://c2e6p1udf2nyi"] + +[ext_resource type="Script" uid="uid://sos2jppfpm05" path="res://AbilityTest.gd" id="1_0fbet"] +[ext_resource type="Resource" uid="uid://bht3xjy7lpe0j" path="res://test ability.tres" id="2_qtu10"] +[ext_resource type="Resource" uid="uid://bq0raek4edihm" path="res://empowered test ability.tres" id="3_ee4bf"] +[ext_resource type="Script" uid="uid://cyrakc1tyy74o" path="res://EnhancedNode.gd" id="4_ee4bf"] +[ext_resource type="Script" uid="uid://dkgneigm55ij7" path="res://TestBoxTest.cs" id="5_kbped"] + +[node name="Control" type="Control" unique_id=498246024 node_paths=PackedStringArray("label")] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_0fbet") +label = NodePath("RichTextLabel") +ability = ExtResource("2_qtu10") +empowered_ability = ExtResource("3_ee4bf") + +[node name="RichTextLabel" type="RichTextLabel" parent="." unique_id=1353651725] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="EnhancedArea2D" type="Area2D" parent="." unique_id=1192052226] +script = ExtResource("4_ee4bf") +metadata/_custom_type_script = "uid://cyrakc1tyy74o" + +[node name="Node2D" type="Node2D" parent="." unique_id=939302476] +script = ExtResource("5_kbped") diff --git a/empowered test ability.tres b/empowered test ability.tres new file mode 100644 index 0000000..c4b3c3b --- /dev/null +++ b/empowered test ability.tres @@ -0,0 +1,10 @@ +[gd_resource type="Resource" script_class="EmpoweredAbility" load_steps=2 format=3 uid="uid://bq0raek4edihm"] + +[ext_resource type="Script" uid="uid://xhcrf1lvfvc6" path="res://EmpoweredAbility.gd" id="1_y5vf6"] + +[resource] +script = ExtResource("1_y5vf6") +power_multiplier = 2.5 +name = "Empowered Test Ability" +power = 5.0 +metadata/_custom_type_script = "uid://xhcrf1lvfvc6" diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..1a10d58 --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..f7b4e0e --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cwblvv75nqbb3" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..edd50ac --- /dev/null +++ b/project.godot @@ -0,0 +1,28 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[animation] + +compatibility/default_parent_skeleton_in_mesh_instance_3d=true + +[application] + +config/name="Tutorials" +run/main_scene="uid://c2e6p1udf2nyi" +config/features=PackedStringArray("4.6", "C#") +config/icon="res://icon.svg" + +[dotnet] + +project/assembly_name="Tutorials" + +[editor_plugins] + +enabled=PackedStringArray("res://addons/custom_inspector_tools/plugin.cfg") diff --git a/test ability.tres b/test ability.tres new file mode 100644 index 0000000..967b4cd --- /dev/null +++ b/test ability.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" script_class="Ability" load_steps=2 format=3 uid="uid://bht3xjy7lpe0j"] + +[ext_resource type="Script" uid="uid://cnjayq2d3r2dq" path="res://Ability.gd" id="1_p30r6"] + +[resource] +script = ExtResource("1_p30r6") +name = "Test" +power = 5.0 +metadata/_custom_type_script = "uid://cnjayq2d3r2dq"