cirnogodot/addons/weapon_creator/BulletViewer.gd

428 lines
15 KiB
GDScript3
Raw Normal View History

@tool
extends PanelContainer
# Displays all bullets from the Resources/Bullets folders in a grid format
# Shows sprite and name, with tooltip showing resource path
signal bullet_selected(bullet_resource_path: String)
signal duplicate_bullet_requested(bullet_data: Dictionary)
signal bullet_deleted(bullet_name: String, bullet_path: String)
signal bullet_duplication_started(bullet_name: String, is_3d: bool)
var _editor_interface: EditorInterface
var _grid_container: HFlowContainer
var _show_2d_checkbox: CheckBox
var _show_3d_checkbox: CheckBox
var _dock: PanelContainer
const SETTING_SHOW_2D = "weapon_creator/bullet_filter_show_2d"
const SETTING_SHOW_3D = "weapon_creator/bullet_filter_show_3d"
func setup(editor_interface: EditorInterface, dock: PanelContainer = null) -> void:
_editor_interface = editor_interface
_dock = dock
if _show_2d_checkbox:
_show_2d_checkbox.button_pressed = _load_filter_setting(SETTING_SHOW_2D, true)
if _show_3d_checkbox:
_show_3d_checkbox.button_pressed = _load_filter_setting(SETTING_SHOW_3D, true)
refresh_bullets()
func _ready() -> void:
_build_ui()
refresh_bullets()
func _build_ui() -> void:
var margin = MarginContainer.new()
margin.add_theme_constant_override("margin_left", 8)
margin.add_theme_constant_override("margin_top", 8)
margin.add_theme_constant_override("margin_right", 8)
margin.add_theme_constant_override("margin_bottom", 8)
add_child(margin)
var vbox = VBoxContainer.new()
vbox.add_theme_constant_override("separation", 8)
margin.add_child(vbox)
# Header with title, filters, and buttons
var header_hbox = HBoxContainer.new()
vbox.add_child(header_hbox)
var create_2d_button = Button.new()
create_2d_button.text = "Create (2D)"
create_2d_button.pressed.connect(_on_create_bullet_pressed.bind(false))
header_hbox.add_child(create_2d_button)
var create_3d_button = Button.new()
create_3d_button.text = "Create (3D)"
create_3d_button.pressed.connect(_on_create_bullet_pressed.bind(true))
header_hbox.add_child(create_3d_button)
# Spacer
var spacer = Control.new()
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header_hbox.add_child(spacer)
_show_2d_checkbox = CheckBox.new()
_show_2d_checkbox.text = "2D"
_show_2d_checkbox.button_pressed = true
_show_2d_checkbox.toggled.connect(_on_filter_changed)
header_hbox.add_child(_show_2d_checkbox)
_show_3d_checkbox = CheckBox.new()
_show_3d_checkbox.text = "3D"
_show_3d_checkbox.button_pressed = true
_show_3d_checkbox.toggled.connect(_on_filter_changed)
header_hbox.add_child(_show_3d_checkbox)
var refresh_button = Button.new()
refresh_button.text = "Refresh"
refresh_button.pressed.connect(refresh_bullets)
header_hbox.add_child(refresh_button)
vbox.add_child(HSeparator.new())
# Scroll container for grid
var scroll = ScrollContainer.new()
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
vbox.add_child(scroll)
_grid_container = HFlowContainer.new()
_grid_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_grid_container.add_theme_constant_override("h_separation", 8)
_grid_container.add_theme_constant_override("v_separation", 8)
scroll.add_child(_grid_container)
func refresh_bullets() -> void:
if not _grid_container:
return
# Clear existing items
for child in _grid_container.get_children():
child.queue_free()
var show_2d = _show_2d_checkbox == null or _show_2d_checkbox.button_pressed
var show_3d = _show_3d_checkbox == null or _show_3d_checkbox.button_pressed
var bullet_count = 0
# Load 2D bullets
if show_2d:
bullet_count += _load_bullets_from_directory("res://Resources/Bullets/", false)
# Load 3D bullets
if show_3d:
bullet_count += _load_bullets_from_directory("res://Resources/Bullets/3D/", true)
if bullet_count == 0:
_add_error_label("No bullets found")
func _load_bullets_from_directory(dir_path: String, is_3d: bool) -> int:
var dir = DirAccess.open(dir_path)
if dir == null:
return 0
var count = 0
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
if not dir.current_is_dir() and file_name.ends_with(".tres"):
var bullet_path = dir_path + file_name
var bullet_resource = load(bullet_path)
if bullet_resource and bullet_resource.get_script():
var script_path = bullet_resource.get_script().resource_path
if script_path and "BulletResource.cs" in script_path:
_create_bullet_tile(bullet_resource, is_3d)
count += 1
file_name = dir.get_next()
dir.list_dir_end()
return count
func _create_bullet_tile(bullet_resource: Resource, is_3d: bool) -> void:
var panel = PanelContainer.new()
panel.custom_minimum_size = Vector2(100, 145)
panel.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
panel.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
panel.mouse_filter = Control.MOUSE_FILTER_STOP
panel.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
var vbox = VBoxContainer.new()
vbox.add_theme_constant_override("separation", 4)
vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
var sprite: Texture2D = bullet_resource.get("BulletSprite")
# Container for sprite with badge overlay
var sprite_container = Control.new()
sprite_container.custom_minimum_size = Vector2(64, 64)
sprite_container.mouse_filter = Control.MOUSE_FILTER_IGNORE
vbox.add_child(sprite_container)
var texture_rect = TextureRect.new()
texture_rect.custom_minimum_size = Vector2(64, 64)
texture_rect.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
texture_rect.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
texture_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
texture_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
sprite_container.add_child(texture_rect)
if sprite:
texture_rect.texture = sprite
else:
var placeholder_label = Label.new()
placeholder_label.text = "No Icon"
placeholder_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
placeholder_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
placeholder_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
placeholder_label.add_theme_font_size_override("font_size", 10)
placeholder_label.set_anchors_preset(Control.PRESET_FULL_RECT)
sprite_container.add_child(placeholder_label)
# Add 2D/3D badge
var badge = Label.new()
badge.text = "3D" if is_3d else "2D"
badge.add_theme_font_size_override("font_size", 14)
if is_3d:
badge.add_theme_color_override("font_color", Color(1.0, 0.2, 0.2))
else:
badge.add_theme_color_override("font_color", Color(0.3, 0.5, 1.0))
badge.add_theme_color_override("font_outline_color", Color(0.15, 0.15, 0.15))
badge.add_theme_constant_override("outline_size", 3)
badge.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
badge.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
badge.mouse_filter = Control.MOUSE_FILTER_IGNORE
badge.position = Vector2(38, 44)
badge.size = Vector2(24, 20)
sprite_container.add_child(badge)
var name_label = Label.new()
var bullet_name: String = bullet_resource.resource_path.get_file().get_basename()
name_label.text = bullet_name if bullet_name else "Unknown"
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
name_label.vertical_alignment = VERTICAL_ALIGNMENT_TOP
name_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
name_label.custom_minimum_size = Vector2(92, 48)
name_label.clip_text = false
name_label.max_lines_visible = 3
name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
name_label.add_theme_font_size_override("font_size", 14)
name_label.add_theme_constant_override("line_spacing", -2)
vbox.add_child(name_label)
panel.add_child(vbox)
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
vbox.add_theme_constant_override("margin_left", 4)
vbox.add_theme_constant_override("margin_top", 4)
vbox.add_theme_constant_override("margin_right", 4)
vbox.add_theme_constant_override("margin_bottom", 4)
var bullet_path = bullet_resource.resource_path
if bullet_path:
panel.tooltip_text = bullet_path
else:
panel.tooltip_text = "Built-in resource (no file path)"
panel.gui_input.connect(_on_bullet_gui_input.bind(panel, bullet_resource))
_grid_container.add_child(panel)
func _on_bullet_gui_input(event: InputEvent, panel: PanelContainer, bullet_resource: Resource) -> void:
if event is InputEventMouseButton:
var mouse_event = event as InputEventMouseButton
if mouse_event.pressed:
if mouse_event.button_index == MOUSE_BUTTON_LEFT:
_on_bullet_clicked(bullet_resource)
elif mouse_event.button_index == MOUSE_BUTTON_RIGHT:
_show_context_menu(panel, bullet_resource)
func _on_bullet_clicked(bullet_resource: Resource) -> void:
if not _editor_interface:
push_error("Editor interface not available")
return
if bullet_resource:
_editor_interface.edit_resource(bullet_resource)
bullet_selected.emit(bullet_resource.resource_path)
func _show_context_menu(panel: PanelContainer, bullet_resource: Resource) -> void:
var popup = PopupMenu.new()
popup.add_item("Open Bullet", 0)
popup.add_separator()
# Determine if this is a 2D or 3D bullet
var is_3d = "3D" in bullet_resource.resource_path
popup.add_item("Duplicate Bullet (2D)", 1)
popup.add_item("Duplicate Bullet (3D)", 2)
popup.add_separator()
popup.add_item("Copy Resource Path", 3)
popup.add_separator()
popup.add_item("Delete", 4)
popup.id_pressed.connect(_on_context_menu_id_pressed.bind(popup, bullet_resource))
get_tree().root.add_child(popup)
var mouse_pos = DisplayServer.mouse_get_position()
popup.position = mouse_pos
popup.popup()
popup.popup_hide.connect(func(): popup.queue_free())
func _on_context_menu_id_pressed(id: int, popup: PopupMenu, bullet_resource: Resource) -> void:
match id:
0:
_open_bullet(bullet_resource)
1:
_duplicate_bullet(bullet_resource, false)
2:
_duplicate_bullet(bullet_resource, true)
3:
_copy_bullet_resource_path(bullet_resource)
4:
_confirm_delete(bullet_resource)
func _open_bullet(bullet_resource: Resource) -> void:
if not _editor_interface:
push_error("Editor interface not available")
return
if bullet_resource:
_editor_interface.edit_resource(bullet_resource)
func _duplicate_bullet(bullet_resource: Resource, is_3d: bool) -> void:
if not _editor_interface:
push_error("Editor interface not available")
return
var bullet_name = bullet_resource.resource_path.get_file().get_basename()
var dimension = "3D" if is_3d else "2D"
if _dock:
_dock.call("add_log", "=== Duplicating Bullet (" + dimension + ") ===", Color.CYAN)
_dock.call("add_log", "Source: " + bullet_name, Color.CYAN)
bullet_duplication_started.emit(bullet_name, is_3d)
# Extract bullet data
var prefill_data = {}
prefill_data["bullet_sprite"] = bullet_resource.get("BulletSprite")
prefill_data["bullet_size"] = bullet_resource.get("BulletSize")
prefill_data["bullet_speed"] = bullet_resource.get("BulletSpeed")
prefill_data["bullet_damage"] = bullet_resource.get("BulletDamage")
prefill_data["max_damage"] = bullet_resource.get("MaxDamage")
prefill_data["knockback"] = bullet_resource.get("Knockback")
prefill_data["life_time"] = bullet_resource.get("LifeTime")
prefill_data["graze_value"] = bullet_resource.get("GrazeValue")
prefill_data["bullet_scene"] = bullet_resource.get("BulletScene")
prefill_data["destruction_particles_scene"] = bullet_resource.get("DestructionParticlesScene")
_open_bullet_dialog(is_3d, prefill_data)
func _on_duplicate_bullet_confirmed(bullet_data: Dictionary) -> void:
duplicate_bullet_requested.emit(bullet_data)
get_tree().create_timer(0.5).timeout.connect(refresh_bullets)
func _copy_bullet_resource_path(bullet_resource: Resource) -> void:
if bullet_resource and bullet_resource.resource_path:
DisplayServer.clipboard_set(bullet_resource.resource_path)
print("Copied bullet resource path: ", bullet_resource.resource_path)
func _confirm_delete(bullet_resource: Resource) -> void:
var bullet_name: String = bullet_resource.resource_path.get_file().get_basename()
var bullet_path = bullet_resource.resource_path
var dialog_text = "Are you REALLY sure you want to delete this bullet?\n\n"
dialog_text += "Bullet: " + bullet_name + "\n\n"
dialog_text += "Path: " + bullet_path + "\n\n"
dialog_text += "This action cannot be undone!"
var confirm_dialog = ConfirmationDialog.new()
confirm_dialog.dialog_text = dialog_text
confirm_dialog.title = "Confirm Deletion"
confirm_dialog.ok_button_text = "Delete"
confirm_dialog.confirmed.connect(_perform_delete.bind(bullet_resource, confirm_dialog))
confirm_dialog.close_requested.connect(func(): confirm_dialog.queue_free())
confirm_dialog.canceled.connect(func(): confirm_dialog.queue_free())
get_tree().root.add_child(confirm_dialog)
confirm_dialog.popup_centered()
func _perform_delete(bullet_resource: Resource, dialog: ConfirmationDialog) -> void:
var bullet_path = bullet_resource.resource_path
var bullet_name = bullet_resource.resource_path.get_file().get_basename()
if not bullet_path:
push_error("Cannot delete built-in resources")
dialog.queue_free()
return
var deleted_success = false
if DirAccess.remove_absolute(bullet_path) == OK:
print("Deleted bullet resource: ", bullet_path)
deleted_success = true
else:
push_error("Failed to delete bullet resource: ", bullet_path)
if _editor_interface:
_editor_interface.get_resource_filesystem().scan()
dialog.queue_free()
if deleted_success:
bullet_deleted.emit(bullet_name, bullet_path)
if _dock:
_dock.call("add_log", "=== Bullet Deleted ===", Color.ORANGE)
_dock.call("add_log", "Bullet: " + bullet_name, Color.ORANGE)
_dock.call("add_log", "✓ Deleted BulletResource: " + bullet_path, Color.GREEN)
refresh_bullets()
func _on_create_bullet_pressed(is_3d: bool) -> void:
_open_bullet_dialog(is_3d, {})
func _open_bullet_dialog(is_3d: bool, prefill_data: Dictionary = {}) -> void:
var dialog_script = load("res://addons/weapon_creator/BulletCreatorDialog.gd")
var dialog = Window.new()
dialog.set_script(dialog_script)
get_tree().root.add_child(dialog)
dialog.call_deferred("setup_bullet", _editor_interface, is_3d, prefill_data)
dialog.call_deferred("connect", "bullet_data_confirmed", _on_duplicate_bullet_confirmed)
dialog.call_deferred("popup_centered")
func _add_error_label(error_message: String) -> void:
var label = Label.new()
label.text = error_message
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.modulate = Color.ORANGE_RED
_grid_container.add_child(label)
func _on_filter_changed(_toggled: bool) -> void:
_save_filter_settings()
refresh_bullets()
func _load_filter_setting(key: String, default_value: bool) -> bool:
if not _editor_interface:
return default_value
var editor_settings = _editor_interface.get_editor_settings()
if editor_settings and editor_settings.has_setting(key):
return editor_settings.get_setting(key)
return default_value
func _save_filter_settings() -> void:
if not _editor_interface:
return
var editor_settings = _editor_interface.get_editor_settings()
if editor_settings:
editor_settings.set_setting(SETTING_SHOW_2D, _show_2d_checkbox.button_pressed)
editor_settings.set_setting(SETTING_SHOW_3D, _show_3d_checkbox.button_pressed)