From 673368a200a29c200f26213462b878d05c6d2019 Mon Sep 17 00:00:00 2001 From: Minimata Date: Fri, 3 Apr 2026 15:33:46 +0200 Subject: [PATCH] Added rider plugin and turned Empowered Action into a forge-resources-managed ability --- addons/rider-plugin/CONTRIBUTING.md | 33 ++++ addons/rider-plugin/LICENCE | 21 +++ addons/rider-plugin/README.md | 51 ++++++ addons/rider-plugin/icons/icon.svg | 144 ++++++++++++++++ addons/rider-plugin/icons/icon.svg.import | 43 +++++ addons/rider-plugin/plugin.cfg | 12 ++ addons/rider-plugin/presets.json | 15 ++ addons/rider-plugin/rider-plugin.gd | 66 ++++++++ addons/rider-plugin/rider-plugin.gd.uid | 1 + addons/rider-plugin/screenshots/Toolbar.png | 3 + .../screenshots/Toolbar.png.import | 40 +++++ addons/rider-plugin/scripts/json_utils.gd | 23 +++ addons/rider-plugin/scripts/json_utils.gd.uid | 1 + .../scripts/locator/rider_locator_service.gd | 112 +++++++++++++ .../locator/rider_locator_service.gd.uid | 1 + .../scripts/presets/preset_applier.gd | 35 ++++ .../scripts/presets/preset_applier.gd.uid | 1 + .../settings/editor_settings_service.gd | 17 ++ .../settings/editor_settings_service.gd.uid | 1 + forge/abilities/ForgeInstantEndBehavior.cs | 23 +++ .../abilities/ForgeInstantEndBehavior.cs.uid | 1 + project.godot | 2 +- .../player_controller/PlayerController.tscn | 4 +- .../resources/empowered_action.tres | 156 ++++++++++++++++++ .../resources/forge/exploding_sword.tres | 24 +++ .../resources/forge/instant_end_behavior.tres | 7 + .../scripts/PlayerController.cs | 26 ++- 27 files changed, 854 insertions(+), 9 deletions(-) create mode 100644 addons/rider-plugin/CONTRIBUTING.md create mode 100644 addons/rider-plugin/LICENCE create mode 100644 addons/rider-plugin/README.md create mode 100644 addons/rider-plugin/icons/icon.svg create mode 100644 addons/rider-plugin/icons/icon.svg.import create mode 100644 addons/rider-plugin/plugin.cfg create mode 100644 addons/rider-plugin/presets.json create mode 100644 addons/rider-plugin/rider-plugin.gd create mode 100644 addons/rider-plugin/rider-plugin.gd.uid create mode 100644 addons/rider-plugin/screenshots/Toolbar.png create mode 100644 addons/rider-plugin/screenshots/Toolbar.png.import create mode 100644 addons/rider-plugin/scripts/json_utils.gd create mode 100644 addons/rider-plugin/scripts/json_utils.gd.uid create mode 100644 addons/rider-plugin/scripts/locator/rider_locator_service.gd create mode 100644 addons/rider-plugin/scripts/locator/rider_locator_service.gd.uid create mode 100644 addons/rider-plugin/scripts/presets/preset_applier.gd create mode 100644 addons/rider-plugin/scripts/presets/preset_applier.gd.uid create mode 100644 addons/rider-plugin/scripts/settings/editor_settings_service.gd create mode 100644 addons/rider-plugin/scripts/settings/editor_settings_service.gd.uid create mode 100644 forge/abilities/ForgeInstantEndBehavior.cs create mode 100644 forge/abilities/ForgeInstantEndBehavior.cs.uid create mode 100644 scenes/player_controller/resources/empowered_action.tres create mode 100644 scenes/player_controller/resources/forge/exploding_sword.tres create mode 100644 scenes/player_controller/resources/forge/instant_end_behavior.tres diff --git a/addons/rider-plugin/CONTRIBUTING.md b/addons/rider-plugin/CONTRIBUTING.md new file mode 100644 index 00000000..16ddc5f3 --- /dev/null +++ b/addons/rider-plugin/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contribution + +Hello, thanks for taking time and helping out with the addon! + +Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. Please sign the CLA before sending the PR: https://www.jetbrains.com/agreements/cla/. + +#### Local setup + +Open the `godot-editor-plugin/CMakeLists.txt` in Rider 2026.1+, select `rider-gdextension` target in the run configuration selector. +There are some more fixes coming in 2026.2, which will allow working on gdscript and cpp files within the same workspace. + +#### Signing the binaries + +Plugin binaries need to be signed to comply with modern operating system security requirements. Unsigned dynamic libraries may fail to load or trigger security warnings: + +- **macOS**: Requires code signing (and often notarization) for `.dylib` files. Unsigned libraries may simply fail to load. +- **Windows**: SmartScreen may block unsigned binaries, and corporate environments often enforce signed code only. +- **Linux**: Generally allows unsigned `.so` files, but some sandboxed environments (Flatpak, Snap) impose restrictions. + +**How signing is implemented:** + +1. **TeamCity Configuration**: [ijplatform_master_Net_Deploy_Plugins_Public_Godot](https://buildserver.labs.intellij.net/buildConfiguration/ijplatform_master_Net_Deploy_Plugins_Public_Godot) (internal JetBrains link) + - Note: The automatic trigger on new tags is currently not working, so manual triggering is required. + +2. **Release Process**: + - First, use your GitHub Action to prepare a **pre-release** with a tag + - Manually trigger the TeamCity configuration on the necessary branch/tag + - The configuration will download assets, sign them, and upload them back (removing the pre-release flag) + - The pre-release flag is required at the start; otherwise, the signing configuration will skip the release (protection against double signing) + +3. **Signing existing releases**: + - You can run signing on already existing old releases/tags + - Mark them as pre-release first, then run the configuration on the necessary tag diff --git a/addons/rider-plugin/LICENCE b/addons/rider-plugin/LICENCE new file mode 100644 index 00000000..f1f8c075 --- /dev/null +++ b/addons/rider-plugin/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 JetBrains + +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. \ No newline at end of file diff --git a/addons/rider-plugin/README.md b/addons/rider-plugin/README.md new file mode 100644 index 00000000..0e7c4ad9 --- /dev/null +++ b/addons/rider-plugin/README.md @@ -0,0 +1,51 @@ +# JetBrains Rider Integration – Godot addon + +This addon currently provides two features: +1. Finds all Rider installations in the system and provides a selector on the `Text Editor` -> `External` tab in the settings to select one. +2. Provides the "Use Rider" toggle in the Godot toolbar and, when enabled, applies a set of editor settings recommended for working with JetBrains Rider. The goal is to make it trivial to switch between Rider‑optimized settings and stock Godot settings with a single click. + +## Quick start + +Requirements: +- Godot 4.2.2+ + +Install: +1. Inside the Godot editor, it can be installed from the AssetLib view or [downloaded](https://godotengine.org/asset-library/asset/4576) +2. [Optional] Change the initial value of `active` in the plugin.cfg +3. [Optional] Change the initial values in the presets.json file. +4. Enable "JetBrains Rider External Editor" plugin in the Project → Project Settings… → Plugins tab. + +Use: +- A toolbar toggle named "Use Rider" will appear. Click it to turn the preset On/Off. + +Screenshot: +![Toolbar toggle](screenshots/Toolbar.png) + +## What the toggle changes + +The preset values live in `presets.json`. + +When ON: +- Write the values from the "on" preset into the Editor Settings. + +When OFF: +- Write the values from the "off" preset into the Editor Settings. + +Note: The plugin does not currently auto‑set Rider’s executable path or flags. See Plans below. + +## Setting Rider to be the external editor + +The plugin automatically detects installed Rider versions on your system and provides a convenient dropdown menu to +select which installation to use as your external editor. + +- The plugin scans common installation locations for Rider on Windows, macOS, and Linux. +- Detected installations appear in the "Select Rider" dropdown in the toolbar. +- When you select a Rider installation, the plugin automatically updates the `dotnet/editor/external_editor_path` editor + setting. + +## License +See `addons/rider-plugin/LICENCE`. + +## Acknowledgements +Created by JetBrains to streamline using Rider with Godot. +Initial idea https://github.com/sszigeti/toggle_external_editor diff --git a/addons/rider-plugin/icons/icon.svg b/addons/rider-plugin/icons/icon.svg new file mode 100644 index 00000000..04cb8437 --- /dev/null +++ b/addons/rider-plugin/icons/icon.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/rider-plugin/icons/icon.svg.import b/addons/rider-plugin/icons/icon.svg.import new file mode 100644 index 00000000..05cbbf52 --- /dev/null +++ b/addons/rider-plugin/icons/icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://wrdrj6wednn8" +path="res://.godot/imported/icon.svg-45c914cff7482ba9564963fe65b548e4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rider-plugin/icons/icon.svg" +dest_files=["res://.godot/imported/icon.svg-45c914cff7482ba9564963fe65b548e4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/addons/rider-plugin/plugin.cfg b/addons/rider-plugin/plugin.cfg new file mode 100644 index 00000000..fcf0faa0 --- /dev/null +++ b/addons/rider-plugin/plugin.cfg @@ -0,0 +1,12 @@ +[plugin] + +name="JetBrains Rider External Editor" +description="Provides a toolbar toggle, to switch a set of settings on and off. Default set of settings helps to enable/disable the following settings recommended by the Rider documentation https://www.jetbrains.com/help/rider/Godot.html#optimize-godot-editor-for-rider" +author="JetBrains" +version="1.0.0" +script="rider-plugin.gd" + +[presets] + +active="on" +presets="presets.json" diff --git a/addons/rider-plugin/presets.json b/addons/rider-plugin/presets.json new file mode 100644 index 00000000..5e38bf3f --- /dev/null +++ b/addons/rider-plugin/presets.json @@ -0,0 +1,15 @@ +{ + "on": { + "text_editor/external/use_external_editor": true, + "interface/editor/import_resources_when_unfocused": true, + "interface/editor/save_on_focus_loss": true, + "text_editor/behavior/files/auto_reload_scripts_on_external_change": true, + "run/window_placement/game_embed_mode": -1 + }, + "off": { + "text_editor/external/use_external_editor": false, + "interface/editor/import_resources_when_unfocused": false, + "interface/editor/save_on_focus_loss": false, + "text_editor/behavior/files/auto_reload_scripts_on_external_change": false + } +} diff --git a/addons/rider-plugin/rider-plugin.gd b/addons/rider-plugin/rider-plugin.gd new file mode 100644 index 00000000..9bbb7cd5 --- /dev/null +++ b/addons/rider-plugin/rider-plugin.gd @@ -0,0 +1,66 @@ +@tool +extends EditorPlugin + +var editor_settings: EditorSettings +var checkbutton: CheckButton +var _preset_applier: PresetApplier +var _settings_service: EditorSettingsService +var _locator_service: RiderLocatorService +var _plugin_cfg_path: String +var _presets_json_path: String + +func _enter_tree() -> void: + editor_settings = EditorInterface.get_editor_settings() + var script_path := (get_script() as Script).resource_path + var plugin_dir := script_path.get_base_dir() + _plugin_cfg_path = plugin_dir + "/plugin.cfg" + var cfg := ConfigFile.new() + var err := cfg.load(_plugin_cfg_path) + if err != OK: + push_warning("Failed to load plugin.cfg: %s" % [err]) + return + var active_str := str(cfg.get_value("presets", "active", "on")) + var is_active := active_str == "on" + var presets_rel_path := str(cfg.get_value("presets", "presets", "presets.json")) + _presets_json_path = plugin_dir + "/" + presets_rel_path + + # Build UI + checkbutton = CheckButton.new() + checkbutton.text = "Use Rider" + checkbutton.tooltip_text = "Shortcut for setting recommended settings" + checkbutton.button_pressed = is_active + checkbutton.pressed.connect(_on_checkbutton_pressed) + add_control_to_container(EditorPlugin.CONTAINER_TOOLBAR, checkbutton) + + # Initialize services and panel + _settings_service = EditorSettingsService.new() + _locator_service = RiderLocatorService.new() + _preset_applier = PresetApplier.new(_presets_json_path) + + _locator_service.add_selector_in_editor_interface(_settings_service) + + # Ensure settings reflect current state on startup + _preset_applier.apply_preset(editor_settings, is_active) + +func _on_checkbutton_pressed() -> void: + var cfg := ConfigFile.new() + if cfg.load(_plugin_cfg_path) != OK: + push_warning("Failed to load plugin.cfg to update state") + return + var is_active := checkbutton.button_pressed + var key := _preset_applier.get_preset_key(is_active) + cfg.set_value("presets", "active", key) + var save_err := cfg.save(_plugin_cfg_path) + if save_err != OK: + push_warning("Failed to save plugin.cfg: %s" % [save_err]) + # Apply selected preset to editor settings + _preset_applier.apply_preset(editor_settings, is_active) + +func _exit_tree() -> void: + if checkbutton != null: + remove_control_from_container(EditorPlugin.CONTAINER_TOOLBAR, checkbutton) + checkbutton.queue_free() + + var args = OS.get_cmdline_args() + if "--rider-addon-tests" in args: + print("==== rider-addon-tests finished ====") diff --git a/addons/rider-plugin/rider-plugin.gd.uid b/addons/rider-plugin/rider-plugin.gd.uid new file mode 100644 index 00000000..80edc2ba --- /dev/null +++ b/addons/rider-plugin/rider-plugin.gd.uid @@ -0,0 +1 @@ +uid://c1x58xm2w1n20 diff --git a/addons/rider-plugin/screenshots/Toolbar.png b/addons/rider-plugin/screenshots/Toolbar.png new file mode 100644 index 00000000..482b9258 --- /dev/null +++ b/addons/rider-plugin/screenshots/Toolbar.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bfac26f06f5ec6f33eaf81cd24e4b8472123f989a27191c006078269b74faf8 +size 47400 diff --git a/addons/rider-plugin/screenshots/Toolbar.png.import b/addons/rider-plugin/screenshots/Toolbar.png.import new file mode 100644 index 00000000..7466a982 --- /dev/null +++ b/addons/rider-plugin/screenshots/Toolbar.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cypcl8lffxy6r" +path="res://.godot/imported/Toolbar.png-a521e2493bd3c08a829245b3129bb58f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/rider-plugin/screenshots/Toolbar.png" +dest_files=["res://.godot/imported/Toolbar.png-a521e2493bd3c08a829245b3129bb58f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/addons/rider-plugin/scripts/json_utils.gd b/addons/rider-plugin/scripts/json_utils.gd new file mode 100644 index 00000000..660086b3 --- /dev/null +++ b/addons/rider-plugin/scripts/json_utils.gd @@ -0,0 +1,23 @@ +@tool +## Simple JSON helpers for editor/runtime use. +## Keeps file and JSON parsing concerns out of feature code. +class_name JsonUtils + +static func load_from_file(path: String) -> Variant: + # Returns parsed JSON value (Dictionary/Array/etc.) or null on error. + var file := FileAccess.open(path, FileAccess.READ) + if file == null: + push_warning("JsonUtils: Failed to open file: %s" % path) + return null + var text := file.get_as_text() + file.close() + var data: Variant = JSON.parse_string(text) + if data == null: + push_warning("JsonUtils: Invalid JSON in file: %s" % path) + return null + return data + +static func load_dict_from_file(path: String) -> Dictionary: + # Returns Dictionary or empty {} on error. + var data := load_from_file(path) as Dictionary + return data diff --git a/addons/rider-plugin/scripts/json_utils.gd.uid b/addons/rider-plugin/scripts/json_utils.gd.uid new file mode 100644 index 00000000..e780a44c --- /dev/null +++ b/addons/rider-plugin/scripts/json_utils.gd.uid @@ -0,0 +1 @@ +uid://bci4kmk7h4j6a diff --git a/addons/rider-plugin/scripts/locator/rider_locator_service.gd b/addons/rider-plugin/scripts/locator/rider_locator_service.gd new file mode 100644 index 00000000..8f54727d --- /dev/null +++ b/addons/rider-plugin/scripts/locator/rider_locator_service.gd @@ -0,0 +1,112 @@ +@tool +extends RefCounted +class_name RiderLocatorService + +var _installations_found: Array = [] +var _thread: Thread = null + +func get_installations() -> Array: + var result: Array = RiderLocator.new().get_installations() # from the gdextension + return result + +# todo +func fix_external_editor_if_supplied_in_commandline(_settings_service: EditorSettingsService, editor_settings: EditorSettings) -> bool: + # When Godot is started from Rider (or vice versa), we may receive the Rider path + # via command-line so we can keep Godot's external editor setting in sync. + # Supported form (only this one): + # --my_rider_path="/absolute/path/to/rider with possible spaces" + var args : Array = OS.get_cmdline_args() + var provided_rider_path := "" + for a_raw in args: + var a: String = str(a_raw) + if a.begins_with("--my_rider_path="): + provided_rider_path = a.substr("--my_rider_path=".length()) + break + + if provided_rider_path.is_empty(): + return false + + provided_rider_path = trim_quotes(provided_rider_path) + + # Validate existence (file or dir) + var looks_existing := FileAccess.file_exists(provided_rider_path) || DirAccess.dir_exists_absolute(provided_rider_path) + if looks_existing: + _settings_service.set_external_editor_path(editor_settings, provided_rider_path) + print("Rider path provided via CLI (my_rider_path): ", provided_rider_path) + return true + else: + push_warning("my_rider_path was provided but does not exist: %s" % [provided_rider_path]) + return false + +func trim_quotes(s: String) -> String: + if s.begins_with('"') and s.ends_with('"') and s.length() >= 2: + return s.substr(1, s.length() - 2) + return s + + +func add_selector_in_editor_interface(_settings_service: EditorSettingsService): + _update_selector(_installations_found) + + if _installations_found.is_empty() and _thread == null: + _thread = Thread.new() + _thread.start(_load_installations) + +func _load_installations() -> void: + var array: Array = get_installations() + call_deferred("_on_installations_loaded", array) + +func _on_installations_loaded(array: Array): + _installations_found = array + if _thread: + _thread.wait_to_finish() + _thread = null + _update_selector(_installations_found) + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE and _thread != null: + _thread.wait_to_finish() + +func _update_selector(array: Array): + var name := "text_editor/external/editor" + var settings := EditorInterface.get_editor_settings() + + if !(settings.has_setting(name)): + settings.set(name, 0) + + var installations: Array = ["Custom"] + for element in array: + var display_name: String = element.get("display", "") + # Replace special characters that break PROPERTY_HINT_ENUM format + # Comma is the enum delimiter, colon is used for explicit value assignment + display_name = display_name.replace(",", " •").replace(":", " -") + installations.append(display_name) + var options :String = ",".join(installations) + + settings.add_property_info({ + "name": name, + "type":TYPE_INT, + "hint":PROPERTY_HINT_ENUM, + "hint_string": options + }) + + # Connect to settings changes to update external editor path when selection changes + if not settings.settings_changed.is_connected(_on_selection_changed): + settings.settings_changed.connect(_on_selection_changed.bind()) + +func _on_selection_changed() -> void: + var name := "text_editor/external/editor" + var settings := EditorInterface.get_editor_settings() + var selected_index: int = settings.get_setting(name) + + # Index 0 is "Custom", so user manages the path manually + if selected_index == 0: + return + + # Map to actual installation (offset by 1 because of "Custom" at index 0) + var installation_index := selected_index - 1 + var installations_array = _installations_found + if installation_index >= 0 and installation_index < installations_array.size(): + var installation = installations_array[installation_index] + var new_path: String = installation.get("path", "") + if not new_path.is_empty(): + EditorSettingsService.new().set_external_editor_path(settings, new_path) diff --git a/addons/rider-plugin/scripts/locator/rider_locator_service.gd.uid b/addons/rider-plugin/scripts/locator/rider_locator_service.gd.uid new file mode 100644 index 00000000..f7fcdaf6 --- /dev/null +++ b/addons/rider-plugin/scripts/locator/rider_locator_service.gd.uid @@ -0,0 +1 @@ +uid://cfsu1rbg0ypem diff --git a/addons/rider-plugin/scripts/presets/preset_applier.gd b/addons/rider-plugin/scripts/presets/preset_applier.gd new file mode 100644 index 00000000..b09d0253 --- /dev/null +++ b/addons/rider-plugin/scripts/presets/preset_applier.gd @@ -0,0 +1,35 @@ +@tool +extends RefCounted +class_name PresetApplier + +var presets_path: String + +func _init(p_path: String) -> void: + presets_path = p_path + +func get_preset_key(is_active: bool) -> String: + return "on" if is_active else "off" + +func apply_preset(editor_settings: EditorSettings, is_active: bool) -> void: + var data: Dictionary = JsonUtils.load_dict_from_file(presets_path) + if data.is_empty(): + push_warning("Failed to load presets: %s" % presets_path) + return + + var new_preset_key := get_preset_key(is_active) + var previous_preset_key := get_preset_key(not is_active) + + if not data.has(new_preset_key): + push_warning("Preset '%s' not found in presets.json" % new_preset_key) + return + + var new_preset := data[new_preset_key] as Dictionary + # Reset keys from previous preset that are missing in the new preset + if data.has(previous_preset_key): + var previous_preset := data[previous_preset_key] as Dictionary + for key in previous_preset: + if not new_preset.has(key): + editor_settings.set_setting(str(key), editor_settings.property_get_revert(str(key))) + # Apply the new preset + for key in new_preset: + editor_settings.set_setting(str(key), new_preset[key]) diff --git a/addons/rider-plugin/scripts/presets/preset_applier.gd.uid b/addons/rider-plugin/scripts/presets/preset_applier.gd.uid new file mode 100644 index 00000000..af4fdde7 --- /dev/null +++ b/addons/rider-plugin/scripts/presets/preset_applier.gd.uid @@ -0,0 +1 @@ +uid://bjiaycxso8h8u diff --git a/addons/rider-plugin/scripts/settings/editor_settings_service.gd b/addons/rider-plugin/scripts/settings/editor_settings_service.gd new file mode 100644 index 00000000..e2f2d978 --- /dev/null +++ b/addons/rider-plugin/scripts/settings/editor_settings_service.gd @@ -0,0 +1,17 @@ +@tool +extends RefCounted +class_name EditorSettingsService + +func set_external_editor_path(editor_settings: EditorSettings, path: String) -> void: + editor_settings.set_setting("text_editor/external/exec_path", path) + +func has_valid_external_editor_path(editor_settings: EditorSettings) -> bool: + var has_setting: bool = editor_settings.has_setting("text_editor/external/exec_path") + if not has_setting: + return false + var path : String = editor_settings.get_setting("text_editor/external/exec_path") + var exists := not path.is_empty() && (FileAccess.file_exists(path) || DirAccess.dir_exists_absolute(path)) + return exists + +func set_use_external_editor(editor_settings: EditorSettings, enabled: bool) -> void: + editor_settings.set_setting("text_editor/external/use_external_editor", enabled) diff --git a/addons/rider-plugin/scripts/settings/editor_settings_service.gd.uid b/addons/rider-plugin/scripts/settings/editor_settings_service.gd.uid new file mode 100644 index 00000000..3f50ddcc --- /dev/null +++ b/addons/rider-plugin/scripts/settings/editor_settings_service.gd.uid @@ -0,0 +1 @@ +uid://bdiu78ot0rrkc diff --git a/forge/abilities/ForgeInstantEndBehavior.cs b/forge/abilities/ForgeInstantEndBehavior.cs new file mode 100644 index 00000000..f14de0ef --- /dev/null +++ b/forge/abilities/ForgeInstantEndBehavior.cs @@ -0,0 +1,23 @@ +using Gamesmiths.Forge.Abilities; +using Gamesmiths.Forge.Godot.Resources.Abilities; +using Godot; + +namespace Movementtests.forge.abilities; + +public class InstantEndBehavior : IAbilityBehavior +{ + public void OnStarted(AbilityBehaviorContext context) + { + context.AbilityHandle.CommitAbility(); + context.InstanceHandle.End(); + } + + public void OnEnded(AbilityBehaviorContext context) {} +} + +[Tool] +[GlobalClass] +public partial class ForgeInstantEndBehavior : ForgeAbilityBehavior +{ + public override IAbilityBehavior GetBehavior() => new InstantEndBehavior(); +} \ No newline at end of file diff --git a/forge/abilities/ForgeInstantEndBehavior.cs.uid b/forge/abilities/ForgeInstantEndBehavior.cs.uid new file mode 100644 index 00000000..6a011639 --- /dev/null +++ b/forge/abilities/ForgeInstantEndBehavior.cs.uid @@ -0,0 +1 @@ +uid://c7s5v7ii4nujg diff --git a/project.godot b/project.godot index 50e56ae6..54716ac1 100644 --- a/project.godot +++ b/project.godot @@ -46,7 +46,7 @@ movie_writer/movie_file="D:/Godot/Projects/movement-tests/communication/movie.av [editor_plugins] -enabled=PackedStringArray("res://addons/csg_toolkit/plugin.cfg", "res://addons/forge/plugin.cfg", "res://addons/gdUnit4/plugin.cfg", "res://addons/godot_state_charts/plugin.cfg", "res://addons/guide/plugin.cfg", "res://addons/maaacks_game_template/plugin.cfg", "res://addons/shaker/plugin.cfg") +enabled=PackedStringArray("res://addons/csg_toolkit/plugin.cfg", "res://addons/forge/plugin.cfg", "res://addons/gdUnit4/plugin.cfg", "res://addons/godot_state_charts/plugin.cfg", "res://addons/guide/plugin.cfg", "res://addons/maaacks_game_template/plugin.cfg", "res://addons/rider-plugin/plugin.cfg", "res://addons/shaker/plugin.cfg") [gui] diff --git a/scenes/player_controller/PlayerController.tscn b/scenes/player_controller/PlayerController.tscn index 413a1407..4bb0fa02 100644 --- a/scenes/player_controller/PlayerController.tscn +++ b/scenes/player_controller/PlayerController.tscn @@ -19,7 +19,6 @@ [ext_resource type="Resource" uid="uid://t612lts1wi1s" path="res://inputs/base_mode/move_right.tres" id="6_q7bng"] [ext_resource type="Script" uid="uid://cwbvxlfvmocc1" path="res://scenes/player_controller/scripts/StairsSystem.cs" id="7_bmt5a"] [ext_resource type="Resource" uid="uid://brswsknpgwal2" path="res://inputs/base_mode/move_front.tres" id="7_m8gvy"] -[ext_resource type="Resource" uid="uid://7dpkk5rk3di5" path="res://scenes/player_controller/resources/forge/empowered_action.tres" id="7_qheee"] [ext_resource type="PackedScene" uid="uid://bctpe34ddamg5" path="res://scenes/components/knockback/CKnockback.tscn" id="7_x835q"] [ext_resource type="Resource" uid="uid://s1l0n1iitc6m" path="res://inputs/base_mode/move_back.tres" id="8_jb43f"] [ext_resource type="Resource" uid="uid://j1o5ud0plk4" path="res://inputs/base_mode/aim_release.tres" id="8_lhb11"] @@ -28,6 +27,7 @@ [ext_resource type="PackedScene" uid="uid://wq1okogkhc5l" path="res://scenes/player_controller/components/mantle/mantle_system.tscn" id="8_qu4wy"] [ext_resource type="AudioStream" uid="uid://clfggn87oeg1s" path="res://scenes/player_controller/audio/InteractiveSFX.tres" id="9_jb43f"] [ext_resource type="Resource" uid="uid://bebstkm608wxx" path="res://inputs/base_mode/aim_pressed.tres" id="9_nob5r"] +[ext_resource type="Resource" uid="uid://dccuj66egxfwh" path="res://scenes/player_controller/resources/empowered_action.tres" id="10_2rkt1"] [ext_resource type="Resource" uid="uid://bdit2jy5gbpts" path="res://inputs/base_mode/jump.tres" id="10_4u7i3"] [ext_resource type="Script" uid="uid://cxihb42t2mfqi" path="res://addons/forge/nodes/ForgeAttributeSet.cs" id="10_pw5r7"] [ext_resource type="Script" uid="uid://ccovd5i0wr3kk" path="res://addons/forge/editor/attributes/AttributeValues.cs" id="11_2rkt1"] @@ -142,8 +142,8 @@ bg_color = Color(0.15869555, 0.64034444, 0.906125, 1) [node name="Player" type="CharacterBody3D" unique_id=709076448] collision_mask = 272 script = ExtResource("1_poq2x") -EmpoweredAction = ExtResource("7_qheee") ManaRegen = ExtResource("3_n24vh") +EmpoweredActionAbility = ExtResource("10_2rkt1") AbilityLoadout = [ExtResource("4_11013")] AimAssistStrength = 0.3 AimAssistReductionWhenCloseToTarget = 0.1 diff --git a/scenes/player_controller/resources/empowered_action.tres b/scenes/player_controller/resources/empowered_action.tres new file mode 100644 index 00000000..fa38a645 --- /dev/null +++ b/scenes/player_controller/resources/empowered_action.tres @@ -0,0 +1,156 @@ +[gd_resource type="Resource" script_class="ForgeAbilityData" format=3 uid="uid://dccuj66egxfwh"] + +[ext_resource type="Script" uid="uid://dhxfbxh54pyxp" path="res://addons/forge/resources/abilities/ForgeAbilityData.cs" id="1_7rg1m"] +[ext_resource type="Script" uid="uid://cw525n4mjqgw0" path="res://addons/forge/resources/ForgeTagContainer.cs" id="1_16foq"] +[ext_resource type="Resource" uid="uid://crgwob8t8yysq" path="res://scenes/player_controller/resources/forge/instant_end_behavior.tres" id="1_odwcb"] +[ext_resource type="Script" uid="uid://cn3b4ya15fg7e" path="res://addons/forge/resources/magnitudes/ForgeScalableFloat.cs" id="1_qpqxp"] +[ext_resource type="Script" uid="uid://2gm1hdhi8u08" path="res://addons/forge/resources/magnitudes/ForgeModifierMagnitude.cs" id="2_3t6pm"] +[ext_resource type="Script" uid="uid://dngf30hxy5go4" path="res://addons/forge/resources/components/ModifierTags.cs" id="2_g4w5p"] +[ext_resource type="Script" uid="uid://1hgogislo1l6" path="res://addons/forge/resources/magnitudes/ForgeScalableInt.cs" id="3_16foq"] +[ext_resource type="Script" uid="uid://b83hf13nj37k3" path="res://addons/forge/resources/ForgeEffectData.cs" id="4_g4w5p"] +[ext_resource type="Script" uid="uid://cmrsxccn0ei4j" path="res://addons/forge/resources/ForgeCue.cs" id="7_ekcln"] +[ext_resource type="Script" uid="uid://bdfcavbjyhxxa" path="res://addons/forge/resources/ForgeModifier.cs" id="8_odwcb"] + +[sub_resource type="Resource" id="Resource_h116a"] +script = ExtResource("1_16foq") +ContainerTags = Array[String](["cooldown.empoweredaction"]) +metadata/_custom_type_script = "uid://cw525n4mjqgw0" + +[sub_resource type="Resource" id="Resource_mgrka"] +script = ExtResource("2_g4w5p") +TagsToAdd = SubResource("Resource_h116a") +metadata/_custom_type_script = "uid://dngf30hxy5go4" + +[sub_resource type="Resource" id="Resource_ekcln"] +script = ExtResource("1_qpqxp") +BaseValue = 1.0 + +[sub_resource type="Resource" id="Resource_odwcb"] +script = ExtResource("1_qpqxp") + +[sub_resource type="Resource" id="Resource_psy6a"] +script = ExtResource("1_qpqxp") + +[sub_resource type="Resource" id="Resource_j4bwy"] +script = ExtResource("1_qpqxp") +BaseValue = 1.0 + +[sub_resource type="Resource" id="Resource_s60jg"] +script = ExtResource("1_qpqxp") + +[sub_resource type="Resource" id="Resource_wdif6"] +script = ExtResource("1_qpqxp") + +[sub_resource type="Resource" id="Resource_inx6r"] +script = ExtResource("1_qpqxp") +BaseValue = 0.5 +metadata/_custom_type_script = "uid://cn3b4ya15fg7e" + +[sub_resource type="Resource" id="Resource_4jm88"] +script = ExtResource("2_3t6pm") +ScalableFloat = SubResource("Resource_inx6r") +Coefficient = SubResource("Resource_j4bwy") +PreMultiplyAdditiveValue = SubResource("Resource_wdif6") +PostMultiplyAdditiveValue = SubResource("Resource_s60jg") +CalculatorCoefficient = SubResource("Resource_ekcln") +CalculatorPreMultiplyAdditiveValue = SubResource("Resource_psy6a") +CalculatorPostMultiplyAdditiveValue = SubResource("Resource_odwcb") +metadata/_custom_type_script = "uid://2gm1hdhi8u08" + +[sub_resource type="Resource" id="Resource_lmnuh"] +script = ExtResource("3_16foq") +BaseValue = 1 + +[sub_resource type="Resource" id="Resource_xp6fe"] +script = ExtResource("3_16foq") +BaseValue = 1 + +[sub_resource type="Resource" id="Resource_egh2b"] +script = ExtResource("4_g4w5p") +Name = "Empowered Action Cooldown" +Modifiers = [] +Components = Array[Object]([SubResource("Resource_mgrka")]) +Executions = [] +DurationType = 2 +Duration = SubResource("Resource_4jm88") +StackLimit = SubResource("Resource_xp6fe") +InitialStack = SubResource("Resource_lmnuh") +Cues = [] +metadata/_custom_type_script = "uid://b83hf13nj37k3" + +[sub_resource type="Resource" id="Resource_4mhqs"] +script = ExtResource("1_16foq") +ContainerTags = Array[String](["cues.resources.mana"]) +metadata/_custom_type_script = "uid://cw525n4mjqgw0" + +[sub_resource type="Resource" id="Resource_itmis"] +script = ExtResource("7_ekcln") +CueKeys = SubResource("Resource_4mhqs") +MaxValue = 100 +MagnitudeType = 2 +MagnitudeAttribute = "PlayerAttributeSet.Mana" +metadata/_custom_type_script = "uid://cmrsxccn0ei4j" + +[sub_resource type="Resource" id="Resource_8dsdw"] +script = ExtResource("3_16foq") +BaseValue = 1 + +[sub_resource type="Resource" id="Resource_clulf"] +script = ExtResource("1_qpqxp") +BaseValue = 1.0 + +[sub_resource type="Resource" id="Resource_4kkx2"] +script = ExtResource("1_qpqxp") + +[sub_resource type="Resource" id="Resource_5vdhj"] +script = ExtResource("1_qpqxp") + +[sub_resource type="Resource" id="Resource_nx5he"] +script = ExtResource("1_qpqxp") +BaseValue = 1.0 + +[sub_resource type="Resource" id="Resource_st5kh"] +script = ExtResource("1_qpqxp") + +[sub_resource type="Resource" id="Resource_wl5ql"] +script = ExtResource("1_qpqxp") + +[sub_resource type="Resource" id="Resource_uv4a1"] +script = ExtResource("1_qpqxp") +BaseValue = -30.0 +metadata/_custom_type_script = "uid://cn3b4ya15fg7e" + +[sub_resource type="Resource" id="Resource_dhni4"] +script = ExtResource("8_odwcb") +Attribute = "PlayerAttributeSet.Mana" +ScalableFloat = SubResource("Resource_uv4a1") +Coefficient = SubResource("Resource_nx5he") +PreMultiplyAdditiveValue = SubResource("Resource_wl5ql") +PostMultiplyAdditiveValue = SubResource("Resource_st5kh") +CalculatorCoefficient = SubResource("Resource_clulf") +CalculatorPreMultiplyAdditiveValue = SubResource("Resource_5vdhj") +CalculatorPostMultiplyAdditiveValue = SubResource("Resource_4kkx2") +metadata/_custom_type_script = "uid://bdfcavbjyhxxa" + +[sub_resource type="Resource" id="Resource_w5rmc"] +script = ExtResource("3_16foq") +BaseValue = 1 + +[sub_resource type="Resource" id="Resource_mtef8"] +script = ExtResource("4_g4w5p") +Name = "Empowered Action Cost" +Modifiers = Array[Object]([SubResource("Resource_dhni4")]) +Components = [] +Executions = [] +StackLimit = SubResource("Resource_w5rmc") +InitialStack = SubResource("Resource_8dsdw") +Cues = Array[Object]([SubResource("Resource_itmis")]) +metadata/_custom_type_script = "uid://b83hf13nj37k3" + +[resource] +script = ExtResource("1_7rg1m") +Name = "Empowered Action" +CooldownEffects = [SubResource("Resource_egh2b")] +CostEffect = SubResource("Resource_mtef8") +AbilityBehavior = ExtResource("1_odwcb") +metadata/_custom_type_script = "uid://dhxfbxh54pyxp" diff --git a/scenes/player_controller/resources/forge/exploding_sword.tres b/scenes/player_controller/resources/forge/exploding_sword.tres new file mode 100644 index 00000000..8c6faa07 --- /dev/null +++ b/scenes/player_controller/resources/forge/exploding_sword.tres @@ -0,0 +1,24 @@ +[gd_resource type="Resource" script_class="ForgeAbilityData" format=3 uid="uid://ifeavnlps7hy"] + +[ext_resource type="Script" uid="uid://cw525n4mjqgw0" path="res://addons/forge/resources/ForgeTagContainer.cs" id="1_l3coe"] +[ext_resource type="Script" uid="uid://dhxfbxh54pyxp" path="res://addons/forge/resources/abilities/ForgeAbilityData.cs" id="1_ot53g"] +[ext_resource type="Script" uid="uid://dpakv7agvir6y" path="res://addons/forge/resources/ForgeTag.cs" id="2_un8hi"] + +[sub_resource type="Resource" id="Resource_l76xb"] +script = ExtResource("1_l3coe") +ContainerTags = Array[String](["abilities.weapon"]) +metadata/_custom_type_script = "uid://cw525n4mjqgw0" + +[sub_resource type="Resource" id="Resource_g5tg7"] +script = ExtResource("2_un8hi") +Tag = "events.weapon.startedflying" +metadata/_custom_type_script = "uid://dpakv7agvir6y" + +[resource] +script = ExtResource("1_ot53g") +Name = "Exploding Sword" +CooldownEffects = null +TriggerSource = 1 +TriggerTag = SubResource("Resource_g5tg7") +AbilityTags = SubResource("Resource_l76xb") +metadata/_custom_type_script = "uid://dhxfbxh54pyxp" diff --git a/scenes/player_controller/resources/forge/instant_end_behavior.tres b/scenes/player_controller/resources/forge/instant_end_behavior.tres new file mode 100644 index 00000000..2e10891f --- /dev/null +++ b/scenes/player_controller/resources/forge/instant_end_behavior.tres @@ -0,0 +1,7 @@ +[gd_resource type="Resource" script_class="ForgeInstantEndBehavior" format=3 uid="uid://crgwob8t8yysq"] + +[ext_resource type="Script" uid="uid://c7s5v7ii4nujg" path="res://forge/abilities/ForgeInstantEndBehavior.cs" id="1_hly5b"] + +[resource] +script = ExtResource("1_hly5b") +metadata/_custom_type_script = "uid://c7s5v7ii4nujg" diff --git a/scenes/player_controller/scripts/PlayerController.cs b/scenes/player_controller/scripts/PlayerController.cs index 4cef0e23..743c7148 100644 --- a/scenes/player_controller/scripts/PlayerController.cs +++ b/scenes/player_controller/scripts/PlayerController.cs @@ -114,13 +114,19 @@ public partial class PlayerController : CharacterBody3D, [ExportGroup("General")] [Export] public ForgeTagContainer BaseTags { get; set; } = new(); - [Export] public REmpoweredAction EmpoweredAction = null!; [Export] public RManaRegen ManaRegen = null!; [ExportGroup("Abilities")] + [ExportSubgroup("Common and defaults")] + [Export] public ForgeAbilityData EmpoweredActionAbility = null!; + [Export] public ForgeAbilityData[] DefaultPermanentAbilities = []; [ExportSubgroup("WeaponThrow")] [Export] public RExplodingSword[] AbilityLoadout = []; + [ExportGroup("Effects")] + [ExportSubgroup("Common and defaults")] + [Export] public ForgeEffectData[] DefaultPermanentEffects = []; + // Combat stuff [ExportCategory("Combat")] [ExportGroup("General")] @@ -469,13 +475,21 @@ public partial class PlayerController : CharacterBody3D, Abilities = new(this); Events = new(); - var empoweredActionData = EmpoweredAction.Ability(tagsManager); - // Grant permanently _empoweredActionHandle = Abilities.GrantAbilityPermanently( - empoweredActionData, + EmpoweredActionAbility.GetAbilityData(), abilityLevel: 1, levelOverridePolicy: LevelComparison.None, sourceEntity: this); + foreach (var ability in DefaultPermanentAbilities) + { + Abilities.GrantAbilityPermanently( + ability.GetAbilityData(), + abilityLevel: 1, + levelOverridePolicy: LevelComparison.None, + sourceEntity: this); + } + // var empoweredActionData = EmpoweredAction.Ability(tagsManager); + // // Grant permanently var manaRegenEffect = new Effect(ManaRegen.ManaRegen(tagsManager), new EffectOwnership(this, this)); _manaRegenEffectHandle = EffectsManager.ApplyEffect(manaRegenEffect); @@ -2068,8 +2082,8 @@ public partial class PlayerController : CharacterBody3D, // Inhibit Mana Regeneration for a while after using an empowered action // TODO: Use Forge events instead of relying on direct referencing - _manaRegenEffectHandle!.SetInhibit(true); - GetTree().CreateTimer(EmpoweredAction.ManaRegenPause).Timeout += () => {_manaRegenEffectHandle!.SetInhibit(false);}; + // _manaRegenEffectHandle!.SetInhibit(true); + // GetTree().CreateTimer(EmpoweredAction.ManaRegenPause).Timeout += () => {_manaRegenEffectHandle!.SetInhibit(false);}; _isWallJumpAvailable = true; _canDashAirborne = true;