Refactor creator dialogs to use centralized settings management and improve UI state handling

This commit is contained in:
MaddoScientisto 2026-02-08 20:33:52 +01:00
commit 99281deef4
12 changed files with 510 additions and 242 deletions

View file

@ -5,32 +5,83 @@ extends Window
# Base class for all creator dialogs
# Ensures consistent initialization and UI building patterns
const WeaponCreatorSettings = preload("res://addons/weapon_creator/WeaponCreatorSettings.gd")
const SETTINGS_PATH = "user://weapon_creator_settings.tres"
signal data_confirmed(data: Dictionary)
var editor_interface: EditorInterface
var prefill_data: Dictionary = {}
var _ui_built: bool = false
var settings: Resource
# Base setup - override _custom_setup() in derived classes for additional initialization
func setup(editor_iface: EditorInterface, prefill: Dictionary = {}) -> void:
editor_interface = editor_iface
prefill_data = prefill
if ResourceLoader.exists(SETTINGS_PATH):
settings = ResourceLoader.load(SETTINGS_PATH)
if not settings:
settings = WeaponCreatorSettings.new()
_custom_setup()
_update_title()
if _ui_built:
_apply_data()
# Override this in derived classes for custom initialization
func _custom_setup() -> void:
pass
func _ready() -> void:
if not settings:
if ResourceLoader.exists(SETTINGS_PATH):
settings = ResourceLoader.load(SETTINGS_PATH)
if not settings:
settings = WeaponCreatorSettings.new()
_configure_window()
_update_title()
close_requested.connect(_on_cancel_pressed)
position = (DisplayServer.screen_get_size() - size) / 2
close_requested.connect(_on_window_close_requested)
_build_ui()
_ui_built = true
_apply_data()
# Restore position after UI is built, or center if no saved position
_restore_or_center_position()
func _restore_or_center_position() -> void:
var saved_pos = _get_saved_position()
if saved_pos != Vector2i.ZERO:
position = saved_pos
else:
# Use popup_centered_clamped to properly center on the current screen
popup_centered_clamped(size, 0.75)
func _get_saved_position() -> Vector2i:
# Override in derived classes to return their specific saved position
return Vector2i.ZERO
func _on_window_close_requested() -> void:
_save_dialog_size_and_position()
_on_cancel_pressed()
func _save_dialog_size_and_position() -> void:
_save_dialog_size()
_save_dialog_position()
func _save_dialog_size() -> void:
# Override in derived classes to save their specific dialog size
pass
func _save_dialog_position() -> void:
# Override in derived classes to save their specific dialog position
pass
func _configure_window() -> void:
size = Vector2i(750, 850)

View file

