cirnogodot/addons/weapon_creator/WeaponCreatorDialog.gd

547 lines
18 KiB
GDScript3
Raw Normal View History

@tool
extends BaseCreatorDialog
2026-02-08 13:11:14 +01:00
# Popup window for configuring weapon parameters before creation
# Displays all input fields and creation/cancel buttons
signal weapon_data_confirmed(weapon_data: Dictionary)
# Basic info fields
var weapon_name_field: LineEdit
var item_key_field: LineEdit
var ammo_key_field: LineEdit
var short_name_field: LineEdit
var description_field: TextEdit
# Sprite selection fields
var sprite_resource: Texture2D
var sprite_picker: EditorResourcePicker
var sprite_preview: TextureRect
# Bullet selection fields
2026-02-08 13:11:14 +01:00
var bullet_dropdown: OptionButton
var bullet_path_field: LineEdit
# Weapon stats fields
var priority_field: SpinBox
var ammo_per_shot_field: SpinBox
var rate_of_fire_field: SpinBox
var bullet_capacity_field: SpinBox
var reload_time_field: SpinBox
var infinite_ammo_check: CheckBox
var recharge_time_field: SpinBox
var recharge_amount_field: SpinBox
var bullets_per_shot_field: SpinBox
var spread_angle_field: SpinBox
var random_spread_field: SpinBox
# Mode
var is_3d_mode: bool = true
var _setup_called: bool = false
# 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
_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()
func _custom_setup() -> void:
# Called by parent setup() - use for any additional initialization
pass
2026-02-08 13:11:14 +01:00
func _ready() -> void:
# 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
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:
2026-02-08 13:11:14 +01:00
# Basic Info Section
_build_basic_info_section(container)
2026-02-08 13:11:14 +01:00
# Stats Section
_build_stats_section(container)
func _build_buttons(vbox: VBoxContainer) -> void:
2026-02-08 13:11:14 +01:00
var button_hbox = HBoxContainer.new()
button_hbox.alignment = BoxContainer.ALIGNMENT_CENTER
button_hbox.add_theme_constant_override("separation", 8)
vbox.add_child(button_hbox)
var cancel_button = Button.new()
2026-02-08 13:11:14 +01:00
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)
var create_button = Button.new()
2026-02-08 13:11:14 +01:00
create_button.text = "Create Weapon"
create_button.custom_minimum_size = Vector2(150, 0)
create_button.pressed.connect(_on_create_pressed)
button_hbox.add_child(create_button)
func _build_basic_info_section(parent: Control) -> void:
var section = VBoxContainer.new()
section.add_theme_constant_override("separation", 4)
parent.add_child(section)
var header = Label.new()
header.text = "Basic Information"
header.add_theme_font_size_override("font_size", 16)
section.add_child(header)
# Weapon Name
weapon_name_field = _add_line_edit_field(section, "Weapon Name:", "e.g., Ice Rifle")
weapon_name_field.text_changed.connect(_on_weapon_name_changed)
# Item Key
item_key_field = _add_line_edit_field(section, "Item Key:", "AUTO_GENERATED")
# Ammo Key
ammo_key_field = _add_line_edit_field(section, "Ammo Key:", "(optional, leave empty for none)")
# Short Name
short_name_field = _add_line_edit_field(section, "Short Name:", "e.g., IR-7")
# Description
var desc_label = Label.new()
desc_label.text = "Description:"
section.add_child(desc_label)
description_field = TextEdit.new()
description_field.custom_minimum_size = Vector2(0, 60)
description_field.placeholder_text = "Enter weapon description..."
section.add_child(description_field)
section.add_child(HSeparator.new())
# Sprite Selection
var sprite_header = Label.new()
sprite_header.text = "Item Sprite"
sprite_header.add_theme_font_size_override("font_size", 14)
section.add_child(sprite_header)
_build_sprite_selector(section)
section.add_child(HSeparator.new())
2026-02-08 13:11:14 +01:00
# Bullet Selection
var bullet_header = Label.new()
bullet_header.text = "Bullet Configuration"
bullet_header.add_theme_font_size_override("font_size", 14)
section.add_child(bullet_header)
# Bullet dropdown
var bullet_hbox = HBoxContainer.new()
section.add_child(bullet_hbox)
var bullet_label = Label.new()
bullet_label.text = "Bullet Type:"
bullet_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
bullet_hbox.add_child(bullet_label)
bullet_dropdown = OptionButton.new()
bullet_dropdown.size_flags_horizontal = Control.SIZE_EXPAND_FILL
bullet_dropdown.item_selected.connect(_on_bullet_selected)
bullet_hbox.add_child(bullet_dropdown)
# Populate bullet dropdown
_populate_bullet_dropdown()
# Bullet Path (read-only display)
bullet_path_field = _add_line_edit_field(section, "Bullet Path:", "")
bullet_path_field.editable = false
bullet_path_field.text = "res://Resources/Bullets/simple_ice_bullet.tres"
func _build_sprite_selector(parent: Control) -> void:
# Container for sprite selector with preview
var sprite_container = HBoxContainer.new()
sprite_container.add_theme_constant_override("separation", 8)
parent.add_child(sprite_container)
# Left side: Label and resource picker
var left_vbox = VBoxContainer.new()
left_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
left_vbox.add_theme_constant_override("separation", 4)
sprite_container.add_child(left_vbox)
# Resource picker
var picker_hbox = HBoxContainer.new()
picker_hbox.add_theme_constant_override("separation", 4)
left_vbox.add_child(picker_hbox)
var picker_label = Label.new()
picker_label.text = "Sprite:"
picker_label.custom_minimum_size = Vector2(120, 0)
picker_hbox.add_child(picker_label)
sprite_picker = EditorResourcePicker.new()
sprite_picker.base_type = "Texture2D"
sprite_picker.editable = true
sprite_picker.size_flags_horizontal = Control.SIZE_EXPAND_FILL
sprite_picker.resource_changed.connect(_on_sprite_resource_changed)
sprite_picker.resource_selected.connect(_on_resource_picker_opening)
picker_hbox.add_child(sprite_picker)
# Setup editor integration if interface is available
if editor_interface:
_setup_sprite_picker()
# Right side: Preview
var preview_container = PanelContainer.new()
preview_container.custom_minimum_size = Vector2(64, 64)
sprite_container.add_child(preview_container)
sprite_preview = TextureRect.new()
sprite_preview.custom_minimum_size = Vector2(64, 64)
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 _on_sprite_resource_changed(resource: Resource) -> void:
if resource is Texture2D:
sprite_resource = resource
sprite_preview.texture = resource
print("Sprite resource changed: ", resource.resource_path if resource.resource_path else "[Unsaved Resource]")
elif resource == null:
sprite_resource = null
sprite_preview.texture = null
print("Sprite resource cleared")
else:
push_warning("Selected resource is not a Texture2D")
# Restore window focus after resource picker closes
call_deferred("_restore_window_focus")
func _on_resource_picker_opening(_resource: Resource, _inspect: bool) -> void:
# Called when resource picker button is clicked
pass
func _restore_window_focus() -> void:
# Bring window back to front after file picker closes
if visible:
move_to_foreground()
func _setup_sprite_picker() -> void:
# Ensure sprite picker is properly integrated with editor
if sprite_picker and editor_interface:
# EditorResourcePicker automatically integrates with editor when in editor context
# Just ensure toggle_mode is set correctly
sprite_picker.toggle_mode = false
2026-02-08 13:11:14 +01:00
func _build_stats_section(parent: Control) -> void:
var section = VBoxContainer.new()
section.add_theme_constant_override("separation", 4)
parent.add_child(section)
var header = Label.new()
header.text = "Weapon Statistics"
header.add_theme_font_size_override("font_size", 16)
section.add_child(header)
var grid = GridContainer.new()
grid.columns = 2
grid.add_theme_constant_override("h_separation", 8)
grid.add_theme_constant_override("v_separation", 4)
section.add_child(grid)
# Create all stat fields
priority_field = _add_spinbox_field(grid, "Priority:", 0, 100, 1, 1)
ammo_per_shot_field = _add_spinbox_field(grid, "Ammo Per Shot:", 0, 1000, 1, 1)
rate_of_fire_field = _add_spinbox_field(grid, "Rate of Fire (s):", 0, 100, 0.01, 0.2)
bullet_capacity_field = _add_spinbox_field(grid, "Bullet Capacity:", 0, 10000, 1, 100)
reload_time_field = _add_spinbox_field(grid, "Reload Time (s):", 0, 100, 0.1, 2.0)
# Infinite ammo checkbox
var inf_label = Label.new()
inf_label.text = "Infinite Ammo:"
grid.add_child(inf_label)
infinite_ammo_check = CheckBox.new()
grid.add_child(infinite_ammo_check)
recharge_time_field = _add_spinbox_field(grid, "Recharge Time (s):", 0, 100, 0.1, 0)
recharge_amount_field = _add_spinbox_field(grid, "Recharge Amount:", 0, 1000, 1, 0)
bullets_per_shot_field = _add_spinbox_field(grid, "Bullets Per Shot:", 1, 100, 1, 1)
spread_angle_field = _add_spinbox_field(grid, "Spread Angle (°):", 0, 360, 0.1, 0)
random_spread_field = _add_spinbox_field(grid, "Random Spread (°):", 0, 360, 0.1, 0)
func _add_line_edit_field(parent: Control, label_text: String, placeholder: String) -> LineEdit:
var hbox = HBoxContainer.new()
parent.add_child(hbox)
var label = Label.new()
label.text = label_text
label.custom_minimum_size = Vector2(120, 0)
hbox.add_child(label)
var line_edit = LineEdit.new()
line_edit.placeholder_text = placeholder
line_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
hbox.add_child(line_edit)
return line_edit
func _add_spinbox_field(parent: Control, label_text: String, min_val: float, max_val: float, step_val: float, default_val: float) -> SpinBox:
var label = Label.new()
label.text = label_text
parent.add_child(label)
var spinbox = SpinBox.new()
spinbox.min_value = min_val
spinbox.max_value = max_val
spinbox.step = step_val
spinbox.value = default_val
spinbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
parent.add_child(spinbox)
return spinbox
func _set_default_values() -> void:
# Basic info defaults
weapon_name_field.text = "New Weapon"
item_key_field.text = "NEW_WEAPON"
ammo_key_field.text = ""
short_name_field.text = "NW-1"
description_field.text = "A new weapon for testing"
# Select default bullet based on mode
var default_bullet = settings.get("default_bullet_3d") if is_3d_mode else settings.get("default_bullet_2d")
_select_bullet_by_path(default_bullet)
2026-02-08 13:11:14 +01:00
# Weapon stats defaults
priority_field.value = 10
ammo_per_shot_field.value = 1
rate_of_fire_field.value = 0.2
bullet_capacity_field.value = 50
reload_time_field.value = 1.0
infinite_ammo_check.button_pressed = false
recharge_time_field.value = 0.5
recharge_amount_field.value = 5
bullets_per_shot_field.value = 1
spread_angle_field.value = 0.0
random_spread_field.value = 0.0
func _populate_bullet_dropdown() -> void:
# Get all bullet resources from the appropriate Bullets folder
var bullets_dir = settings.get("bullets_3d_dir") if is_3d_mode else settings.get("bullets_dir")
2026-02-08 13:11:14 +01:00
var dir = DirAccess.open(bullets_dir)
if dir == null:
push_error("Could not open bullets directory: " + bullets_dir)
return
dir.list_dir_begin()
var file_name = dir.get_next()
var bullet_files: Array[String] = []
while file_name != "":
if not dir.current_is_dir() and file_name.ends_with(".tres"):
bullet_files.append(bullets_dir + file_name)
file_name = dir.get_next()
dir.list_dir_end()
# Sort alphabetically
bullet_files.sort()
# Add to dropdown
for bullet_path in bullet_files:
var bullet_name = bullet_path.get_file().get_basename()
bullet_dropdown.add_item(bullet_name)
bullet_dropdown.set_item_metadata(bullet_dropdown.item_count - 1, bullet_path)
func _select_bullet_by_path(path: String) -> void:
# Find and select the bullet in the dropdown
for i in range(bullet_dropdown.item_count):
if bullet_dropdown.get_item_metadata(i) == path:
bullet_dropdown.select(i)
bullet_path_field.text = path
return
# If not found, select first item
if bullet_dropdown.item_count > 0:
bullet_dropdown.select(0)
bullet_path_field.text = bullet_dropdown.get_item_metadata(0)
func _on_bullet_selected(index: int) -> void:
# Update the bullet path field when dropdown selection changes
var selected_path = bullet_dropdown.get_item_metadata(index)
bullet_path_field.text = selected_path
2026-02-08 13:11:14 +01:00
func _on_weapon_name_changed(new_name: String) -> void:
# Auto-generate item key from weapon name
var generated_key = new_name.to_upper().replace(" ", "_")
generated_key = generated_key.replace("-", "_").replace("'", "").replace("\"", "")
item_key_field.text = generated_key
func _on_create_pressed() -> void:
# Validate inputs
if not _validate_inputs():
return
# Build weapon data dictionary
var weapon_data = {
"weapon_name": weapon_name_field.text,
"weapon_item_key": item_key_field.text,
"ammo_key": ammo_key_field.text,
"weapon_short_name": short_name_field.text,
"weapon_description": description_field.text,
"sprite_resource": sprite_resource,
2026-02-08 13:11:14 +01:00
"default_bullet_path": bullet_path_field.text,
"is_3d": is_3d_mode,
2026-02-08 13:11:14 +01:00
"priority": int(priority_field.value),
"ammo_per_shot": int(ammo_per_shot_field.value),
"rate_of_fire": rate_of_fire_field.value,
"bullet_capacity": int(bullet_capacity_field.value),
"reload_time": reload_time_field.value,
"infinite_ammo": infinite_ammo_check.button_pressed,
"recharge_time": recharge_time_field.value,
"recharge_amount": int(recharge_amount_field.value),
"bullets_per_shot": int(bullets_per_shot_field.value),
"spread_angle": spread_angle_field.value,
"random_spread": random_spread_field.value
}
# Save dialog size
_save_dialog_size()
2026-02-08 13:11:14 +01:00
# Emit signal with weapon data
weapon_data_confirmed.emit(weapon_data)
# Close the window
hide()
queue_free()
func _on_cancel_pressed() -> void:
# Save dialog size before closing
_save_dialog_size()
2026-02-08 13:11:14 +01:00
# Close without creating
hide()
queue_free()
func _validate_inputs() -> bool:
# Check weapon name
if weapon_name_field.text.strip_edges().is_empty():
_show_error("Weapon name cannot be empty")
return false
# Check item key
if item_key_field.text.strip_edges().is_empty():
_show_error("Item key cannot be empty")
return false
# Check item key format
var key = item_key_field.text
if not key.to_upper() == key:
_show_warning("Item key should be uppercase")
# Check if resources already exist
var dimension_suffix = "_3D" if is_3d_mode else "_2D"
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.")
return false
if ResourceLoader.exists(item_resource_path):
_show_error("Item resource already exists at:\n" + item_resource_path + "\n\nPlease choose a different item key.")
return false
2026-02-08 13:11:14 +01:00
# Check bullet path
if not ResourceLoader.exists(bullet_path_field.text):
_show_error("Bullet resource does not exist at: " + bullet_path_field.text)
return false
return true
func _show_error(message: String) -> void:
var dialog = AcceptDialog.new()
dialog.dialog_text = message
dialog.title = "Error"
add_child(dialog)
dialog.popup_centered()
func _show_warning(message: String) -> void:
var dialog = AcceptDialog.new()
dialog.dialog_text = message
dialog.title = "Warning"
add_child(dialog)
dialog.popup_centered()
func _apply_prefill_data() -> void:
# Apply prefilled data from duplicating an existing weapon
if prefill_data.has("weapon_name"):
weapon_name_field.text = prefill_data["weapon_name"]
if prefill_data.has("item_key"):
item_key_field.text = prefill_data["item_key"]
if prefill_data.has("ammo_key"):
ammo_key_field.text = prefill_data["ammo_key"]
if prefill_data.has("short_name"):
short_name_field.text = prefill_data["short_name"]
if prefill_data.has("description"):
description_field.text = prefill_data["description"]
if prefill_data.has("sprite_resource") and prefill_data["sprite_resource"] != null:
sprite_resource = prefill_data["sprite_resource"]
sprite_preview.texture = sprite_resource
sprite_picker.edited_resource = sprite_resource
if prefill_data.has("bullet_path"):
_select_bullet_by_path(prefill_data["bullet_path"])
# Apply weapon stats
if prefill_data.has("priority"):
priority_field.value = prefill_data["priority"]
if prefill_data.has("ammo_per_shot"):
ammo_per_shot_field.value = prefill_data["ammo_per_shot"]
if prefill_data.has("rate_of_fire"):
rate_of_fire_field.value = prefill_data["rate_of_fire"]
if prefill_data.has("bullet_capacity"):
bullet_capacity_field.value = prefill_data["bullet_capacity"]
if prefill_data.has("reload_time"):
reload_time_field.value = prefill_data["reload_time"]
if prefill_data.has("infinite_ammo"):
infinite_ammo_check.button_pressed = prefill_data["infinite_ammo"]
if prefill_data.has("recharge_time"):
recharge_time_field.value = prefill_data["recharge_time"]
if prefill_data.has("recharge_amount"):
recharge_amount_field.value = prefill_data["recharge_amount"]
if prefill_data.has("bullets_per_shot"):
bullets_per_shot_field.value = prefill_data["bullets_per_shot"]
if prefill_data.has("spread_angle"):
spread_angle_field.value = prefill_data["spread_angle"]
if prefill_data.has("random_spread"):
random_spread_field.value = prefill_data["random_spread"]