cirnogodot/addons/weapon_creator/BulletCreatorDialog.gd

394 lines
13 KiB
GDScript3
Raw Normal View History

@tool
extends Window
# 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
# Buttons
var create_button: Button
var cancel_button: Button
# Mode and prefill data
var is_3d_mode: bool = true
var prefill_data: Dictionary = {}
var _ui_built: bool = false
var editor_interface: EditorInterface
func setup(editor_iface: EditorInterface, is_3d: bool = true, prefill: Dictionary = {}) -> void:
editor_interface = editor_iface
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()
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)
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())
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()
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()
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
}
bullet_data_confirmed.emit(bullet_data)
hide()
queue_free()
func _on_cancel_pressed() -> void:
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 = "res://Resources/Bullets/"
if is_3d_mode:
bullets_dir += "3D/"
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()