@ -1,5 +1,5 @@
@tool
extends Window
extends BaseCreatorDialog
# Popup window for configuring bullet parameters before creation
@ -19,91 +19,71 @@ var life_time_field: SpinBox
var graze_value_field: SpinBox
var destruction_particles_picker: EditorResourcePicker
# Buttons
var create_button: Button
var cancel_button: Button
# Mode and prefill data
# Mode
var is_3d_mode: bool = true
var prefill_data: Dictionary = {}
var _ui_built: bool = false
var editor_interface: EditorInterface
var _setup_called: bool = false
func setup(editor_iface: EditorInterface, is_3d: bool = true, prefill: Dictionary = {}) -> void:
editor_interface = editor_iface
# Public method with custom signature - calls base setup internally
func setup_bullet(editor_iface: EditorInterface, is_3d: bool = true, prefill: Dictionary = {}) -> void:
is_3d_mode = is_3d
prefill_data = prefill
var mode_text = "3D" if is_3d_mode else "2D"
var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New"
title = action_text + " Bullet (" + mode_text + ")"
if _ui_built:
if prefill_data.is_empty():
_set_default_values()
else:
_apply_prefill_data()
_setup_called = true
# Call parent setup with standard signature
setup(editor_iface, prefill)
func _custom_setup() -> void:
# Called by parent setup() - use for any additional initialization
pass
func _ready() -> void:
var mode_text = "3D" if is_3d_mode else "2D"
var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New"
title = action_text + " Bullet (" + mode_text + ")"
size = Vector2i(750, 750)
# Wait for setup to be called if it hasn't been yet
if not _setup_called:
await get_tree().process_frame
super._ready()
func _configure_window() -> void:
size = settings.get("bullet_dialog_size") if settings else Vector2i(750, 850)
transient = false
exclusive = false
unresizable = false
close_requested.connect(_on_cancel_pressed)
position = (DisplayServer.screen_get_size() - size) / 2
_build_ui()
_ui_built = true
if prefill_data.is_empty():
_set_default_values()
else:
_apply_prefill_data()
func _build_ui() -> void:
var margin = MarginContainer.new()
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
margin.add_theme_constant_override("margin_left", 12)
margin.add_theme_constant_override("margin_top", 12)
margin.add_theme_constant_override("margin_right", 12)
margin.add_theme_constant_override("margin_bottom", 12)
add_child(margin)
var vbox = VBoxContainer.new()
vbox.add_theme_constant_override("separation", 8)
margin.add_child(vbox)
var scroll = ScrollContainer.new()
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
vbox.add_child(scroll)
var main_vbox = VBoxContainer.new()
main_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
main_vbox.add_theme_constant_override("separation", 12)
scroll.add_child(main_vbox)
_build_basic_section(main_vbox)
_build_stats_section(main_vbox)
vbox.add_child(HSeparator.new())
func _update_title() -> void:
var mode_text = "3D" if is_3d_mode else "2D"
var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New"
title = action_text + " Bullet (" + mode_text + ")"
func _get_saved_position() -> Vector2i:
if settings:
var pos = settings.get("bullet_dialog_position")
return pos if pos else Vector2i.ZERO
return Vector2i.ZERO
func _save_dialog_size() -> void:
if settings:
settings.set("bullet_dialog_size", size)
settings.call("save_settings")
func _save_dialog_position() -> void:
if settings:
settings.set("bullet_dialog_position", position)
settings.call("save_settings")
func _build_content(container: VBoxContainer) -> void:
_build_basic_section(container)
_build_stats_section(container)
func _build_buttons(vbox: VBoxContainer) -> void:
var button_hbox = HBoxContainer.new()
button_hbox.alignment = BoxContainer.ALIGNMENT_CENTER
button_hbox.add_theme_constant_override("separation", 8)
vbox.add_child(button_hbox)
cancel_button = Button.new()
var cancel_button = Button.new()
cancel_button.text = "Cancel"
cancel_button.custom_minimum_size = Vector2(100, 0)
cancel_button.pressed.connect(_on_cancel_pressed)
button_hbox.add_child(cancel_button)
create_button = Button.new()
var create_button = Button.new()
create_button.text = "Create Bullet"
create_button.custom_minimum_size = Vector2(150, 0)
create_button.pressed.connect(_on_create_pressed)
@ -351,12 +331,14 @@ func _on_create_pressed() -> void:
"destruction_particles_scene": destruction_particles_picker.edited_resource
}
_save_dialog_size()
bullet_data_confirmed.emit(bullet_data)
hide()
queue_free()
func _on_cancel_pressed() -> void:
_save_dialog_size()
hide()
queue_free()
@ -369,9 +351,7 @@ func _validate_inputs() -> bool:
if is_3d_mode and not bullet_name.ends_with("_3D"):
bullet_name += "_3D"
var bullets_dir = "res://Resources/Bullets/"
if is_3d_mode:
bullets_dir += "3D/"
var bullets_dir = settings.get("bullets_3d_dir") if is_3d_mode else settings.get("bullets_dir")
var bullet_path = bullets_dir + bullet_name + ".tres"

View file

@ -395,7 +395,7 @@ func _open_bullet_dialog(is_3d: bool, prefill_data: Dictionary = {}) -> void:
get_tree().root.add_child(dialog)
dialog.call_deferred("setup", _editor_interface, is_3d, prefill_data)
dialog.call_deferred("setup_bullet", _editor_interface, is_3d, prefill_data)
dialog.call_deferred("connect", "bullet_data_confirmed", _on_duplicate_bullet_confirmed)
dialog.call_deferred("popup_centered")

View file

