Added rider plugin and turned Empowered Action into a forge-resources-managed ability

This commit is contained in:
2026-04-03 15:33:46 +02:00
parent c1108e96d7
commit 673368a200
27 changed files with 854 additions and 9 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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 Rideroptimized 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 autoset Riders 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

View File

@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.0"
id="katman_1"
x="0px"
y="0px"
viewBox="0 0 512 512"
xml:space="preserve"
sodipodi:docname="JetBrains Rider Icon.svg"
width="512"
height="512"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs43" /><sodipodi:namedview
id="namedview41"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
showgrid="false"
inkscape:zoom="1.0980553"
inkscape:cx="256.36233"
inkscape:cy="212.19333"
inkscape:window-width="1920"
inkscape:window-height="1027"
inkscape:window-x="-8"
inkscape:window-y="22"
inkscape:window-maximized="1"
inkscape:current-layer="katman_1" />
<style
type="text/css"
id="style2">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#SVGID_00000130622934180993703410000017420799098261276343_);}
.st2{fill:url(#SVGID_00000060739771362873723200000017991140373209755022_);}
.st3{fill:#FFFFFF;}
</style>
<symbol
id="rider"
viewBox="-35 -35 70 70">
<linearGradient
id="SVGID_1_"
gradientUnits="userSpaceOnUse"
x1="30.4897"
y1="5.1188998"
x2="-23.4683"
y2="-25.8451">
<stop
offset="0"
style="stop-color:#DD1265"
id="stop4" />
<stop
offset="0.483"
style="stop-color:#DD1265"
id="stop6" />
<stop
offset="0.942"
style="stop-color:#FDB60D"
id="stop8" />
</linearGradient>
<path
class="st0"
d="M 35,-7.7 -14.1,-35 18.8,13.9 25.5,9.5 Z"
id="path11" />
<linearGradient
id="SVGID_00000169517474296634577950000016895268102491994795_"
gradientUnits="userSpaceOnUse"
x1="-1.5839"
y1="-28.888"
x2="19.805099"
y2="30.174999">
<stop
offset="0.139"
style="stop-color:#087CFA"
id="stop13" />
<stop
offset="0.476"
style="stop-color:#DD1265"
id="stop15" />
<stop
offset="0.958"
style="stop-color:#087CFA"
id="stop17" />
</linearGradient>
<path
style="fill:url(#SVGID_00000169517474296634577950000016895268102491994795_)"
d="M 15.5,-18.9 9.3,-33.9 -4.3,-20.5 1.2,28.1 14.4,35 35,23 Z"
id="path20" />
<linearGradient
id="SVGID_00000135668350575375336630000014638141720088491653_"
gradientUnits="userSpaceOnUse"
x1="-17.5865"
y1="-27.071199"
x2="-1.7875"
y2="29.073799">
<stop
offset="0.278"
style="stop-color:#DD1265"
id="stop22" />
<stop
offset="0.968"
style="stop-color:#FDB60D"
id="stop24" />
</linearGradient>
<path
style="fill:url(#SVGID_00000135668350575375336630000014638141720088491653_)"
d="m -14.1,-35 -20.9,14.1 7.8,48.1 20.1,7.7 26,-21 z"
id="path27" />
<path
d="M 21,-21 H -21 V 21 H 21 Z"
id="path29" />
<path
class="st3"
d="m -0.6,13.6 h -15.8 v 2.7 h 15.8 z"
id="path31" />
<path
class="st3"
d="m 0.5,-15.8 h 6.2 c 5,0 8.4,3.4 8.4,7.7 0,4.4 -3.4,7.9 -8.4,7.9 L 0.5,0 Z m 3.5,3.2 v 9.5 h 2.7 c 2.8,0 4.8,-1.9 4.8,-4.6 0,-2.8 -1.9,-4.7 -4.8,-4.7 z"
id="path33" />
<path
class="st3"
d="m -15.7,-15.8 h 7.2 c 2,0 3.5,0.6 4.6,1.6 0.9,0.9 1.3,2.1 1.3,3.6 v 0.1 c 0,1.3 -0.3,2.3 -0.9,3.1 -0.6,0.8 -1.4,1.4 -2.4,1.8 L -2,0 h -4.1 l -3.3,-4.8 h -2.9 V 0 h -3.5 z m 7,7.7 c 0.8,0 1.5,-0.2 2,-0.6 0.5,-0.4 0.7,-1 0.7,-1.6 v -0.1 c 0,-0.8 -0.2,-1.3 -0.7,-1.7 -0.5,-0.3 -1.1,-0.6 -2,-0.6 h -3.4 v 4.5 h 3.4 z"
id="path35" />
</symbol>
<use
xlink:href="#rider"
width="70"
height="70"
x="-35"
y="-35"
transform="matrix(7.0682519,0,0,7.2481031,256,256)"
style="overflow:visible"
id="use38" />
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -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

View File

@@ -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"

View File

@@ -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
}
}

View File

@@ -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 ====")

View File

@@ -0,0 +1 @@
uid://c1x58xm2w1n20

BIN
addons/rider-plugin/screenshots/Toolbar.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1 @@
uid://bci4kmk7h4j6a

View File

@@ -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)

View File

@@ -0,0 +1 @@
uid://cfsu1rbg0ypem

View File

@@ -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])

View File

@@ -0,0 +1 @@
uid://bjiaycxso8h8u

View File

@@ -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)

View File

@@ -0,0 +1 @@
uid://bdiu78ot0rrkc

View File

@@ -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();
}

View File

@@ -0,0 +1 @@
uid://c7s5v7ii4nujg

View File

@@ -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]

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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;