Added rpg system for combat

added lots of loot to find
added level up system
This commit is contained in:
2026-01-11 23:12:09 +01:00
parent ab16194c39
commit 3a7fb29d58
32 changed files with 5076 additions and 96 deletions

566
src/scripts/inventory_ui.gd Normal file
View File

@@ -0,0 +1,566 @@
extends CanvasLayer
# Minimalistic Inventory UI with Stats Panel
# Toggle with Tab key, shows stats, equipment slots and inventory items
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"
# UI Nodes
var container: Control = null
var stats_panel: Control = null
var equipment_panel: Control = null
var inventory_grid: GridContainer = null
var scroll_container: ScrollContainer = null
# Stats labels
var label_base_stats: Label = null
var label_base_stats_value: Label = null
var label_derived_stats: Label = null
var label_derived_stats_value: Label = null
# Store button/item mappings for selection highlighting
var inventory_buttons: Dictionary = {} # item -> button
var equipment_buttons: Dictionary = {} # slot_name -> button
# Equipment slot buttons
var equipment_slots: Dictionary = {
"mainhand": null,
"offhand": null,
"headgear": null,
"armour": null,
"boots": null,
"accessory": null
}
func _ready():
# Set layer to be above game but below chat
layer = 150
# Create UI structure
_setup_ui()
# Find local player
call_deferred("_find_local_player")
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 _setup_ui():
# Main container (anchored to bottom-left, larger to fit stats + inventory)
container = Control.new()
container.name = "InventoryContainer"
container.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
container.offset_left = 10 # 10px padding from left
container.offset_right = 10 + 620 # Width 620px (stats 200px + inventory 400px + gap 20px)
container.offset_top = -350 # Height 340px (350 - 10)
container.offset_bottom = -10 # 10px padding from bottom
container.visible = false
add_child(container)
# Background panel
var background = ColorRect.new()
background.name = "Background"
background.color = Color(0.1, 0.1, 0.1, 0.85)
background.mouse_filter = Control.MOUSE_FILTER_IGNORE
container.add_child(background)
background.set_anchors_preset(Control.PRESET_FULL_RECT)
# HBox for stats and inventory side by side
var hbox = HBoxContainer.new()
hbox.name = "HBox"
container.add_child(hbox)
hbox.set_anchors_preset(Control.PRESET_FULL_RECT)
hbox.add_theme_constant_override("separation", 10)
# Stats panel (left side)
_create_stats_panel()
hbox.add_child(stats_panel)
# Inventory/Equipment panel (right side)
var inv_panel = VBoxContainer.new()
inv_panel.name = "InventoryPanel"
inv_panel.custom_minimum_size = Vector2(400, 0)
hbox.add_child(inv_panel)
inv_panel.add_theme_constant_override("separation", 5)
# Equipment label
var eq_label = Label.new()
eq_label.text = "Equipment"
eq_label.add_theme_font_size_override("font_size", 14)
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:
eq_label.add_theme_font_override("font", standard_font_resource)
inv_panel.add_child(eq_label)
equipment_panel = GridContainer.new()
equipment_panel.name = "EquipmentPanel"
equipment_panel.columns = 3
equipment_panel.add_theme_constant_override("h_separation", 5)
equipment_panel.add_theme_constant_override("v_separation", 5)
inv_panel.add_child(equipment_panel)
# Create equipment slot buttons
_create_equipment_slots()
# Inventory label
var inv_label = Label.new()
inv_label.text = "Inventory"
inv_label.add_theme_font_size_override("font_size", 14)
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
standard_font_resource = load("res://assets/fonts/standard_font.png")
if standard_font_resource:
inv_label.add_theme_font_override("font", standard_font_resource)
inv_panel.add_child(inv_label)
# Scroll container for inventory
scroll_container = ScrollContainer.new()
scroll_container.name = "InventoryScroll"
scroll_container.custom_minimum_size = Vector2(380, 120)
inv_panel.add_child(scroll_container)
# Inventory grid
inventory_grid = GridContainer.new()
inventory_grid.name = "InventoryGrid"
inventory_grid.columns = 6
inventory_grid.add_theme_constant_override("h_separation", 3)
inventory_grid.add_theme_constant_override("v_separation", 3)
scroll_container.add_child(inventory_grid)
func _create_stats_panel():
stats_panel = VBoxContainer.new()
stats_panel.name = "StatsPanel"
stats_panel.custom_minimum_size = Vector2(200, 0)
# Stats label
var stats_label = Label.new()
stats_label.text = "Stats"
stats_label.add_theme_font_size_override("font_size", 14)
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_label.add_theme_font_override("font", standard_font_resource)
stats_panel.add_child(stats_label)
# HBox for stats columns
var stats_hbox = HBoxContainer.new()
stats_hbox.name = "StatsHBox"
stats_panel.add_child(stats_hbox)
stats_hbox.add_theme_constant_override("separation", 5)
# Base stats label
label_base_stats = Label.new()
label_base_stats.name = "LabelBaseStats"
label_base_stats.text = "Level\n\nHP\nMP\n\nSTR\nDEX\nEND\nINT\nWIS\nLCK"
label_base_stats.add_theme_font_size_override("font_size", 10)
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
var font_resource = load("res://assets/fonts/standard_font.png")
if font_resource:
label_base_stats.add_theme_font_override("font", font_resource)
stats_hbox.add_child(label_base_stats)
# Base stats values label
label_base_stats_value = Label.new()
label_base_stats_value.name = "LabelBaseStatsValue"
label_base_stats_value.text = "1\n\n30/30\n20/20\n\n10\n10\n10\n10\n10\n10"
label_base_stats_value.add_theme_font_size_override("font_size", 10)
label_base_stats_value.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
var font_resource = load("res://assets/fonts/standard_font.png")
if font_resource:
label_base_stats_value.add_theme_font_override("font", font_resource)
stats_hbox.add_child(label_base_stats_value)
# Derived stats label
label_derived_stats = Label.new()
label_derived_stats.name = "LabelDerivedStats"
label_derived_stats.text = "XP\nCoin\n\n\n\nDMG\nDEF\nMovSpd\nAtkSpd\nSight\nSpellAmp\nCrit%"
label_derived_stats.add_theme_font_size_override("font_size", 10)
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
var font_resource = load("res://assets/fonts/standard_font.png")
if font_resource:
label_derived_stats.add_theme_font_override("font", font_resource)
stats_hbox.add_child(label_derived_stats)
# Derived stats values label
label_derived_stats_value = Label.new()
label_derived_stats_value.name = "LabelDerivedStatsValue"
label_derived_stats_value.text = "0/100\n0\n\n\n\n2.0\n2.0\n2.1\n1.4\n7.0\n5.0\n12.0%"
label_derived_stats_value.add_theme_font_size_override("font_size", 10)
label_derived_stats_value.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
if ResourceLoader.exists("res://assets/fonts/standard_font.png"):
var font_resource = load("res://assets/fonts/standard_font.png")
if font_resource:
label_derived_stats_value.add_theme_font_override("font", font_resource)
stats_hbox.add_child(label_derived_stats_value)
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 = ["Weapon", "Shield", "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
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
var button = Button.new()
button.name = slot_name + "_btn"
button.custom_minimum_size = Vector2(60, 60)
button.flat = true
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 _on_equipment_slot_pressed(slot_name: String):
if not local_player or not local_player.character_stats:
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_selection_highlight()
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()
# Highlight selected equipment slot
if selected_type == "equipment" and selected_slot != "":
var button = equipment_buttons.get(selected_slot)
if button:
var highlight = ColorRect.new()
highlight.name = "Highlight"
highlight.color = Color(1.0, 1.0, 0.0, 0.6)
highlight.mouse_filter = Control.MOUSE_FILTER_IGNORE
highlight.z_index = 10
button.add_child(highlight)
highlight.set_anchors_preset(Control.PRESET_FULL_RECT)
# Highlight selected inventory item
if selected_type == "item" and selected_item:
var button = inventory_buttons.get(selected_item)
if button:
var highlight = ColorRect.new()
highlight.name = "Highlight"
highlight.color = Color(1.0, 1.0, 0.0, 0.6)
highlight.mouse_filter = Control.MOUSE_FILTER_IGNORE
highlight.z_index = 10
button.add_child(highlight)
highlight.set_anchors_preset(Control.PRESET_FULL_RECT)
func _update_ui():
if not local_player or not local_player.character_stats:
return
var char_stats = local_player.character_stats
# Clear button mappings
inventory_buttons.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()
var texture_path = ""
var use_equipment_path = false
if slot_name == "mainhand" or slot_name == "offhand":
texture_path = equipped_item.spritePath
use_equipment_path = false
else:
texture_path = equipped_item.equipmentPath if equipped_item.equipmentPath != "" else equipped_item.spritePath
use_equipment_path = (equipped_item.equipmentPath != "")
var texture = load(texture_path)
if texture:
sprite.texture = texture
if use_equipment_path:
sprite.hframes = 35
sprite.vframes = 8
else:
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.scale = Vector2(1.2, 1.2)
button.add_child(sprite)
# Update inventory grid
for child in inventory_grid.get_children():
child.queue_free()
for item in char_stats.inventory:
var button = Button.new()
button.custom_minimum_size = Vector2(60, 60)
button.flat = true
button.connect("pressed", _on_inventory_item_pressed.bind(item))
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.scale = Vector2(1.2, 1.2)
button.add_child(sprite)
inventory_grid.add_child(button)
inventory_buttons[item] = button
# Update selection highlight
_update_selection_highlight()
# Update stats
_update_stats()
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_selection_highlight()
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
# 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)
selected_item = null
selected_slot = ""
selected_type = ""
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)
selected_item = null
selected_slot = ""
selected_type = ""
elif selected_item.item_type == Item.ItemType.Restoration:
_use_consumable_item(selected_item)
selected_item = null
selected_slot = ""
selected_type = ""
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()
if not local_player:
_find_local_player()
func _close_inventory():
if not is_open:
return
is_open = false
if container:
container.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