mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-01 10:45:33 +00:00
394 lines
13 KiB
GDScript3
394 lines
13 KiB
GDScript3
|
|
@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()
|
|||
|
|
|