@ -1,16 +1,11 @@
@tool
extends Window
extends BaseCreatorDialog
# Dialog for creating new items
# Provides input fields for all item properties
signal item_data_confirmed(item_data: Dictionary)
# Editor reference
var editor_interface: EditorInterface
var prefill_data: Dictionary = {}
var _ui_built: bool = false
# UI elements
var _item_name_edit: LineEdit
var _item_key_edit: LineEdit
@ -46,72 +41,35 @@ const ITEM_TYPE_NAMES = [
"KeyItem"
]
func setup(editor_iface: EditorInterface, prefill: Dictionary = {}) -> void:
editor_interface = editor_iface
prefill_data = prefill
var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New"
title = action_text + " Item"
if _ui_built:
if prefill_data.is_empty():
_set_default_values()
else:
_apply_prefill_data()
func _ready() -> void:
var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New"
title = action_text + " Item"
size = Vector2i(750, 850)
func _configure_window() -> void:
size = settings.get("item_dialog_size") if settings else Vector2i(750, 850)
transient = false
exclusive = false
unresizable = false
close_requested.connect(_on_cancel_pressed)
position = (DisplayServer.screen_get_size() - size) / 2
_build_ui()
_ui_built = true
if prefill_data.is_empty():
_set_default_values()
else:
_apply_prefill_data()
func _set_default_values() -> void:
pass
func _update_title() -> void:
var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New"
title = action_text + " Item"
func _build_ui() -> void:
var margin = MarginContainer.new()
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
margin.add_theme_constant_override("margin_left", 12)
margin.add_theme_constant_override("margin_top", 12)
margin.add_theme_constant_override("margin_right", 12)
margin.add_theme_constant_override("margin_bottom", 12)
add_child(margin)
var vbox = VBoxContainer.new()
vbox.add_theme_constant_override("separation", 8)
margin.add_child(vbox)
var scroll = ScrollContainer.new()
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
vbox.add_child(scroll)
var content_vbox = VBoxContainer.new()
content_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
content_vbox.add_theme_constant_override("separation", 12)
scroll.add_child(content_vbox)
_build_content(content_vbox)
vbox.add_child(HSeparator.new())
_build_buttons(vbox)
func _get_saved_position() -> Vector2i:
if settings:
var pos = settings.get("item_dialog_position")
return pos if pos else Vector2i.ZERO
return Vector2i.ZERO
func _save_dialog_size() -> void:
if settings:
settings.set("item_dialog_size", size)
settings.call("save_settings")
func _save_dialog_position() -> void:
if settings:
settings.set("item_dialog_position", position)
settings.call("save_settings")
func _build_content(container: VBoxContainer) -> void:
_build_basic_info_section(container)
_build_properties_section(container)
func _build_buttons(parent: VBoxContainer) -> void:
var button_hbox = HBoxContainer.new()
@ -131,7 +89,7 @@ func _build_buttons(parent: VBoxContainer) -> void:
create_button.pressed.connect(_on_create_pressed)
button_hbox.add_child(create_button)
func _build_content(content_vbox: VBoxContainer) -> void:
func _build_basic_info_section(content_vbox: VBoxContainer) -> void:
# Basic Info Section
content_vbox.add_child(_create_section_label("Basic Information"))
@ -204,7 +162,8 @@ func _build_content(content_vbox: VBoxContainer) -> void:
_sprite_preview.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
_sprite_preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
preview_container.add_child(_sprite_preview)
func _build_properties_section(content_vbox: VBoxContainer) -> void:
content_vbox.add_child(HSeparator.new())
# Properties Section
@ -342,10 +301,12 @@ func _on_create_pressed() -> void:
"selectable": _selectable_check.button_pressed
}
_save_dialog_size()
item_data_confirmed.emit(item_data)
queue_free()
func _on_cancel_pressed() -> void:
_save_dialog_size()
queue_free()
func _show_error(message: String) -> void:

View file

