mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-01 07:45:33 +00:00
501 lines
18 KiB
GDScript
501 lines
18 KiB
GDScript
@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")
|
|
|