diff --git a/addons/weapon_creator/WeaponCreatorDialog.gd b/addons/weapon_creator/WeaponCreatorDialog.gd index 16891c0d..65a4d504 100644 --- a/addons/weapon_creator/WeaponCreatorDialog.gd +++ b/addons/weapon_creator/WeaponCreatorDialog.gd @@ -12,6 +12,14 @@ var item_key_field: LineEdit var ammo_key_field: LineEdit var short_name_field: LineEdit var description_field: TextEdit + +# Sprite selection fields +var sprite_resource: Texture2D +var sprite_picker: EditorResourcePicker +var sprite_preview: TextureRect +var editor_interface: EditorInterface + +# Bullet selection fields var bullet_dropdown: OptionButton var bullet_path_field: LineEdit @@ -32,13 +40,21 @@ var random_spread_field: SpinBox var create_button: Button var cancel_button: Button +func setup(editor_iface: EditorInterface) -> void: + editor_interface = editor_iface + # If sprite picker already exists, ensure it has editor context + if sprite_picker: + _setup_sprite_picker() + func _ready() -> void: # Window configuration title = "Create New Weapon" - size = Vector2i(600, 700) + size = Vector2i(750, 950) transient = true exclusive = true - popup_window = true + + # Connect close request (X button) to cancel action + close_requested.connect(_on_cancel_pressed) # Center on screen position = (DisplayServer.screen_get_size() - size) / 2 @@ -133,6 +149,16 @@ func _build_basic_info_section(parent: Control) -> void: section.add_child(HSeparator.new()) + # Sprite Selection + var sprite_header = Label.new() + sprite_header.text = "Item Sprite" + sprite_header.add_theme_font_size_override("font_size", 14) + section.add_child(sprite_header) + + _build_sprite_selector(section) + + section.add_child(HSeparator.new()) + # Bullet Selection var bullet_header = Label.new() bullet_header.text = "Bullet Configuration" @@ -161,6 +187,70 @@ func _build_basic_info_section(parent: Control) -> void: bullet_path_field.editable = false bullet_path_field.text = "res://Resources/Bullets/simple_ice_bullet.tres" +func _build_sprite_selector(parent: Control) -> void: + # Container for sprite selector with preview + var sprite_container = HBoxContainer.new() + sprite_container.add_theme_constant_override("separation", 8) + parent.add_child(sprite_container) + + # Left side: Label and resource picker + var left_vbox = VBoxContainer.new() + left_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL + left_vbox.add_theme_constant_override("separation", 4) + sprite_container.add_child(left_vbox) + + # Resource picker + var picker_hbox = HBoxContainer.new() + picker_hbox.add_theme_constant_override("separation", 4) + left_vbox.add_child(picker_hbox) + + var picker_label = Label.new() + picker_label.text = "Sprite:" + picker_label.custom_minimum_size = Vector2(120, 0) + picker_hbox.add_child(picker_label) + + sprite_picker = EditorResourcePicker.new() + sprite_picker.base_type = "Texture2D" + sprite_picker.editable = true + sprite_picker.size_flags_horizontal = Control.SIZE_EXPAND_FILL + sprite_picker.resource_changed.connect(_on_sprite_resource_changed) + picker_hbox.add_child(sprite_picker) + + # Setup editor integration if interface is available + if editor_interface: + _setup_sprite_picker() + + # Right side: Preview + var preview_container = PanelContainer.new() + preview_container.custom_minimum_size = Vector2(64, 64) + sprite_container.add_child(preview_container) + + sprite_preview = TextureRect.new() + sprite_preview.custom_minimum_size = Vector2(64, 64) + sprite_preview.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL + sprite_preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + preview_container.add_child(sprite_preview) + +func _on_sprite_resource_changed(resource: Resource) -> void: + # Update sprite resource and preview when changed via EditorResourcePicker + if resource is Texture2D: + sprite_resource = resource + sprite_preview.texture = resource + print("Sprite resource changed: ", resource.resource_path if resource.resource_path else "[Unsaved Resource]") + elif resource == null: + sprite_resource = null + sprite_preview.texture = null + print("Sprite resource cleared") + else: + push_warning("Selected resource is not a Texture2D") + +func _setup_sprite_picker() -> void: + # Ensure sprite picker is properly integrated with editor + if sprite_picker and editor_interface: + # EditorResourcePicker automatically integrates with editor when in editor context + # Just ensure toggle_mode is set correctly + sprite_picker.toggle_mode = false + func _build_stats_section(parent: Control) -> void: var section = VBoxContainer.new() section.add_theme_constant_override("separation", 4) @@ -299,6 +389,7 @@ func _on_bullet_selected(index: int) -> void: var selected_path = bullet_dropdown.get_item_metadata(index) bullet_path_field.text = selected_path + func _on_weapon_name_changed(new_name: String) -> void: # Auto-generate item key from weapon name var generated_key = new_name.to_upper().replace(" ", "_") @@ -317,6 +408,7 @@ func _on_create_pressed() -> void: "ammo_key": ammo_key_field.text, "weapon_short_name": short_name_field.text, "weapon_description": description_field.text, + "sprite_resource": sprite_resource, "default_bullet_path": bullet_path_field.text, "priority": int(priority_field.value), "ammo_per_shot": int(ammo_per_shot_field.value), diff --git a/addons/weapon_creator/WeaponCreatorDialog.gd.uid b/addons/weapon_creator/WeaponCreatorDialog.gd.uid new file mode 100644 index 00000000..79e327b0 --- /dev/null +++ b/addons/weapon_creator/WeaponCreatorDialog.gd.uid @@ -0,0 +1 @@ +uid://byyekqx5glmun diff --git a/addons/weapon_creator/WeaponCreatorDock.gd b/addons/weapon_creator/WeaponCreatorDock.gd index 1973a1dc..3b250193 100644 --- a/addons/weapon_creator/WeaponCreatorDock.gd +++ b/addons/weapon_creator/WeaponCreatorDock.gd @@ -10,8 +10,13 @@ signal create_weapon_requested(weapon_data: Dictionary) var open_dialog_button: Button var clear_button: Button var log_output: RichTextLabel +var weapon_viewer: PanelContainer var _is_creating: bool = false +var _editor_interface: EditorInterface + +func setup(editor_interface: EditorInterface) -> void: + _editor_interface = editor_interface func _ready() -> void: _build_ui() @@ -57,14 +62,34 @@ func _build_ui() -> void: vbox.add_child(HSeparator.new()) - # Log section + # Horizontal split container for weapon viewer and log + var hsplit = HSplitContainer.new() + hsplit.size_flags_vertical = Control.SIZE_EXPAND_FILL + vbox.add_child(hsplit) + + # Log section (left side) + var log_vbox = VBoxContainer.new() + log_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL + log_vbox.add_theme_constant_override("separation", 4) + hsplit.add_child(log_vbox) + + # Weapon Viewer (right side) + var weapon_viewer_script = load("res://addons/weapon_creator/WeaponViewer.gd") + weapon_viewer = PanelContainer.new() + weapon_viewer.set_script(weapon_viewer_script) + weapon_viewer.custom_minimum_size = Vector2(300, 0) + weapon_viewer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + if _editor_interface: + weapon_viewer.call("setup", _editor_interface) + hsplit.add_child(weapon_viewer) + var log_label = Label.new() log_label.text = "Output Log:" - vbox.add_child(log_label) + log_vbox.add_child(log_label) var log_scroll = ScrollContainer.new() log_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL - vbox.add_child(log_scroll) + log_vbox.add_child(log_scroll) log_output = RichTextLabel.new() log_output.bbcode_enabled = true @@ -79,6 +104,10 @@ func _on_open_dialog_pressed() -> void: var dialog = Window.new() dialog.set_script(dialog_script) + # Setup editor interface + if _editor_interface: + dialog.call("setup", _editor_interface) + # Connect to the confirmation signal dialog.weapon_data_confirmed.connect(_on_weapon_data_confirmed) @@ -109,3 +138,7 @@ func set_creation_complete() -> void: # Re-enable the create button after creation is complete _is_creating = false open_dialog_button.disabled = false + + # Refresh weapon viewer to show newly created weapon + if weapon_viewer: + weapon_viewer.call("refresh_weapons") diff --git a/addons/weapon_creator/WeaponCreatorDock.gd.uid b/addons/weapon_creator/WeaponCreatorDock.gd.uid new file mode 100644 index 00000000..6e5b7a7c --- /dev/null +++ b/addons/weapon_creator/WeaponCreatorDock.gd.uid @@ -0,0 +1 @@ +uid://dpt41usbrbnul diff --git a/addons/weapon_creator/WeaponCreatorPlugin.gd b/addons/weapon_creator/WeaponCreatorPlugin.gd index 1b8716e3..23b81ebc 100644 --- a/addons/weapon_creator/WeaponCreatorPlugin.gd +++ b/addons/weapon_creator/WeaponCreatorPlugin.gd @@ -12,6 +12,9 @@ func _enter_tree() -> void: dock_instance = PanelContainer.new() dock_instance.set_script(dock_script) + # Pass editor interface to dock + dock_instance.call("setup", get_editor_interface()) + # Connect the create button signal from the dock if dock_instance.has_signal("create_weapon_requested"): dock_instance.create_weapon_requested.connect(_on_create_weapon_requested) @@ -32,6 +35,7 @@ func _on_create_weapon_requested(weapon_data: Dictionary) -> void: var ammo_key: String = weapon_data.get("ammo_key", "") var weapon_short_name: String = weapon_data.get("weapon_short_name", "NW-1") var weapon_description: String = weapon_data.get("weapon_description", "A new weapon") + var sprite_resource: Texture2D = weapon_data.get("sprite_resource", null) var default_bullet_path: String = weapon_data.get("default_bullet_path", "res://Resources/Bullets/simple_ice_bullet.tres") # Weapon stats @@ -70,7 +74,7 @@ func _on_create_weapon_requested(weapon_data: Dictionary) -> void: # Step 2: Create LootItem resource if _create_loot_item_resource(item_resource_path, weapon_name, weapon_short_name, - weapon_description, weapon_item_key, weapon_resource_path): + weapon_description, weapon_item_key, weapon_resource_path, sprite_resource): dock_instance.call("add_log", "✓ Created LootItem at: " + item_resource_path, Color.GREEN) else: dock_instance.call("add_log", "✗ Failed to create LootItem", Color.RED) @@ -87,9 +91,8 @@ func _on_create_weapon_requested(weapon_data: Dictionary) -> void: dock_instance.call("add_log", "=== Weapon Creation Complete ===", Color.CYAN) dock_instance.call("add_log", "Next steps:") - dock_instance.call("add_log", "1. Add sounds to the weapon resource") - dock_instance.call("add_log", "2. Add a sprite texture to the LootItem") - dock_instance.call("add_log", "3. Test the weapon in-game") + dock_instance.call("add_log", "1. Add sounds to the weapon resource if needed") + dock_instance.call("add_log", "2. Test the weapon in-game") dock_instance.call("set_creation_complete") # Refresh the filesystem @@ -151,7 +154,7 @@ func _create_weapon_resource(path: String, weapon_name: String, item_key: String func _create_loot_item_resource(path: String, item_name: String, short_name: String, description: String, item_key: String, - weapon_resource_path: String) -> bool: + weapon_resource_path: String, sprite_resource: Texture2D) -> bool: # Check if file already exists if ResourceLoader.exists(path): dock_instance.call("add_log", "Warning: LootItem already exists at " + path, Color.YELLOW) @@ -185,6 +188,12 @@ func _create_loot_item_resource(path: String, item_name: String, short_name: Str loot_item.set("Selectable", true) loot_item.set("DropScenePath3D", "res://Scenes/Items/GenericItem3D.tscn") + # Set sprite if provided (supports all Texture2D types including AtlasTexture) + if sprite_resource != null: + loot_item.set("InventorySprite", sprite_resource) + var sprite_type = sprite_resource.get_class() + dock_instance.call("add_log", "✓ Set inventory sprite (" + sprite_type + ")", Color.GREEN) + # Save the resource var err = ResourceSaver.save(loot_item, path) if err != OK: diff --git a/addons/weapon_creator/WeaponCreatorPlugin.gd.uid b/addons/weapon_creator/WeaponCreatorPlugin.gd.uid new file mode 100644 index 00000000..730eb898 --- /dev/null +++ b/addons/weapon_creator/WeaponCreatorPlugin.gd.uid @@ -0,0 +1 @@ +uid://dtk437yfdttf diff --git a/addons/weapon_creator/WeaponViewer.gd b/addons/weapon_creator/WeaponViewer.gd new file mode 100644 index 00000000..f50161a0 --- /dev/null +++ b/addons/weapon_creator/WeaponViewer.gd @@ -0,0 +1,179 @@ +@tool +extends PanelContainer + +# 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) + +var _editor_interface: EditorInterface +var _grid_container: HFlowContainer +var _items_database_path := "res://Resources/ItemsDatabase.tres" + +func setup(editor_interface: EditorInterface) -> void: + _editor_interface = editor_interface + +func _ready() -> void: + _build_ui() + refresh_weapons() + +func _build_ui() -> void: + # Main margin container + 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) + + # Main vbox + var vbox = VBoxContainer.new() + vbox.add_theme_constant_override("separation", 8) + margin.add_child(vbox) + + # Header with title and refresh button + var header_hbox = HBoxContainer.new() + vbox.add_child(header_hbox) + + var title = Label.new() + title.text = "Weapons" + title.size_flags_horizontal = Control.SIZE_EXPAND_FILL + header_hbox.add_child(title) + + var refresh_button = Button.new() + refresh_button.text = "Refresh" + refresh_button.pressed.connect(refresh_weapons) + 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) + + # Flow container for responsive wrapping + _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_weapons() -> void: + if not _grid_container: + return + + # Clear existing items + for child in _grid_container.get_children(): + child.queue_free() + + # 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) + var weapon_count = 0 + for loot_item in loot_items: + var weapon_data = loot_item.get("WeaponData3D") + if weapon_data != null: + _create_weapon_tile(loot_item, weapon_data) + 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) -> void: + var button = Button.new() + button.custom_minimum_size = Vector2(100, 110) + button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN + button.size_flags_vertical = Control.SIZE_SHRINK_BEGIN + + var vbox = VBoxContainer.new() + vbox.add_theme_constant_override("separation", 2) + vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE + + var sprite: Texture2D = loot_item.get("InventorySprite") + + 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 + + if sprite: + texture_rect.texture = sprite + vbox.add_child(texture_rect) + 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.custom_minimum_size = Vector2(64, 64) + placeholder_label.mouse_filter = Control.MOUSE_FILTER_IGNORE + placeholder_label.add_theme_font_size_override("font_size", 10) + vbox.add_child(placeholder_label) + + 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(90, 0) + name_label.size_flags_vertical = Control.SIZE_EXPAND_FILL + name_label.clip_text = true + name_label.max_lines_visible = 2 + name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE + name_label.add_theme_font_size_override("font_size", 11) + name_label.add_theme_constant_override("line_spacing", -2) + vbox.add_child(name_label) + + button.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: + button.tooltip_text = weapon_path + else: + button.tooltip_text = "Built-in resource (no file path)" + + button.pressed.connect(_on_weapon_clicked.bind(weapon_data, weapon_path)) + + _grid_container.add_child(button) + +func _on_weapon_clicked(weapon_data: Resource, weapon_path: String) -> void: + if not _editor_interface: + push_error("Editor interface not available") + return + + # Select the weapon resource in the inspector + if weapon_data: + _editor_interface.edit_resource(weapon_data) + _editor_interface.get_inspector().set_current_tab(0) + weapon_selected.emit(weapon_path) + +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) + diff --git a/addons/weapon_creator/WeaponViewer.gd.uid b/addons/weapon_creator/WeaponViewer.gd.uid new file mode 100644 index 00000000..094636c5 --- /dev/null +++ b/addons/weapon_creator/WeaponViewer.gd.uid @@ -0,0 +1 @@ +uid://b5oxs5f5sfuk2