@ -0,0 +1,152 @@
@tool
class_name SettingsDialog
extends BaseCreatorDialog
# Settings dialog for configuring Weapon Creator plugin defaults
# UI fields
var weapons_dir_field: LineEdit
var items_dir_field: LineEdit
var bullets_dir_field: LineEdit
var bullets_3d_dir_field: LineEdit
var items_database_field: LineEdit
var default_bullet_2d_field: LineEdit
var default_bullet_3d_field: LineEdit
var reset_button: Button
func _init() -> void:
if ResourceLoader.exists(SETTINGS_PATH):
settings = ResourceLoader.load(SETTINGS_PATH)
if not settings:
settings = WeaponCreatorSettings.new()
func _configure_window() -> void:
var saved_size = settings.get("settings_dialog_size") if settings else null
size = saved_size if saved_size else Vector2i(600, 600)
transient = false
exclusive = false
unresizable = false
func _update_title() -> void:
title = "Weapon Creator Settings"
func _get_saved_position() -> Vector2i:
if settings:
var pos = settings.get("settings_dialog_position")
return pos if pos else Vector2i.ZERO
return Vector2i.ZERO
func _save_dialog_size() -> void:
settings.set("settings_dialog_size", size)
func _save_dialog_position() -> void:
settings.set("settings_dialog_position", position)
func _build_content(container: VBoxContainer) -> void:
var section_label = _create_section_label("Resource Paths")
container.add_child(section_label)
var weapons_input = _create_input("Weapons Directory:")
weapons_dir_field = weapons_input.edit
container.add_child(weapons_input.container)
var items_input = _create_input("Items Directory:")
items_dir_field = items_input.edit
container.add_child(items_input.container)
var bullets_input = _create_input("Bullets Directory:")
bullets_dir_field = bullets_input.edit
container.add_child(bullets_input.container)
var bullets_3d_input = _create_input("Bullets 3D Directory:")
bullets_3d_dir_field = bullets_3d_input.edit
container.add_child(bullets_3d_input.container)
var database_input = _create_input("Items Database:")
items_database_field = database_input.edit
container.add_child(database_input.container)
container.add_child(HSeparator.new())
var defaults_label = _create_section_label("Default Bullets")
container.add_child(defaults_label)
var bullet_2d_input = _create_input("Default Bullet 2D:")
default_bullet_2d_field = bullet_2d_input.edit
container.add_child(bullet_2d_input.container)
var bullet_3d_input = _create_input("Default Bullet 3D:")
default_bullet_3d_field = bullet_3d_input.edit
container.add_child(bullet_3d_input.container)
container.add_child(HSeparator.new())
var reset_section = _create_section_label("Reset Options")
container.add_child(reset_section)
reset_button = Button.new()
reset_button.text = "Reset All Settings (Filters, Dialog Sizes, Defaults)"
reset_button.pressed.connect(_on_reset_pressed)
container.add_child(reset_button)
func _build_buttons(vbox: VBoxContainer) -> void:
var button_hbox = HBoxContainer.new()
button_hbox.alignment = BoxContainer.ALIGNMENT_END
button_hbox.add_theme_constant_override("separation", 8)
vbox.add_child(button_hbox)
var cancel_button = Button.new()
cancel_button.text = "Cancel"
cancel_button.pressed.connect(_on_cancel_pressed)
button_hbox.add_child(cancel_button)
var save_button = Button.new()
save_button.text = "Save"
save_button.pressed.connect(_on_create_pressed)
button_hbox.add_child(save_button)
func _set_default_values() -> void:
weapons_dir_field.text = settings.get("weapons_dir")
items_dir_field.text = settings.get("items_dir")
bullets_dir_field.text = settings.get("bullets_dir")
bullets_3d_dir_field.text = settings.get("bullets_3d_dir")
items_database_field.text = settings.get("items_database_path")
default_bullet_2d_field.text = settings.get("default_bullet_2d")
default_bullet_3d_field.text = settings.get("default_bullet_3d")
func _apply_prefill_data() -> void:
_set_default_values()
func _on_create_pressed() -> void:
settings.set("weapons_dir", weapons_dir_field.text)
settings.set("items_dir", items_dir_field.text)
settings.set("bullets_dir", bullets_dir_field.text)
settings.set("bullets_3d_dir", bullets_3d_dir_field.text)
settings.set("items_database_path", items_database_field.text)
settings.set("default_bullet_2d", default_bullet_2d_field.text)
settings.set("default_bullet_3d", default_bullet_3d_field.text)
_save_dialog_size()
_save_dialog_position()
settings.call("save_settings")
queue_free()
func _on_reset_pressed() -> void:
var dialog = ConfirmationDialog.new()
dialog.dialog_text = "This will reset all settings including:\n- Resource paths\n- Default bullets\n- All dialog sizes\n- All dialog positions\n- All viewer filters\n\nAre you sure?"
dialog.title = "Reset All Settings"
add_child(dialog)
dialog.confirmed.connect(func():
settings.call("reset_to_defaults")
_set_default_values()
dialog.queue_free()
)
dialog.canceled.connect(func():
dialog.queue_free()
)
dialog.popup_centered()

View file

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

View file

