added inventory equipment system
This commit is contained in:
@@ -41,5 +41,3 @@ color = Color(0.671875, 0.671875, 0.671875, 1)
|
|||||||
|
|
||||||
[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="." unique_id=1141138343]
|
[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="." unique_id=1141138343]
|
||||||
stream = ExtResource("6_6c6v5")
|
stream = ExtResource("6_6c6v5")
|
||||||
autoplay = true
|
|
||||||
stream_paused = true
|
|
||||||
|
|||||||
188
src/scenes/inventory_ui.tscn
Normal file
188
src/scenes/inventory_ui.tscn
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
[gd_scene format=3 uid="uid://cxs0ybxk2blth"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://vm6intetgl40" path="res://scripts/inventory_ui.gd" id="1_inventory_ui"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_selection"]
|
||||||
|
bg_color = Color(0, 0, 0, 0)
|
||||||
|
border_width_left = 2
|
||||||
|
border_width_top = 2
|
||||||
|
border_width_right = 2
|
||||||
|
border_width_bottom = 2
|
||||||
|
border_color = Color(1, 1, 0, 1)
|
||||||
|
|
||||||
|
[node name="InventoryUI" type="CanvasLayer" unique_id=-1294967296]
|
||||||
|
layer = 150
|
||||||
|
script = ExtResource("1_inventory_ui")
|
||||||
|
|
||||||
|
[node name="InventoryContainer" type="Control" parent="." unique_id=-294967296]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 3
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
grow_vertical = 0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="InventoryContainer" unique_id=935107028]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 3
|
||||||
|
anchor_left = 1.0
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = -610.0
|
||||||
|
offset_top = -226.0
|
||||||
|
grow_horizontal = 0
|
||||||
|
grow_vertical = 0
|
||||||
|
|
||||||
|
[node name="Background" type="ColorRect" parent="InventoryContainer/MarginContainer" unique_id=705032704]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
mouse_filter = 1
|
||||||
|
color = Color(0.1, 0.1, 0.1, 0.85)
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="InventoryContainer/MarginContainer" unique_id=1015792177]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="HBox" type="HBoxContainer" parent="InventoryContainer/MarginContainer/VBoxContainer" unique_id=1705032704]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
|
[node name="StatsPanel" type="VBoxContainer" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox" unique_id=-1589934592]
|
||||||
|
custom_minimum_size = Vector2(200, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 5
|
||||||
|
|
||||||
|
[node name="StatsLabel" type="Label" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/StatsPanel" unique_id=-589934592]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "Stats"
|
||||||
|
|
||||||
|
[node name="StatsHBox" type="HBoxContainer" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/StatsPanel" unique_id=410065408]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 5
|
||||||
|
|
||||||
|
[node name="LabelBaseStats" type="Label" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/StatsPanel/StatsHBox" unique_id=1410065408]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 10
|
||||||
|
text = "Level
|
||||||
|
|
||||||
|
HP
|
||||||
|
MP
|
||||||
|
|
||||||
|
STR
|
||||||
|
DEX
|
||||||
|
END
|
||||||
|
INT
|
||||||
|
WIS
|
||||||
|
LCK"
|
||||||
|
|
||||||
|
[node name="LabelBaseStatsValue" type="Label" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/StatsPanel/StatsHBox" unique_id=-1884901888]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 10
|
||||||
|
text = "1
|
||||||
|
|
||||||
|
30/30
|
||||||
|
20/20
|
||||||
|
|
||||||
|
10
|
||||||
|
10
|
||||||
|
10
|
||||||
|
10
|
||||||
|
10
|
||||||
|
10"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="LabelDerivedStats" type="Label" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/StatsPanel/StatsHBox" unique_id=-884901888]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 10
|
||||||
|
text = "XP
|
||||||
|
Coin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DMG
|
||||||
|
DEF
|
||||||
|
MovSpd
|
||||||
|
AtkSpd
|
||||||
|
Sight
|
||||||
|
SpellAmp
|
||||||
|
Crit%"
|
||||||
|
|
||||||
|
[node name="LabelDerivedStatsValue" type="Label" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/StatsPanel/StatsHBox" unique_id=115098112]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 10
|
||||||
|
text = "0/100
|
||||||
|
0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
2.0
|
||||||
|
2.0
|
||||||
|
2.1
|
||||||
|
1.4
|
||||||
|
7.0
|
||||||
|
5.0
|
||||||
|
12.0%"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="InventoryPanel" type="VBoxContainer" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox" unique_id=1115098112]
|
||||||
|
custom_minimum_size = Vector2(400, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 5
|
||||||
|
|
||||||
|
[node name="EquipmentLabel" type="Label" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel" unique_id=2115098112]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "Equipment"
|
||||||
|
|
||||||
|
[node name="EquipmentSpacer" type="Control" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel" unique_id=3000000001]
|
||||||
|
custom_minimum_size = Vector2(0, 8)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="EquipmentPanel" type="GridContainer" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel" unique_id=-1179869184]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/h_separation = 15
|
||||||
|
theme_override_constants/v_separation = 15
|
||||||
|
columns = 3
|
||||||
|
|
||||||
|
[node name="EquipmentBottomSpacer" type="Control" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel" unique_id=3000000002]
|
||||||
|
custom_minimum_size = Vector2(0, 8)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="InventoryLabel" type="Label" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel" unique_id=-179869184]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 14
|
||||||
|
text = "Inventory"
|
||||||
|
|
||||||
|
[node name="InventoryScroll" type="ScrollContainer" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel" unique_id=820130816]
|
||||||
|
custom_minimum_size = Vector2(380, 120)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="InventoryVBox" type="VBoxContainer" parent="InventoryContainer/MarginContainer/VBoxContainer/HBox/InventoryPanel/InventoryScroll" unique_id=1820130816]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/separation = -4
|
||||||
|
|
||||||
|
[node name="SelectionRectangle" type="Panel" parent="InventoryContainer/MarginContainer/VBoxContainer" unique_id=-1474836480]
|
||||||
|
z_index = 100
|
||||||
|
custom_minimum_size = Vector2(38, 38)
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_selection")
|
||||||
|
|
||||||
|
[node name="InfoPanel" type="VBoxContainer" parent="InventoryContainer/MarginContainer/VBoxContainer" unique_id=-474836480]
|
||||||
|
custom_minimum_size = Vector2(0, 80)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="InfoLabel" type="Label" parent="InventoryContainer/MarginContainer/VBoxContainer/InfoPanel" unique_id=525163520]
|
||||||
|
custom_minimum_size = Vector2(300, 64)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 10
|
||||||
|
vertical_alignment = 1
|
||||||
|
autowrap_mode = 3
|
||||||
@@ -2091,17 +2091,19 @@ func _create_chat_ui():
|
|||||||
push_error("GameWorld: Failed to instantiate chat_ui.tscn!")
|
push_error("GameWorld: Failed to instantiate chat_ui.tscn!")
|
||||||
|
|
||||||
func _create_inventory_ui():
|
func _create_inventory_ui():
|
||||||
# Create inventory UI programmatically (using CanvasLayer)
|
# Load inventory UI scene
|
||||||
var inventory_ui_script = load("res://scripts/inventory_ui.gd")
|
var inventory_ui_scene = load("res://scenes/inventory_ui.tscn")
|
||||||
if not inventory_ui_script:
|
if not inventory_ui_scene:
|
||||||
push_error("GameWorld: Could not load inventory_ui.gd script!")
|
push_error("GameWorld: Could not load inventory_ui.tscn scene!")
|
||||||
return
|
return
|
||||||
|
|
||||||
var inventory_ui = CanvasLayer.new()
|
var inventory_ui = inventory_ui_scene.instantiate()
|
||||||
inventory_ui.set_script(inventory_ui_script)
|
if inventory_ui:
|
||||||
inventory_ui.name = "InventoryUI"
|
inventory_ui.name = "InventoryUI"
|
||||||
add_child(inventory_ui)
|
add_child(inventory_ui)
|
||||||
print("GameWorld: Inventory UI created and added to scene tree")
|
print("GameWorld: Inventory UI scene instantiated and added to scene tree")
|
||||||
|
else:
|
||||||
|
push_error("GameWorld: Failed to instantiate inventory_ui.tscn!")
|
||||||
|
|
||||||
func _send_player_join_message(peer_id: int, player_info: Dictionary):
|
func _send_player_join_message(peer_id: int, player_info: Dictionary):
|
||||||
# Send a chat message when a player joins
|
# Send a chat message when a player joins
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
943
src/scripts/inventory_ui_refactored.gd
Normal file
943
src/scripts/inventory_ui_refactored.gd
Normal file
@@ -0,0 +1,943 @@
|
|||||||
|
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
|
||||||
1
src/scripts/inventory_ui_refactored.gd.uid
Normal file
1
src/scripts/inventory_ui_refactored.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ch6orv505wr0b
|
||||||
@@ -504,24 +504,14 @@ func _process_pickup_on_server(player: Node):
|
|||||||
player.character_stats.add_item(item)
|
player.character_stats.add_item(item)
|
||||||
print(name, " picked up item: ", item.item_name, " (added to inventory)")
|
print(name, " picked up item: ", item.item_name, " (added to inventory)")
|
||||||
elif item.item_type == Item.ItemType.Restoration:
|
elif item.item_type == Item.ItemType.Restoration:
|
||||||
# Consumable item - use immediately
|
# Consumable item - add to inventory (use with F key in inventory)
|
||||||
if player.character_stats:
|
if player.character_stats:
|
||||||
# Apply modifiers (hp, mp, etc.)
|
player.character_stats.add_item(item)
|
||||||
if item.modifiers.has("hp"):
|
print(name, " picked up item: ", item.item_name, " (added to inventory)")
|
||||||
var hp_heal = item.modifiers["hp"]
|
|
||||||
if player.has_method("heal"):
|
|
||||||
player.heal(hp_heal)
|
|
||||||
if item.modifiers.has("mp"):
|
|
||||||
var mana_amount = item.modifiers["mp"]
|
|
||||||
player.character_stats.restore_mana(mana_amount)
|
|
||||||
|
|
||||||
# TODO: Handle other modifiers (dodge_chance, res_all, etc.) - these would need duration tracking
|
|
||||||
|
|
||||||
print(name, " used item: ", item.item_name)
|
|
||||||
|
|
||||||
# Show floating text with item name
|
# Show floating text with item name (uppercase)
|
||||||
var items_texture = load(item.spritePath)
|
var items_texture = load(item.spritePath)
|
||||||
var display_text = item.item_name
|
var display_text = item.item_name.to_upper() # Always uppercase
|
||||||
var text_color = Color.WHITE
|
var text_color = Color.WHITE
|
||||||
|
|
||||||
# Color code based on item type
|
# Color code based on item type
|
||||||
|
|||||||
@@ -599,6 +599,18 @@ func _apply_appearance_to_sprites():
|
|||||||
func _on_character_changed(_char: CharacterStats):
|
func _on_character_changed(_char: CharacterStats):
|
||||||
# Update appearance when character stats change (e.g., equipment)
|
# Update appearance when character stats change (e.g., equipment)
|
||||||
_apply_appearance_to_sprites()
|
_apply_appearance_to_sprites()
|
||||||
|
|
||||||
|
# Sync equipment changes to other clients
|
||||||
|
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
|
||||||
|
# Sync equipment to all clients
|
||||||
|
var equipment_data = {}
|
||||||
|
for slot_name in character_stats.equipment.keys():
|
||||||
|
var item = character_stats.equipment[slot_name]
|
||||||
|
if item:
|
||||||
|
equipment_data[slot_name] = item.save() # Serialize item data
|
||||||
|
else:
|
||||||
|
equipment_data[slot_name] = null
|
||||||
|
_sync_equipment.rpc(equipment_data)
|
||||||
|
|
||||||
func _get_player_color() -> Color:
|
func _get_player_color() -> Color:
|
||||||
# Legacy function - now returns white (no color tint)
|
# Legacy function - now returns white (no color tint)
|
||||||
@@ -782,18 +794,20 @@ func _physics_process(delta):
|
|||||||
if is_dead:
|
if is_dead:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Skip input if controls are disabled (e.g., when player reached exit)
|
# Handle knockback timer (always handle knockback, even when controls are disabled)
|
||||||
if controls_disabled:
|
|
||||||
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0) # Slow down movement
|
|
||||||
return
|
|
||||||
|
|
||||||
# Handle knockback timer
|
|
||||||
if is_knocked_back:
|
if is_knocked_back:
|
||||||
knockback_time += delta
|
knockback_time += delta
|
||||||
if knockback_time >= knockback_duration:
|
if knockback_time >= knockback_duration:
|
||||||
is_knocked_back = false
|
is_knocked_back = false
|
||||||
knockback_time = 0.0
|
knockback_time = 0.0
|
||||||
|
|
||||||
|
# Skip input if controls are disabled (e.g., when inventory is open)
|
||||||
|
# But still allow knockback to continue (handled above)
|
||||||
|
if controls_disabled:
|
||||||
|
if not is_knocked_back:
|
||||||
|
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0) # Slow down movement
|
||||||
|
return
|
||||||
|
|
||||||
# Check if being held by someone
|
# Check if being held by someone
|
||||||
var being_held_by_someone = false
|
var being_held_by_someone = false
|
||||||
for other_player in get_tree().get_nodes_in_group("player"):
|
for other_player in get_tree().get_nodes_in_group("player"):
|
||||||
@@ -2556,6 +2570,29 @@ func _sync_stats_update(kills_count: int, coins_count: int):
|
|||||||
character_stats.coin = coins_count
|
character_stats.coin = coins_count
|
||||||
print(name, " stats synced from server: kills=", kills_count, " coins=", coins_count)
|
print(name, " stats synced from server: kills=", kills_count, " coins=", coins_count)
|
||||||
|
|
||||||
|
@rpc("any_peer", "reliable")
|
||||||
|
func _sync_equipment(equipment_data: Dictionary):
|
||||||
|
# Client receives equipment update from server
|
||||||
|
# Update equipment to match other players
|
||||||
|
# Only process if we're not the authority (remote player)
|
||||||
|
if is_multiplayer_authority():
|
||||||
|
return # Authority ignores this (it's the sender)
|
||||||
|
|
||||||
|
if not character_stats:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update equipment from data
|
||||||
|
for slot_name in equipment_data.keys():
|
||||||
|
var item_data = equipment_data[slot_name]
|
||||||
|
if item_data != null:
|
||||||
|
character_stats.equipment[slot_name] = Item.new(item_data)
|
||||||
|
else:
|
||||||
|
character_stats.equipment[slot_name] = null
|
||||||
|
|
||||||
|
# Update appearance
|
||||||
|
_apply_appearance_to_sprites()
|
||||||
|
print(name, " equipment synced from server")
|
||||||
|
|
||||||
func heal(amount: float):
|
func heal(amount: float):
|
||||||
if is_dead:
|
if is_dead:
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user