cirnogodot/addons/weapon_creator/BulletCreatorDialog.gd

373 lines
13 KiB
GDScript3
Raw Permalink Normal View History

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