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:
parent
9ab6deddff
commit
b01059de50
9 changed files with 295 additions and 0 deletions
20
enemy.gd
20
enemy.gd
|
|
@ -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
15
hud.gd
|
|
@ -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
|
||||
|
|
|
|||
28
loot_tables/goblin_loot.tres
Normal file
28
loot_tables/goblin_loot.tres
Normal 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")]
|
||||
28
loot_tables/skeleton_loot.tres
Normal file
28
loot_tables/skeleton_loot.tres
Normal 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
118
loot_window.gd
Normal 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
61
loot_window.tscn
Normal 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"
|
||||
11
player.gd
11
player.gd
|
|
@ -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!")
|
||||
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
11
world.gd
11
world.gd
|
|
@ -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...")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue