cirnogodot/addons/weapon_creator/WeaponViewer.gd

428 lines
16 KiB
GDScript

@tool
extends BaseViewer
# Displays all weapons from the ItemsDatabase in a grid format
# Shows sprite and name, with tooltip showing resource path
# Clicking opens the weapon resource in the inspector
signal weapon_selected(weapon_resource_path: String)
signal duplicate_weapon_requested(weapon_data: Dictionary)
signal weapon_deleted(weapon_name: String, weapon_path: String, item_path: String)
signal weapon_duplication_started(weapon_name: String, is_3d: bool)
var _items_database_path := "res://Resources/ItemsDatabase.tres"
const SETTING_SHOW_2D = "weapon_creator/filter_show_2d"
const SETTING_SHOW_3D = "weapon_creator/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_weapon_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_weapon_pressed.bind(true))
header_hbox.add_child(create_3d_button)
func refresh() -> void:
_clear_grid()
# Load ItemsDatabase
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
# Filter for weapons only (items with WeaponData3D or WeaponData)
var show_2d = _should_show_2d()
var show_3d = _should_show_3d()
var weapon_count = 0
for loot_item in loot_items:
var weapon_data_3d = loot_item.get("WeaponData3D")
var weapon_data_2d = loot_item.get("WeaponData")
if weapon_data_2d != null and show_2d:
_create_weapon_tile(loot_item, weapon_data_2d, false)
weapon_count += 1
if weapon_data_3d != null and show_3d:
_create_weapon_tile(loot_item, weapon_data_3d, true)
weapon_count += 1
if weapon_count == 0:
_add_error_label("No weapons found in database")
func _create_weapon_tile(loot_item: Resource, weapon_data: 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 = loot_item.get("InventorySprite")
# 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 in bottom-right corner with colored text and dark outline
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)) # Bright red for 3D
else:
badge.add_theme_color_override("font_color", Color(0.3, 0.5, 1.0)) # Bright blue for 2D
badge.add_theme_color_override("font_outline_color", Color(0.15, 0.15, 0.15)) # Dark gray outline
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 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 weapon_path = weapon_data.resource_path
if weapon_path:
panel.tooltip_text = weapon_path
else:
panel.tooltip_text = "Built-in resource (no file path)"
panel.gui_input.connect(_on_weapon_gui_input.bind(panel, loot_item, weapon_data))
_grid_container.add_child(panel)
func _on_weapon_clicked(weapon_data: Resource, weapon_path: String) -> void:
if not _editor_interface:
push_error("Editor interface not available")
return
if weapon_data:
_editor_interface.edit_resource(weapon_data)
weapon_selected.emit(weapon_path)
func _on_weapon_gui_input(event: InputEvent, panel: PanelContainer, loot_item: Resource, weapon_data: Resource) -> void:
if event is InputEventMouseButton:
var mouse_event = event as InputEventMouseButton
if mouse_event.pressed:
if mouse_event.button_index == MOUSE_BUTTON_LEFT:
var weapon_path = weapon_data.resource_path if weapon_data else ""
_on_weapon_clicked(weapon_data, weapon_path)
elif mouse_event.button_index == MOUSE_BUTTON_RIGHT:
_show_context_menu(panel, loot_item, weapon_data)
func _show_context_menu(panel: PanelContainer, loot_item: Resource, weapon_data: Resource) -> void:
var popup = PopupMenu.new()
popup.add_item("Open Weapon", 0)
popup.add_item("Open Item", 1)
popup.add_separator()
popup.add_item("Duplicate Weapon (2D)", 2)
popup.add_item("Duplicate Weapon (3D)", 3)
popup.add_separator()
popup.add_item("Copy Weapon Resource Path", 4)
popup.add_item("Copy Item Resource Path", 5)
popup.add_separator()
popup.add_item("Delete", 6)
popup.id_pressed.connect(_on_context_menu_id_pressed.bind(popup, loot_item, weapon_data))
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, weapon_data: Resource) -> void:
match id:
0:
_open_weapon(weapon_data)
1:
_open_item(loot_item)
2:
_duplicate_weapon(loot_item, weapon_data, false)
3:
_duplicate_weapon(loot_item, weapon_data, true)
4:
_copy_weapon_resource_path(weapon_data)
5:
_copy_item_resource_path(loot_item)
6:
_confirm_delete(loot_item, weapon_data)
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 _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 _duplicate_weapon(loot_item: Resource, weapon_data: Resource, is_3d: bool) -> void:
if not _editor_interface:
push_error("Editor interface not available")
return
var weapon_name = loot_item.get("ItemName") if loot_item else "Unknown"
var dimension = "3D" if is_3d else "2D"
# Log to dock
_log_to_dock("=== Duplicating Weapon (" + dimension + ") ===", Color.CYAN)
_log_to_dock("Source: " + weapon_name, Color.CYAN)
_log_to_dock("Opening creation dialog with prefilled data...", Color.CYAN)
# Also log to Godot console
print("Duplicating weapon (", dimension, "): ", weapon_name)
weapon_duplication_started.emit(weapon_name, is_3d)
# Extract all weapon data into a prefill dictionary
var prefill_data = {}
# Extract from LootItem
if loot_item:
prefill_data["weapon_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")
# Extract from WeaponResource
if weapon_data:
var bullet_data = weapon_data.get("BulletData")
if bullet_data and bullet_data.resource_path:
prefill_data["bullet_path"] = bullet_data.resource_path
prefill_data["ammo_key"] = weapon_data.get("AmmoKey")
prefill_data["priority"] = weapon_data.get("Priority")
prefill_data["ammo_per_shot"] = weapon_data.get("AmmoPerShot")
prefill_data["rate_of_fire"] = weapon_data.get("RateOfFire")
prefill_data["bullet_capacity"] = weapon_data.get("BulletCapacity")
prefill_data["reload_time"] = weapon_data.get("ReloadTime")
prefill_data["infinite_ammo"] = weapon_data.get("InfiniteAmmo")
prefill_data["recharge_time"] = weapon_data.get("RechargeTime")
prefill_data["recharge_amount"] = weapon_data.get("RechargeAmount")
prefill_data["bullets_per_shot"] = weapon_data.get("BulletsPerShot")
prefill_data["spread_angle"] = weapon_data.get("SpreadAngle")
prefill_data["random_spread"] = weapon_data.get("RandomSpread")
# Open creation dialog with prefilled data
var dialog_script = load("res://addons/weapon_creator/WeaponCreatorDialog.gd")
var dialog = Window.new()
dialog.set_script(dialog_script)
# Add to scene tree first
get_tree().root.add_child(dialog)
# Setup with prefill data using call_deferred to ensure script is ready
dialog.call_deferred("setup_weapon", _editor_interface, is_3d, prefill_data)
# Connect to confirmation signal using call_deferred
dialog.call_deferred("connect", "weapon_data_confirmed", _on_duplicate_weapon_confirmed)
# Show the dialog
dialog.call_deferred("popup_centered")
func _on_duplicate_weapon_confirmed(weapon_data: Dictionary) -> void:
# Forward the weapon data to be created
duplicate_weapon_requested.emit(weapon_data)
# Refresh after a short delay to allow file system to update
get_tree().create_timer(0.5).timeout.connect(refresh)
func _copy_weapon_resource_path(weapon_data: Resource) -> void:
if weapon_data and weapon_data.resource_path:
DisplayServer.clipboard_set(weapon_data.resource_path)
print("Copied weapon resource path: ", weapon_data.resource_path)
else:
push_warning("No resource path available for weapon (built-in resource)")
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, weapon_data: Resource) -> void:
var item_name: String = loot_item.get("ItemName")
var weapon_path = weapon_data.resource_path
var loot_item_path = loot_item.resource_path
var dialog_text = "Are you REALLY sure you want to delete this weapon?\n\n"
dialog_text += "Weapon: " + (item_name if item_name else "Unknown") + "\n\n"
dialog_text += "This will delete:\n"
dialog_text += "- WeaponResource: " + (weapon_path if weapon_path else "Built-in") + "\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 += "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, weapon_data, 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, weapon_data: Resource, dialog: ConfirmationDialog) -> void:
var weapon_path = weapon_data.resource_path
var loot_item_path = loot_item.resource_path
var weapon_name = loot_item.get("ItemName") if loot_item else "Unknown"
if not weapon_path or 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 weapon_deleted_success = false
var item_deleted_success = false
if DirAccess.remove_absolute(weapon_path) == OK:
print("Deleted weapon resource: ", weapon_path)
weapon_deleted_success = true
else:
push_error("Failed to delete weapon resource: ", weapon_path)
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()
# Emit deletion signal and log if both files were deleted successfully
if weapon_deleted_success and item_deleted_success:
weapon_deleted.emit(weapon_name, weapon_path, loot_item_path)
# Log to dock
_log_to_dock("=== Weapon Deleted ===", Color.ORANGE)
_log_to_dock("Weapon: " + weapon_name, Color.ORANGE)
_log_to_dock("✓ Deleted WeaponResource: " + weapon_path, Color.GREEN)
_log_to_dock("✓ Deleted LootItem: " + loot_item_path, Color.GREEN)
_log_to_dock("✓ Removed from ItemsDatabase", Color.GREEN)
refresh()
func _on_create_weapon_pressed(is_3d: bool) -> void:
if not _editor_interface:
push_error("Editor interface not available")
return
var dialog_script = load("res://addons/weapon_creator/WeaponCreatorDialog.gd")
var dialog = Window.new()
dialog.set_script(dialog_script)
get_tree().root.add_child(dialog)
dialog.call_deferred("setup_weapon", _editor_interface, is_3d)
dialog.call_deferred("connect", "weapon_data_confirmed", _on_duplicate_weapon_confirmed)
dialog.call_deferred("popup_centered")