@tool extends BaseViewer # 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) const SETTING_SHOW_2D = "weapon_creator/bullet_filter_show_2d" const SETTING_SHOW_3D = "weapon_creator/bullet_filter_show_3d" func _supports_2d_3d_filter() -> bool: return true func _get_filter_setting_2d_key() -> String: return SETTING_SHOW_2D func _get_filter_setting_3d_key() -> String: return SETTING_SHOW_3D func _build_header_buttons(header_hbox: HBoxContainer) -> void: 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) func refresh() -> void: _clear_grid() var show_2d = _should_show_2d() var show_3d = _should_show_3d() 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" _log_to_dock("=== Duplicating Bullet (" + dimension + ") ===", Color.CYAN) _log_to_dock("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) 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) _log_to_dock("=== Bullet Deleted ===", Color.ORANGE) _log_to_dock("Bullet: " + bullet_name, Color.ORANGE) _log_to_dock("✓ Deleted BulletResource: " + bullet_path, Color.GREEN) refresh() 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")