Inventar, Equipment, Klassensystem und Waffenschaden hinzugefügt

- CharacterClass mit Klassen (Krieger, Schurke, Magier) und unbewaffnetem Schaden
- Equipment-System mit 7 Slots, Seltenheiten und Stats
- Inventar-System mit 20 Slots und Gold
- LootTable/LootEntry für Gegner-Drops
- Character Panel (C) mit Stats und Equipment-Anzeige
- Inventory Panel (I) mit Item-Grid und Tooltips
- Klassenauswahl-Menü bei Spielstart
- Waffenschaden in Equipment-Spalte, unbewaffnet klassenabhängig

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Andre 2026-03-15 20:10:05 +01:00
parent 1616431d1c
commit 9ab6deddff
32 changed files with 1115 additions and 0 deletions

32
character_class.gd Normal file
View file

@ -0,0 +1,32 @@
# CharacterClass.gd
# Definiert Charakterklassen mit Grundstats und Main-Stat
extends Resource
class_name CharacterClass
enum MainStat { STRENGTH, AGILITY, INTELLIGENCE }
@export var class_name_de: String = "Krieger"
@export var main_stat: MainStat = MainStat.STRENGTH
# Grund-Stats auf Level 1
@export var base_strength: int = 10
@export var base_agility: int = 10
@export var base_intelligence: int = 10
@export var base_stamina: int = 10 # Beeinflusst HP
# Stat-Zuwachs pro Level
@export var strength_per_level: float = 2.0
@export var agility_per_level: float = 2.0
@export var intelligence_per_level: float = 2.0
@export var stamina_per_level: float = 2.0
# Unbewaffneter Schaden (klassenabhängig)
@export var unarmed_min_damage: int = 1
@export var unarmed_max_damage: int = 2
@export var unarmed_attack_speed: float = 2.0 # Langsamer als mit Waffe
# HP pro Stamina-Punkt
const HP_PER_STAMINA = 10
# Schaden-Skalierung mit Main-Stat
const DAMAGE_PER_MAIN_STAT = 0.5

1
character_class.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://ci45xxb5vn857

104
character_panel.gd Normal file
View file

