@tool extends BaseCreatorDialog # 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 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 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: # Basic Info Section _build_basic_info_section(container) # Stats Section _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) 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) 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) 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()) # 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 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) # 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") 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 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, "default_bullet_path": bullet_path_field.text, "is_3d": is_3d_mode, "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() # 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() # 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 # 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"]