From 1b572e82bf49892251713384357d2f03d25d5978 Mon Sep 17 00:00:00 2001 From: MaddoScientisto Date: Sun, 8 Feb 2026 19:07:20 +0100 Subject: [PATCH] Add item creation and viewer dialogs with filtering options for 2D/3D items --- addons/weapon_creator/BaseCreatorDialog.gd | 156 +++++ .../weapon_creator/BaseCreatorDialog.gd.uid | 1 + addons/weapon_creator/ItemCreatorDialog.gd | 396 ++++++++++++ .../weapon_creator/ItemCreatorDialog.gd.uid | 1 + addons/weapon_creator/ItemViewer.gd | 566 ++++++++++++++++++ addons/weapon_creator/ItemViewer.gd.uid | 1 + addons/weapon_creator/WeaponCreatorDock.gd | 30 + addons/weapon_creator/WeaponCreatorPlugin.gd | 85 +++ 8 files changed, 1236 insertions(+) create mode 100644 addons/weapon_creator/BaseCreatorDialog.gd create mode 100644 addons/weapon_creator/BaseCreatorDialog.gd.uid create mode 100644 addons/weapon_creator/ItemCreatorDialog.gd create mode 100644 addons/weapon_creator/ItemCreatorDialog.gd.uid create mode 100644 addons/weapon_creator/ItemViewer.gd create mode 100644 addons/weapon_creator/ItemViewer.gd.uid diff --git a/addons/weapon_creator/BaseCreatorDialog.gd b/addons/weapon_creator/BaseCreatorDialog.gd new file mode 100644 index 00000000..19175e7e --- /dev/null +++ b/addons/weapon_creator/BaseCreatorDialog.gd @@ -0,0 +1,156 @@ +@tool +class_name BaseCreatorDialog +extends Window + +# Base class for all creator dialogs +# Ensures consistent initialization and UI building patterns + +signal data_confirmed(data: Dictionary) + +var editor_interface: EditorInterface +var prefill_data: Dictionary = {} +var _ui_built: bool = false + +func setup(editor_iface: EditorInterface, prefill: Dictionary = {}) -> void: + editor_interface = editor_iface + prefill_data = prefill + + _update_title() + + if _ui_built: + _apply_data() + +func _ready() -> void: + _configure_window() + _update_title() + + close_requested.connect(_on_cancel_pressed) + position = (DisplayServer.screen_get_size() - size) / 2 + + _build_ui() + _ui_built = true + + _apply_data() + +func _configure_window() -> void: + size = Vector2i(750, 850) + transient = false + exclusive = false + unresizable = false + +func _update_title() -> void: + var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New" + title = action_text + " Item" + +func _build_ui() -> void: + var margin = MarginContainer.new() + margin.set_anchors_preset(Control.PRESET_FULL_RECT) + margin.add_theme_constant_override("margin_left", 12) + margin.add_theme_constant_override("margin_top", 12) + margin.add_theme_constant_override("margin_right", 12) + margin.add_theme_constant_override("margin_bottom", 12) + add_child(margin) + + var vbox = VBoxContainer.new() + vbox.add_theme_constant_override("separation", 8) + margin.add_child(vbox) + + var scroll = ScrollContainer.new() + scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + vbox.add_child(scroll) + + var content_vbox = VBoxContainer.new() + content_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL + content_vbox.add_theme_constant_override("separation", 12) + scroll.add_child(content_vbox) + + _build_content(content_vbox) + + vbox.add_child(HSeparator.new()) + + _build_buttons(vbox) + +func _build_content(container: VBoxContainer) -> void: + push_warning("_build_content should be overridden in derived class") + +func _build_buttons(vbox: VBoxContainer) -> void: + var button_hbox = HBoxContainer.new() + button_hbox.alignment = BoxContainer.ALIGNMENT_END + button_hbox.add_theme_constant_override("separation", 8) + vbox.add_child(button_hbox) + + var cancel_button = Button.new() + cancel_button.text = "Cancel" + cancel_button.pressed.connect(_on_cancel_pressed) + button_hbox.add_child(cancel_button) + + var create_button = Button.new() + create_button.text = "Create" + create_button.pressed.connect(_on_create_pressed) + button_hbox.add_child(create_button) + +func _apply_data() -> void: + if prefill_data.is_empty(): + _set_default_values() + else: + _apply_prefill_data() + +func _set_default_values() -> void: + pass + +func _apply_prefill_data() -> void: + pass + +func _on_cancel_pressed() -> void: + queue_free() + +func _on_create_pressed() -> void: + push_warning("_on_create_pressed should be overridden in derived class") + +func _show_error(message: String) -> void: + var dialog = AcceptDialog.new() + dialog.dialog_text = message + dialog.title = "Error" + add_child(dialog) + dialog.popup_centered() + dialog.confirmed.connect(func(): dialog.queue_free()) + +# Helper methods for creating common UI elements +func _create_input(label_text: String) -> Dictionary: + var hbox = HBoxContainer.new() + + var label = Label.new() + label.text = label_text + label.custom_minimum_size = Vector2(120, 0) + hbox.add_child(label) + + var edit = LineEdit.new() + edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL + hbox.add_child(edit) + + return {"container": hbox, "edit": edit} + +func _create_spinbox(label_text: String, default_value: float, min_value: float, max_value: float, step: float) -> Dictionary: + var hbox = HBoxContainer.new() + + var label = Label.new() + label.text = label_text + label.custom_minimum_size = Vector2(120, 0) + hbox.add_child(label) + + var spinbox = SpinBox.new() + spinbox.min_value = min_value + spinbox.max_value = max_value + spinbox.step = step + spinbox.value = default_value + spinbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL + hbox.add_child(spinbox) + + return {"container": hbox, "spinbox": spinbox} + +func _create_section_label(text: String) -> Label: + var label = Label.new() + label.text = text + label.add_theme_font_size_override("font_size", 16) + return label diff --git a/addons/weapon_creator/BaseCreatorDialog.gd.uid b/addons/weapon_creator/BaseCreatorDialog.gd.uid new file mode 100644 index 00000000..7866455a --- /dev/null +++ b/addons/weapon_creator/BaseCreatorDialog.gd.uid @@ -0,0 +1 @@ +uid://vom73sy0ykrr diff --git a/addons/weapon_creator/ItemCreatorDialog.gd b/addons/weapon_creator/ItemCreatorDialog.gd new file mode 100644 index 00000000..a0131741 --- /dev/null +++ b/addons/weapon_creator/ItemCreatorDialog.gd @@ -0,0 +1,396 @@ +@tool +extends Window + +# Dialog for creating new items +# Provides input fields for all item properties + +signal item_data_confirmed(item_data: Dictionary) + +# Editor reference +var editor_interface: EditorInterface +var prefill_data: Dictionary = {} +var _ui_built: bool = false + +# UI elements +var _item_name_edit: LineEdit +var _item_key_edit: LineEdit +var _short_name_edit: LineEdit +var _description_edit: TextEdit +var _sprite_picker: EditorResourcePicker +var _sprite_preview: TextureRect +var _selected_sprite: Texture2D = null + +var _item_type_option: OptionButton +var _tier_spin: SpinBox +var _price_spin: SpinBox +var _amount_spin: SpinBox +var _max_spin: SpinBox +var _pickup_if_maxed_check: CheckBox +var _consume_on_use_check: CheckBox +var _selectable_check: CheckBox + +const ITEM_TYPE_NAMES = [ + "KeycardRed", + "KeycardBlue", + "KeycardGreen", + "Ammo", + "Medkit", + "FrogBomb", + "Bomb", + "Mine", + "Battery", + "Weapon", + "Power", + "Points", + "Credits", + "KeyItem" +] + +func setup(editor_iface: EditorInterface, prefill: Dictionary = {}) -> void: + editor_interface = editor_iface + prefill_data = prefill + + var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New" + title = action_text + " Item" + + if _ui_built: + if prefill_data.is_empty(): + _set_default_values() + else: + _apply_prefill_data() + +func _ready() -> void: + var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New" + title = action_text + " Item" + size = Vector2i(750, 850) + transient = false + exclusive = false + unresizable = false + + close_requested.connect(_on_cancel_pressed) + position = (DisplayServer.screen_get_size() - size) / 2 + + _build_ui() + _ui_built = true + + if prefill_data.is_empty(): + _set_default_values() + else: + _apply_prefill_data() + +func _set_default_values() -> void: + pass + +func _update_title() -> void: + var action_text = "Duplicate" if not prefill_data.is_empty() else "Create New" + title = action_text + " Item" + +func _build_ui() -> void: + var margin = MarginContainer.new() + margin.set_anchors_preset(Control.PRESET_FULL_RECT) + margin.add_theme_constant_override("margin_left", 12) + margin.add_theme_constant_override("margin_top", 12) + margin.add_theme_constant_override("margin_right", 12) + margin.add_theme_constant_override("margin_bottom", 12) + add_child(margin) + + var vbox = VBoxContainer.new() + vbox.add_theme_constant_override("separation", 8) + margin.add_child(vbox) + + var scroll = ScrollContainer.new() + scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + vbox.add_child(scroll) + + var content_vbox = VBoxContainer.new() + content_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL + content_vbox.add_theme_constant_override("separation", 12) + scroll.add_child(content_vbox) + + _build_content(content_vbox) + + vbox.add_child(HSeparator.new()) + _build_buttons(vbox) + +func _build_buttons(parent: VBoxContainer) -> void: + var button_hbox = HBoxContainer.new() + button_hbox.alignment = BoxContainer.ALIGNMENT_CENTER + button_hbox.add_theme_constant_override("separation", 8) + parent.add_child(button_hbox) + + var cancel_button = Button.new() + cancel_button.text = "Cancel" + cancel_button.custom_minimum_size = Vector2(100, 0) + cancel_button.pressed.connect(_on_cancel_pressed) + button_hbox.add_child(cancel_button) + + var create_button = Button.new() + create_button.text = "Create" + create_button.custom_minimum_size = Vector2(100, 0) + create_button.pressed.connect(_on_create_pressed) + button_hbox.add_child(create_button) + +func _build_content(content_vbox: VBoxContainer) -> void: + # Basic Info Section + content_vbox.add_child(_create_section_label("Basic Information")) + + var name_input = _create_input("Item Name:") + content_vbox.add_child(name_input["container"]) + _item_name_edit = name_input["edit"] + + var key_input = _create_input("Item Key:") + content_vbox.add_child(key_input["container"]) + _item_key_edit = key_input["edit"] + + var short_name_input = _create_input("Short Name:") + content_vbox.add_child(short_name_input["container"]) + _short_name_edit = short_name_input["edit"] + + var desc_label = Label.new() + desc_label.text = "Description:" + content_vbox.add_child(desc_label) + + _description_edit = TextEdit.new() + _description_edit.custom_minimum_size = Vector2(0, 60) + _description_edit.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY + content_vbox.add_child(_description_edit) + + content_vbox.add_child(HSeparator.new()) + + # Sprite Section + var sprite_label = Label.new() + sprite_label.text = "Inventory Sprite" + sprite_label.add_theme_font_size_override("font_size", 14) + content_vbox.add_child(sprite_label) + + var sprite_container = HBoxContainer.new() + sprite_container.add_theme_constant_override("separation", 8) + content_vbox.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) + + 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) + _sprite_picker.resource_selected.connect(_on_resource_picker_opening) + picker_hbox.add_child(_sprite_picker) + + 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) + + content_vbox.add_child(HSeparator.new()) + + # Properties Section + var properties_label = Label.new() + properties_label.text = "Properties" + properties_label.add_theme_font_size_override("font_size", 16) + content_vbox.add_child(properties_label) + + var type_hbox = HBoxContainer.new() + content_vbox.add_child(type_hbox) + + var type_label = Label.new() + type_label.text = "Item Type:" + type_label.custom_minimum_size = Vector2(120, 0) + type_hbox.add_child(type_label) + + _item_type_option = OptionButton.new() + _item_type_option.size_flags_horizontal = Control.SIZE_EXPAND_FILL + for item_type in ITEM_TYPE_NAMES: + _item_type_option.add_item(item_type) + type_hbox.add_child(_item_type_option) + + var tier_spin = _create_spinbox("Tier:", 0, 0, 100, 1) + content_vbox.add_child(tier_spin["container"]) + _tier_spin = tier_spin["spinbox"] + + var price_spin = _create_spinbox("Price:", 0, 0, 999999, 1) + content_vbox.add_child(price_spin["container"]) + _price_spin = price_spin["spinbox"] + + var amount_spin = _create_spinbox("Amount:", 1, 0, 999999, 1) + content_vbox.add_child(amount_spin["container"]) + _amount_spin = amount_spin["spinbox"] + + var max_spin = _create_spinbox("Max:", 1, 0, 999999, 1) + content_vbox.add_child(max_spin["container"]) + _max_spin = max_spin["spinbox"] + + _pickup_if_maxed_check = CheckBox.new() + _pickup_if_maxed_check.text = "Pickup If Maxed" + content_vbox.add_child(_pickup_if_maxed_check) + + _consume_on_use_check = CheckBox.new() + _consume_on_use_check.text = "Consume On Use" + content_vbox.add_child(_consume_on_use_check) + + _selectable_check = CheckBox.new() + _selectable_check.text = "Selectable" + content_vbox.add_child(_selectable_check) + +func _apply_prefill_data() -> void: + if prefill_data.is_empty(): + return + + if prefill_data.has("item_name"): + _item_name_edit.text = str(prefill_data["item_name"]) + if prefill_data.has("item_key"): + _item_key_edit.text = str(prefill_data["item_key"]) + if prefill_data.has("short_name"): + _short_name_edit.text = str(prefill_data["short_name"]) + if prefill_data.has("description"): + _description_edit.text = str(prefill_data["description"]) + if prefill_data.has("sprite_resource") and prefill_data["sprite_resource"] != null: + _selected_sprite = prefill_data["sprite_resource"] + _sprite_preview.texture = _selected_sprite + _sprite_picker.edited_resource = _selected_sprite + if prefill_data.has("item_type"): + _item_type_option.selected = int(prefill_data["item_type"]) + if prefill_data.has("tier"): + _tier_spin.value = int(prefill_data["tier"]) + if prefill_data.has("price"): + _price_spin.value = int(prefill_data["price"]) + if prefill_data.has("amount"): + _amount_spin.value = int(prefill_data["amount"]) + if prefill_data.has("max"): + _max_spin.value = int(prefill_data["max"]) + if prefill_data.has("pickup_if_maxed"): + _pickup_if_maxed_check.button_pressed = bool(prefill_data["pickup_if_maxed"]) + if prefill_data.has("consume_on_use"): + _consume_on_use_check.button_pressed = bool(prefill_data["consume_on_use"]) + if prefill_data.has("selectable"): + _selectable_check.button_pressed = bool(prefill_data["selectable"]) + +func _on_sprite_resource_changed(resource: Resource) -> void: + if resource is Texture2D: + _selected_sprite = resource + _sprite_preview.texture = resource + print("Sprite resource changed: ", resource.resource_path if resource.resource_path else "[Unsaved Resource]") + elif resource == null: + _selected_sprite = null + _sprite_preview.texture = null + print("Sprite resource cleared") + else: + push_warning("Selected resource is not a Texture2D") + + call_deferred("_restore_window_focus") + +func _on_resource_picker_opening(_resource: Resource, _inspect: bool) -> void: + pass + +func _restore_window_focus() -> void: + if visible: + move_to_foreground() + +func _setup_sprite_picker() -> void: + if _sprite_picker and editor_interface: + _sprite_picker.toggle_mode = false + + +func _on_create_pressed() -> void: + var item_name = _item_name_edit.text.strip_edges() + var item_key = _item_key_edit.text.strip_edges() + + if item_name.is_empty(): + _show_error("Item Name cannot be empty") + return + + if item_key.is_empty(): + _show_error("Item Key cannot be empty") + return + + var item_data = { + "item_name": item_name, + "item_key": item_key, + "short_name": _short_name_edit.text.strip_edges(), + "description": _description_edit.text, + "sprite_resource": _selected_sprite, + "item_type": _item_type_option.selected, + "tier": int(_tier_spin.value), + "price": int(_price_spin.value), + "amount": int(_amount_spin.value), + "max": int(_max_spin.value), + "pickup_if_maxed": _pickup_if_maxed_check.button_pressed, + "consume_on_use": _consume_on_use_check.button_pressed, + "selectable": _selectable_check.button_pressed + } + + item_data_confirmed.emit(item_data) + queue_free() + +func _on_cancel_pressed() -> void: + queue_free() + +func _show_error(message: String) -> void: + var dialog = AcceptDialog.new() + dialog.dialog_text = message + dialog.title = "Error" + add_child(dialog) + dialog.popup_centered() + dialog.confirmed.connect(func(): dialog.queue_free()) + +# Helper methods +func _create_input(label_text: String) -> Dictionary: + var hbox = HBoxContainer.new() + + var label = Label.new() + label.text = label_text + label.custom_minimum_size = Vector2(120, 0) + hbox.add_child(label) + + var edit = LineEdit.new() + edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL + hbox.add_child(edit) + + return {"container": hbox, "edit": edit} + +func _create_spinbox(label_text: String, default_value: float, min_value: float, max_value: float, step: float) -> Dictionary: + var hbox = HBoxContainer.new() + + var label = Label.new() + label.text = label_text + label.custom_minimum_size = Vector2(120, 0) + hbox.add_child(label) + + var spinbox = SpinBox.new() + spinbox.min_value = min_value + spinbox.max_value = max_value + spinbox.step = step + spinbox.value = default_value + spinbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL + hbox.add_child(spinbox) + + return {"container": hbox, "spinbox": spinbox} + +func _create_section_label(text: String) -> Label: + var label = Label.new() + label.text = text + label.add_theme_font_size_override("font_size", 16) + return label diff --git a/addons/weapon_creator/ItemCreatorDialog.gd.uid b/addons/weapon_creator/ItemCreatorDialog.gd.uid new file mode 100644 index 00000000..96dace07 --- /dev/null +++ b/addons/weapon_creator/ItemCreatorDialog.gd.uid @@ -0,0 +1 @@ +uid://cwy7bmjb6bsab diff --git a/addons/weapon_creator/ItemViewer.gd b/addons/weapon_creator/ItemViewer.gd new file mode 100644 index 00000000..c94cebdd --- /dev/null +++ b/addons/weapon_creator/ItemViewer.gd @@ -0,0 +1,566 @@ +@tool +extends PanelContainer + +# 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 _editor_interface: EditorInterface +var _grid_container: HFlowContainer +var _items_database_path := "res://Resources/ItemsDatabase.tres" +var _show_2d_checkbox: CheckBox +var _show_3d_checkbox: CheckBox +var _show_neither_checkbox: CheckBox +var _dock: PanelContainer + +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 setup(editor_interface: EditorInterface, dock: PanelContainer = null) -> void: + _editor_interface = editor_interface + _dock = dock + + if _show_2d_checkbox: + _show_2d_checkbox.button_pressed = _load_filter_setting(SETTING_SHOW_2D, true) + if _show_3d_checkbox: + _show_3d_checkbox.button_pressed = _load_filter_setting(SETTING_SHOW_3D, true) + if _show_neither_checkbox: + _show_neither_checkbox.button_pressed = _load_filter_setting(SETTING_SHOW_NEITHER, true) + + refresh_items() + +func _ready() -> void: + _build_ui() + refresh_items() + +func _build_ui() -> void: + 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) + + var vbox = VBoxContainer.new() + vbox.add_theme_constant_override("separation", 8) + margin.add_child(vbox) + + var header_hbox = HBoxContainer.new() + vbox.add_child(header_hbox) + + var create_button = Button.new() + create_button.text = "Create Item" + create_button.pressed.connect(_on_create_item_pressed) + header_hbox.add_child(create_button) + + var spacer = Control.new() + spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + header_hbox.add_child(spacer) + + _show_2d_checkbox = CheckBox.new() + _show_2d_checkbox.text = "2D" + _show_2d_checkbox.button_pressed = true + _show_2d_checkbox.toggled.connect(_on_filter_changed) + header_hbox.add_child(_show_2d_checkbox) + + _show_3d_checkbox = CheckBox.new() + _show_3d_checkbox.text = "3D" + _show_3d_checkbox.button_pressed = true + _show_3d_checkbox.toggled.connect(_on_filter_changed) + header_hbox.add_child(_show_3d_checkbox) + + _show_neither_checkbox = CheckBox.new() + _show_neither_checkbox.text = "Neither" + _show_neither_checkbox.button_pressed = 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) + + var refresh_button = Button.new() + refresh_button.text = "Refresh" + refresh_button.pressed.connect(refresh_items) + header_hbox.add_child(refresh_button) + + vbox.add_child(HSeparator.new()) + + var scroll = ScrollContainer.new() + scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + vbox.add_child(scroll) + + _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_items() -> void: + if not _grid_container: + return + + for child in _grid_container.get_children(): + child.queue_free() + + 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 = _show_2d_checkbox == null or _show_2d_checkbox.button_pressed + var show_3d = _show_3d_checkbox == null or _show_3d_checkbox.button_pressed + 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_error_label("No items match the current filter") + +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 != "") + +func _on_filter_changed(_toggled: bool) -> void: + _save_filter_settings() + refresh_items() + +func _load_filter_setting(key: String, default_value: bool) -> bool: + if not _editor_interface: + return default_value + var editor_settings = _editor_interface.get_editor_settings() + if editor_settings and editor_settings.has_setting(key): + return editor_settings.get_setting(key) + return default_value + +func _save_filter_settings() -> void: + if not _editor_interface: + return + var editor_settings = _editor_interface.get_editor_settings() + if editor_settings: + editor_settings.set_setting(SETTING_SHOW_2D, _show_2d_checkbox.button_pressed) + editor_settings.set_setting(SETTING_SHOW_3D, _show_3d_checkbox.button_pressed) + 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" + + if _dock: + _dock.call("add_log", "=== Duplicating Item ===", Color.CYAN) + _dock.call("add_log", "Source: " + item_name, Color.CYAN) + _dock.call("add_log", "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_items) + +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_items() + +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) + +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") + diff --git a/addons/weapon_creator/ItemViewer.gd.uid b/addons/weapon_creator/ItemViewer.gd.uid new file mode 100644 index 00000000..cfa53745 --- /dev/null +++ b/addons/weapon_creator/ItemViewer.gd.uid @@ -0,0 +1 @@ +uid://ss8kgnlsw60s diff --git a/addons/weapon_creator/WeaponCreatorDock.gd b/addons/weapon_creator/WeaponCreatorDock.gd index 3b1cc340..b3af2c88 100644 --- a/addons/weapon_creator/WeaponCreatorDock.gd +++ b/addons/weapon_creator/WeaponCreatorDock.gd @@ -6,12 +6,14 @@ extends PanelContainer signal create_weapon_requested(weapon_data: Dictionary) signal create_bullet_requested(bullet_data: Dictionary) +signal create_item_requested(item_data: Dictionary) # UI elements var clear_button: Button var log_output: RichTextLabel var weapon_viewer: PanelContainer var bullet_viewer: PanelContainer +var item_viewer: PanelContainer var tab_container: TabContainer var _is_creating: bool = false @@ -74,10 +76,21 @@ func _build_ui() -> void: bullet_viewer.bullet_duplication_started.connect(_on_bullet_duplication_started) tab_container.add_child(bullet_viewer) + # Item Viewer Tab + var item_viewer_script = load("res://addons/weapon_creator/ItemViewer.gd") + item_viewer = PanelContainer.new() + item_viewer.set_script(item_viewer_script) + item_viewer.name = "Items" + item_viewer.duplicate_item_requested.connect(_on_item_data_confirmed) + item_viewer.item_deleted.connect(_on_item_deleted) + item_viewer.item_duplication_started.connect(_on_item_duplication_started) + tab_container.add_child(item_viewer) + # Setup after adding to scene tree if _editor_interface: weapon_viewer.setup(_editor_interface, self) bullet_viewer.setup(_editor_interface, self) + item_viewer.setup(_editor_interface, self) # Log header with label and clear button var log_header_hbox = HBoxContainer.new() @@ -140,6 +153,21 @@ func _on_bullet_duplication_started(bullet_name: String, is_3d: bool) -> void: # Logging is handled directly in BulletViewer pass +func _on_item_data_confirmed(item_data: Dictionary) -> void: + if _is_creating: + return + + _is_creating = true + log_output.clear() + + create_item_requested.emit(item_data) + +func _on_item_deleted(item_name: String, item_path: String) -> void: + pass + +func _on_item_duplication_started(item_name: String) -> void: + pass + func add_log(message: String, color: Color = Color.WHITE) -> void: # Add colored message to log output log_output.append_text("[color=#" + color.to_html(false) + "]" + message + "[/color]\n") @@ -151,3 +179,5 @@ func set_creation_complete() -> void: weapon_viewer.call("refresh_weapons") if bullet_viewer: bullet_viewer.call("refresh_bullets") + if item_viewer: + item_viewer.call("refresh_items") diff --git a/addons/weapon_creator/WeaponCreatorPlugin.gd b/addons/weapon_creator/WeaponCreatorPlugin.gd index 625c39cb..b4aef068 100644 --- a/addons/weapon_creator/WeaponCreatorPlugin.gd +++ b/addons/weapon_creator/WeaponCreatorPlugin.gd @@ -20,6 +20,8 @@ func _enter_tree() -> void: dock_instance.create_weapon_requested.connect(_on_create_weapon_requested) if dock_instance.has_signal("create_bullet_requested"): dock_instance.create_bullet_requested.connect(_on_create_bullet_requested) + if dock_instance.has_signal("create_item_requested"): + dock_instance.create_item_requested.connect(_on_create_item_requested) # Add the dock to the editor (bottom dock area) add_control_to_bottom_panel(dock_instance, "Weapon Creator") @@ -281,6 +283,89 @@ func _on_create_bullet_requested(bullet_data: Dictionary) -> void: get_editor_interface().get_resource_filesystem().scan() +func _on_create_item_requested(item_data: Dictionary) -> void: + var item_name: String = item_data.get("item_name", "New Item") + var item_key: String = item_data.get("item_key", "NEW_ITEM") + var short_name: String = item_data.get("short_name", "NI") + var description: String = item_data.get("description", "A new item") + var sprite_resource: Texture2D = item_data.get("sprite_resource", null) + var item_type: int = item_data.get("item_type", 0) + var tier: int = item_data.get("tier", 0) + var price: int = item_data.get("price", 0) + var amount: int = item_data.get("amount", 1) + var max: int = item_data.get("max", 1) + var pickup_if_maxed: bool = item_data.get("pickup_if_maxed", false) + var consume_on_use: bool = item_data.get("consume_on_use", false) + var selectable: bool = item_data.get("selectable", false) + + var item_resource_path: String = "res://Resources/Items/" + item_key + "_Item.tres" + var items_database_path: String = "res://Resources/ItemsDatabase.tres" + + dock_instance.call("add_log", "=== Starting Item Creation ===") + dock_instance.call("add_log", "Item Name: " + item_name) + dock_instance.call("add_log", "Item Key: " + item_key) + + if _create_loot_item_only(item_resource_path, item_name, short_name, description, + item_key, sprite_resource, item_type, tier, price, + amount, max, pickup_if_maxed, consume_on_use, selectable): + 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) + dock_instance.call("set_creation_complete") + return + + if _add_to_items_database(items_database_path, item_resource_path): + dock_instance.call("add_log", "✓ Added to ItemsDatabase", Color.GREEN) + else: + dock_instance.call("add_log", "✗ Failed to add to ItemsDatabase", Color.RED) + dock_instance.call("set_creation_complete") + return + + dock_instance.call("add_log", "=== Item Creation Complete ===", Color.CYAN) + dock_instance.call("set_creation_complete") + + get_editor_interface().get_resource_filesystem().scan() + +func _create_loot_item_only(path: String, item_name: String, short_name: String, + description: String, item_key: String, sprite_resource: Texture2D, + item_type: int, tier: int, price: int, amount: int, max: int, + pickup_if_maxed: bool, consume_on_use: bool, selectable: bool) -> bool: + if ResourceLoader.exists(path): + dock_instance.call("add_log", "Warning: LootItem already exists at " + path, Color.YELLOW) + return false + + var loot_item_script = load("res://Scripts/Resources/LootItem.cs") + if loot_item_script == null: + dock_instance.call("add_log", "Error: Could not load LootItem.cs script", Color.RED) + return false + + var loot_item = Resource.new() + loot_item.set_script(loot_item_script) + + loot_item.set("ItemName", item_name) + loot_item.set("ShortName", short_name) + loot_item.set("ItemDescription", description) + loot_item.set("ItemKey", item_key) + loot_item.set("Item", item_type) + loot_item.set("Tier", tier) + loot_item.set("Price", price) + loot_item.set("Amount", amount) + loot_item.set("Max", max) + loot_item.set("PickupIfMaxed", pickup_if_maxed) + loot_item.set("ConsumeOnUse", consume_on_use) + loot_item.set("Selectable", selectable) + + if sprite_resource != null: + loot_item.set("InventorySprite", sprite_resource) + + var err = ResourceSaver.save(loot_item, path) + if err != OK: + dock_instance.call("add_log", "Error saving LootItem: " + str(err), Color.RED) + return false + + return true + + func _create_bullet_resource(path: String, bullet_data: Dictionary) -> bool: if ResourceLoader.exists(path): dock_instance.call("add_log", "Warning: BulletResource already exists at " + path, Color.YELLOW)