@ -0,0 +1,104 @@
# CharacterPanel.gd
# Zeigt Charakterinfos: Klasse, Level, Stats und Equipment
extends CanvasLayer
var panel_visible = false
@onready var panel = $Panel
@onready var class_label = $Panel/HBoxContainer/StatsColumn/ClassLabel
@onready var level_label = $Panel/HBoxContainer/StatsColumn/LevelLabel
@onready var stats_container = $Panel/HBoxContainer/StatsColumn/StatsContainer
@onready var str_label = $Panel/HBoxContainer/StatsColumn/StatsContainer/StrLabel
@onready var agi_label = $Panel/HBoxContainer/StatsColumn/StatsContainer/AgiLabel
@onready var int_label = $Panel/HBoxContainer/StatsColumn/StatsContainer/IntLabel
@onready var sta_label = $Panel/HBoxContainer/StatsColumn/StatsContainer/StaLabel
@onready var armor_label = $Panel/HBoxContainer/StatsColumn/StatsContainer/ArmorLabel
@onready var hp_label = $Panel/HBoxContainer/StatsColumn/HPLabel
@onready var damage_label = $Panel/HBoxContainer/EquipmentColumn/DamageLabel
@onready var dps_label = $Panel/HBoxContainer/EquipmentColumn/DPSLabel
# Equipment Slots
@onready var head_slot = $Panel/HBoxContainer/EquipmentColumn/EquipmentContainer/HeadSlot
@onready var chest_slot = $Panel/HBoxContainer/EquipmentColumn/EquipmentContainer/ChestSlot
@onready var hands_slot = $Panel/HBoxContainer/EquipmentColumn/EquipmentContainer/HandsSlot
@onready var legs_slot = $Panel/HBoxContainer/EquipmentColumn/EquipmentContainer/LegsSlot
@onready var feet_slot = $Panel/HBoxContainer/EquipmentColumn/EquipmentContainer/FeetSlot
@onready var weapon_slot = $Panel/HBoxContainer/EquipmentColumn/WeaponSlot
@onready var offhand_slot = $Panel/HBoxContainer/EquipmentColumn/OffhandSlot
func _ready():
panel.visible = false
func toggle():
panel_visible = !panel_visible
panel.visible = panel_visible
func update_stats(player):
if player.character_class:
var main_stat_name = ""
match player.character_class.main_stat:
CharacterClass.MainStat.STRENGTH:
main_stat_name = "STR"
CharacterClass.MainStat.AGILITY:
main_stat_name = "AGI"
CharacterClass.MainStat.INTELLIGENCE:
main_stat_name = "INT"
class_label.text = player.character_class.class_name_de + " (Haupt: " + main_stat_name + ")"
else:
class_label.text = "Keine Klasse"
level_label.text = "Level " + str(player.level) + " (" + str(player.current_xp) + "/" + str(player.xp_to_next_level) + " XP)"
str_label.text = "Stärke: " + str(player.strength)
agi_label.text = "Beweglichkeit: " + str(player.agility)
int_label.text = "Intelligenz: " + str(player.intelligence)
sta_label.text = "Ausdauer: " + str(player.stamina)
armor_label.text = "Rüstung: " + str(player.armor)
hp_label.text = "HP: " + str(player.current_hp) + " / " + str(player.max_hp)
# Waffen-Stats (jetzt in Equipment-Spalte)
var weapon = player.get_equipped_weapon()
if weapon:
damage_label.text = "Schaden: " + str(weapon.min_damage) + "-" + str(weapon.max_damage) + " (%.1fs)" % weapon.attack_speed
else:
# Unbewaffnet: klassenabhängiger Schaden
if player.character_class:
var min_dmg = player.character_class.unarmed_min_damage
var max_dmg = player.character_class.unarmed_max_damage
var atk_spd = player.character_class.unarmed_attack_speed
damage_label.text = "Unbewaffnet: " + str(min_dmg) + "-" + str(max_dmg) + " (%.1fs)" % atk_spd
else:
damage_label.text = "Unbewaffnet: 1-2 (2.0s)"
dps_label.text = "DPS: %.1f" % player.get_dps()
# Main-Stat hervorheben
str_label.modulate = Color(1, 1, 1)
agi_label.modulate = Color(1, 1, 1)
int_label.modulate = Color(1, 1, 1)
if player.character_class:
match player.character_class.main_stat:
CharacterClass.MainStat.STRENGTH:
str_label.modulate = Color(1, 0.8, 0.2)
CharacterClass.MainStat.AGILITY:
agi_label.modulate = Color(1, 0.8, 0.2)
CharacterClass.MainStat.INTELLIGENCE:
int_label.modulate = Color(1, 0.8, 0.2)
# Equipment aktualisieren
_update_equipment_slot(head_slot, "Kopf", player.equipment[Equipment.Slot.HEAD])
_update_equipment_slot(chest_slot, "Brust", player.equipment[Equipment.Slot.CHEST])
_update_equipment_slot(hands_slot, "Hände", player.equipment[Equipment.Slot.HANDS])
_update_equipment_slot(legs_slot, "Beine", player.equipment[Equipment.Slot.LEGS])
_update_equipment_slot(feet_slot, "Füße", player.equipment[Equipment.Slot.FEET])
_update_equipment_slot(weapon_slot, "Waffe", player.equipment[Equipment.Slot.WEAPON])
_update_equipment_slot(offhand_slot, "Nebenhand", player.equipment[Equipment.Slot.OFFHAND])
func _update_equipment_slot(label: Label, slot_name: String, item: Equipment):
if item == null:
label.text = slot_name + ": -"
label.modulate = Color(0.6, 0.6, 0.6)
else:
label.text = slot_name + ": " + item.item_name
label.modulate = Equipment.get_rarity_color(item.rarity)

1
character_panel.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://7pdlor66gi51

155
character_panel.tscn Normal file
View file

@ -0,0 +1,155 @@
[gd_scene format=3 uid="uid://character_panel"]
[ext_resource type="Script" path="res://character_panel.gd" id="1_panel"]
[node name="CharacterPanel" type="CanvasLayer"]
script = ExtResource("1_panel")
[node name="Panel" type="Panel" parent="."]
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 20.0
offset_top = -200.0
offset_right = 520.0
offset_bottom = 200.0
grow_vertical = 2
[node name="HBoxContainer" type="HBoxContainer" parent="Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 15.0
offset_top = 15.0
offset_right = -15.0
offset_bottom = -15.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 20
[node name="StatsColumn" type="VBoxContainer" parent="Panel/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 6
[node name="Title" type="Label" parent="Panel/HBoxContainer/StatsColumn"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "Charakter"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="Panel/HBoxContainer/StatsColumn"]
layout_mode = 2
[node name="ClassLabel" type="Label" parent="Panel/HBoxContainer/StatsColumn"]
layout_mode = 2
theme_override_font_sizes/font_size = 16
text = "Krieger (Haupt: STR)"
[node name="LevelLabel" type="Label" parent="Panel/HBoxContainer/StatsColumn"]
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "Level 1 (0/100 XP)"
[node name="HSeparator2" type="HSeparator" parent="Panel/HBoxContainer/StatsColumn"]
layout_mode = 2
[node name="StatsContainer" type="VBoxContainer" parent="Panel/HBoxContainer/StatsColumn"]
layout_mode = 2
theme_override_constants/separation = 4
[node name="StrLabel" type="Label" parent="Panel/HBoxContainer/StatsColumn/StatsContainer"]
layout_mode = 2
text = "Stärke: 15"
[node name="AgiLabel" type="Label" parent="Panel/HBoxContainer/StatsColumn/StatsContainer"]
layout_mode = 2
text = "Beweglichkeit: 8"
[node name="IntLabel" type="Label" parent="Panel/HBoxContainer/StatsColumn/StatsContainer"]
layout_mode = 2
text = "Intelligenz: 5"
[node name="StaLabel" type="Label" parent="Panel/HBoxContainer/StatsColumn/StatsContainer"]
layout_mode = 2
text = "Ausdauer: 12"
[node name="ArmorLabel" type="Label" parent="Panel/HBoxContainer/StatsColumn/StatsContainer"]
layout_mode = 2
text = "Rüstung: 0"
[node name="HSeparator3" type="HSeparator" parent="Panel/HBoxContainer/StatsColumn"]
layout_mode = 2
[node name="HPLabel" type="Label" parent="Panel/HBoxContainer/StatsColumn"]
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "HP: 120 / 120"
[node name="VSeparator" type="VSeparator" parent="Panel/HBoxContainer"]
layout_mode = 2
[node name="EquipmentColumn" type="VBoxContainer" parent="Panel/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 6
[node name="EquipTitle" type="Label" parent="Panel/HBoxContainer/EquipmentColumn"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "Ausrüstung"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="Panel/HBoxContainer/EquipmentColumn"]
layout_mode = 2
[node name="EquipmentContainer" type="VBoxContainer" parent="Panel/HBoxContainer/EquipmentColumn"]
layout_mode = 2
theme_override_constants/separation = 4
[node name="HeadSlot" type="Label" parent="Panel/HBoxContainer/EquipmentColumn/EquipmentContainer"]
layout_mode = 2
text = "Kopf: -"
[node name="ChestSlot" type="Label" parent="Panel/HBoxContainer/EquipmentColumn/EquipmentContainer"]
layout_mode = 2
text = "Brust: -"
[node name="HandsSlot" type="Label" parent="Panel/HBoxContainer/EquipmentColumn/EquipmentContainer"]
layout_mode = 2
text = "Hände: -"
[node name="LegsSlot" type="Label" parent="Panel/HBoxContainer/EquipmentColumn/EquipmentContainer"]
layout_mode = 2
text = "Beine: -"
[node name="FeetSlot" type="Label" parent="Panel/HBoxContainer/EquipmentColumn/EquipmentContainer"]
layout_mode = 2
text = "Füße: -"
[node name="HSeparator2" type="HSeparator" parent="Panel/HBoxContainer/EquipmentColumn"]
layout_mode = 2
[node name="WeaponSlot" type="Label" parent="Panel/HBoxContainer/EquipmentColumn"]
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "Waffe: -"
[node name="OffhandSlot" type="Label" parent="Panel/HBoxContainer/EquipmentColumn"]
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "Nebenhand: -"
[node name="HSeparator3" type="HSeparator" parent="Panel/HBoxContainer/EquipmentColumn"]
layout_mode = 2
[node name="DamageLabel" type="Label" parent="Panel/HBoxContainer/EquipmentColumn"]
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "Schaden: 3-6 (1.5s)"
[node name="DPSLabel" type="Label" parent="Panel/HBoxContainer/EquipmentColumn"]
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "DPS: 5.0"

81
class_selection_menu.gd Normal file
View file

@ -0,0 +1,81 @@
# ClassSelectionMenu.gd
# Menü zur Auswahl der Charakterklasse beim Spielstart
extends CanvasLayer
signal class_selected(character_class: CharacterClass)
const WARRIOR_CLASS = preload("res://classes/warrior.tres")
const ROGUE_CLASS = preload("res://classes/rogue.tres")
const MAGE_CLASS = preload("res://classes/mage.tres")
var selected_class: CharacterClass = null
@onready var panel = $Panel
@onready var warrior_btn = $Panel/VBoxContainer/ClassButtons/WarriorBtn
@onready var rogue_btn = $Panel/VBoxContainer/ClassButtons/RogueBtn
@onready var mage_btn = $Panel/VBoxContainer/ClassButtons/MageBtn
@onready var start_btn = $Panel/VBoxContainer/StartBtn
@onready var class_info = $Panel/VBoxContainer/ClassInfo
@onready var stats_label = $Panel/VBoxContainer/StatsLabel
func _ready():
# Spiel pausieren während Menü offen
get_tree().paused = true
process_mode = Node.PROCESS_MODE_ALWAYS
# Buttons verbinden
warrior_btn.pressed.connect(_on_warrior_selected)
rogue_btn.pressed.connect(_on_rogue_selected)
mage_btn.pressed.connect(_on_mage_selected)
start_btn.pressed.connect(_on_start_pressed)
# Start-Button deaktiviert bis Klasse gewählt
start_btn.disabled = true
class_info.text = "Wähle eine Klasse!"
stats_label.text = ""
func _on_warrior_selected():
selected_class = WARRIOR_CLASS
_update_selection("Krieger", WARRIOR_CLASS)
_highlight_button(warrior_btn)
func _on_rogue_selected():
selected_class = ROGUE_CLASS
_update_selection("Schurke", ROGUE_CLASS)
_highlight_button(rogue_btn)
func _on_mage_selected():
selected_class = MAGE_CLASS
_update_selection("Magier", MAGE_CLASS)
_highlight_button(mage_btn)
func _update_selection(cls_name: String, char_class: CharacterClass):
var main_stat_name = ""
match char_class.main_stat:
CharacterClass.MainStat.STRENGTH:
main_stat_name = "Stärke"
CharacterClass.MainStat.AGILITY:
main_stat_name = "Beweglichkeit"
CharacterClass.MainStat.INTELLIGENCE:
main_stat_name = "Intelligenz"
class_info.text = cls_name + " - Haupt-Stat: " + main_stat_name
stats_label.text = "STR: " + str(char_class.base_strength) + " AGI: " + str(char_class.base_agility) + " INT: " + str(char_class.base_intelligence) + " STA: " + str(char_class.base_stamina)
start_btn.disabled = false
func _highlight_button(active_btn: Button):
# Alle Buttons zurücksetzen
warrior_btn.modulate = Color(1, 1, 1)
rogue_btn.modulate = Color(1, 1, 1)
mage_btn.modulate = Color(1, 1, 1)
# Aktiven Button hervorheben
active_btn.modulate = Color(1, 0.8, 0.2)
func _on_start_pressed():
if selected_class == null:
return
# Signal senden und Menü schließen
class_selected.emit(selected_class)
get_tree().paused = false
queue_free()

View file

@ -0,0 +1 @@
uid://db8m2uw42hqfc

80
class_selection_menu.tscn Normal file
View file

@ -0,0 +1,80 @@
[gd_scene format=3 uid="uid://class_selection_menu"]
[ext_resource type="Script" path="res://class_selection_menu.gd" id="1_menu"]
[node name="ClassSelectionMenu" type="CanvasLayer"]
script = ExtResource("1_menu")
[node name="Panel" type="Panel" parent="."]
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -200.0
offset_top = -180.0
offset_right = 200.0
offset_bottom = 180.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 20.0
offset_top = 20.0
offset_right = -20.0
offset_bottom = -20.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 15
[node name="Title" type="Label" parent="Panel/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 28
text = "Wähle deine Klasse"
horizontal_alignment = 1
[node name="ClassButtons" type="HBoxContainer" parent="Panel/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 20
alignment = 1
[node name="WarriorBtn" type="Button" parent="Panel/VBoxContainer/ClassButtons"]
custom_minimum_size = Vector2(100, 80)
layout_mode = 2
theme_override_font_sizes/font_size = 16
text = "Krieger"
[node name="RogueBtn" type="Button" parent="Panel/VBoxContainer/ClassButtons"]
custom_minimum_size = Vector2(100, 80)
layout_mode = 2
theme_override_font_sizes/font_size = 16
text = "Schurke"
[node name="MageBtn" type="Button" parent="Panel/VBoxContainer/ClassButtons"]
custom_minimum_size = Vector2(100, 80)
layout_mode = 2
theme_override_font_sizes/font_size = 16
text = "Magier"
[node name="ClassInfo" type="Label" parent="Panel/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "Wähle eine Klasse!"
horizontal_alignment = 1
[node name="StatsLabel" type="Label" parent="Panel/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = ""
horizontal_alignment = 1
[node name="StartBtn" type="Button" parent="Panel/VBoxContainer"]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
theme_override_font_sizes/font_size = 20
disabled = true
text = "Spiel starten"

19
classes/mage.tres Normal file
View file

@ -0,0 +1,19 @@
[gd_resource type="Resource" script_class="CharacterClass" format=3]
[ext_resource type="Script" path="res://character_class.gd" id="1"]
[resource]
script = ExtResource("1")
class_name_de = "Magier"
main_stat = 2
base_strength = 5
base_agility = 8
base_intelligence = 15
base_stamina = 8
strength_per_level = 1.0
agility_per_level = 1.5
intelligence_per_level = 3.0
stamina_per_level = 1.5
unarmed_min_damage = 1
unarmed_max_damage = 2
unarmed_attack_speed = 2.0

19
classes/rogue.tres Normal file
View file

@ -0,0 +1,19 @@
[gd_resource type="Resource" script_class="CharacterClass" format=3]
[ext_resource type="Script" path="res://character_class.gd" id="1"]
[resource]
script = ExtResource("1")
class_name_de = "Schurke"
main_stat = 1
base_strength = 8
base_agility = 15
base_intelligence = 7
base_stamina = 10
strength_per_level = 1.5
agility_per_level = 3.0
intelligence_per_level = 1.5
stamina_per_level = 2.0
unarmed_min_damage = 1
unarmed_max_damage = 3
unarmed_attack_speed = 1.5

19
classes/warrior.tres Normal file
View file

@ -0,0 +1,19 @@
[gd_resource type="Resource" script_class="CharacterClass" format=3]
[ext_resource type="Script" path="res://character_class.gd" id="1"]
[resource]
script = ExtResource("1")
class_name_de = "Krieger"
main_stat = 0
base_strength = 15
base_agility = 8
base_intelligence = 5
base_stamina = 12
strength_per_level = 3.0
agility_per_level = 1.5
intelligence_per_level = 1.0
stamina_per_level = 2.5
unarmed_min_damage = 2
unarmed_max_damage = 4
unarmed_attack_speed = 1.8

63
equipment.gd Normal file
View file

@ -0,0 +1,63 @@
# Equipment.gd
# Resource für Ausrüstungsgegenstände
extends Resource
class_name Equipment
enum Slot {
HEAD, # Helm
CHEST, # Brustpanzer
HANDS, # Handschuhe
LEGS, # Beinschienen
FEET, # Stiefel
WEAPON, # Waffe
OFFHAND # Nebenhand (Schild, etc.)
}
enum Rarity {
COMMON, # Weiß
UNCOMMON, # Grün
RARE, # Blau
EPIC # Lila
}
@export var item_name: String = "Unbekannt"
@export var slot: Slot = Slot.WEAPON
@export var rarity: Rarity = Rarity.COMMON
# Stats die das Item gibt
@export var armor: int = 0
@export var strength: int = 0
@export var agility: int = 0
@export var intelligence: int = 0
@export var stamina: int = 0
@export var haste: float = 0.0 # Angriffsgeschwindigkeit (0.1 = 10% schneller)
# Nur für Waffen
@export var min_damage: int = 0
@export var max_damage: int = 0
@export var attack_speed: float = 1.5 # Sekunden zwischen Angriffen
@export var weapon_range: float = 3.0
# Icon für UI
@export var icon: Texture2D
# Slot-Namen für Anzeige
static func get_slot_name(s: Slot) -> String:
match s:
Slot.HEAD: return "Kopf"
Slot.CHEST: return "Brust"
Slot.HANDS: return "Hände"
Slot.LEGS: return "Beine"
Slot.FEET: return "Füße"
Slot.WEAPON: return "Waffe"
Slot.OFFHAND: return "Nebenhand"
return "Unbekannt"
# Seltenheitsfarbe
static func get_rarity_color(r: Rarity) -> Color:
match r:
Rarity.COMMON: return Color(1, 1, 1) # Weiß
Rarity.UNCOMMON: return Color(0.2, 0.8, 0.2) # Grün
Rarity.RARE: return Color(0.3, 0.5, 1.0) # Blau
Rarity.EPIC: return Color(0.7, 0.3, 0.9) # Lila
return Color(1, 1, 1)

1
equipment.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://re0xiie1udfq

18
equipment/iron_helm.tres Normal file
View file

@ -0,0 +1,18 @@
[gd_resource type="Resource" script_class="Equipment" load_steps=2 format=3]
[ext_resource type="Script" path="res://equipment.gd" id="1_equipment"]
[resource]
script = ExtResource("1_equipment")
item_name = "Eisenhelm"
slot = 0
rarity = 0
armor = 5
strength = 1
agility = 0
intelligence = 0
stamina = 1
min_damage = 0
max_damage = 0
attack_speed = 1.5
weapon_range = 3.0

18
equipment/iron_sword.tres Normal file
View file

@ -0,0 +1,18 @@
[gd_resource type="Resource" script_class="Equipment" load_steps=2 format=3]
[ext_resource type="Script" path="res://equipment.gd" id="1_equipment"]
[resource]
script = ExtResource("1_equipment")
item_name = "Eisenschwert"
slot = 5
rarity = 0
armor = 0
strength = 2
agility = 0
intelligence = 0
stamina = 0
min_damage = 3
max_damage = 6
attack_speed = 1.5
weapon_range = 3.0

View file

@ -0,0 +1,18 @@
[gd_resource type="Resource" script_class="Equipment" load_steps=2 format=3]
[ext_resource type="Script" path="res://equipment.gd" id="1_equipment"]
[resource]
script = ExtResource("1_equipment")
item_name = "Lederrüstung"
slot = 1
rarity = 0
armor = 8
strength = 0
agility = 1
intelligence = 0
stamina = 2
min_damage = 0
max_damage = 0
attack_speed = 1.5
weapon_range = 3.0

View file

@ -0,0 +1,18 @@
[gd_resource type="Resource" script_class="Equipment" load_steps=2 format=3]
[ext_resource type="Script" path="res://equipment.gd" id="1_equipment"]
[resource]
script = ExtResource("1_equipment")
item_name = "Stahlschwert"
slot = 5
rarity = 1
armor = 0
strength = 4
agility = 0
intelligence = 0
stamina = 0
min_damage = 5
max_damage = 9
attack_speed = 1.4
weapon_range = 3.0

View file

@ -0,0 +1,18 @@
[gd_resource type="Resource" script_class="Equipment" load_steps=2 format=3]
[ext_resource type="Script" path="res://equipment.gd" id="1_equipment"]
[resource]
script = ExtResource("1_equipment")
item_name = "Holzschild"
slot = 6
rarity = 0
armor = 6
strength = 0
agility = 0
intelligence = 0
stamina = 2
min_damage = 0
max_damage = 0
attack_speed = 1.5
weapon_range = 3.0

9
heavy_strike.tres Normal file
View file

@ -0,0 +1,9 @@
[gd_resource type="Resource" script_class="Attack" load_steps=2 format=3]
[ext_resource type="Script" path="res://resources/attack.gd" id="1"]
[resource]
script = ExtResource("1")
name = "Heavy Strike"
damage_type = 0
icon = null

25
icons/autoattack_icon.svg Normal file
View file

@ -0,0 +1,25 @@
<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">
<!-- Autoattack Icon - Faust -->
<defs>
<linearGradient id="fist" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#ffd4a3;stop-opacity:1" />
<stop offset="100%" style="stop-color:#d4a574;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Circular arrow (autoattack symbol) -->
<path d="M 45 15 A 12 12 0 1 1 33 3" stroke="#4CAF50" stroke-width="3" fill="none" stroke-linecap="round"/>
<polygon points="45,15 50,10 50,20" fill="#4CAF50"/>
<!-- Fist palm -->
<rect x="18" y="35" width="16" height="20" rx="3" fill="url(#fist)" stroke="#b8945f" stroke-width="2"/>
<!-- Thumb -->
<ellipse cx="35" cy="45" rx="4" ry="8" fill="url(#fist)" stroke="#b8945f" stroke-width="2"/>
<!-- Fingers -->
<rect x="18" y="28" width="3" height="10" rx="1.5" fill="url(#fist)" stroke="#b8945f" stroke-width="1"/>
<rect x="22" y="25" width="3" height="12" rx="1.5" fill="url(#fist)" stroke="#b8945f" stroke-width="1"/>
<rect x="26" y="26" width="3" height="11" rx="1.5" fill="url(#fist)" stroke="#b8945f" stroke-width="1"/>
<rect x="30" y="28" width="3" height="9" rx="1.5" fill="url(#fist)" stroke="#b8945f" stroke-width="1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bkujmu8r6370p"
path="res://.godot/imported/autoattack_icon.svg-8c48165bd13a879d5f6cdbc811e19882.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icons/autoattack_icon.svg"
dest_files=["res://.godot/imported/autoattack_icon.svg-8c48165bd13a879d5f6cdbc811e19882.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1,26 @@
<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">
<!-- Heavy Strike Icon - Schwert mit Schlageffekt -->
<defs>
<linearGradient id="blade" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#e0e0e0;stop-opacity:1" />
<stop offset="100%" style="stop-color:#808080;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Impact lines (red) -->
<path d="M 10 15 L 20 10" stroke="#ff4444" stroke-width="3" stroke-linecap="round"/>
<path d="M 8 25 L 18 20" stroke="#ff4444" stroke-width="3" stroke-linecap="round"/>
<path d="M 12 35 L 22 30" stroke="#ff4444" stroke-width="3" stroke-linecap="round"/>
<!-- Sword blade -->
<polygon points="25,10 35,10 40,50 20,50" fill="url(#blade)" stroke="#404040" stroke-width="2"/>
<!-- Sword guard -->
<rect x="15" y="48" width="30" height="4" fill="#8B4513" stroke="#654321" stroke-width="1"/>
<!-- Sword handle -->
<rect x="25" y="50" width="10" height="10" fill="#654321" stroke="#3d2817" stroke-width="1"/>
<!-- Pommel -->
<circle cx="30" cy="62" r="4" fill="#DAA520" stroke="#B8860B" stroke-width="1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dqkaowq4cvtbl"
path="res://.godot/imported/heavy_strike_icon.svg-ae86ca74d5a61e67598cea5812ee34ac.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icons/heavy_strike_icon.svg"
dest_files=["res://.godot/imported/heavy_strike_icon.svg-ae86ca74d5a61e67598cea5812ee34ac.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

77
inventory.gd Normal file
View file

@ -0,0 +1,77 @@
# Inventory.gd
# Verwaltet das Spieler-Inventar mit Items und Gold
extends Resource
class_name Inventory
signal inventory_changed
signal gold_changed(new_amount: int)
const MAX_SLOTS = 20 # Maximale Inventarplätze
var items: Array[Equipment] = []
var gold: int = 0
func _init():
items = []
gold = 0
# Item hinzufügen - gibt true zurück wenn erfolgreich
func add_item(item: Equipment) -> bool:
if items.size() >= MAX_SLOTS:
print("Inventar voll!")
return false
items.append(item)
inventory_changed.emit()
print("Item erhalten: ", item.item_name)
return true
# Item an Index entfernen
func remove_item_at(index: int) -> Equipment:
if index < 0 or index >= items.size():
return null
var item = items[index]
items.remove_at(index)
inventory_changed.emit()
return item
# Item direkt entfernen
func remove_item(item: Equipment) -> bool:
var index = items.find(item)
if index == -1:
return false
items.remove_at(index)
inventory_changed.emit()
return true
# Gold hinzufügen
func add_gold(amount: int):
gold += amount
gold_changed.emit(gold)
print("+", amount, " Gold (Gesamt: ", gold, ")")
# Gold ausgeben - gibt true zurück wenn genug vorhanden
func spend_gold(amount: int) -> bool:
if gold < amount:
print("Nicht genug Gold!")
return false
gold -= amount
gold_changed.emit(gold)
return true
# Prüfen ob Inventar voll ist
func is_full() -> bool:
return items.size() >= MAX_SLOTS
# Anzahl freier Slots
func free_slots() -> int:
return MAX_SLOTS - items.size()
# Item an Index holen
func get_item(index: int) -> Equipment:
if index < 0 or index >= items.size():
return null
return items[index]
# Anzahl Items
func item_count() -> int:
return items.size()

1
inventory.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://b7x1jr36w0bfg

128
inventory_panel.gd Normal file
View file

@ -0,0 +1,128 @@
# InventoryPanel.gd
# UI für das Spieler-Inventar
extends CanvasLayer
signal item_selected(item: Equipment, index: int)
var panel_visible = false
var player = null
@onready var panel = $Panel
@onready var gold_label = $Panel/VBoxContainer/Header/GoldLabel
@onready var item_grid = $Panel/VBoxContainer/ScrollContainer/ItemGrid
const SLOT_SIZE = 50
func _ready():
panel.visible = false
func setup(p):
player = p
if player and player.inventory:
player.inventory.inventory_changed.connect(_on_inventory_changed)
player.inventory.gold_changed.connect(_on_gold_changed)
_refresh_inventory()
func toggle():
panel_visible = !panel_visible
panel.visible = panel_visible
if panel_visible:
_refresh_inventory()
func _on_inventory_changed():
_refresh_inventory()
func _on_gold_changed(new_amount: int):
gold_label.text = str(new_amount) + " Gold"
func _refresh_inventory():
if player == null or player.inventory == null:
return
# Gold aktualisieren
gold_label.text = str(player.inventory.gold) + " Gold"
# Alte Slots entfernen
for child in item_grid.get_children():
child.queue_free()
# Slots erstellen (immer MAX_SLOTS anzeigen)
for i in range(Inventory.MAX_SLOTS):
var slot = _create_slot(i)
item_grid.add_child(slot)
func _create_slot(index: int) -> Panel:
var slot = Panel.new()
slot.custom_minimum_size = Vector2(SLOT_SIZE, SLOT_SIZE)
# Slot-Hintergrund stylen
var style = StyleBoxFlat.new()
style.bg_color = Color(0.15, 0.15, 0.15)
style.border_color = Color(0.3, 0.3, 0.3)
style.set_border_width_all(1)
slot.add_theme_stylebox_override("panel", style)
# Item vorhanden?
if player.inventory and index < player.inventory.item_count():
var item = player.inventory.get_item(index)
if item:
# Item-Name Label
var label = Label.new()
label.text = item.item_name.substr(0, 3) # Erste 3 Buchstaben
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
label.add_theme_font_size_override("font_size", 10)
label.modulate = Equipment.get_rarity_color(item.rarity)
label.anchors_preset = Control.PRESET_FULL_RECT
slot.add_child(label)
# Rahmen in Seltenheitsfarbe
style.border_color = Equipment.get_rarity_color(item.rarity)
style.set_border_width_all(2)
# Klick-Handler
slot.gui_input.connect(_on_slot_clicked.bind(index, item))
# Tooltip
slot.tooltip_text = _get_item_tooltip(item)
return slot
func _on_slot_clicked(event: InputEvent, index: int, item: Equipment):
if event is InputEventMouseButton and event.pressed:
if event.button_index == MOUSE_BUTTON_LEFT:
# Linksklick: Item auswählen/anlegen
item_selected.emit(item, index)
elif event.button_index == MOUSE_BUTTON_RIGHT:
# Rechtsklick: Item direkt anlegen
if player:
var old_item = player.equip_item(item)
player.inventory.remove_item(item)
if old_item:
player.inventory.add_item(old_item)
_refresh_inventory()
func _get_item_tooltip(item: Equipment) -> String:
var tooltip = item.item_name + "\n"
tooltip += Equipment.get_slot_name(item.slot) + "\n"
tooltip += "---\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"
tooltip += "\n[Rechtsklick zum Anlegen]"
return tooltip

1
inventory_panel.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://bjmcu0tyxqxmd

63
inventory_panel.tscn Normal file
View file

@ -0,0 +1,63 @@
[gd_scene format=3 uid="uid://inventory_panel"]
[ext_resource type="Script" path="res://inventory_panel.gd" id="1_panel"]
[node name="InventoryPanel" type="CanvasLayer"]
script = ExtResource("1_panel")
[node name="Panel" type="Panel" parent="."]
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -320.0
offset_top = -250.0
offset_right = -20.0
offset_bottom = 250.0
grow_horizontal = 0
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 10.0
offset_top = 10.0
offset_right = -10.0
offset_bottom = -10.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 8
[node name="Header" type="HBoxContainer" parent="Panel/VBoxContainer"]
layout_mode = 2
[node name="Title" type="Label" parent="Panel/VBoxContainer/Header"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 20
text = "Inventar"
[node name="GoldLabel" type="Label" parent="Panel/VBoxContainer/Header"]
layout_mode = 2
theme_override_font_sizes/font_size = 16
theme_override_colors/font_color = Color(1, 0.85, 0, 1)
text = "0 Gold"
horizontal_alignment = 2
[node name="HSeparator" type="HSeparator" parent="Panel/VBoxContainer"]
layout_mode = 2
[node name="ScrollContainer" type="ScrollContainer" parent="Panel/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="ItemGrid" type="GridContainer" parent="Panel/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/h_separation = 5
theme_override_constants/v_separation = 5
columns = 5

7
loot_entry.gd Normal file
View file

@ -0,0 +1,7 @@
# LootEntry.gd
# Ein einzelner Eintrag in einer LootTable
extends Resource
class_name LootEntry
@export var item: Equipment
@export_range(0.0, 1.0) var drop_chance: float = 0.1 # 10% Standard

1
loot_entry.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://cx2w8nkuoylv5

24
loot_table.gd Normal file
View file

@ -0,0 +1,24 @@
# LootTable.gd
# Definiert mögliche Drops eines Gegners
extends Resource
class_name LootTable
# Gold-Drop
@export var min_gold: int = 1
@export var max_gold: int = 5
# Item-Drops mit Wahrscheinlichkeiten
@export var possible_drops: Array[LootEntry] = []
# Generiert Loot basierend auf Tabelle
func generate_loot() -> Dictionary:
var result = {
"gold": randi_range(min_gold, max_gold),
"items": []
}
for entry in possible_drops:
if randf() <= entry.drop_chance:
result["items"].append(entry.item)
return result

1
loot_table.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://dej1tpamsi71r