@tool extends BaseViewer # Displays all items from the ItemsDatabase in a grid format # Shows sprite, name, and item type badge # Clicking opens the item resource in the inspector signal item_selected(item_resource_path: String) signal duplicate_item_requested(item_data: Dictionary) signal item_deleted(item_name: String, item_path: String) signal item_duplication_started(item_name: String) var _items_database_path := "res://Resources/ItemsDatabase.tres" var _show_neither_checkbox: CheckBox const SETTING_SHOW_2D = "weapon_creator/item_filter_show_2d" const SETTING_SHOW_3D = "weapon_creator/item_filter_show_3d" const SETTING_SHOW_NEITHER = "weapon_creator/item_filter_show_neither" # ItemTypes enum values mapped to colors const ITEM_TYPE_COLORS = { 0: Color(1.0, 0.2, 0.2), # KeycardRed - Red 1: Color(0.3, 0.5, 1.0), # KeycardBlue - Blue 2: Color(0.3, 1.0, 0.3), # KeycardGreen - Green 3: Color(1.0, 0.8, 0.2), # Ammo - Yellow 4: Color(1.0, 0.3, 0.3), # Medkit - Light Red 5: Color(0.5, 1.0, 0.3), # FrogBomb - Lime 6: Color(1.0, 0.5, 0.0), # Bomb - Orange 7: Color(0.8, 0.3, 0.0), # Mine - Brown/Orange 8: Color(1.0, 1.0, 0.2), # Battery - Bright Yellow 9: Color(0.7, 0.7, 0.7), # Weapon - Gray 10: Color(1.0, 0.2, 1.0), # Power - Magenta 11: Color(1.0, 1.0, 1.0), # Points - White 12: Color(0.8, 0.8, 0.0), # Credits - Gold 13: Color(0.5, 0.3, 1.0) # KeyItem - Purple } const ITEM_TYPE_NAMES = [ "KeycardRed", "KeycardBlue", "KeycardGreen", "Ammo", "Medkit", "FrogBomb", "Bomb", "Mine", "Battery", "Weapon", "Power", "Points", "Credits", "KeyItem" ] 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 # Override to add the "Neither" checkbox func _build_ui() -> void: super._build_ui() # Find the header_hbox and add the Neither checkbox after 3D checkbox var margin = get_child(0) as MarginContainer var vbox = margin.get_child(0) as VBoxContainer var header_hbox = vbox.get_child(0) as HBoxContainer # The refresh button is the last child, so insert before it var refresh_button = header_hbox.get_child(header_hbox.get_child_count() - 1) _show_neither_checkbox = CheckBox.new() _show_neither_checkbox.text = "Neither" _show_neither_checkbox.button_pressed = _load_filter_setting(SETTING_SHOW_NEITHER, true) _show_neither_checkbox.toggled.connect(_on_filter_changed) _show_neither_checkbox.tooltip_text = "Show items that don't have 2D or 3D properties set" header_hbox.add_child(_show_neither_checkbox) header_hbox.move_child(_show_neither_checkbox, header_hbox.get_child_count() - 2) func _build_header_buttons(header_hbox: HBoxContainer) -> void: var create_button = Button.new() create_button.text = "Create Item" create_button.pressed.connect(_on_create_item_pressed) header_hbox.add_child(create_button) func setup(editor_interface: EditorInterface, dock: PanelContainer = null) -> void: super.setup(editor_interface, dock) if _show_neither_checkbox: _show_neither_checkbox.button_pressed = _load_filter_setting(SETTING_SHOW_NEITHER, true) func refresh() -> void: _clear_grid() if not ResourceLoader.exists(_items_database_path): _add_error_label("ItemsDatabase not found at: " + _items_database_path) return var items_database = load(_items_database_path) if items_database == null: _add_error_label("Failed to load ItemsDatabase") return var loot_items = items_database.get("LootItems") if loot_items == null: _add_error_label("No LootItems found in database") return var show_2d = _should_show_2d() var show_3d = _should_show_3d() var show_neither = _show_neither_checkbox == null or _show_neither_checkbox.button_pressed var item_count = 0 for loot_item in loot_items: var has_2d = _item_has_2d(loot_item) var has_3d = _item_has_3d(loot_item) var should_show = false if has_2d and has_3d: should_show = show_2d or show_3d elif has_2d: should_show = show_2d elif has_3d: should_show = show_3d else: should_show = show_neither if should_show: _create_item_tile(loot_item, has_2d, has_3d) item_count += 1 if item_count == 0: _add_info_label("No items match current filters") func _item_has_2d(loot_item: Resource) -> bool: var weapon_data = loot_item.get("WeaponData") var drop_scene = loot_item.get("DropScenePath") return weapon_data != null or (drop_scene != null and drop_scene != "") func _item_has_3d(loot_item: Resource) -> bool: var weapon_data_3d = loot_item.get("WeaponData3D") var drop_scene_3d = loot_item.get("DropScenePath3D") return weapon_data_3d != null or (drop_scene_3d != null and drop_scene_3d != "") # Override to save the Neither checkbox setting func _save_filter_settings() -> void: super._save_filter_settings() if not _editor_interface: return var editor_settings = _editor_interface.get_editor_settings() if editor_settings and _show_neither_checkbox: editor_settings.set_setting(SETTING_SHOW_NEITHER, _show_neither_checkbox.button_pressed) func _create_item_tile(loot_item: Resource, has_2d: bool, has_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 = loot_item.get("InventorySprite") 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 item type badge in top-left corner with color coding var item_type: int = loot_item.get("Item") var item_type_color = ITEM_TYPE_COLORS.get(item_type, Color.WHITE) var item_type_name = ITEM_TYPE_NAMES[item_type] if item_type < ITEM_TYPE_NAMES.size() else str(item_type) var type_badge = Label.new() type_badge.text = item_type_name type_badge.add_theme_font_size_override("font_size", 10) type_badge.add_theme_color_override("font_color", item_type_color) type_badge.add_theme_color_override("font_outline_color", Color(0.15, 0.15, 0.15)) type_badge.add_theme_constant_override("outline_size", 3) type_badge.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT type_badge.vertical_alignment = VERTICAL_ALIGNMENT_TOP type_badge.mouse_filter = Control.MOUSE_FILTER_IGNORE type_badge.position = Vector2(2, 2) type_badge.custom_minimum_size = Vector2(60, 12) type_badge.clip_text = true sprite_container.add_child(type_badge) # Add 2D/3D/Neither badge in bottom-right corner var dimension_badge = Label.new() if has_2d and has_3d: dimension_badge.text = "2D+3D" dimension_badge.add_theme_color_override("font_color", Color(0.8, 0.2, 0.8)) # Purple elif has_2d: dimension_badge.text = "2D" dimension_badge.add_theme_color_override("font_color", Color(0.3, 0.5, 1.0)) # Blue elif has_3d: dimension_badge.text = "3D" dimension_badge.add_theme_color_override("font_color", Color(1.0, 0.2, 0.2)) # Red else: dimension_badge.text = "??" dimension_badge.add_theme_color_override("font_color", Color(1.0, 0.5, 0.0)) # Orange (warning) dimension_badge.add_theme_font_size_override("font_size", 12) dimension_badge.add_theme_color_override("font_outline_color", Color(0.15, 0.15, 0.15)) dimension_badge.add_theme_constant_override("outline_size", 3) dimension_badge.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT dimension_badge.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM dimension_badge.mouse_filter = Control.MOUSE_FILTER_IGNORE dimension_badge.position = Vector2(24, 44) dimension_badge.size = Vector2(38, 20) sprite_container.add_child(dimension_badge) var name_label = Label.new() var item_name: String = loot_item.get("ItemName") name_label.text = item_name if item_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 item_path = loot_item.resource_path if item_path: panel.tooltip_text = item_path else: panel.tooltip_text = "Built-in resource (no file path)" panel.gui_input.connect(_on_item_gui_input.bind(panel, loot_item, has_2d, has_3d)) _grid_container.add_child(panel) func _on_item_clicked(loot_item: Resource, item_path: String) -> void: if not _editor_interface: push_error("Editor interface not available") return if loot_item: _editor_interface.edit_resource(loot_item) item_selected.emit(item_path) func _on_item_gui_input(event: InputEvent, panel: PanelContainer, loot_item: Resource, has_2d: bool, has_3d: bool) -> void: if event is InputEventMouseButton: var mouse_event = event as InputEventMouseButton if mouse_event.pressed: if mouse_event.button_index == MOUSE_BUTTON_LEFT: var item_path = loot_item.resource_path if loot_item else "" _on_item_clicked(loot_item, item_path) elif mouse_event.button_index == MOUSE_BUTTON_RIGHT: _show_context_menu(panel, loot_item, has_2d, has_3d) func _show_context_menu(panel: PanelContainer, loot_item: Resource, has_2d: bool, has_3d: bool) -> void: var popup = PopupMenu.new() popup.add_item("Open Item", 0) var weapon_data_2d = loot_item.get("WeaponData") var weapon_data_3d = loot_item.get("WeaponData3D") if weapon_data_2d or weapon_data_3d: popup.add_separator() if weapon_data_2d: popup.add_item("Open Weapon (2D)", 1) if weapon_data_3d: popup.add_item("Open Weapon (3D)", 2) popup.add_separator() popup.add_item("Duplicate Item", 3) popup.add_separator() popup.add_item("Copy Item Resource Path", 4) popup.add_separator() popup.add_item("Delete", 5) popup.id_pressed.connect(_on_context_menu_id_pressed.bind(popup, loot_item)) 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, loot_item: Resource) -> void: match id: 0: _open_item(loot_item) 1: _open_weapon(loot_item.get("WeaponData")) 2: _open_weapon(loot_item.get("WeaponData3D")) 3: _duplicate_item(loot_item) 4: _copy_item_resource_path(loot_item) 5: _confirm_delete(loot_item) func _open_item(loot_item: Resource) -> void: if not _editor_interface: push_error("Editor interface not available") return if loot_item: _editor_interface.edit_resource(loot_item) func _open_weapon(weapon_data: Resource) -> void: if not _editor_interface: push_error("Editor interface not available") return if weapon_data: _editor_interface.edit_resource(weapon_data) func _duplicate_item(loot_item: Resource) -> void: if not _editor_interface: push_error("Editor interface not available") return var item_name = loot_item.get("ItemName") if loot_item else "Unknown" _log_to_dock("=== Duplicating Item ===", Color.CYAN) _log_to_dock("Source: " + item_name, Color.CYAN) _log_to_dock("Opening creation dialog with prefilled data...", Color.CYAN) print("Duplicating item: ", item_name) item_duplication_started.emit(item_name) var prefill_data = {} if loot_item: prefill_data["item_name"] = loot_item.get("ItemName") prefill_data["item_key"] = loot_item.get("ItemKey") prefill_data["short_name"] = loot_item.get("ShortName") prefill_data["description"] = loot_item.get("ItemDescription") prefill_data["sprite_resource"] = loot_item.get("InventorySprite") prefill_data["item_type"] = loot_item.get("Item") prefill_data["tier"] = loot_item.get("Tier") prefill_data["price"] = loot_item.get("Price") prefill_data["amount"] = loot_item.get("Amount") prefill_data["max"] = loot_item.get("Max") prefill_data["pickup_if_maxed"] = loot_item.get("PickupIfMaxed") prefill_data["consume_on_use"] = loot_item.get("ConsumeOnUse") prefill_data["selectable"] = loot_item.get("Selectable") var dialog_script = load("res://addons/weapon_creator/ItemCreatorDialog.gd") var dialog = Window.new() dialog.set_script(dialog_script) get_tree().root.add_child(dialog) dialog.call_deferred("setup", _editor_interface, prefill_data) dialog.call_deferred("connect", "item_data_confirmed", _on_duplicate_item_confirmed) dialog.call_deferred("popup_centered") func _on_duplicate_item_confirmed(item_data: Dictionary) -> void: duplicate_item_requested.emit(item_data) get_tree().create_timer(0.5).timeout.connect(refresh) func _copy_item_resource_path(loot_item: Resource) -> void: if loot_item and loot_item.resource_path: DisplayServer.clipboard_set(loot_item.resource_path) print("Copied item resource path: ", loot_item.resource_path) else: push_warning("No resource path available for item (built-in resource)") func _confirm_delete(loot_item: Resource) -> void: var item_name: String = loot_item.get("ItemName") var loot_item_path = loot_item.resource_path var dialog_text = "Are you sure you want to delete this item?\n\n" dialog_text += "Item: " + (item_name if item_name else "Unknown") + "\n\n" dialog_text += "This will delete:\n" dialog_text += "- LootItem: " + (loot_item_path if loot_item_path else "Built-in") + "\n" dialog_text += "- Remove the item from ItemsDatabase\n\n" dialog_text += "Note: This will NOT delete any linked weapon resources or drop scenes.\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(loot_item, 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(loot_item: Resource, dialog: ConfirmationDialog) -> void: var loot_item_path = loot_item.resource_path var item_name = loot_item.get("ItemName") if loot_item else "Unknown" if not loot_item_path: push_error("Cannot delete built-in resources") dialog.queue_free() return var items_database = load(_items_database_path) if not items_database: push_error("Failed to load ItemsDatabase") dialog.queue_free() return var loot_items = items_database.get("LootItems") if not loot_items: push_error("No LootItems found in database") dialog.queue_free() return var index = loot_items.find(loot_item) if index >= 0: loot_items.remove_at(index) var save_result = ResourceSaver.save(items_database, _items_database_path) if save_result != OK: push_error("Failed to save ItemsDatabase after removing item") var file_system = _editor_interface.get_resource_filesystem() var item_deleted_success = false if DirAccess.remove_absolute(loot_item_path) == OK: print("Deleted loot item resource: ", loot_item_path) item_deleted_success = true else: push_error("Failed to delete loot item resource: ", loot_item_path) if file_system: file_system.scan() dialog.queue_free() if item_deleted_success: item_deleted.emit(item_name, loot_item_path) if _dock: _dock.call("add_log", "=== Item Deleted ===", Color.ORANGE) _dock.call("add_log", "Item: " + item_name, Color.ORANGE) _dock.call("add_log", "✓ Deleted LootItem: " + loot_item_path, Color.GREEN) _dock.call("add_log", "✓ Removed from ItemsDatabase", Color.GREEN) _dock.call("add_log", "Note: Linked assets were NOT deleted", Color.YELLOW) refresh() func _on_create_item_pressed() -> void: if not _editor_interface: push_error("Editor interface not available") return var dialog_script = load("res://addons/weapon_creator/ItemCreatorDialog.gd") var dialog = Window.new() dialog.set_script(dialog_script) get_tree().root.add_child(dialog) dialog.call_deferred("setup", _editor_interface) dialog.call_deferred("connect", "item_data_confirmed", _on_duplicate_item_confirmed) dialog.call_deferred("popup_centered")