@tool extends BaseCreatorDialog # Popup window for configuring bullet parameters before creation signal bullet_data_confirmed(bullet_data: Dictionary) # UI fields var bullet_name_field: LineEdit var bullet_sprite_picker: EditorResourcePicker var bullet_sprite_preview: TextureRect var bullet_scene_picker: EditorResourcePicker var bullet_size_field: SpinBox var bullet_speed_field: SpinBox var bullet_damage_field: SpinBox var max_damage_field: SpinBox var knockback_field: SpinBox var life_time_field: SpinBox var graze_value_field: SpinBox var destruction_particles_picker: EditorResourcePicker # Mode var is_3d_mode: bool = true var _setup_called: bool = false # 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 _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: # 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 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) 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 Bullet" create_button.custom_minimum_size = Vector2(150, 0) create_button.pressed.connect(_on_create_pressed) button_hbox.add_child(create_button) func _build_basic_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) bullet_name_field = _add_line_edit_field(section, "Bullet Name:", "e.g., ice_bullet") section.add_child(HSeparator.new()) # Sprite Selection var sprite_header = Label.new() sprite_header.text = "Bullet Sprite" sprite_header.add_theme_font_size_override("font_size", 14) section.add_child(sprite_header) var sprite_container = HBoxContainer.new() sprite_container.add_theme_constant_override("separation", 8) section.add_child(sprite_container) var sprite_vbox = VBoxContainer.new() sprite_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL sprite_vbox.add_theme_constant_override("separation", 4) sprite_container.add_child(sprite_vbox) var sprite_picker_hbox = HBoxContainer.new() sprite_picker_hbox.add_theme_constant_override("separation", 4) sprite_vbox.add_child(sprite_picker_hbox) var sprite_label = Label.new() sprite_label.text = "Sprite:" sprite_label.custom_minimum_size = Vector2(120, 0) sprite_picker_hbox.add_child(sprite_label) bullet_sprite_picker = EditorResourcePicker.new() bullet_sprite_picker.base_type = "Texture2D" bullet_sprite_picker.editable = true bullet_sprite_picker.size_flags_horizontal = Control.SIZE_EXPAND_FILL bullet_sprite_picker.resource_changed.connect(_on_sprite_changed) bullet_sprite_picker.resource_selected.connect(_on_resource_picker_opening) sprite_picker_hbox.add_child(bullet_sprite_picker) var preview_container = PanelContainer.new() preview_container.custom_minimum_size = Vector2(64, 64) sprite_container.add_child(preview_container) bullet_sprite_preview = TextureRect.new() bullet_sprite_preview.custom_minimum_size = Vector2(64, 64) bullet_sprite_preview.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL bullet_sprite_preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED preview_container.add_child(bullet_sprite_preview) section.add_child(HSeparator.new()) # Bullet Scene var scene_header = Label.new() scene_header.text = "Bullet Scene" scene_header.add_theme_font_size_override("font_size", 14) section.add_child(scene_header) var scene_hbox = HBoxContainer.new() scene_hbox.add_theme_constant_override("separation", 4) section.add_child(scene_hbox) var scene_label = Label.new() scene_label.text = "Scene:" scene_label.custom_minimum_size = Vector2(120, 0) scene_hbox.add_child(scene_label) bullet_scene_picker = EditorResourcePicker.new() bullet_scene_picker.base_type = "PackedScene" bullet_scene_picker.editable = true bullet_scene_picker.size_flags_horizontal = Control.SIZE_EXPAND_FILL bullet_scene_picker.resource_selected.connect(_on_resource_picker_opening) scene_hbox.add_child(bullet_scene_picker) section.add_child(HSeparator.new()) # Destruction Particles var particles_header = Label.new() particles_header.text = "Destruction Particles (Optional)" particles_header.add_theme_font_size_override("font_size", 14) section.add_child(particles_header) var particles_hbox = HBoxContainer.new() particles_hbox.add_theme_constant_override("separation", 4) section.add_child(particles_hbox) var particles_label = Label.new() particles_label.text = "Particles Scene:" particles_label.custom_minimum_size = Vector2(120, 0) particles_hbox.add_child(particles_label) destruction_particles_picker = EditorResourcePicker.new() destruction_particles_picker.base_type = "PackedScene" destruction_particles_picker.editable = true destruction_particles_picker.size_flags_horizontal = Control.SIZE_EXPAND_FILL destruction_particles_picker.resource_selected.connect(_on_resource_picker_opening) particles_hbox.add_child(destruction_particles_picker) 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 = "Bullet 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) bullet_size_field = _add_spinbox_field(grid, "Bullet Size:", 0, 100, 0.1, 1.0) bullet_speed_field = _add_spinbox_field(grid, "Bullet Speed:", 0, 10000, 1, 100) bullet_damage_field = _add_spinbox_field(grid, "Bullet Damage:", 0, 10000, 0.1, 1.0) max_damage_field = _add_spinbox_field(grid, "Max Damage:", 0, 10000, 0.1, 1.0) knockback_field = _add_spinbox_field(grid, "Knockback:", 0, 1000, 0.1, 1.0) life_time_field = _add_spinbox_field(grid, "Life Time (s):", 0, 100, 0.1, 10.0) graze_value_field = _add_spinbox_field(grid, "Graze Value:", 0, 100, 0.1, 0.2) 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: bullet_name_field.text = "new_bullet" # Set default bullet scene based on mode if is_3d_mode: var default_scene = load("res://Scenes/Weapons/base_generic_bullet_3D.tscn") bullet_scene_picker.edited_resource = default_scene bullet_size_field.value = 1.0 bullet_speed_field.value = 100.0 bullet_damage_field.value = 1.0 max_damage_field.value = 1.0 knockback_field.value = 1.0 life_time_field.value = 10.0 graze_value_field.value = 0.2 func _apply_prefill_data() -> void: if prefill_data.has("bullet_sprite") and prefill_data["bullet_sprite"] != null: bullet_sprite_picker.edited_resource = prefill_data["bullet_sprite"] bullet_sprite_preview.texture = prefill_data["bullet_sprite"] if prefill_data.has("bullet_scene") and prefill_data["bullet_scene"] != null: bullet_scene_picker.edited_resource = prefill_data["bullet_scene"] if prefill_data.has("bullet_size"): bullet_size_field.value = prefill_data["bullet_size"] if prefill_data.has("bullet_speed"): bullet_speed_field.value = prefill_data["bullet_speed"] if prefill_data.has("bullet_damage"): bullet_damage_field.value = prefill_data["bullet_damage"] if prefill_data.has("max_damage"): max_damage_field.value = prefill_data["max_damage"] if prefill_data.has("knockback"): knockback_field.value = prefill_data["knockback"] if prefill_data.has("life_time"): life_time_field.value = prefill_data["life_time"] if prefill_data.has("graze_value"): graze_value_field.value = prefill_data["graze_value"] if prefill_data.has("destruction_particles_scene") and prefill_data["destruction_particles_scene"] != null: destruction_particles_picker.edited_resource = prefill_data["destruction_particles_scene"] func _on_sprite_changed(resource: Resource) -> void: if resource is Texture2D: bullet_sprite_preview.texture = resource # 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 - store current state pass func _restore_window_focus() -> void: # Bring window back to front after file picker closes if visible: move_to_foreground() func _on_create_pressed() -> void: if not _validate_inputs(): return var dimension_suffix = "_3D" if is_3d_mode else "" var bullet_name = bullet_name_field.text.strip_edges() # Ensure name ends with dimension suffix if 3D if is_3d_mode and not bullet_name.ends_with("_3D"): bullet_name += "_3D" var bullet_data = { "bullet_name": bullet_name, "is_3d": is_3d_mode, "bullet_sprite": bullet_sprite_picker.edited_resource, "bullet_scene": bullet_scene_picker.edited_resource, "bullet_size": bullet_size_field.value, "bullet_speed": bullet_speed_field.value, "bullet_damage": bullet_damage_field.value, "max_damage": max_damage_field.value, "knockback": knockback_field.value, "life_time": life_time_field.value, "graze_value": graze_value_field.value, "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() func _validate_inputs() -> bool: if bullet_name_field.text.strip_edges().is_empty(): _show_error("Bullet name cannot be empty") return false var bullet_name = bullet_name_field.text.strip_edges() if is_3d_mode and not bullet_name.ends_with("_3D"): bullet_name += "_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" if ResourceLoader.exists(bullet_path): _show_error("Bullet resource already exists at:\n" + bullet_path + "\n\nPlease choose a different name.") return false if not bullet_scene_picker.edited_resource: _show_error("Bullet scene is required") 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()