@ -1,5 +1,5 @@
@tool
extends Window
extends BaseCreatorDialog
# Popup window for configuring weapon parameters before creation
# Displays all input fields and creation/cancel buttons
@ -17,7 +17,6 @@ var description_field: TextEdit
var sprite_resource: Texture2D
var sprite_picker: EditorResourcePicker
var sprite_preview: TextureRect
var editor_interface: EditorInterface
# Bullet selection fields
var bullet_dropdown: OptionButton
@ -36,109 +35,78 @@ var bullets_per_shot_field: SpinBox
var spread_angle_field: SpinBox
var random_spread_field: SpinBox
# Buttons
var create_button: Button
var cancel_button: Button
# Mode and prefill data
# Mode
var is_3d_mode: bool = true
var prefill_data: Dictionary = {}
var _ui_built: bool = false
var _setup_called: bool = false
func setup(editor_iface: EditorInterface, is_3d: bool = true, prefill: Dictionary = {}) -> void:
editor_interface = editor_iface
# Public method with custom signature - calls base setup internally
func setup_weapon(editor_iface: EditorInterface, is_3d: bool = true, prefill: Dictionary = {}) -> void:
is_3d_mode = is_3d
prefill_data = prefill
# Update title based on mode and prefill status
var mode_text = "3D" if is_3d_mode else "2D"
var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New"
title = action_text + " Weapon (" + mode_text + ")"
_setup_called = true
# Call parent setup with standard signature
setup(editor_iface, prefill)
# If sprite picker already exists, ensure it has editor context
if sprite_picker:
_setup_sprite_picker()
# If UI is already built (setup called after _ready), apply prefill data now
if _ui_built:
if prefill_data.is_empty():
_set_default_values()
else:
_apply_prefill_data()
func _custom_setup() -> void:
# Called by parent setup() - use for any additional initialization
pass
func _ready() -> void:
# Window configuration
var mode_text = "3D" if is_3d_mode else "2D"
var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New"
title = action_text + " Weapon (" + mode_text + ")"
size = Vector2i(750, 950)
# Wait for setup to be called if it hasn't been yet
if not _setup_called:
await get_tree().process_frame
super._ready()
func _configure_window() -> void:
size = settings.get("weapon_dialog_size") if settings else Vector2i(750, 950)
transient = false
exclusive = false
unresizable = false
# Connect close request (X button) to cancel action
close_requested.connect(_on_cancel_pressed)
# Center on screen
position = (DisplayServer.screen_get_size() - size) / 2
_build_ui()
_ui_built = true
# Apply prefill data if provided, otherwise use defaults
# Note: this might be overridden by setup() if called via call_deferred
if prefill_data.is_empty():
_set_default_values()
else:
_apply_prefill_data()
func _build_ui() -> void:
# Main margin container
var margin = MarginContainer.new()
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
margin.add_theme_constant_override("margin_left", 12)
margin.add_theme_constant_override("margin_top", 12)
margin.add_theme_constant_override("margin_right", 12)
margin.add_theme_constant_override("margin_bottom", 12)
add_child(margin)
# Main vbox
var vbox = VBoxContainer.new()
vbox.add_theme_constant_override("separation", 8)
margin.add_child(vbox)
# Scroll container for form
var scroll = ScrollContainer.new()
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
vbox.add_child(scroll)
var main_vbox = VBoxContainer.new()
main_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
main_vbox.add_theme_constant_override("separation", 12)
scroll.add_child(main_vbox)
func _update_title() -> void:
var mode_text = "3D" if is_3d_mode else "2D"
var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New"
title = action_text + " Weapon (" + mode_text + ")"
func _get_saved_position() -> Vector2i:
if settings:
var pos = settings.get("weapon_dialog_position")
return pos if pos else Vector2i.ZERO
return Vector2i.ZERO
func _save_dialog_size() -> void:
if settings:
settings.set("weapon_dialog_size", size)
settings.call("save_settings")
func _save_dialog_position() -> void:
if settings:
settings.set("weapon_dialog_position", position)
settings.call("save_settings")
func _build_content(container: VBoxContainer) -> void:
# Basic Info Section
_build_basic_info_section(main_vbox)
_build_basic_info_section(container)
# Stats Section
_build_stats_section(main_vbox)
vbox.add_child(HSeparator.new())
# Buttons at bottom
_build_stats_section(container)
func _build_buttons(vbox: VBoxContainer) -> void:
var button_hbox = HBoxContainer.new()
button_hbox.alignment = BoxContainer.ALIGNMENT_CENTER
button_hbox.add_theme_constant_override("separation", 8)
vbox.add_child(button_hbox)
cancel_button = Button.new()
var cancel_button = Button.new()
cancel_button.text = "Cancel"
cancel_button.custom_minimum_size = Vector2(100, 0)
cancel_button.pressed.connect(_on_cancel_pressed)
button_hbox.add_child(cancel_button)
create_button = Button.new()
var create_button = Button.new()
create_button.text = "Create Weapon"
create_button.custom_minimum_size = Vector2(150, 0)
create_button.pressed.connect(_on_create_pressed)
@ -369,7 +337,7 @@ func _set_default_values() -> void:
description_field.text = "A new weapon for testing"
# Select default bullet based on mode
var default_bullet = "res://Resources/Bullets/3D/icicle_repeater_bullets_3D.tres" if is_3d_mode else "res://Resources/Bullets/simple_ice_bullet.tres"
var default_bullet = settings.get("default_bullet_3d") if is_3d_mode else settings.get("default_bullet_2d")
_select_bullet_by_path(default_bullet)
# Weapon stats defaults
@ -387,9 +355,7 @@ func _set_default_values() -> void:
func _populate_bullet_dropdown() -> void:
# Get all bullet resources from the appropriate Bullets folder
var bullets_dir = "res://Resources/Bullets/"
if is_3d_mode:
bullets_dir += "3D/"
var bullets_dir = settings.get("bullets_3d_dir") if is_3d_mode else settings.get("bullets_dir")
var dir = DirAccess.open(bullets_dir)
@ -470,6 +436,9 @@ func _on_create_pressed() -> void:
"random_spread": random_spread_field.value
}
# Save dialog size
_save_dialog_size()
# Emit signal with weapon data
weapon_data_confirmed.emit(weapon_data)
@ -478,6 +447,8 @@ func _on_create_pressed() -> void:
queue_free()
func _on_cancel_pressed() -> void:
# Save dialog size before closing
_save_dialog_size()
# Close without creating
hide()
queue_free()
@ -500,8 +471,8 @@ func _validate_inputs() -> bool:
# Check if resources already exist
var dimension_suffix = "_3D" if is_3d_mode else "_2D"
var weapon_resource_path: String = "res://Resources/Weapons/" + key + dimension_suffix + ".tres"
var item_resource_path: String = "res://Resources/Items/" + key + "_Item" + dimension_suffix + ".tres"
var weapon_resource_path: String = settings.get("weapons_dir") + key + dimension_suffix + ".tres"
var item_resource_path: String = settings.get("items_dir") + key + "_Item" + dimension_suffix + ".tres"
if ResourceLoader.exists(weapon_resource_path):
_show_error("Weapon resource already exists at:\n" + weapon_resource_path + "\n\nPlease choose a different item key.")

