Loot-System, Gegner-Drops und Gold-Anzeige hinzugefügt

- Enemy droppt Loot bei Tod (Gold + Items basierend auf LootTable)
- LootWindow zeigt Beute an mit "Alles aufheben" Button
- Gold-Anzeige im HUD unter XP-Leiste
- Beispiel LootTables: Goblin (2-8 Gold) und Skeleton (5-15 Gold)
- Loot-System in World verdrahtet

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Andre 2026-03-15 20:14:22 +01:00
parent 9ab6deddff
commit b01059de50
9 changed files with 295 additions and 0 deletions

View file

@ -3,6 +3,7 @@
extends CharacterBody3D
signal enemy_died(spawn_position: Vector3, xp_reward: int)
signal enemy_dropped_loot(loot: Dictionary, world_position: Vector3)
const SPEED = 3.0
const PATROL_SPEED = 1.5
@ -36,6 +37,9 @@ var attack_damage: int = 5
# XP-Belohnung (skaliert mit Level)
var xp_reward: int = 25
# Loot-System
@export var loot_table: LootTable
var target = null # Spieler-Referenz (wird von World gesetzt)
var can_attack = true
var spawn_position: Vector3 # Ursprüngliche Spawn-Position
@ -119,9 +123,25 @@ func die():
# XP an Spieler geben
if target and target.has_method("gain_xp"):
target.gain_xp(xp_reward)
# Loot generieren und droppen
_drop_loot()
enemy_died.emit(spawn_position, xp_reward)
queue_free()
# Loot generieren basierend auf LootTable
func _drop_loot():
if loot_table == null:
# Standard-Gold-Drop wenn keine LootTable zugewiesen
var gold = randi_range(1, 3) * level
var loot = {"gold": gold, "items": []}
enemy_dropped_loot.emit(loot, global_position)
return
var loot = loot_table.generate_loot()
# Gold mit Level skalieren
loot["gold"] = loot["gold"] * level
enemy_dropped_loot.emit(loot, global_position)
func _physics_process(delta):
if not is_on_floor():
velocity.y -= GRAVITY * delta

15
hud.gd
View file

