started working on fog darkness

This commit is contained in:
2026-01-13 00:16:08 +01:00
parent 82a70aa6a2
commit 89a41397d1
30 changed files with 1613 additions and 386 deletions

View File

@@ -7,36 +7,41 @@ extends CanvasLayer
var is_open: bool = false
var local_player: Node = null
var is_updating_ui: bool = false # Prevent recursive UI updates
# 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"
var selected_item: Item = null # Selected inventory item
var selected_slot: String = "" # Selected equipment slot name
var selected_type: String = "" # "item" or "equipment"
var is_first_open: bool = true # Track if this is the first time opening
# 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)
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/MarginContainer/VBoxContainer/HBox/StatsPanel
@onready var equipment_panel: GridContainer = $InventoryContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel/EquipmentPanel
@onready var scroll_container: ScrollContainer = $InventoryContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel/InventoryScroll
@onready var inventory_grid: VBoxContainer = $InventoryContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel/InventoryScroll/InventoryVBox
@onready var selection_rectangle: Panel = $InventoryContainer/MarginContainer/VBoxContainer/SelectionRectangle
@onready var info_panel: Control = $InventoryContainer/MarginContainer/VBoxContainer/InfoPanel
@onready var info_label: Label = $InventoryContainer/MarginContainer/VBoxContainer/InfoPanel/InfoLabel
@onready var label_base_stats: Label = $InventoryContainer/MarginContainer/VBoxContainer/HBox/StatsPanel/StatsHBox/LabelBaseStats
@onready var label_base_stats_value: Label = $InventoryContainer/MarginContainer/VBoxContainer/HBox/StatsPanel/StatsHBox/LabelBaseStatsValue
@onready var label_derived_stats: Label = $InventoryContainer/MarginContainer/VBoxContainer/HBox/StatsPanel/StatsHBox/LabelDerivedStats
@onready var label_derived_stats_value: Label = $InventoryContainer/MarginContainer/VBoxContainer/HBox/StatsPanel/StatsHBox/LabelDerivedStatsValue
@onready var stats_panel: Control = $InventoryContainer/MarginContainer/MarginContainer/VBoxContainer/HBox/StatsPanel
@onready var equipment_panel: GridContainer = $InventoryContainer/MarginContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel/EquipmentPanel
@onready var scroll_container: ScrollContainer = $InventoryContainer/MarginContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel/InventoryScroll
@onready var inventory_grid: VBoxContainer = $InventoryContainer/MarginContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel/InventoryScroll/InventoryVBox
@onready var selection_rectangle: Panel = $InventoryContainer/MarginContainer/MarginContainer/VBoxContainer/SelectionRectangle
@onready var info_panel: Control = $InventoryContainer/MarginContainer/MarginContainer/VBoxContainer/InfoPanel
@onready var info_label: Label = $InventoryContainer/MarginContainer/MarginContainer/VBoxContainer/InfoPanel/InfoLabel
@onready var label_base_stats: Label = $InventoryContainer/MarginContainer/MarginContainer/VBoxContainer/HBox/StatsPanel/StatsHBox/LabelBaseStats
@onready var label_base_stats_value: Label = $InventoryContainer/MarginContainer/MarginContainer/VBoxContainer/HBox/StatsPanel/StatsHBox/LabelBaseStatsValue
@onready var label_derived_stats: Label = $InventoryContainer/MarginContainer/MarginContainer/VBoxContainer/HBox/StatsPanel/StatsHBox/LabelDerivedStats
@onready var label_derived_stats_value: Label = $InventoryContainer/MarginContainer/MarginContainer/VBoxContainer/HBox/StatsPanel/StatsHBox/LabelDerivedStatsValue
@onready var sfx_potion: AudioStreamPlayer2D = $SfxPotion
@onready var sfx_food: AudioStreamPlayer2D = $SfxFood
@onready var sfx_armour: AudioStreamPlayer2D = $SfxArmour
# 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)
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 = {
@@ -47,7 +52,7 @@ var equipment_slots: Dictionary = {
"boots": null,
"accessory": null
}
var equipment_slots_list: Array = ["mainhand", "offhand", "headgear", "armour", "boots", "accessory"] # Order for navigation
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
@@ -65,10 +70,7 @@ func _ready():
# Load styleboxes for inventory slots (like inspiration system)
_setup_styleboxes()
# Setup fonts for labels
_setup_fonts()
# Create equipment slot buttons (dynamically)
_create_equipment_slots()
@@ -79,23 +81,40 @@ func _ready():
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
# Create styleboxes exactly like inspiration inventory system
var selected_tex = preload("res://assets/gfx/ui/inventory_slot_kenny_white.png")
style_box_empty = StyleBoxEmpty.new()
if slot_texture:
if selected_tex:
# Scale factor for the slot background (1.5x to match larger item sprites)
# Since StyleBoxTexture doesn't support texture_scale, we use expand_margin
# to make the texture fill more space
# For 1.5x scale on a 36px button (which was 24px originally), we need to expand
# The button is now 36px, so to make a 24px texture appear 1.5x (36px), we use negative margins
# Actually, let's use smaller positive margins to avoid clipping
var margin_scale = 3.0 # Smaller margin to avoid clipping in upper left corner
style_box_hover = StyleBoxTexture.new()
style_box_hover.texture = slot_texture
style_box_hover.texture = selected_tex
style_box_hover.expand_margin_left = margin_scale
style_box_hover.expand_margin_top = margin_scale
style_box_hover.expand_margin_right = margin_scale
style_box_hover.expand_margin_bottom = margin_scale
style_box_focused = StyleBoxTexture.new()
style_box_focused.texture = slot_texture
style_box_focused.texture = selected_tex
style_box_focused.expand_margin_left = margin_scale
style_box_focused.expand_margin_top = margin_scale
style_box_focused.expand_margin_right = margin_scale
style_box_focused.expand_margin_bottom = margin_scale
style_box_pressed = StyleBoxTexture.new()
style_box_pressed.texture = slot_texture
style_box_pressed.texture = selected_tex
style_box_pressed.expand_margin_left = margin_scale
style_box_pressed.expand_margin_top = margin_scale
style_box_pressed.expand_margin_right = margin_scale
style_box_pressed.expand_margin_bottom = margin_scale
else:
# Fallback to empty styleboxes if texture not found
style_box_hover = StyleBoxEmpty.new()
@@ -106,41 +125,17 @@ func _setup_styleboxes():
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("MarginContainer/VBoxContainer/HBox/InventoryPanel/EquipmentLabel")
if eq_label:
eq_label.add_theme_font_override("font", standard_font_resource)
var inv_label = container.get_node_or_null("MarginContainer/VBoxContainer/HBox/InventoryPanel/InventoryLabel")
if inv_label:
inv_label.add_theme_font_override("font", standard_font_resource)
var stats_label = container.get_node_or_null("MarginContainer/VBoxContainer/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
# Ensure it's on top and visible
selection_rectangle.z_index = 100
selection_rectangle.z_as_relative = false
selection_rectangle.mouse_filter = Control.MOUSE_FILTER_IGNORE # Don't block mouse input
# Ensure it's on top
selection_rectangle.z_index = 100
selection_rectangle.z_as_relative = false
func _find_local_player():
# Find the local player
@@ -207,7 +202,6 @@ func _create_equipment_slots():
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:
@@ -217,8 +211,9 @@ func _create_equipment_slots():
# 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)
# Button size increased to accommodate 1.5x scaled texture (24 * 1.5 = 36)
button.custom_minimum_size = Vector2(36, 36) # Increased to fit 1.5x scaled texture
button.size = Vector2(36, 36)
if style_box_empty:
button.add_theme_stylebox_override("normal", style_box_empty)
if style_box_hover:
@@ -227,8 +222,16 @@ func _create_equipment_slots():
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.flat = false # Use styleboxes instead of flat
button.focus_mode = Control.FOCUS_ALL # Allow button to receive focus
button.size_flags_horizontal = 0
button.size_flags_vertical = 0
button.connect("pressed", _on_equipment_slot_pressed.bind(slot_name))
# Connect focus_entered like inspiration system (for keyboard navigation)
if local_player and local_player.character_stats:
var equipped_item = local_player.character_stats.equipment[slot_name]
if equipped_item:
button.connect("focus_entered", _on_equipment_slot_pressed.bind(slot_name))
slot_container.add_child(button)
equipment_slots[slot_name] = button
@@ -259,6 +262,10 @@ func _on_equipment_slot_pressed(slot_name: String):
if not local_player or not local_player.character_stats:
return
# Prevent updates during UI refresh (prevents infinite loops from focus_entered)
if is_updating_ui:
return
# Only select if there's an item equipped
if not _has_equipment_in_slot(slot_name):
return
@@ -276,85 +283,110 @@ func _on_equipment_slot_pressed(slot_name: String):
_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()
# This function is kept for compatibility but now uses _update_selection_rectangle()
_update_selection_rectangle()
# Removed _clear_button_highlight and _apply_button_highlight - using focus system instead
func _update_selection_rectangle():
# Update visual selection rectangle position and visibility
if not selection_rectangle:
return
var target_button: Button = null
var target_position: Vector2 = Vector2.ZERO
var should_show: bool = false
# Get the parent of selection_rectangle (VBoxContainer) to calculate relative positions
var selection_parent = selection_rectangle.get_parent()
if not selection_parent:
# Update visual selection indicator - use button focus like inspiration system
# Hide the old selection rectangle
if selection_rectangle:
selection_rectangle.visible = false
return
# Find and focus the selected button (like inspiration system uses grab_focus())
var target_button: Button = null
if selected_type == "equipment" and selected_slot != "":
# Show rectangle on equipment slot (only if it has an item)
# Focus 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 selection_rectangle's parent (VBoxContainer)
var button_global_pos = target_button.global_position
var parent_global_pos = selection_parent.global_position
target_position = button_global_pos - parent_global_pos
should_show = true
elif selected_type == "item" and inventory_selection_row >= 0 and inventory_selection_row < inventory_rows_list.size():
# Show rectangle on inventory item
# Focus inventory item
var row = inventory_rows_list[inventory_selection_row]
if row and inventory_selection_col >= 0 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 selection_rectangle's parent (VBoxContainer)
var button_global_pos = target_button.global_position
var parent_global_pos = selection_parent.global_position
target_position = button_global_pos - parent_global_pos
should_show = true
# Only show and position if we have a valid target
if should_show and target_button:
selection_rectangle.visible = true
selection_rectangle.position = target_position
selection_rectangle.size = Vector2(38, 38)
# Grab focus on selected button (this will automatically show the focus stylebox)
if target_button:
if target_button.is_inside_tree():
# Don't grab focus if button already has focus (prevents infinite loops)
if target_button.has_focus():
return
# Ensure button is visible and ready
if not target_button.visible:
target_button.visible = true
# Wait a frame to ensure button is fully ready for focus
await get_tree().process_frame
# Check again if already focused (might have changed during await)
if target_button.has_focus():
return
# Ensure button can receive focus
if target_button.focus_mode == Control.FOCUS_NONE:
target_button.focus_mode = Control.FOCUS_ALL
# Try direct grab_focus (only if not already focused)
if not target_button.has_focus():
target_button.grab_focus()
# Also use call_deferred as backup to ensure it's set
target_button.call_deferred("grab_focus")
print("InventoryUI: Focus grabbed on button - has_focus: ", target_button.has_focus())
else:
# If not in tree yet, wait a frame and try again
await get_tree().process_frame
if target_button.is_inside_tree():
target_button.grab_focus()
target_button.call_deferred("grab_focus")
print("InventoryUI: Focus grabbed on button (after wait)")
else:
print("InventoryUI: Button still not in tree after wait")
else:
selection_rectangle.visible = false
print("InventoryUI: No button to focus - selected_type: ", selected_type)
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
if is_open:
# Animate selection highlight border color on selected button
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 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
# Find the selected button and update its highlight color
var selected_button: Button = null
if selected_type == "equipment" and selected_slot != "":
if _has_equipment_in_slot(selected_slot):
selected_button = equipment_buttons.get(selected_slot)
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 row and inventory_selection_col >= 0 and inventory_selection_col < row.get_child_count():
selected_button = row.get_child(inventory_selection_col) as Button
if selected_button and selected_button.has_meta("highlight_stylebox"):
var stylebox = selected_button.get_meta("highlight_stylebox") as StyleBoxFlat
if stylebox:
stylebox.border_color = animated_color
func _update_ui():
if not local_player or not local_player.character_stats:
return
# Prevent recursive updates
if is_updating_ui:
return
is_updating_ui = true
var char_stats = local_player.character_stats
# Ensure containers don't clip their children (allows expand_margin to show properly)
if scroll_container:
scroll_container.clip_contents = false # Allow buttons to extend beyond scroll bounds
if inventory_grid:
inventory_grid.clip_contents = false # Allow buttons to extend beyond grid bounds
if equipment_panel:
equipment_panel.clip_contents = false # Allow buttons to extend beyond grid bounds
# Debug: Print inventory contents
print("InventoryUI: Updating UI - inventory size: ", char_stats.inventory.size())
for i in range(char_stats.inventory.size()):
@@ -366,6 +398,9 @@ func _update_ui():
inventory_items_list.clear()
inventory_rows_list.clear()
# Wait for old buttons to be fully freed before creating new ones
await get_tree().process_frame
# Update equipment slots
for slot_name in equipment_slots.keys():
var button = equipment_slots[slot_name]
@@ -389,33 +424,53 @@ func _update_ui():
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
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 arrows)
if equipped_item.can_have_multiple_of and equipped_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(equipped_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)
# Update inventory grid - clear existing HBoxContainers
for child in inventory_grid.get_children():
child.queue_free()
# Wait for old buttons to be fully freed before creating new ones
await get_tree().process_frame
# 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_per_row = 8 # Items per row (3 rows = 24 total items max)
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
current_hbox.add_theme_constant_override("separation", 0) # No separation like inspiration
# Ensure HBoxContainer doesn't clip child buttons
current_hbox.clip_contents = false
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)
# Button size increased to accommodate 1.5x scaled texture (24 * 1.5 = 36)
button.custom_minimum_size = Vector2(36, 36) # Increased to fit 1.5x scaled texture
button.size = Vector2(36, 36)
if style_box_empty:
button.add_theme_stylebox_override("normal", style_box_empty)
if style_box_hover:
@@ -424,8 +479,13 @@ func _update_ui():
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.flat = false # Use styleboxes
button.focus_mode = Control.FOCUS_ALL # Allow button to receive focus
button.connect("pressed", _on_inventory_item_pressed.bind(item))
# Connect focus_entered like inspiration system (for keyboard navigation)
# Note: focus_entered will trigger when we call grab_focus(), but _on_inventory_item_pressed
# just updates selection state, so it should be safe
button.connect("focus_entered", _on_inventory_item_pressed.bind(item))
current_hbox.add_child(button)
# Add item sprite (like inspiration system - positioned at 4,4 with centered=false)
@@ -437,23 +497,28 @@ func _update_ui():
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
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:
# Add quantity label if item quantity > 1 (show for all stacked items)
if 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.vertical_alignment = VERTICAL_ALIGNMENT_TOP
quantity_label.size = Vector2(36, 36)
quantity_label.custom_minimum_size = Vector2(36, 36)
quantity_label.position = Vector2(0, 0)
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)
# Use dmg_numbers.png font (same as damage_number.gd)
var dmg_font_resource = load("res://assets/fonts/dmg_numbers.png")
if dmg_font_resource:
var font_file = FontFile.new()
font_file.font_data = dmg_font_resource
quantity_label.add_theme_font_override("font", font_file)
quantity_label.add_theme_font_size_override("font_size", 16)
quantity_label.z_index = 100 # High z-index to show above item sprite
button.add_child(quantity_label)
inventory_buttons[item] = button
@@ -468,19 +533,74 @@ func _update_ui():
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()
# Update selection only if selected_type is already set (don't auto-update during initialization)
if selected_type != "":
_update_selection_from_navigation()
_update_selection_rectangle()
_update_info_panel()
_set_selection()
# Reset update flag
is_updating_ui = false
func _set_selection():
# NOW check for items AFTER UI is updated
# Initialize selection - prefer inventory, but if empty, check equipment
# Check if we have inventory items
if inventory_rows_list.size() > 0 and inventory_items_list.size() > 0:
selected_type = "item"
inventory_selection_row = 0
inventory_selection_col = 0
# Ensure selection is set correctly
_update_selection_from_navigation()
# Debug: Print selection state
print("InventoryUI: Initial selection - type: ", selected_type, " row: ", inventory_selection_row, " col: ", inventory_selection_col, " item: ", selected_item)
# Now set focus - buttons should be ready
await _update_selection_rectangle() # Await to ensure focus is set
_update_info_panel()
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]
# Ensure selection is set correctly
_update_selection_from_navigation()
# Debug: Print selection state
print("InventoryUI: Initial selection - type: ", selected_type, " slot: ", selected_slot, " item: ", selected_item)
# Now set focus - buttons should be ready
await _update_selection_rectangle() # Await to ensure focus is set
_update_info_panel()
else:
# Nothing to select (only print this AFTER UI is updated)
selected_type = ""
if selection_rectangle:
selection_rectangle.visible = false
if info_label:
info_label.text = ""
print("InventoryUI: No items to select")
pass
func _update_selection_from_navigation():
# Update selected_item/selected_slot based on navigation position
# Early return if selected_type is not set yet (prevents errors during initialization)
if selected_type == "":
print("InventoryUI: _update_selection_from_navigation() - selected_type is empty, skipping")
return
print("InventoryUI: _update_selection_from_navigation() - selected_type: ", selected_type, " inventory_rows_list.size(): ", inventory_rows_list.size(), " inventory_items_list.size(): ", inventory_items_list.size())
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:
selected_item = null
print("InventoryUI: Selected equipment slot: ", slot_name, " item: ", selected_item)
else:
# Empty slot - switch to inventory
selected_type = "item"
@@ -492,21 +612,35 @@ func _update_selection_from_navigation():
_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():
print("InventoryUI: Checking item selection - row: ", inventory_selection_row, " col: ", inventory_selection_col, " row exists: ", row != null, " row child count: ", row.get_child_count() if row else 0)
if row and inventory_selection_col >= 0 and inventory_selection_col < row.get_child_count():
var item_index = inventory_selection_row * 10 + inventory_selection_col
print("InventoryUI: Calculated item_index: ", item_index, " inventory_items_list.size(): ", inventory_items_list.size())
if item_index >= 0 and item_index < inventory_items_list.size():
selected_item = inventory_items_list[item_index]
selected_slot = ""
print("InventoryUI: Selected inventory item: ", selected_item.item_name if selected_item else "null")
else:
selected_item = null
selected_slot = ""
print("InventoryUI: item_index out of range!")
else:
selected_item = null
selected_slot = ""
print("InventoryUI: Row or column invalid!")
else:
print("InventoryUI: selected_type invalid or row out of range!")
func _format_item_info(item: Item) -> String:
# Format item description, stats modifiers, and controls
var text = ""
# Item name (always show)
text += item.item_name
# Description
if item.description != "":
text += item.description
else:
text += item.item_name
text += "\n" + item.description
text += "\n\n"
@@ -538,7 +672,7 @@ func _format_item_info(item: Item) -> String:
stat_lines.append("MAXMP: +%d" % item.modifiers["maxmp"])
if stat_lines.size() > 0:
text += "\n".join(stat_lines)
text += ", ".join(stat_lines)
text += "\n\n"
# Controls
@@ -550,25 +684,28 @@ func _format_item_info(item: Item) -> String:
elif item.item_type == Item.ItemType.Restoration:
text += "Press F to consume"
# Only show "Press E to drop" for inventory items, not equipment
if selected_type == "item":
text += "\nPress E to drop"
text += ", Press E to drop"
return text
func _update_info_panel():
# Update info panel based on selected item
if not info_label:
print("InventoryUI: _update_info_panel() - info_label is null!")
return
print("InventoryUI: _update_info_panel() - selected_item: ", selected_item, " selected_type: ", selected_type)
if selected_item:
info_label.text = _format_item_info(selected_item)
print("InventoryUI: Info panel text set: ", info_label.text.substr(0, 50) if info_label.text.length() > 50 else info_label.text)
else:
info_label.text = ""
print("InventoryUI: Info panel text cleared (no selected_item)")
func _navigate_inventory(direction: String):
# Handle navigation within inventory
var items_per_row = 10
match direction:
"left":
if inventory_selection_col > 0:
@@ -598,7 +735,7 @@ func _navigate_inventory(direction: String):
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
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
@@ -625,7 +762,6 @@ func _navigate_equipment(direction: String):
# 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)
@@ -637,7 +773,7 @@ func _navigate_equipment(direction: String):
equipment_selection_index = next_index
"up":
# Find next filled slot in row above (same column)
var current_row = int(equipment_selection_index / 3)
var current_row: int = floor(equipment_selection_index / 3.0)
var current_col = equipment_selection_index % 3
if current_row > 0:
var target_index = (current_row - 1) * 3 + current_col
@@ -647,12 +783,12 @@ func _navigate_equipment(direction: String):
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
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 = int(equipment_selection_index / 3)
var current_row: int = floor(equipment_selection_index / 3.0)
var current_col = equipment_selection_index % 3
if current_row < 1:
var target_index = (current_row + 1) * 3 + current_col
@@ -660,37 +796,35 @@ func _navigate_equipment(direction: String):
if _has_equipment_in_slot(target_slot):
equipment_selection_index = target_index
else:
# No filled slot below, move to inventory
# No filled slot below, move to inventory (only if inventory has items)
if inventory_rows_list.size() > 0 and inventory_items_list.size() > 0:
selected_type = "item"
inventory_selection_row = 0
inventory_selection_col = current_col
# Clamp to valid range
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
_update_selection_from_navigation()
_update_selection_rectangle()
_update_info_panel()
return
# No inventory items, stay on equipment
else:
# Already at bottom row, move to inventory (only if inventory has items)
if inventory_rows_list.size() > 0 and inventory_items_list.size() > 0:
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
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
_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
# No inventory items, stay on equipment
_update_selection_from_navigation()
_update_selection_rectangle()
@@ -700,6 +834,10 @@ func _on_inventory_item_pressed(item: Item):
if not local_player or not local_player.character_stats:
return
# Prevent updates during UI refresh (prevents infinite loops from focus_entered)
if is_updating_ui:
return
selected_item = item
selected_slot = ""
selected_type = "item"
@@ -707,16 +845,25 @@ func _on_inventory_item_pressed(item: Item):
# Update navigation position
var item_index = inventory_items_list.find(item)
if item_index >= 0:
var items_per_row = 10
inventory_selection_row = int(item_index / items_per_row)
var items_per_row: int = 8
inventory_selection_row = floor(item_index / float(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()
# Always update stats when character changes (even if inventory is closed)
# Equipment changes affect max HP/MP which should be reflected everywhere
_update_stats()
# Only update UI if inventory is open (prevents unnecessary updates)
if not is_open:
return
# Prevent recursive updates
if is_updating_ui:
return
_update_ui()
func _input(event):
# Toggle with Tab key
@@ -773,6 +920,9 @@ func _handle_f_key():
var equipped_item = char_stats.equipment[selected_slot]
if equipped_item:
char_stats.unequip_item(equipped_item)
# Play armour sound when unequipping
if sfx_armour:
sfx_armour.play()
# After unequipping, if all equipment is empty, go to inventory
var has_any_equipment = false
for slot in equipment_slots_list:
@@ -832,6 +982,10 @@ func _handle_f_key():
char_stats.equip_item(selected_item)
# Play armour sound when equipping
if sfx_armour:
sfx_armour.play()
# If this was the last item, set selection state BEFORE _update_ui()
# so that _update_selection_from_navigation() works correctly
if was_last_item and target_slot_name != "":
@@ -856,6 +1010,18 @@ func _use_consumable_item(item: Item):
var char_stats = local_player.character_stats
# Determine if it's a potion or food based on item name
var is_potion = "potion" in item.item_name.to_lower()
# Play appropriate sound
if is_potion:
if sfx_potion:
sfx_potion.play()
else:
# Food item
if sfx_food:
sfx_food.play()
if item.modifiers.has("hp"):
var hp_heal = item.modifiers["hp"]
if local_player.has_method("heal"):
@@ -880,6 +1046,11 @@ func _handle_e_key():
var char_stats = local_player.character_stats
# Play armour sound when dropping equipment
if selected_item.item_type == Item.ItemType.Equippable:
if sfx_armour:
sfx_armour.play()
if not selected_item in char_stats.inventory:
return
@@ -893,10 +1064,24 @@ func _handle_e_key():
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"):
# In multiplayer, clients need to request server to spawn loot
if multiplayer.has_multiplayer_peer() and not multiplayer.is_server():
# Client: send drop request to server
if game_world and game_world.has_method("_request_item_drop"):
game_world._request_item_drop.rpc_id(1, selected_item.save(), drop_position, local_player.get_multiplayer_authority())
else:
# Fallback: try to spawn locally (won't sync)
if entities_node:
var loot = ItemLootHelper.spawn_item_loot(selected_item, drop_position, entities_node, game_world)
if loot and 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())
else:
# Server or single-player: spawn directly
if entities_node:
var loot = ItemLootHelper.spawn_item_loot(selected_item, drop_position, entities_node, game_world)
if loot and 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())
@@ -913,38 +1098,34 @@ func _open_inventory():
if is_open:
return
# Workaround: On first open, immediately close and reopen to ensure proper initialization
if is_first_open:
is_first_open = false
is_open = true
if container:
container.visible = true
_lock_player_controls(true)
_update_ui()
# Wait a frame
await get_tree().process_frame
# Close immediately
_close_inventory()
# Wait a frame
await get_tree().process_frame
# Now reopen properly (will continue with normal flow below)
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()
_update_info_panel()
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 = ""
if selection_rectangle:
selection_rectangle.visible = false
if info_label:
info_label.text = ""
# Reset selection state BEFORE updating UI (so _update_ui doesn't try to update selection)
selected_type = ""
selected_item = null
selected_slot = ""
_update_ui()
if not local_player:
_find_local_player()