View file

@ -4,12 +4,16 @@ extends PanelContainer
# UI dock for the Weapon Creator plugin
# Provides tabs for weapons and bullets, with creation dialogs and logs
const WeaponCreatorSettings = preload("res://addons/weapon_creator/WeaponCreatorSettings.gd")
const SETTINGS_PATH = "user://weapon_creator_settings.tres"
signal create_weapon_requested(weapon_data: Dictionary)
signal create_bullet_requested(bullet_data: Dictionary)
signal create_item_requested(item_data: Dictionary)
# UI elements
var clear_button: Button
var options_button: Button
var log_output: RichTextLabel
var weapon_viewer: PanelContainer
var bullet_viewer: PanelContainer
@ -18,11 +22,22 @@ var tab_container: TabContainer
var _is_creating: bool = false
var _editor_interface: EditorInterface
var settings: Resource
func setup(editor_interface: EditorInterface) -> void:
_editor_interface = editor_interface
if ResourceLoader.exists(SETTINGS_PATH):
settings = ResourceLoader.load(SETTINGS_PATH)
if not settings:
settings = WeaponCreatorSettings.new()
func _ready() -> void:
if not settings:
if ResourceLoader.exists(SETTINGS_PATH):
settings = ResourceLoader.load(SETTINGS_PATH)
if not settings:
settings = WeaponCreatorSettings.new()
_build_ui()
func _build_ui() -> void:
@ -50,11 +65,33 @@ func _build_ui() -> void:
log_vbox.add_theme_constant_override("separation", 4)
hsplit.add_child(log_vbox)
# Tab Container for Weapons and Bullets (right side)
# Right side container with tabs and options button
var right_vbox = VBoxContainer.new()
right_vbox.custom_minimum_size = Vector2(300, 0)
right_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
right_vbox.add_theme_constant_override("separation", 4)
hsplit.add_child(right_vbox)
# Header row with options button aligned to right
var header_hbox = HBoxContainer.new()
header_hbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
right_vbox.add_child(header_hbox)
# Spacer to push options button to the right
var spacer = Control.new()
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header_hbox.add_child(spacer)
options_button = Button.new()
options_button.text = "⚙ Settings"
options_button.pressed.connect(_on_options_pressed)
header_hbox.add_child(options_button)
# Tab Container for Weapons and Bullets
tab_container = TabContainer.new()
tab_container.custom_minimum_size = Vector2(300, 0)
tab_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
hsplit.add_child(tab_container)
tab_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
right_vbox.add_child(tab_container)
# Weapon Viewer Tab
var weapon_viewer_script = load("res://addons/weapon_creator/WeaponViewer.gd")
@ -131,6 +168,29 @@ func _on_weapon_data_confirmed(weapon_data: Dictionary) -> void:
func _on_clear_button_pressed() -> void:
log_output.clear()
func _on_options_pressed() -> void:
var settings_dialog_script = load("res://addons/weapon_creator/SettingsDialog.gd")
var settings_dialog = Window.new()
settings_dialog.set_script(settings_dialog_script)
settings_dialog.setup(_editor_interface)
add_child(settings_dialog)
settings_dialog.popup_centered()
# Reload settings after dialog closes
settings_dialog.tree_exited.connect(func():
if ResourceLoader.exists(SETTINGS_PATH):
settings = ResourceLoader.load(SETTINGS_PATH)
if not settings:
settings = WeaponCreatorSettings.new()
# Refresh all viewers to pick up new settings
if weapon_viewer:
weapon_viewer.call("refresh_weapons")
if bullet_viewer:
bullet_viewer.call("refresh_bullets")
if item_viewer:
item_viewer.call("refresh_items")
)
func _on_weapon_deleted(weapon_name: String, weapon_path: String, item_path: String) -> void:
pass