@ -10,6 +10,7 @@ signal slot_clicked(slot_index: int)
# Level/XP UI (wird dynamisch erstellt)
var level_label: Label
var xp_bar: ProgressBar
var gold_label: Label
@onready var action_slots = [
$Control/ActionBar/A1,
$Control/ActionBar/A2,
@ -130,6 +131,20 @@ func _create_level_ui():
control.add_child(xp_bar)
# Gold Label
gold_label = Label.new()
gold_label.name = "GoldLabel"
gold_label.position = Vector2(20, 78)
gold_label.add_theme_font_size_override("font_size", 14)
gold_label.add_theme_color_override("font_color", Color(1, 0.85, 0, 1))
gold_label.text = "0 Gold"
control.add_child(gold_label)
# Gold aktualisieren
func update_gold(amount: int):
if gold_label:
gold_label.text = str(amount) + " Gold"
# HP-Leiste und Text aktualisieren
func update_health(current_hp, max_hp):
health_bar.max_value = max_hp

View file

@ -0,0 +1,28 @@
[gd_resource type="Resource" script_class="LootTable" format=3]
[ext_resource type="Script" path="res://loot_table.gd" id="1"]
[ext_resource type="Script" path="res://loot_entry.gd" id="2"]
[ext_resource type="Resource" path="res://equipment/iron_sword.tres" id="3"]
[ext_resource type="Resource" path="res://equipment/leather_chest.tres" id="4"]
[ext_resource type="Resource" path="res://equipment/iron_helm.tres" id="5"]
[sub_resource type="Resource" id="entry_1"]
script = ExtResource("2")
item = ExtResource("3")
drop_chance = 0.15
[sub_resource type="Resource" id="entry_2"]
script = ExtResource("2")
item = ExtResource("4")
drop_chance = 0.1
[sub_resource type="Resource" id="entry_3"]
script = ExtResource("2")
item = ExtResource("5")
drop_chance = 0.1
[resource]
script = ExtResource("1")
min_gold = 2
max_gold = 8
possible_drops = [SubResource("entry_1"), SubResource("entry_2"), SubResource("entry_3")]

View file

@ -0,0 +1,28 @@
[gd_resource type="Resource" script_class="LootTable" format=3]
[ext_resource type="Script" path="res://loot_table.gd" id="1"]
[ext_resource type="Script" path="res://loot_entry.gd" id="2"]
[ext_resource type="Resource" path="res://equipment/steel_sword.tres" id="3"]
[ext_resource type="Resource" path="res://equipment/wooden_shield.tres" id="4"]
[ext_resource type="Resource" path="res://equipment/iron_helm.tres" id="5"]
[sub_resource type="Resource" id="entry_1"]
script = ExtResource("2")
item = ExtResource("3")
drop_chance = 0.1
[sub_resource type="Resource" id="entry_2"]
script = ExtResource("2")
item = ExtResource("4")
drop_chance = 0.12
[sub_resource type="Resource" id="entry_3"]
script = ExtResource("2")
item = ExtResource("5")
drop_chance = 0.15
[resource]
script = ExtResource("1")
min_gold = 5
max_gold = 15
possible_drops = [SubResource("entry_1"), SubResource("entry_2"), SubResource("entry_3")]

118
loot_window.gd Normal file
View file

@ -0,0 +1,118 @@
# LootWindow.gd
# Zeigt Loot eines besiegten Gegners an und lässt Spieler Items aufheben
extends CanvasLayer
var player = null
var current_loot: Dictionary = {} # {"gold": int, "items": [Equipment]}
var loot_world_position: Vector3 = Vector3.ZERO
var panel_visible = false
const LOOT_PICKUP_RANGE = 5.0
@onready var panel = $Panel
@onready var gold_label = $Panel/VBoxContainer/GoldLabel
@onready var item_list = $Panel/VBoxContainer/ScrollContainer/ItemList
@onready var loot_all_button = $Panel/VBoxContainer/LootAllButton
func _ready():
panel.visible = false
loot_all_button.pressed.connect(_on_loot_all)
func setup(p):
player = p
# Loot anzeigen
func show_loot(loot: Dictionary, world_pos: Vector3):
current_loot = loot
loot_world_position = world_pos
_refresh_display()
panel_visible = true
panel.visible = true
func hide_loot():
panel_visible = false
panel.visible = false
current_loot = {}
func _refresh_display():
# Gold anzeigen
if current_loot.get("gold", 0) > 0:
gold_label.text = str(current_loot["gold"]) + " Gold"
gold_label.visible = true
else:
gold_label.visible = false
# Items anzeigen
for child in item_list.get_children():
child.queue_free()
var items = current_loot.get("items", [])
for i in range(items.size()):
var item = items[i]
var button = Button.new()
button.text = item.item_name + " (" + Equipment.get_slot_name(item.slot) + ")"
button.modulate = Equipment.get_rarity_color(item.rarity)
button.pressed.connect(_on_loot_item.bind(i))
button.tooltip_text = _get_item_tooltip(item)
item_list.add_child(button)
# Wenn kein Loot mehr da, Fenster schließen
if current_loot.get("gold", 0) <= 0 and items.size() == 0:
hide_loot()
# Einzelnes Item aufheben
func _on_loot_item(index: int):
if player == null:
return
var items = current_loot.get("items", [])
if index >= items.size():
return
var item = items[index]
if player.inventory.add_item(item):
items.remove_at(index)
_refresh_display()
# Alles aufheben
func _on_loot_all():
if player == null:
return
# Gold aufheben
var gold = current_loot.get("gold", 0)
if gold > 0:
player.inventory.add_gold(gold)
current_loot["gold"] = 0
# Items aufheben
var items = current_loot.get("items", [])
var remaining = []
for item in items:
if not player.inventory.add_item(item):
remaining.append(item) # Inventar voll
current_loot["items"] = remaining
_refresh_display()
func _get_item_tooltip(item: Equipment) -> String:
var tooltip = item.item_name + "\n"
tooltip += Equipment.get_slot_name(item.slot) + "\n"
if item.slot == Equipment.Slot.WEAPON:
tooltip += "Schaden: " + str(item.min_damage) + "-" + str(item.max_damage) + "\n"
tooltip += "Tempo: " + str(item.attack_speed) + "s\n"
if item.armor > 0:
tooltip += "Rüstung: +" + str(item.armor) + "\n"
if item.strength > 0:
tooltip += "Stärke: +" + str(item.strength) + "\n"
if item.agility > 0:
tooltip += "Beweglichkeit: +" + str(item.agility) + "\n"
if item.intelligence > 0:
tooltip += "Intelligenz: +" + str(item.intelligence) + "\n"
if item.stamina > 0:
tooltip += "Ausdauer: +" + str(item.stamina) + "\n"
if item.haste > 0:
tooltip += "Tempo: +" + str(int(item.haste * 100)) + "%\n"
return tooltip

61
loot_window.tscn Normal file
View file

@ -0,0 +1,61 @@
[gd_scene format=3 uid="uid://loot_window"]
[ext_resource type="Script" path="res://loot_window.gd" id="1_loot"]
[node name="LootWindow" type="CanvasLayer"]
script = ExtResource("1_loot")
[node name="Panel" type="Panel" parent="."]
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -130.0
offset_top = -350.0
offset_right = 130.0
offset_bottom = -150.0
grow_horizontal = 2
grow_vertical = 0
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -8.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 6
[node name="Title" type="Label" parent="Panel/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 16
text = "Beute"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="Panel/VBoxContainer"]
layout_mode = 2
[node name="GoldLabel" type="Label" parent="Panel/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 14
theme_override_colors/font_color = Color(1, 0.85, 0, 1)
text = "0 Gold"
[node name="ScrollContainer" type="ScrollContainer" parent="Panel/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="ItemList" type="VBoxContainer" parent="Panel/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 4
[node name="LootAllButton" type="Button" parent="Panel/VBoxContainer"]
layout_mode = 2
text = "Alles aufheben"

View file

@ -61,6 +61,7 @@ const HEAVY_STRIKE_RANGE = 4.0
@onready var hud = $HUD
@onready var character_panel = $CharacterPanel
@onready var inventory_panel = $InventoryPanel
@onready var loot_window = $LootWindow
func _ready():
# Stats aus Klasse berechnen
@ -80,6 +81,12 @@ func _ready():
# Inventar Panel initialisieren
inventory_panel.setup(self)
# Loot Window initialisieren
loot_window.setup(self)
# Gold im HUD aktualisieren wenn sich Gold ändert
inventory.gold_changed.connect(func(amount): hud.update_gold(amount))
# Stats basierend auf Klasse und Level berechnen
func _calculate_stats():
if character_class == null:
@ -251,6 +258,10 @@ func heal(amount):
current_hp = clamp(current_hp + amount, 0, max_hp)
hud.update_health(current_hp, max_hp)
# Loot empfangen und Fenster anzeigen
func receive_loot(loot: Dictionary, world_pos: Vector3):
loot_window.show_loot(loot, world_pos)
func die():
print("Spieler gestorben!")

View file

@ -6,6 +6,7 @@
[ext_resource type="PackedScene" uid="uid://bej3excyoxrdh" path="res://hud.tscn" id="4_hqtel"]
[ext_resource type="PackedScene" uid="uid://character_panel" path="res://character_panel.tscn" id="5_char_panel"]
[ext_resource type="PackedScene" uid="uid://inventory_panel" path="res://inventory_panel.tscn" id="6_inv_panel"]
[ext_resource type="PackedScene" uid="uid://loot_window" path="res://loot_window.tscn" id="7_loot_win"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4flbx"]
radius = 0.6
@ -33,3 +34,5 @@ transform = Transform3D(2, 0, 0, 0, 1.8126155, 0.84523654, 0, -0.84523654, 1.812
[node name="CharacterPanel" parent="." instance=ExtResource("5_char_panel")]
[node name="InventoryPanel" parent="." instance=ExtResource("6_inv_panel")]
[node name="LootWindow" parent="." instance=ExtResource("7_loot_win")]

View file

@ -10,6 +10,9 @@ const RESPAWN_TIME = 5.0
const STARTER_WEAPON = preload("res://equipment/iron_sword.tres")
const STARTER_CHEST = preload("res://equipment/leather_chest.tres")
# Loot Tables
const GOBLIN_LOOT = preload("res://loot_tables/goblin_loot.tres")
@onready var player = $Player
func _ready():
@ -39,10 +42,18 @@ func _on_class_selected(character_class: CharacterClass):
func _setup_enemy(enemy):
if enemy and player:
enemy.target = player
if enemy.loot_table == null:
enemy.loot_table = GOBLIN_LOOT
enemy.enemy_died.connect(_on_enemy_died)
enemy.enemy_dropped_loot.connect(_on_enemy_dropped_loot)
else:
print("Fehler: Player oder Enemy nicht gefunden!")
# Loot-Drop an Spieler weiterleiten
func _on_enemy_dropped_loot(loot: Dictionary, world_pos: Vector3):
if player:
player.receive_loot(loot, world_pos)
# Gegner gestorben: Nach 5 Sekunden respawnen
func _on_enemy_died(spawn_position: Vector3, _xp_reward: int):
print("Respawn in ", RESPAWN_TIME, " Sekunden...")