extends CanvasLayer # Minimalistic Inventory UI with Stats Panel # Toggle with Tab key, shows stats, equipment slots and inventory items # Uses inventory_slot graphics like inspiration inventory system # Supports keyboard navigation with arrow keys var is_open: bool = false var local_player: Node = null # Selection tracking var selected_item: Item = null # Selected inventory item var selected_slot: String = "" # Selected equipment slot name var selected_type: String = "" # "item" or "equipment" # Navigation tracking (for keyboard navigation) var inventory_selection_row: int = 0 # Current inventory row (0-based) var inventory_selection_col: int = 0 # Current inventory column (0-based) var equipment_selection_index: int = 0 # Current equipment slot index (0-5: mainhand, offhand, headgear, armour, boots, accessory) # UI Nodes (from scene) @onready var container: Control = $InventoryContainer @onready var stats_panel: Control = $InventoryContainer/HBox/StatsPanel @onready var equipment_panel: GridContainer = $InventoryContainer/HBox/InventoryPanel/EquipmentPanel @onready var scroll_container: ScrollContainer = $InventoryContainer/HBox/InventoryPanel/InventoryScroll @onready var inventory_grid: VBoxContainer = $InventoryContainer/HBox/InventoryPanel/InventoryScroll/InventoryVBox @onready var selection_rectangle: Panel = $InventoryContainer/SelectionRectangle @onready var info_panel: Control = $InventoryContainer/InfoPanel @onready var info_label: Label = $InventoryContainer/InfoPanel/InfoLabel @onready var label_base_stats: Label = $InventoryContainer/HBox/StatsPanel/StatsHBox/LabelBaseStats @onready var label_base_stats_value: Label = $InventoryContainer/HBox/StatsPanel/StatsHBox/LabelBaseStatsValue @onready var label_derived_stats: Label = $InventoryContainer/HBox/StatsPanel/StatsHBox/LabelDerivedStats @onready var label_derived_stats_value: Label = $InventoryContainer/HBox/StatsPanel/StatsHBox/LabelDerivedStatsValue # Store button/item mappings for selection highlighting var inventory_buttons: Dictionary = {} # item -> button var equipment_buttons: Dictionary = {} # slot_name -> button var inventory_items_list: Array = [] # Flat list of items for navigation var inventory_rows_list: Array = [] # List of HBoxContainers (rows) # Equipment slot buttons var equipment_slots: Dictionary = { "mainhand": null, "offhand": null, "headgear": null, "armour": null, "boots": null, "accessory": null } var equipment_slots_list: Array = ["mainhand", "offhand", "headgear", "armour", "boots", "accessory"] # Order for navigation # StyleBoxes for inventory slots (like inspiration system) var style_box_hover: StyleBox = null var style_box_focused: StyleBox = null var style_box_pressed: StyleBox = null var style_box_empty: StyleBox = null var quantity_font: Font = null # Selection animation var selection_animation_time: float = 0.0 func _ready(): # Set layer to be above game but below chat layer = 150 # Load styleboxes for inventory slots (like inspiration system) _setup_styleboxes() # Setup fonts for labels _setup_fonts() # Create equipment slot buttons (dynamically) _create_equipment_slots() # Setup selection rectangle (already in scene, just configure it) _setup_selection_rectangle() # Find local player call_deferred("_find_local_player") func _setup_styleboxes(): # Create styleboxes similar to inspiration inventory system var slot_texture = preload("res://assets/gfx/ui/inventory_slot_kenny_white.png") if not slot_texture: # Fallback if texture doesn't exist slot_texture = null style_box_empty = StyleBoxEmpty.new() if slot_texture: style_box_hover = StyleBoxTexture.new() style_box_hover.texture = slot_texture style_box_focused = StyleBoxTexture.new() style_box_focused.texture = slot_texture style_box_pressed = StyleBoxTexture.new() style_box_pressed.texture = slot_texture else: # Fallback to empty styleboxes if texture not found style_box_hover = StyleBoxEmpty.new() style_box_focused = StyleBoxEmpty.new() style_box_pressed = StyleBoxEmpty.new() # Load quantity font (dmg_numbers.png) if ResourceLoader.exists("res://assets/fonts/dmg_numbers.png"): quantity_font = load("res://assets/fonts/dmg_numbers.png") func _setup_fonts(): # Setup fonts for labels (standard_font.png) var standard_font_resource = null if ResourceLoader.exists("res://assets/fonts/standard_font.png"): standard_font_resource = load("res://assets/fonts/standard_font.png") if standard_font_resource: # Stats panel labels if label_base_stats: label_base_stats.add_theme_font_override("font", standard_font_resource) if label_base_stats_value: label_base_stats_value.add_theme_font_override("font", standard_font_resource) if label_derived_stats: label_derived_stats.add_theme_font_override("font", standard_font_resource) if label_derived_stats_value: label_derived_stats_value.add_theme_font_override("font", standard_font_resource) # Info label if info_label: info_label.add_theme_font_override("font", standard_font_resource) # Equipment and Inventory labels var eq_label = container.get_node_or_null("HBox/InventoryPanel/EquipmentLabel") if eq_label: eq_label.add_theme_font_override("font", standard_font_resource) var inv_label = container.get_node_or_null("HBox/InventoryPanel/InventoryLabel") if inv_label: inv_label.add_theme_font_override("font", standard_font_resource) var stats_label = container.get_node_or_null("HBox/StatsPanel/StatsLabel") if stats_label: stats_label.add_theme_font_override("font", standard_font_resource) func _setup_selection_rectangle(): # Selection rectangle is already in scene, just ensure it's configured correctly if selection_rectangle: selection_rectangle.visible = false func _find_local_player(): # Find the local player var game_world = get_tree().get_first_node_in_group("game_world") if game_world: var player_manager = game_world.get_node_or_null("PlayerManager") if player_manager: var local_players = player_manager.get_local_players() if local_players.size() > 0: local_player = local_players[0] if local_player and local_player.character_stats: # Connect to character_changed signal if local_player.character_stats.character_changed.is_connected(_on_character_changed): local_player.character_stats.character_changed.disconnect(_on_character_changed) local_player.character_stats.character_changed.connect(_on_character_changed) # Initial update _update_ui() _update_stats() func _update_stats(): if not local_player or not local_player.character_stats: return var char_stats = local_player.character_stats # Update base stats label_base_stats_value.text = str(char_stats.level) + "\n\n" + \ str(int(char_stats.hp)) + "/" + str(int(char_stats.maxhp)) + "\n" + \ str(int(char_stats.mp)) + "/" + str(int(char_stats.maxmp)) + "\n\n" + \ str(char_stats.baseStats.str) + "\n" + \ str(char_stats.baseStats.dex) + "\n" + \ str(char_stats.baseStats.end) + "\n" + \ str(char_stats.baseStats.int) + "\n" + \ str(char_stats.baseStats.wis) + "\n" + \ str(char_stats.baseStats.lck) # Update derived stats label_derived_stats_value.text = str(int(char_stats.xp)) + "/" + str(int(char_stats.xp_to_next_level)) + "\n" + \ str(char_stats.coin) + "\n\n\n\n" + \ str(char_stats.damage) + "\n" + \ str(char_stats.defense) + "\n" + \ str(char_stats.move_speed) + "\n" + \ str(char_stats.attack_speed) + "\n" + \ str(char_stats.sight) + "\n" + \ str(char_stats.spell_amp) + "\n" + \ str(char_stats.crit_chance) + "%" func _create_equipment_slots(): # Equipment slot order: mainhand, offhand, headgear, armour, boots, accessory var slot_names = ["mainhand", "offhand", "headgear", "armour", "boots", "accessory"] var slot_labels = ["Main-hand", "Off-hand", "Head", "Armour", "Boots", "Accessory"] for i in range(slot_names.size()): var slot_name = slot_names[i] var slot_label = slot_labels[i] # Container for slot var slot_container = VBoxContainer.new() slot_container.name = slot_name + "_slot" equipment_panel.add_child(slot_container) # Label var label = Label.new() label.text = slot_label label.add_theme_font_size_override("font_size", 10) label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER var standard_font_resource = null if ResourceLoader.exists("res://assets/fonts/standard_font.png"): var font_resource = load("res://assets/fonts/standard_font.png") if font_resource: label.add_theme_font_override("font", font_resource) slot_container.add_child(label) # Button (use styleboxes like inspiration system) var button = Button.new() button.name = slot_name + "_btn" button.custom_minimum_size = Vector2(24, 24) # Smaller like inspiration (24x24 instead of 60x60) button.size = Vector2(24, 24) if style_box_empty: button.add_theme_stylebox_override("normal", style_box_empty) if style_box_hover: button.add_theme_stylebox_override("hover", style_box_hover) if style_box_focused: button.add_theme_stylebox_override("focus", style_box_focused) if style_box_pressed: button.add_theme_stylebox_override("pressed", style_box_pressed) button.flat = false # Use styleboxes instead of flat button.connect("pressed", _on_equipment_slot_pressed.bind(slot_name)) slot_container.add_child(button) equipment_slots[slot_name] = button equipment_buttons[slot_name] = button func _has_equipment_in_slot(slot_name: String) -> bool: # Check if there's an item equipped in this slot if not local_player or not local_player.character_stats: return false return local_player.character_stats.equipment[slot_name] != null func _find_next_filled_equipment_slot(start_index: int, direction: int) -> int: # Find next filled equipment slot, or -1 if none found var current_index = start_index for _i in range(equipment_slots_list.size()): current_index += direction if current_index < 0: current_index = equipment_slots_list.size() - 1 elif current_index >= equipment_slots_list.size(): current_index = 0 var slot_name = equipment_slots_list[current_index] if _has_equipment_in_slot(slot_name): return current_index return -1 func _on_equipment_slot_pressed(slot_name: String): if not local_player or not local_player.character_stats: return # Only select if there's an item equipped if not _has_equipment_in_slot(slot_name): return # Select this slot selected_slot = slot_name selected_item = local_player.character_stats.equipment[slot_name] selected_type = "equipment" if selected_item else "" # Update navigation position if slot_name in equipment_slots_list: equipment_selection_index = equipment_slots_list.find(slot_name) _update_selection_highlight() _update_selection_rectangle() func _update_selection_highlight(): # Reset all button styles for button in equipment_buttons.values(): if button: var highlight = button.get_node_or_null("Highlight") if highlight: highlight.queue_free() for button in inventory_buttons.values(): if button: var highlight = button.get_node_or_null("Highlight") if highlight: highlight.queue_free() func _update_selection_rectangle(): # Update visual selection rectangle position and visibility if not selection_rectangle or not container: return var target_button: Button = null var target_position: Vector2 = Vector2.ZERO if selected_type == "equipment" and selected_slot != "": # Show rectangle on equipment slot (only if it has an item) if _has_equipment_in_slot(selected_slot): target_button = equipment_buttons.get(selected_slot) if target_button and target_button.is_inside_tree(): # Get button position relative to container var button_global_pos = target_button.global_position var container_global_pos = container.global_position target_position = button_global_pos - container_global_pos selection_rectangle.visible = true else: selection_rectangle.visible = false else: selection_rectangle.visible = false elif selected_type == "item" and inventory_selection_row < inventory_rows_list.size(): # Show rectangle on inventory item var row = inventory_rows_list[inventory_selection_row] if row and inventory_selection_col < row.get_child_count(): target_button = row.get_child(inventory_selection_col) as Button if target_button and target_button.is_inside_tree(): # Get button position relative to container var button_global_pos = target_button.global_position var container_global_pos = container.global_position target_position = button_global_pos - container_global_pos selection_rectangle.visible = true else: selection_rectangle.visible = false else: selection_rectangle.visible = false else: selection_rectangle.visible = false if target_button and selection_rectangle.visible: selection_rectangle.position = target_position # Ensure size is correct (48x48 to match sprite scale) selection_rectangle.size = Vector2(48, 48) func _process(delta): if is_open and selection_rectangle and selection_rectangle.visible: # Animate selection rectangle border color selection_animation_time += delta * 2.0 # Speed of animation # Animate between yellow and orange var color1 = Color.YELLOW var color2 = Color(1.0, 0.7, 0.0) # Orange-yellow var t = (sin(selection_animation_time) + 1.0) / 2.0 # 0 to 1 var animated_color = color1.lerp(color2, t) # Update border color var stylebox = selection_rectangle.get_theme_stylebox("panel") as StyleBoxFlat if stylebox: stylebox.border_color = animated_color func _update_ui(): if not local_player or not local_player.character_stats: return var char_stats = local_player.character_stats # Debug: Print inventory contents print("InventoryUI: Updating UI - inventory size: ", char_stats.inventory.size()) for i in range(char_stats.inventory.size()): var item = char_stats.inventory[i] print(" Item ", i, ": ", item.item_name if item else "null") # Clear button mappings inventory_buttons.clear() inventory_items_list.clear() inventory_rows_list.clear() # Update equipment slots for slot_name in equipment_slots.keys(): var button = equipment_slots[slot_name] if not button: continue # Clear existing children (sprite, highlight) for child in button.get_children(): child.queue_free() var equipped_item = char_stats.equipment[slot_name] if equipped_item: var sprite = Sprite2D.new() # Equipment slots should ONLY use spritePath (items_n_shit.png), NOT equipmentPath # equipmentPath is for character rendering only! var texture_path = equipped_item.spritePath var texture = load(texture_path) if texture: sprite.texture = texture sprite.hframes = equipped_item.spriteFrames.x if equipped_item.spriteFrames.x > 0 else 20 sprite.vframes = equipped_item.spriteFrames.y if equipped_item.spriteFrames.y > 0 else 14 sprite.frame = equipped_item.spriteFrame sprite.centered = false # Like inspiration system sprite.position = Vector2(4, 4) # Like inspiration system sprite.scale = Vector2(2.0, 2.0) # 2x size as requested button.add_child(sprite) # Update inventory grid - clear existing HBoxContainers for child in inventory_grid.get_children(): child.queue_free() # Add inventory items using HBoxContainers (like inspiration system) var current_hbox: HBoxContainer = null var items_per_row = 10 # Items per row (like inspiration system - they use 10 per HBox) var items_in_current_row = 0 for item in char_stats.inventory: # Create new HBoxContainer if needed if current_hbox == null or items_in_current_row >= items_per_row: current_hbox = HBoxContainer.new() current_hbox.add_theme_constant_override("separation", 0) # No separation like inspiration inventory_grid.add_child(current_hbox) inventory_rows_list.append(current_hbox) items_in_current_row = 0 # Create button with styleboxes (like inspiration system) var button = Button.new() button.custom_minimum_size = Vector2(24, 24) # Smaller like inspiration (24x24) button.size = Vector2(24, 24) if style_box_empty: button.add_theme_stylebox_override("normal", style_box_empty) if style_box_hover: button.add_theme_stylebox_override("hover", style_box_hover) if style_box_focused: button.add_theme_stylebox_override("focus", style_box_focused) if style_box_pressed: button.add_theme_stylebox_override("pressed", style_box_pressed) button.flat = false # Use styleboxes button.connect("pressed", _on_inventory_item_pressed.bind(item)) current_hbox.add_child(button) # Add item sprite (like inspiration system - positioned at 4,4 with centered=false) if item.spritePath != "": var sprite = Sprite2D.new() var texture = load(item.spritePath) if texture: sprite.texture = texture sprite.hframes = item.spriteFrames.x if item.spriteFrames.x > 0 else 20 sprite.vframes = item.spriteFrames.y if item.spriteFrames.y > 0 else 14 sprite.frame = item.spriteFrame sprite.centered = false # Like inspiration system sprite.position = Vector2(4, 4) # Like inspiration system sprite.scale = Vector2(2.0, 2.0) # 2x size as requested button.add_child(sprite) # Add quantity label if item can have multiple (like inspiration system) if item.can_have_multiple_of and item.quantity > 1: var quantity_label = Label.new() quantity_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT quantity_label.size = Vector2(24, 24) quantity_label.custom_minimum_size = Vector2(0, 0) quantity_label.position = Vector2(10, 2) quantity_label.text = str(item.quantity) if quantity_font: quantity_label.add_theme_font_override("font", quantity_font) quantity_label.add_theme_font_size_override("font_size", 8) quantity_label.scale = Vector2(0.5, 0.5) button.add_child(quantity_label) inventory_buttons[item] = button inventory_items_list.append(item) items_in_current_row += 1 # Clamp selection to valid range if inventory_selection_row >= inventory_rows_list.size(): inventory_selection_row = max(0, inventory_rows_list.size() - 1) if inventory_selection_row >= 0 and inventory_selection_row < inventory_rows_list.size(): var row = inventory_rows_list[inventory_selection_row] if inventory_selection_col >= row.get_child_count(): inventory_selection_col = max(0, row.get_child_count() - 1) # Update selection _update_selection_from_navigation() _update_selection_rectangle() _update_info_panel() func _update_selection_from_navigation(): # Update selected_item/selected_slot based on navigation position if selected_type == "equipment" and equipment_selection_index >= 0 and equipment_selection_index < equipment_slots_list.size(): var slot_name = equipment_slots_list[equipment_selection_index] if _has_equipment_in_slot(slot_name): selected_slot = slot_name if local_player and local_player.character_stats: selected_item = local_player.character_stats.equipment[slot_name] else: # Empty slot - switch to inventory selected_type = "item" selected_slot = "" selected_item = null if inventory_rows_list.size() > 0: inventory_selection_row = 0 inventory_selection_col = 0 _update_selection_from_navigation() elif selected_type == "item" and inventory_selection_row >= 0 and inventory_selection_row < inventory_rows_list.size(): var row = inventory_rows_list[inventory_selection_row] if inventory_selection_col >= 0 and inventory_selection_col < row.get_child_count(): var item_index = inventory_selection_row * 10 + inventory_selection_col if item_index >= 0 and item_index < inventory_items_list.size(): selected_item = inventory_items_list[item_index] selected_slot = "" func _format_item_info(item: Item) -> String: # Format item description, stats modifiers, and controls var text = "" # Description if item.description != "": text += item.description else: text += item.item_name text += "\n\n" # Stats modifiers var stat_lines = [] if item.modifiers.has("str"): stat_lines.append("STR: +%d" % item.modifiers["str"]) if item.modifiers.has("dex"): stat_lines.append("DEX: +%d" % item.modifiers["dex"]) if item.modifiers.has("end"): stat_lines.append("END: +%d" % item.modifiers["end"]) if item.modifiers.has("int"): stat_lines.append("INT: +%d" % item.modifiers["int"]) if item.modifiers.has("wis"): stat_lines.append("WIS: +%d" % item.modifiers["wis"]) if item.modifiers.has("lck"): stat_lines.append("LCK: +%d" % item.modifiers["lck"]) if item.modifiers.has("dmg"): stat_lines.append("DMG: +%d" % item.modifiers["dmg"]) if item.modifiers.has("def"): stat_lines.append("DEF: +%d" % item.modifiers["def"]) if item.modifiers.has("hp"): stat_lines.append("HP: +%d" % item.modifiers["hp"]) if item.modifiers.has("mp"): stat_lines.append("MP: +%d" % item.modifiers["mp"]) if item.modifiers.has("maxhp"): stat_lines.append("MAXHP: +%d" % item.modifiers["maxhp"]) if item.modifiers.has("maxmp"): stat_lines.append("MAXMP: +%d" % item.modifiers["maxmp"]) if stat_lines.size() > 0: text += "\n".join(stat_lines) text += "\n\n" # Controls if item.item_type == Item.ItemType.Equippable: if selected_type == "equipment": text += "Press F to unequip" else: text += "Press F to equip" elif item.item_type == Item.ItemType.Restoration: text += "Press F to consume" if selected_type == "item": text += "\nPress E to drop" return text func _update_info_panel(): # Update info panel based on selected item if not info_label: return if selected_item: info_label.text = _format_item_info(selected_item) else: info_label.text = "" func _navigate_inventory(direction: String): # Handle navigation within inventory var items_per_row = 10 match direction: "left": if inventory_selection_col > 0: inventory_selection_col -= 1 else: # Wrap to end of previous row if inventory_selection_row > 0: inventory_selection_row -= 1 var row = inventory_rows_list[inventory_selection_row] inventory_selection_col = row.get_child_count() - 1 "right": if inventory_selection_row < inventory_rows_list.size(): var row = inventory_rows_list[inventory_selection_row] if inventory_selection_col < row.get_child_count() - 1: inventory_selection_col += 1 else: # Wrap to start of next row if inventory_selection_row < inventory_rows_list.size() - 1: inventory_selection_row += 1 inventory_selection_col = 0 "up": if inventory_selection_row > 0: inventory_selection_row -= 1 # Clamp column to valid range var row = inventory_rows_list[inventory_selection_row] if inventory_selection_col >= row.get_child_count(): inventory_selection_col = row.get_child_count() - 1 else: # Move to equipment slots (only if there are filled slots) var next_equip_index = _find_next_filled_equipment_slot(-1, 1) # Start from end, go forward if next_equip_index >= 0: selected_type = "equipment" equipment_selection_index = next_equip_index selected_slot = equipment_slots_list[next_equip_index] _update_selection_from_navigation() _update_selection_rectangle() _update_info_panel() return "down": if inventory_selection_row < inventory_rows_list.size() - 1: inventory_selection_row += 1 # Clamp column to valid range var row = inventory_rows_list[inventory_selection_row] if inventory_selection_col >= row.get_child_count(): inventory_selection_col = row.get_child_count() - 1 # Can't go down from inventory (already at bottom) _update_selection_from_navigation() _update_selection_rectangle() _update_info_panel() func _navigate_equipment(direction: String): # Handle navigation within equipment slots (only filled slots) # Equipment layout: 3 columns, 2 rows # Row 1: mainhand(0), offhand(1), headgear(2) # Row 2: armour(3), boots(4), accessory(5) match direction: "left": var next_index = _find_next_filled_equipment_slot(equipment_selection_index, -1) if next_index >= 0: equipment_selection_index = next_index "right": var next_index = _find_next_filled_equipment_slot(equipment_selection_index, 1) if next_index >= 0: equipment_selection_index = next_index "up": # Find next filled slot in row above (same column) var current_row = equipment_selection_index / 3 var current_col = equipment_selection_index % 3 if current_row > 0: var target_index = (current_row - 1) * 3 + current_col var target_slot = equipment_slots_list[target_index] if _has_equipment_in_slot(target_slot): equipment_selection_index = target_index else: # Skip to next filled slot in that row var next_index = _find_next_filled_equipment_slot(target_index - 1, 1) if next_index >= 0 and next_index < 3: # Make sure it's in row 0 equipment_selection_index = next_index # Can't go up from equipment (already at top) "down": # Find next filled slot in row below (same column), or move to inventory var current_row = equipment_selection_index / 3 var current_col = equipment_selection_index % 3 if current_row < 1: var target_index = (current_row + 1) * 3 + current_col var target_slot = equipment_slots_list[target_index] if _has_equipment_in_slot(target_slot): equipment_selection_index = target_index else: # No filled slot below, move to inventory selected_type = "item" inventory_selection_row = 0 inventory_selection_col = current_col # Clamp to valid range if inventory_rows_list.size() > 0: var inv_row = inventory_rows_list[0] if inventory_selection_col >= inv_row.get_child_count(): inventory_selection_col = inv_row.get_child_count() - 1 else: inventory_selection_col = 0 _update_selection_from_navigation() _update_selection_rectangle() _update_info_panel() return else: # Already at bottom row, move to inventory selected_type = "item" inventory_selection_row = 0 inventory_selection_col = current_col # Clamp to valid range if inventory_rows_list.size() > 0: var inv_row = inventory_rows_list[0] if inventory_selection_col >= inv_row.get_child_count(): inventory_selection_col = inv_row.get_child_count() - 1 else: inventory_selection_col = 0 _update_selection_from_navigation() _update_selection_rectangle() _update_info_panel() return _update_selection_from_navigation() _update_selection_rectangle() _update_info_panel() func _on_inventory_item_pressed(item: Item): if not local_player or not local_player.character_stats: return selected_item = item selected_slot = "" selected_type = "item" # Update navigation position var item_index = inventory_items_list.find(item) if item_index >= 0: var items_per_row = 10 inventory_selection_row = item_index / items_per_row inventory_selection_col = item_index % items_per_row _update_selection_highlight() _update_selection_rectangle() func _on_character_changed(_char: CharacterStats): _update_ui() _update_stats() func _input(event): # Toggle with Tab key if event is InputEventKey and event.keycode == KEY_TAB and event.pressed and not event.echo: if is_open: _close_inventory() else: _open_inventory() get_viewport().set_input_as_handled() return if not is_open: return # Arrow key navigation if event is InputEventKey and event.pressed and not event.echo: var direction = "" if event.keycode == KEY_LEFT: direction = "left" elif event.keycode == KEY_RIGHT: direction = "right" elif event.keycode == KEY_UP: direction = "up" elif event.keycode == KEY_DOWN: direction = "down" if direction != "": if selected_type == "equipment": _navigate_equipment(direction) else: _navigate_inventory(direction) get_viewport().set_input_as_handled() return # F key: Unequip/equip items if event is InputEventKey and event.keycode == KEY_F and event.pressed and not event.echo: _handle_f_key() get_viewport().set_input_as_handled() return # E key: Drop selected item if event is InputEventKey and event.keycode == KEY_E and event.pressed and not event.echo: _handle_e_key() get_viewport().set_input_as_handled() return func _handle_f_key(): if not local_player or not local_player.character_stats: return var char_stats = local_player.character_stats if selected_type == "equipment" and selected_slot != "": var equipped_item = char_stats.equipment[selected_slot] if equipped_item: char_stats.unequip_item(equipped_item) # After unequipping, if all equipment is empty, go to inventory var has_any_equipment = false for slot in equipment_slots_list: if _has_equipment_in_slot(slot): has_any_equipment = true break if not has_any_equipment: # No equipment left, go to inventory selected_type = "item" selected_slot = "" selected_item = null if inventory_rows_list.size() > 0: inventory_selection_row = 0 inventory_selection_col = 0 else: selected_type = "" else: # Find next filled equipment slot var next_index = _find_next_filled_equipment_slot(equipment_selection_index, 1) if next_index >= 0: equipment_selection_index = next_index selected_slot = equipment_slots_list[next_index] selected_item = char_stats.equipment[selected_slot] else: # No more equipment, go to inventory selected_type = "item" selected_slot = "" selected_item = null if inventory_rows_list.size() > 0: inventory_selection_row = 0 inventory_selection_col = 0 _update_selection_from_navigation() _update_selection_rectangle() return if selected_type == "item" and selected_item: if selected_item.item_type == Item.ItemType.Equippable and selected_item.equipment_type != Item.EquipmentType.NONE: char_stats.equip_item(selected_item) # After equipping, selection might move to equipment slot _update_ui() _update_selection_rectangle() elif selected_item.item_type == Item.ItemType.Restoration: _use_consumable_item(selected_item) _update_info_panel() func _use_consumable_item(item: Item): if not local_player or not local_player.character_stats: return var char_stats = local_player.character_stats if item.modifiers.has("hp"): var hp_heal = item.modifiers["hp"] if local_player.has_method("heal"): local_player.heal(hp_heal) if item.modifiers.has("mp"): var mana_amount = item.modifiers["mp"] char_stats.restore_mana(mana_amount) var index = char_stats.inventory.find(item) if index >= 0: char_stats.inventory.remove_at(index) char_stats.character_changed.emit(char_stats) print(local_player.name, " used item: ", item.item_name) func _handle_e_key(): if not local_player or not local_player.character_stats: return if selected_type != "item" or not selected_item: return var char_stats = local_player.character_stats if not selected_item in char_stats.inventory: return var index = char_stats.inventory.find(selected_item) if index >= 0: char_stats.inventory.remove_at(index) var drop_position = local_player.global_position + Vector2(randf_range(-20, 20), randf_range(-20, 20)) var game_world = get_tree().get_first_node_in_group("game_world") var entities_node = null if game_world: entities_node = game_world.get_node_or_null("Entities") if entities_node: var loot = ItemLootHelper.spawn_item_loot(selected_item, drop_position, entities_node, game_world) if loot: if local_player.has_method("get_multiplayer_authority"): var player_peer_id = local_player.get_multiplayer_authority() loot.set_meta("dropped_by_peer_id", player_peer_id) loot.set_meta("drop_time", Time.get_ticks_msec()) selected_item = null selected_slot = "" selected_type = "" char_stats.character_changed.emit(char_stats) print(local_player.name, " dropped item") func _open_inventory(): if is_open: return is_open = true if container: container.visible = true _lock_player_controls(true) _update_ui() # Initialize selection - prefer inventory, but if empty, check equipment if inventory_rows_list.size() > 0: selected_type = "item" inventory_selection_row = 0 inventory_selection_col = 0 _update_selection_from_navigation() _update_selection_rectangle() else: # No inventory items, try equipment var first_filled_slot = _find_next_filled_equipment_slot(-1, 1) if first_filled_slot >= 0: selected_type = "equipment" equipment_selection_index = first_filled_slot selected_slot = equipment_slots_list[first_filled_slot] _update_selection_from_navigation() _update_selection_rectangle() _update_info_panel() else: # Nothing to select selected_type = "" selection_rectangle.visible = false if info_label: info_label.text = "" if not local_player: _find_local_player() func _close_inventory(): if not is_open: return is_open = false if container: container.visible = false if selection_rectangle: selection_rectangle.visible = false selected_item = null selected_slot = "" selected_type = "" _lock_player_controls(false) func _lock_player_controls(lock: bool): var game_world = get_tree().get_first_node_in_group("game_world") if game_world: var player_manager = game_world.get_node_or_null("PlayerManager") if player_manager: var local_players = player_manager.get_local_players() for player in local_players: player.controls_disabled = lock