View file

@ -4,6 +4,9 @@ extends EditorPlugin
# Editor plugin that provides a UI for creating 3D weapons with all required resources
# Adds a dock panel to the editor with input fields and a create button
const WeaponCreatorSettings = preload("res://addons/weapon_creator/WeaponCreatorSettings.gd")
const SETTINGS_PATH = "user://weapon_creator_settings.tres"
var dock_instance: PanelContainer
func _enter_tree() -> void:
@ -33,6 +36,12 @@ func _exit_tree() -> void:
dock_instance.queue_free()
func _on_create_weapon_requested(weapon_data: Dictionary) -> void:
var settings: Resource = null
if ResourceLoader.exists(SETTINGS_PATH):
settings = ResourceLoader.load(SETTINGS_PATH)
if not settings:
settings = WeaponCreatorSettings.new()
# Extract data from the dictionary
var weapon_name: String = weapon_data.get("weapon_name", "New Weapon")
var weapon_item_key: String = weapon_data.get("weapon_item_key", "NEW_WEAPON")
@ -40,7 +49,7 @@ func _on_create_weapon_requested(weapon_data: Dictionary) -> void:
var weapon_short_name: String = weapon_data.get("weapon_short_name", "NW-1")
var weapon_description: String = weapon_data.get("weapon_description", "A new weapon")
var sprite_resource: Texture2D = weapon_data.get("sprite_resource", null)
var default_bullet_path: String = weapon_data.get("default_bullet_path", "res://Resources/Bullets/3D/icicle_repeater_bullets_3D.tres")
var default_bullet_path: String = weapon_data.get("default_bullet_path", settings.get("default_bullet_3d"))
var is_3d: bool = weapon_data.get("is_3d", true)
# Weapon stats
@ -58,9 +67,9 @@ func _on_create_weapon_requested(weapon_data: Dictionary) -> void:
# Append 2D or 3D to the end of the key
var dimension_suffix = "_3D" if is_3d else "_2D"
var weapon_resource_path: String = "res://Resources/Weapons/" + weapon_item_key + dimension_suffix + ".tres"
var item_resource_path: String = "res://Resources/Items/" + weapon_item_key + "_Item" + dimension_suffix + ".tres"
var items_database_path: String = "res://Resources/ItemsDatabase.tres"
var weapon_resource_path: String = settings.get("weapons_dir") + weapon_item_key + dimension_suffix + ".tres"
var item_resource_path: String = settings.get("items_dir") + weapon_item_key + "_Item" + dimension_suffix + ".tres"
var items_database_path: String = settings.get("items_database_path")
# Feedback to the UI
dock_instance.call("add_log", "=== Starting Weapon Creation ===")
@ -255,12 +264,16 @@ func _add_to_items_database(database_path: String, item_resource_path: String) -
return true
func _on_create_bullet_requested(bullet_data: Dictionary) -> void:
var settings: Resource = null
if ResourceLoader.exists(SETTINGS_PATH):
settings = ResourceLoader.load(SETTINGS_PATH)
if not settings:
settings = WeaponCreatorSettings.new()
var bullet_name: String = bullet_data.get("bullet_name", "new_bullet")
var is_3d: bool = bullet_data.get("is_3d", true)
var bullets_dir = "res://Resources/Bullets/"
if is_3d:
bullets_dir += "3D/"
var bullets_dir = settings.get("bullets_3d_dir") if is_3d else settings.get("bullets_dir")
var bullet_path = bullets_dir + bullet_name + ".tres"
@ -284,6 +297,12 @@ func _on_create_bullet_requested(bullet_data: Dictionary) -> void:
get_editor_interface().get_resource_filesystem().scan()
func _on_create_item_requested(item_data: Dictionary) -> void:
var settings: Resource = null
if ResourceLoader.exists(SETTINGS_PATH):
settings = ResourceLoader.load(SETTINGS_PATH)
if not settings:
settings = WeaponCreatorSettings.new()
var item_name: String = item_data.get("item_name", "New Item")
var item_key: String = item_data.get("item_key", "NEW_ITEM")
var short_name: String = item_data.get("short_name", "NI")
@ -298,8 +317,8 @@ func _on_create_item_requested(item_data: Dictionary) -> void:
var consume_on_use: bool = item_data.get("consume_on_use", false)
var selectable: bool = item_data.get("selectable", false)
var item_resource_path: String = "res://Resources/Items/" + item_key + "_Item.tres"
var items_database_path: String = "res://Resources/ItemsDatabase.tres"
var item_resource_path: String = settings.get("items_dir") + item_key + "_Item.tres"
var items_database_path: String = settings.get("items_database_path")
dock_instance.call("add_log", "=== Starting Item Creation ===")
dock_instance.call("add_log", "Item Name: " + item_name)
@ -409,4 +428,3 @@ func _create_bullet_resource(path: String, bullet_data: Dictionary) -> bool:
return false
return true

View file

@ -0,0 +1,74 @@
@tool
class_name WeaponCreatorSettings
extends Resource
# Settings manager for the Weapon Creator plugin
# Stores configurable defaults and persists user preferences
const SETTINGS_PATH = "user://weapon_creator_settings.tres"
# Resource paths
@export var weapons_dir: String = "res://Resources/Weapons/"
@export var items_dir: String = "res://Resources/Items/"
@export var bullets_dir: String = "res://Resources/Bullets/"
@export var bullets_3d_dir: String = "res://Resources/Bullets/3D/"
@export var items_database_path: String = "res://Resources/ItemsDatabase.tres"
# Default bullet paths
@export var default_bullet_2d: String = "res://Resources/Bullets/simple_ice_bullet.tres"
@export var default_bullet_3d: String = "res://Resources/Bullets/3D/icicle_repeater_bullets_3D.tres"
# Dialog sizes (saved automatically)
@export var weapon_dialog_size: Vector2i = Vector2i(750, 950)
@export var bullet_dialog_size: Vector2i = Vector2i(750, 850)
@export var item_dialog_size: Vector2i = Vector2i(750, 850)
@export var settings_dialog_size: Vector2i = Vector2i(600, 600)
# Dialog positions (saved automatically)
@export var weapon_dialog_position: Vector2i = Vector2i.ZERO
@export var bullet_dialog_position: Vector2i = Vector2i.ZERO
@export var item_dialog_position: Vector2i = Vector2i.ZERO
@export var settings_dialog_position: Vector2i = Vector2i.ZERO
# Viewer filters (saved automatically)
@export var weapon_viewer_filter: String = ""
@export var bullet_viewer_filter: String = ""
@export var item_viewer_filter: String = ""
static func load_settings():
if ResourceLoader.exists(SETTINGS_PATH):
var loaded = ResourceLoader.load(SETTINGS_PATH)
if loaded != null:
return loaded
var new_settings = WeaponCreatorSettings.new()
return new_settings
func save_settings() -> void:
var err = ResourceSaver.save(self, SETTINGS_PATH)
if err != OK:
push_error("Failed to save weapon creator settings: " + str(err))
func reset_to_defaults() -> void:
weapons_dir = "res://Resources/Weapons/"
items_dir = "res://Resources/Items/"
bullets_dir = "res://Resources/Bullets/"
bullets_3d_dir = "res://Resources/Bullets/3D/"
items_database_path = "res://Resources/ItemsDatabase.tres"
default_bullet_2d = "res://Resources/Bullets/simple_ice_bullet.tres"
default_bullet_3d = "res://Resources/Bullets/3D/icicle_repeater_bullets_3D.tres"
weapon_dialog_size = Vector2i(750, 950)
bullet_dialog_size = Vector2i(750, 850)
item_dialog_size = Vector2i(750, 850)
settings_dialog_size = Vector2i(600, 600)
weapon_dialog_position = Vector2i.ZERO
bullet_dialog_position = Vector2i.ZERO
item_dialog_position = Vector2i.ZERO
settings_dialog_position = Vector2i.ZERO
weapon_viewer_filter = ""
bullet_viewer_filter = ""
item_viewer_filter = ""
save_settings()

View file

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

View file

@ -1,4 +1,4 @@
@tool
@tool
extends PanelContainer
# Displays all weapons from the ItemsDatabase in a grid format
@ -385,7 +385,7 @@ func _duplicate_weapon(loot_item: Resource, weapon_data: Resource, is_3d: bool)
get_tree().root.add_child(dialog)
# Setup with prefill data using call_deferred to ensure script is ready
dialog.call_deferred("setup", _editor_interface, is_3d, prefill_data)
dialog.call_deferred("setup_weapon", _editor_interface, is_3d, prefill_data)
# Connect to confirmation signal using call_deferred
dialog.call_deferred("connect", "weapon_data_confirmed", _on_duplicate_weapon_confirmed)
@ -521,7 +521,6 @@ func _on_create_weapon_pressed(is_3d: bool) -> void:
get_tree().root.add_child(dialog)
dialog.call_deferred("setup", _editor_interface, is_3d)
dialog.call_deferred("setup_weapon", _editor_interface, is_3d)
dialog.call_deferred("connect", "weapon_data_confirmed", _on_duplicate_weapon_confirmed)
dialog.call_deferred("popup_centered")