diff --git a/PROJEKTDOKU.md b/PROJEKTDOKU.md
index 105b308..cfcf0d4 100644
--- a/PROJEKTDOKU.md
+++ b/PROJEKTDOKU.md
@@ -25,7 +25,7 @@ Gegner bekämpfen und ihre Charaktere mit verschiedenen Klassen und Ausrüstunge
| Rechtsklick auf Gegner | Ziel markieren + Autoattack starten |
| 1 | Autoattack manuell starten |
| 2 | Heavy Strike (starke Attacke) |
-| 3 – 9 | Aktionsleiste Slots (noch frei) |
+| 3 – 9 | Aktionsleiste Slots (Consumables/Items) |
| C | Charakter-Panel (Stats + Equipment) |
| I | Inventar öffnen/schließen |
| Leertaste | Springen |
@@ -36,7 +36,7 @@ Gegner bekämpfen und ihre Charaktere mit verschiedenen Klassen und Ausrüstunge
## Szenen-Struktur
### world.tscn
-Hauptszene der Spielwelt. Zeigt bei Start das Klassenauswahl-Menü.
+Hauptszene der Spielwelt. Zeigt bei Start das Hauptmenü (Einstellungen), dann Klassenauswahl.
```
World (Node3D)
├── Player (player.tscn)
@@ -90,21 +90,44 @@ Resource-Klasse die eine spielbare Klasse definiert.
| unarmed_min_damage | int | Unbewaffneter Mindestschaden |
| unarmed_max_damage | int | Unbewaffneter Maximalschaden |
| unarmed_attack_speed | float | Unbewaffnete Angriffsgeschwindigkeit |
+| resource_type | ResourceType | NONE, MANA, RAGE, ENERGY |
+| base_resource | int | Basis-Ressource auf Level 1 |
**Konstanten:**
- `HP_PER_STAMINA = 10` — HP pro Stamina-Punkt
+- `MANA_PER_INT = 5` — Mana pro Intelligenz-Punkt
- `DAMAGE_PER_MAIN_STAT = 0.5` — Zusatzschaden pro Main-Stat-Punkt
### Verfügbare Klassen (classes/)
-| Klasse | Main-Stat | STR | AGI | INT | STA | Unbewaffnet |
-|---|---|---|---|---|---|---|
-| Krieger | Stärke | 15 | 8 | 5 | 12 | 2-4 Schaden, 1.8s |
-| Schurke | Beweglichkeit | 8 | 15 | 7 | 10 | 1-3 Schaden, 1.5s |
-| Magier | Intelligenz | 5 | 8 | 15 | 8 | 1-2 Schaden, 2.0s |
+| Klasse | Main-Stat | Ressource | STR | AGI | INT | STA | Unbewaffnet |
+|---|---|---|---|---|---|---|---|
+| Krieger | Stärke | Keine | 15 | 8 | 5 | 12 | 2-4 Schaden, 1.8s |
+| Schurke | Beweglichkeit | Energie (100) | 8 | 15 | 7 | 10 | 1-3 Schaden, 1.5s |
+| Magier | Intelligenz | Mana (100 + INT*5) | 5 | 8 | 15 | 8 | 1-2 Schaden, 2.0s |
+
+### Hauptmenü (main_menu.gd)
+Wird als erstes angezeigt mit Optionen:
+- **Spielen** — Öffnet Klassenauswahl
+- **Einstellungen** — Auflösung (720p-4K), Fenstermodus (Fenster/Randlos/Vollbild), VSync, Anti-Aliasing (Aus/2x/4x/8x)
+- **Beenden** — Spiel schließen
### Klassenauswahl (class_selection_menu.gd)
-Wird bei Spielstart angezeigt. Spieler wählt eine Klasse, danach wird Startausrüstung angelegt.
+Wird nach dem Hauptmenü angezeigt. Spieler wählt eine Klasse, danach wird Startausrüstung angelegt.
+
+---
+
+## Klassen-Ressourcen-System
+
+Jede Klasse kann eine eigene Ressource haben:
+| Ressource | Farbe | Skalierung | Klasse |
+|---|---|---|---|
+| Keine | - | - | Krieger |
+| Mana | Blau | base_resource + INT * 5 | Magier |
+| Energie | Gelb | Fix (base_resource) | Schurke |
+| Wut | Rot | Fix (base_resource) | (geplant) |
+
+Die Ressourcen-Leiste wird nur angezeigt wenn die Klasse eine Ressource hat.
---
@@ -112,7 +135,7 @@ Wird bei Spielstart angezeigt. Spieler wählt eine Klasse, danach wird Startausr
- **XP-Kurve:** Level N benötigt `100 * N` XP (Level 2: 100, Level 3: 200, ...)
- **Stats pro Level:** Basierend auf Klassen-Zuwachsraten
-- **HP bei Level-Up:** Werden vollständig aufgefüllt
+- **HP bei Level-Up:** Werden vollständig aufgefüllt (HP + Klassen-Ressource)
- **Character Panel:** Aktualisiert sich automatisch bei Level-Up
---
@@ -155,12 +178,41 @@ Resource-Klasse für alle Ausrüstungsgegenstände.
### Character Panel (character_panel.gd, C-Taste)
Zeigt zwei Spalten:
- **Stats-Spalte:** Stärke, Beweglichkeit, Intelligenz, Ausdauer, Rüstung, HP
-- **Equipment-Spalte:** Alle 7 Slots mit ausgerüsteten Items, Waffenschaden + DPS
+- **Equipment-Spalte:** Icon-Grid mit Tooltips bei Hover, Waffenschaden + DPS
Unbewaffnet: Zeigt klassenabhängige Schadenswerte.
---
+## Consumable-System
+
+### Consumable (consumable.gd)
+Resource-Klasse für verbrauchbare Items (Tränke, Essen, etc.).
+
+| Property | Typ | Beschreibung |
+|---|---|---|
+| item_name | String | Name des Tranks |
+| effect_type | EffectType | HEAL_HP, RESTORE_MANA, HEAL_AND_MANA |
+| amount | int | Heilungsmenge |
+| cooldown | float | Cooldown nach Benutzung |
+| icon | Texture2D | Icon für UI |
+| stack_size | int | Aktuelle Anzahl im Stack |
+| max_stack | int | Maximale Stack-Größe (20) |
+
+### Vorhandene Tränke (consumables/)
+- **small_hp_potion.tres** — Kleiner Heiltrank (+50 HP, 40% Drop-Chance)
+- **small_mana_potion.tres** — Kleiner Manatrank (+40 Mana, 25% Drop-Chance)
+
+### Benutzung
+- **Rechtsklick im Inventar:** Trank direkt benutzen
+- **Shift+Linksklick im Inventar:** Trank auf nächsten freien Aktionsleisten-Slot legen
+- **Taste 3-9:** Trank über Aktionsleiste benutzen
+- HP-Tränke: Nur wenn HP nicht voll, stellt HP wieder her
+- Mana-Tränke: Nur wenn Mana nicht voll, stellt Ressource wieder her
+- **Stacking:** Gleichnamige Tränke werden automatisch gestackt (max 20)
+
+---
+
## Inventar-System
### Inventory (inventory.gd)
@@ -204,11 +256,11 @@ Ein einzelner Drop-Eintrag.
| Property | Typ | Beschreibung |
|---|---|---|
-| item | Equipment | Das droppbare Item |
+| item | Resource | Das droppbare Item (Equipment oder Consumable) |
| drop_chance | float | Wahrscheinlichkeit (0.0 - 1.0) |
### Vorhandene LootTables (loot_tables/)
-- **goblin_loot.tres** — 2-8 Gold, Eisenschwert (15%), Lederrüstung (10%), Eisenhelm (10%)
+- **goblin_loot.tres** — 2-8 Gold, Eisenschwert (15%), Lederrüstung (10%), Eisenhelm (10%), Heiltrank (40%), Manatrank (25%)
- **skeleton_loot.tres** — 5-15 Gold, Stahlschwert (10%), Holzschild (12%), Eisenhelm (15%)
### LootWindow (loot_window.gd)
@@ -298,15 +350,17 @@ Level-basiert mit automatischer Skalierung:
| Element | Beschreibung |
|---|---|
| HealthBar | Rote HP-Leiste mit Text "aktuell / max" |
+| ResourceBar | Ressourcen-Leiste (Blau=Mana, Gelb=Energie, Rot=Wut), nur sichtbar wenn Klasse eine Ressource hat |
| LevelLabel | "Lv X" Anzeige |
| XPBar | Blaue XP-Leiste |
| GoldLabel | Gold-Anzeige in Goldfarbe |
-| ActionBar | 9 Slots mit Icons, Cooldowns, Klick-Support |
+| ActionBar | 9 Slots mit Icons, Cooldowns, Klick-Support, Stack-Anzeige für Consumables |
---
## Geplante Features
-- [ ] Mana-System für Magier
+- [ ] Wut-Ressource für Krieger
+- [ ] Ressourcen-System für Gegner (nicht alle haben Mana)
- [ ] Spell-System (Feuerbälle etc.)
- [ ] Schadenstypen (Physical, Fire, Ice, Lightning, Poison)
- [ ] Mehrere Gegnertypen
@@ -322,9 +376,12 @@ Level-basiert mit automatischer Skalierung:
DungeonCrawler/
├── assets/ # 3D-Modelle (Kenney GLB)
├── classes/ # Klassen-Definitionen (.tres)
-│ ├── warrior.tres
-│ ├── rogue.tres
-│ └── mage.tres
+│ ├── warrior.tres # Krieger (Ressource: NONE)
+│ ├── rogue.tres # Schurke (Ressource: ENERGY, 100)
+│ └── mage.tres # Magier (Ressource: MANA, 100)
+├── consumables/ # Verbrauchbare Items (.tres)
+│ ├── small_hp_potion.tres
+│ └── small_mana_potion.tres
├── equipment/ # Equipment-Items (.tres)
│ ├── iron_sword.tres
│ ├── steel_sword.tres
@@ -334,19 +391,27 @@ DungeonCrawler/
├── loot_tables/ # Loot-Tabellen (.tres)
│ ├── goblin_loot.tres
│ └── skeleton_loot.tres
-├── icons/ # Skill-Icons (SVG)
+├── icons/ # Icons (SVG)
│ ├── autoattack_icon.svg
-│ └── heavy_strike_icon.svg
+│ ├── heavy_strike_icon.svg
+│ ├── iron_sword_icon.svg
+│ ├── steel_sword_icon.svg
+│ ├── leather_chest_icon.svg
+│ ├── iron_helm_icon.svg
+│ ├── wooden_shield_icon.svg
+│ ├── hp_potion_icon.svg
+│ └── mana_potion_icon.svg
├── camera_pivot.gd # Kamera-Script
-├── character_class.gd # CharacterClass Resource
-├── character_panel.gd # Charakter-Panel Script
+├── character_class.gd # CharacterClass Resource (inkl. ResourceType)
+├── character_panel.gd # Charakter-Panel Script (Icon-Grid)
├── character_panel.tscn # Charakter-Panel Scene
├── class_selection_menu.gd # Klassenauswahl Script
├── class_selection_menu.tscn # Klassenauswahl Scene
+├── consumable.gd # Consumable Resource
├── enemy.gd # Gegner-Script
├── enemy.tscn # Gegner-Scene
├── equipment.gd # Equipment Resource
-├── hud.gd # HUD-Script
+├── hud.gd # HUD-Script (inkl. ResourceBar)
├── hud.tscn # HUD-Scene
├── inventory.gd # Inventar Resource
├── inventory_panel.gd # Inventar-Panel Script
@@ -355,7 +420,9 @@ DungeonCrawler/
├── loot_table.gd # LootTable Resource
├── loot_window.gd # Loot-Fenster Script
├── loot_window.tscn # Loot-Fenster Scene
-├── player.gd # Spieler-Script
+├── main_menu.gd # Hauptmenü Script (Einstellungen)
+├── main_menu.tscn # Hauptmenü Scene
+├── player.gd # Spieler-Script (inkl. Ressourcen, Aktionsleiste)
├── player.tscn # Spieler-Scene
├── world.gd # Welt-Script
├── world.tscn # Hauptszene
diff --git a/character_class.gd b/character_class.gd
index 5304900..19807a8 100644
--- a/character_class.gd
+++ b/character_class.gd
@@ -4,9 +4,12 @@ extends Resource
class_name CharacterClass
enum MainStat { STRENGTH, AGILITY, INTELLIGENCE }
+enum ResourceType { NONE, MANA, RAGE, ENERGY }
@export var class_name_de: String = "Krieger"
@export var main_stat: MainStat = MainStat.STRENGTH
+@export var resource_type: ResourceType = ResourceType.NONE
+@export var base_resource: int = 0 # Basis-Ressource auf Level 1 (0 = keine)
# Grund-Stats auf Level 1
@export var base_strength: int = 10
@@ -27,6 +30,8 @@ enum MainStat { STRENGTH, AGILITY, INTELLIGENCE }
# HP pro Stamina-Punkt
const HP_PER_STAMINA = 10
+# Mana pro Intelligenz-Punkt
+const MANA_PER_INT = 5
# Schaden-Skalierung mit Main-Stat
const DAMAGE_PER_MAIN_STAT = 0.5
diff --git a/character_panel.gd b/character_panel.gd
index a9b55f4..f0df3eb 100644
--- a/character_panel.gd
+++ b/character_panel.gd
@@ -1,13 +1,14 @@
# CharacterPanel.gd
-# Zeigt Charakterinfos: Klasse, Level, Stats und Equipment
+# Zeigt Charakterinfos: Klasse, Level, Stats und Equipment-Icons mit Tooltips
extends CanvasLayer
var panel_visible = false
+const EQUIP_SLOT_SIZE = 48
+
@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
@@ -16,15 +17,18 @@ var panel_visible = false
@onready var hp_label = $Panel/HBoxContainer/StatsColumn/HPLabel
@onready var damage_label = $Panel/HBoxContainer/EquipmentColumn/DamageLabel
@onready var dps_label = $Panel/HBoxContainer/EquipmentColumn/DPSLabel
+@onready var equipment_grid = $Panel/HBoxContainer/EquipmentColumn/EquipmentGrid
-# 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
+# Slot-Reihenfolge und deutsche Namen
+const SLOT_ORDER = [
+ Equipment.Slot.HEAD,
+ Equipment.Slot.CHEST,
+ Equipment.Slot.HANDS,
+ Equipment.Slot.LEGS,
+ Equipment.Slot.FEET,
+ Equipment.Slot.WEAPON,
+ Equipment.Slot.OFFHAND,
+]
func _ready():
panel.visible = false
@@ -57,12 +61,11 @@ func update_stats(player):
hp_label.text = "HP: " + str(player.current_hp) + " / " + str(player.max_hp)
- # Waffen-Stats (jetzt in Equipment-Spalte)
+ # Waffen-Stats
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
@@ -86,19 +89,115 @@ func update_stats(player):
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])
+ # Equipment-Grid mit Icons aktualisieren
+ _rebuild_equipment_grid(player)
-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)
+func _rebuild_equipment_grid(player):
+ # Alte Slots entfernen
+ for child in equipment_grid.get_children():
+ child.queue_free()
+
+ # Slots als Icon-Panels erstellen
+ for slot_type in SLOT_ORDER:
+ var item = player.equipment[slot_type]
+ var slot_panel = _create_equip_slot(slot_type, item)
+ equipment_grid.add_child(slot_panel)
+
+func _create_equip_slot(slot_type: Equipment.Slot, item: Equipment) -> Panel:
+ var slot = Panel.new()
+ slot.custom_minimum_size = Vector2(EQUIP_SLOT_SIZE, EQUIP_SLOT_SIZE)
+
+ # Hintergrund
+ var style = StyleBoxFlat.new()
+ style.bg_color = Color(0.12, 0.12, 0.12)
+ style.border_color = Color(0.3, 0.3, 0.3)
+ style.set_border_width_all(1)
+ style.set_corner_radius_all(4)
+ slot.add_theme_stylebox_override("panel", style)
+
+ if item != null:
+ # Rahmen in Seltenheitsfarbe
+ style.border_color = Equipment.get_rarity_color(item.rarity)
+ style.set_border_width_all(2)
+
+ if item.icon:
+ # Icon anzeigen
+ var icon = TextureRect.new()
+ icon.texture = item.icon
+ icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
+ icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ icon.custom_minimum_size = Vector2(EQUIP_SLOT_SIZE - 6, EQUIP_SLOT_SIZE - 6)
+ icon.position = Vector2(3, 3)
+ slot.add_child(icon)
+ else:
+ # Fallback: Kurzname
+ var label = Label.new()
+ label.text = item.item_name.substr(0, 3)
+ 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)
+
+ # Tooltip mit Item-Eigenschaften
+ slot.tooltip_text = _get_item_tooltip(item)
else:
- label.text = slot_name + ": " + item.item_name
- label.modulate = Equipment.get_rarity_color(item.rarity)
+ # Leerer Slot: Slot-Name als Tooltip
+ slot.tooltip_text = Equipment.get_slot_name(slot_type) + ": Leer"
+
+ # Slot-Kürzel anzeigen
+ var label = Label.new()
+ label.text = _get_slot_short(slot_type)
+ label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+ label.add_theme_font_size_override("font_size", 9)
+ label.modulate = Color(0.4, 0.4, 0.4)
+ label.anchors_preset = Control.PRESET_FULL_RECT
+ slot.add_child(label)
+
+ return slot
+
+func _get_slot_short(slot_type: Equipment.Slot) -> String:
+ match slot_type:
+ Equipment.Slot.HEAD: return "Kopf"
+ Equipment.Slot.CHEST: return "Brust"
+ Equipment.Slot.HANDS: return "Hand"
+ Equipment.Slot.LEGS: return "Bein"
+ Equipment.Slot.FEET: return "Fuß"
+ Equipment.Slot.WEAPON: return "Waffe"
+ Equipment.Slot.OFFHAND: return "Neben"
+ return "?"
+
+func _get_item_tooltip(item: Equipment) -> String:
+ var tooltip = item.item_name + "\n"
+ tooltip += Equipment.get_slot_name(item.slot) + "\n"
+
+ # Seltenheit
+ match item.rarity:
+ Equipment.Rarity.COMMON: tooltip += "Gewöhnlich\n"
+ Equipment.Rarity.UNCOMMON: tooltip += "Ungewöhnlich\n"
+ Equipment.Rarity.RARE: tooltip += "Selten\n"
+ Equipment.Rarity.EPIC: tooltip += "Episch\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"
+ tooltip += "Reichweite: " + str(item.weapon_range) + "\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
diff --git a/character_panel.tscn b/character_panel.tscn
index 41f1646..ac42c7c 100644
--- a/character_panel.tscn
+++ b/character_panel.tscn
@@ -104,46 +104,15 @@ horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="Panel/HBoxContainer/EquipmentColumn"]
layout_mode = 2
-[node name="EquipmentContainer" type="VBoxContainer" parent="Panel/HBoxContainer/EquipmentColumn"]
+[node name="EquipmentGrid" type="GridContainer" 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: -"
+theme_override_constants/h_separation = 6
+theme_override_constants/v_separation = 6
+columns = 4
[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
diff --git a/classes/mage.tres b/classes/mage.tres
index 943b96d..160daa3 100644
--- a/classes/mage.tres
+++ b/classes/mage.tres
@@ -1,11 +1,13 @@
-[gd_resource type="Resource" script_class="CharacterClass" format=3]
+[gd_resource type="Resource" script_class="CharacterClass" format=3 uid="uid://24g7k0k7vbr6"]
-[ext_resource type="Script" path="res://character_class.gd" id="1"]
+[ext_resource type="Script" uid="uid://ci45xxb5vn857" path="res://character_class.gd" id="1"]
[resource]
script = ExtResource("1")
class_name_de = "Magier"
main_stat = 2
+resource_type = 1
+base_resource = 100
base_strength = 5
base_agility = 8
base_intelligence = 15
@@ -14,6 +16,3 @@ 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
diff --git a/classes/rogue.tres b/classes/rogue.tres
index ea63f98..2d01969 100644
--- a/classes/rogue.tres
+++ b/classes/rogue.tres
@@ -1,19 +1,18 @@
-[gd_resource type="Resource" script_class="CharacterClass" format=3]
+[gd_resource type="Resource" script_class="CharacterClass" format=3 uid="uid://cdcx81iw6hxic"]
-[ext_resource type="Script" path="res://character_class.gd" id="1"]
+[ext_resource type="Script" uid="uid://ci45xxb5vn857" path="res://character_class.gd" id="1"]
[resource]
script = ExtResource("1")
class_name_de = "Schurke"
main_stat = 1
+resource_type = 3
+base_resource = 100
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
diff --git a/classes/warrior.tres b/classes/warrior.tres
index 690eb51..246b0b2 100644
--- a/classes/warrior.tres
+++ b/classes/warrior.tres
@@ -1,11 +1,9 @@
-[gd_resource type="Resource" script_class="CharacterClass" format=3]
+[gd_resource type="Resource" script_class="CharacterClass" format=3 uid="uid://bebfnwygqy1gu"]
-[ext_resource type="Script" path="res://character_class.gd" id="1"]
+[ext_resource type="Script" uid="uid://ci45xxb5vn857" 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
diff --git a/consumable.gd b/consumable.gd
new file mode 100644
index 0000000..79b5579
--- /dev/null
+++ b/consumable.gd
@@ -0,0 +1,55 @@
+# Consumable.gd
+# Resource für verbrauchbare Items (Tränke, Essen, etc.)
+extends Resource
+class_name Consumable
+
+enum EffectType {
+ HEAL_HP, # HP heilen
+ RESTORE_MANA, # Mana wiederherstellen
+ HEAL_AND_MANA, # Beides
+}
+
+@export var item_name: String = "Trank"
+@export var effect_type: EffectType = EffectType.HEAL_HP
+@export var amount: int = 50 # Wie viel geheilt/wiederhergestellt wird
+@export var cooldown: float = 1.0 # Cooldown nach Benutzung
+@export var icon: Texture2D
+@export var stack_size: int = 1 # Aktuelle Anzahl im Stack
+@export var max_stack: int = 20 # Maximale Stack-Größe
+
+# Effekt-Beschreibung für Tooltip
+func get_effect_text() -> String:
+ match effect_type:
+ EffectType.HEAL_HP:
+ return "Stellt " + str(amount) + " HP wieder her"
+ EffectType.RESTORE_MANA:
+ return "Stellt " + str(amount) + " Mana wieder her"
+ EffectType.HEAL_AND_MANA:
+ return "Stellt " + str(amount) + " HP und Mana wieder her"
+ return ""
+
+# Effekt auf Spieler anwenden, gibt true zurück wenn verbraucht
+func use(player) -> bool:
+ match effect_type:
+ EffectType.HEAL_HP:
+ if player.current_hp >= player.max_hp:
+ print("HP bereits voll!")
+ return false
+ player.heal(amount)
+ print("+" + str(amount) + " HP")
+ EffectType.RESTORE_MANA:
+ if player.current_mana >= player.max_mana:
+ print("Mana bereits voll!")
+ return false
+ player.restore_mana(amount)
+ print("+" + str(amount) + " Mana")
+ EffectType.HEAL_AND_MANA:
+ if player.current_hp >= player.max_hp and player.current_mana >= player.max_mana:
+ print("HP und Mana bereits voll!")
+ return false
+ player.heal(amount)
+ player.restore_mana(amount)
+ print("+" + str(amount) + " HP und Mana")
+
+ stack_size -= 1
+ return true
diff --git a/consumable.gd.uid b/consumable.gd.uid
new file mode 100644
index 0000000..c31ee27
--- /dev/null
+++ b/consumable.gd.uid
@@ -0,0 +1 @@
+uid://b25cvd8swhsg3
diff --git a/consumables/small_hp_potion.tres b/consumables/small_hp_potion.tres
new file mode 100644
index 0000000..7708eb1
--- /dev/null
+++ b/consumables/small_hp_potion.tres
@@ -0,0 +1,14 @@
+[gd_resource type="Resource" script_class="Consumable" load_steps=3 format=3]
+
+[ext_resource type="Script" path="res://consumable.gd" id="1"]
+[ext_resource type="Texture2D" path="res://icons/hp_potion_icon.svg" id="2_icon"]
+
+[resource]
+script = ExtResource("1")
+item_name = "Kleiner Heiltrank"
+effect_type = 0
+amount = 50
+cooldown = 1.0
+icon = ExtResource("2_icon")
+stack_size = 5
+max_stack = 20
diff --git a/consumables/small_mana_potion.tres b/consumables/small_mana_potion.tres
new file mode 100644
index 0000000..d838cba
--- /dev/null
+++ b/consumables/small_mana_potion.tres
@@ -0,0 +1,14 @@
+[gd_resource type="Resource" script_class="Consumable" load_steps=3 format=3]
+
+[ext_resource type="Script" path="res://consumable.gd" id="1"]
+[ext_resource type="Texture2D" path="res://icons/mana_potion_icon.svg" id="2_icon"]
+
+[resource]
+script = ExtResource("1")
+item_name = "Kleiner Manatrank"
+effect_type = 1
+amount = 40
+cooldown = 1.0
+icon = ExtResource("2_icon")
+stack_size = 5
+max_stack = 20
diff --git a/equipment/iron_helm.tres b/equipment/iron_helm.tres
index f1c45d2..bd28010 100644
--- a/equipment/iron_helm.tres
+++ b/equipment/iron_helm.tres
@@ -1,18 +1,13 @@
-[gd_resource type="Resource" script_class="Equipment" load_steps=2 format=3]
+[gd_resource type="Resource" script_class="Equipment" format=3 uid="uid://d54jjc24pj3t"]
-[ext_resource type="Script" path="res://equipment.gd" id="1_equipment"]
+[ext_resource type="Script" uid="uid://re0xiie1udfq" path="res://equipment.gd" id="1_equipment"]
+[ext_resource type="Texture2D" uid="uid://c8x8d055wjcpu" path="res://icons/iron_helm_icon.svg" id="2_icon"]
[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
+icon = ExtResource("2_icon")
diff --git a/equipment/iron_sword.tres b/equipment/iron_sword.tres
index ee231ee..9557429 100644
--- a/equipment/iron_sword.tres
+++ b/equipment/iron_sword.tres
@@ -1,18 +1,12 @@
-[gd_resource type="Resource" script_class="Equipment" load_steps=2 format=3]
+[gd_resource type="Resource" script_class="Equipment" format=3 uid="uid://vprfv2phlcbc"]
-[ext_resource type="Script" path="res://equipment.gd" id="1_equipment"]
+[ext_resource type="Script" uid="uid://re0xiie1udfq" path="res://equipment.gd" id="1_equipment"]
+[ext_resource type="Texture2D" uid="uid://bi7sh66txlj1c" path="res://icons/iron_sword_icon.svg" id="2_icon"]
[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
+icon = ExtResource("2_icon")
diff --git a/equipment/leather_chest.tres b/equipment/leather_chest.tres
index ef2a30f..865a2e1 100644
--- a/equipment/leather_chest.tres
+++ b/equipment/leather_chest.tres
@@ -1,18 +1,13 @@
-[gd_resource type="Resource" script_class="Equipment" load_steps=2 format=3]
+[gd_resource type="Resource" script_class="Equipment" format=3 uid="uid://cnijhxsjmepss"]
-[ext_resource type="Script" path="res://equipment.gd" id="1_equipment"]
+[ext_resource type="Script" uid="uid://re0xiie1udfq" path="res://equipment.gd" id="1_equipment"]
+[ext_resource type="Texture2D" uid="uid://c6kmscwfv0ssa" path="res://icons/leather_chest_icon.svg" id="2_icon"]
[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
+icon = ExtResource("2_icon")
diff --git a/equipment/steel_sword.tres b/equipment/steel_sword.tres
index b1b01e2..a10502b 100644
--- a/equipment/steel_sword.tres
+++ b/equipment/steel_sword.tres
@@ -1,6 +1,7 @@
-[gd_resource type="Resource" script_class="Equipment" load_steps=2 format=3]
+[gd_resource type="Resource" script_class="Equipment" load_steps=3 format=3]
[ext_resource type="Script" path="res://equipment.gd" id="1_equipment"]
+[ext_resource type="Texture2D" path="res://icons/steel_sword_icon.svg" id="2_icon"]
[resource]
script = ExtResource("1_equipment")
@@ -16,3 +17,4 @@ min_damage = 5
max_damage = 9
attack_speed = 1.4
weapon_range = 3.0
+icon = ExtResource("2_icon")
diff --git a/equipment/wooden_shield.tres b/equipment/wooden_shield.tres
index 10e0186..8fcc184 100644
--- a/equipment/wooden_shield.tres
+++ b/equipment/wooden_shield.tres
@@ -1,6 +1,7 @@
-[gd_resource type="Resource" script_class="Equipment" load_steps=2 format=3]
+[gd_resource type="Resource" script_class="Equipment" load_steps=3 format=3]
[ext_resource type="Script" path="res://equipment.gd" id="1_equipment"]
+[ext_resource type="Texture2D" path="res://icons/wooden_shield_icon.svg" id="2_icon"]
[resource]
script = ExtResource("1_equipment")
@@ -16,3 +17,4 @@ min_damage = 0
max_damage = 0
attack_speed = 1.5
weapon_range = 3.0
+icon = ExtResource("2_icon")
diff --git a/hud.gd b/hud.gd
index 440e2bb..ee4aa1c 100644
--- a/hud.gd
+++ b/hud.gd
@@ -27,6 +27,11 @@ var active_slot = 0
var slot_icons = [] # TextureRect nodes für Icons
var slot_cooldown_overlays = [] # ColorRect für Cooldown-Anzeige
var slot_cooldown_labels = [] # Label für Cooldown-Text
+var slot_stack_labels = [] # Label für Stack-Anzahl
+
+# Ressourcen-Bar (Mana/Energie/Wut)
+var resource_bar: ProgressBar
+var resource_label: Label
func _ready():
_create_level_ui()
@@ -66,6 +71,19 @@ func _ready():
action_slots[i].add_child(cooldown_label)
slot_cooldown_labels.append(cooldown_label)
+ # Stack-Count Label (unten rechts)
+ var stack_label = Label.new()
+ stack_label.name = "StackLabel"
+ stack_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
+ stack_label.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
+ stack_label.size = Vector2(50, 50)
+ stack_label.position = Vector2(-4, -2)
+ stack_label.add_theme_font_size_override("font_size", 11)
+ stack_label.visible = false
+ stack_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
+ action_slots[i].add_child(stack_label)
+ slot_stack_labels.append(stack_label)
+
# Button für Klicks erstellen
var button = Button.new()
button.name = "SlotButton"
@@ -131,10 +149,36 @@ func _create_level_ui():
control.add_child(xp_bar)
+ # Ressourcen-Bar (Mana/Energie/Wut) - unter HP-Bar
+ resource_bar = ProgressBar.new()
+ resource_bar.name = "ResourceBar"
+ resource_bar.position = Vector2(20, 50)
+ resource_bar.size = Vector2(200, 20)
+ resource_bar.show_percentage = false
+ resource_bar.value = 0
+ resource_bar.visible = false # Nur sichtbar wenn Klasse Ressource hat
+
+ var resource_style = StyleBoxFlat.new()
+ resource_style.bg_color = Color(0.2, 0.3, 0.9, 1.0) # Blau für Mana (Standard)
+ resource_bar.add_theme_stylebox_override("fill", resource_style)
+
+ resource_label = Label.new()
+ resource_label.name = "ResourceLabel"
+ resource_label.size = Vector2(200, 20)
+ resource_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ resource_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+
+ resource_bar.add_child(resource_label)
+ control.add_child(resource_bar)
+
+ # Level/XP etwas runter verschieben wegen Ressourcen-Bar
+ level_label.position = Vector2(20, 75)
+ xp_bar.position = Vector2(80, 75)
+
# Gold Label
gold_label = Label.new()
gold_label.name = "GoldLabel"
- gold_label.position = Vector2(20, 78)
+ gold_label.position = Vector2(20, 98)
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"
@@ -145,6 +189,49 @@ func update_gold(amount: int):
if gold_label:
gold_label.text = str(amount) + " Gold"
+# Ressourcen-Leiste aktualisieren (Mana/Energie/Wut)
+func update_resource(current: int, maximum: int, resource_name: String):
+ if resource_bar == null:
+ return
+ if maximum <= 0:
+ resource_bar.visible = false
+ return
+ resource_bar.visible = true
+ resource_bar.max_value = maximum
+ resource_bar.value = current
+ resource_label.text = str(current) + " / " + str(maximum)
+
+ # Farbe je nach Ressourcen-Typ
+ var style = resource_bar.get_theme_stylebox("fill") as StyleBoxFlat
+ if style:
+ match resource_name:
+ "Mana":
+ style.bg_color = Color(0.2, 0.3, 0.9, 1.0) # Blau
+ "Energie":
+ style.bg_color = Color(0.9, 0.8, 0.1, 1.0) # Gelb
+ "Wut":
+ style.bg_color = Color(0.8, 0.15, 0.1, 1.0) # Rot
+
+# Icon-Textur direkt setzen (für Consumables)
+func set_slot_icon_texture(slot_index: int, texture: Texture2D):
+ if slot_index >= 0 and slot_index < 9:
+ slot_icons[slot_index].texture = texture
+
+# Slot-Icon entfernen
+func clear_slot_icon(slot_index: int):
+ if slot_index >= 0 and slot_index < 9:
+ slot_icons[slot_index].texture = null
+
+# Stack-Anzahl auf Slot anzeigen
+func set_slot_stack_count(slot_index: int, count: int):
+ if slot_index < 0 or slot_index >= 9:
+ return
+ if count > 1:
+ slot_stack_labels[slot_index].text = str(count)
+ slot_stack_labels[slot_index].visible = true
+ else:
+ slot_stack_labels[slot_index].visible = false
+
# HP-Leiste und Text aktualisieren
func update_health(current_hp, max_hp):
health_bar.max_value = max_hp
diff --git a/icons/hp_potion_icon.svg b/icons/hp_potion_icon.svg
new file mode 100644
index 0000000..0103703
--- /dev/null
+++ b/icons/hp_potion_icon.svg
@@ -0,0 +1,13 @@
+
diff --git a/icons/hp_potion_icon.svg.import b/icons/hp_potion_icon.svg.import
new file mode 100644
index 0000000..921a909
--- /dev/null
+++ b/icons/hp_potion_icon.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cih3xmw6q88um"
+path="res://.godot/imported/hp_potion_icon.svg-fba9724ef0f842c8b57011d6e4f03fb3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icons/hp_potion_icon.svg"
+dest_files=["res://.godot/imported/hp_potion_icon.svg-fba9724ef0f842c8b57011d6e4f03fb3.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
diff --git a/icons/iron_helm_icon.svg b/icons/iron_helm_icon.svg
new file mode 100644
index 0000000..9ba19b8
--- /dev/null
+++ b/icons/iron_helm_icon.svg
@@ -0,0 +1,13 @@
+
diff --git a/icons/iron_helm_icon.svg.import b/icons/iron_helm_icon.svg.import
new file mode 100644
index 0000000..7a9090f
--- /dev/null
+++ b/icons/iron_helm_icon.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c8x8d055wjcpu"
+path="res://.godot/imported/iron_helm_icon.svg-f08644c5491a1e9fd669ad08b500cdc1.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icons/iron_helm_icon.svg"
+dest_files=["res://.godot/imported/iron_helm_icon.svg-f08644c5491a1e9fd669ad08b500cdc1.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
diff --git a/icons/iron_sword_icon.svg b/icons/iron_sword_icon.svg
new file mode 100644
index 0000000..d590fae
--- /dev/null
+++ b/icons/iron_sword_icon.svg
@@ -0,0 +1,11 @@
+
diff --git a/icons/iron_sword_icon.svg.import b/icons/iron_sword_icon.svg.import
new file mode 100644
index 0000000..ab44f1d
--- /dev/null
+++ b/icons/iron_sword_icon.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bi7sh66txlj1c"
+path="res://.godot/imported/iron_sword_icon.svg-7666556b18881bfee488b89a46108232.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icons/iron_sword_icon.svg"
+dest_files=["res://.godot/imported/iron_sword_icon.svg-7666556b18881bfee488b89a46108232.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
diff --git a/icons/leather_chest_icon.svg b/icons/leather_chest_icon.svg
new file mode 100644
index 0000000..b317dba
--- /dev/null
+++ b/icons/leather_chest_icon.svg
@@ -0,0 +1,14 @@
+
diff --git a/icons/leather_chest_icon.svg.import b/icons/leather_chest_icon.svg.import
new file mode 100644
index 0000000..f4e624d
--- /dev/null
+++ b/icons/leather_chest_icon.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c6kmscwfv0ssa"
+path="res://.godot/imported/leather_chest_icon.svg-c139cc7810b1521bcabf2843303318b5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icons/leather_chest_icon.svg"
+dest_files=["res://.godot/imported/leather_chest_icon.svg-c139cc7810b1521bcabf2843303318b5.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
diff --git a/icons/mana_potion_icon.svg b/icons/mana_potion_icon.svg
new file mode 100644
index 0000000..8b3e036
--- /dev/null
+++ b/icons/mana_potion_icon.svg
@@ -0,0 +1,12 @@
+
diff --git a/icons/mana_potion_icon.svg.import b/icons/mana_potion_icon.svg.import
new file mode 100644
index 0000000..1d9cb42
--- /dev/null
+++ b/icons/mana_potion_icon.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dc6ai8ec7g20"
+path="res://.godot/imported/mana_potion_icon.svg-a748c0946003b46a082e0546c17e9f84.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icons/mana_potion_icon.svg"
+dest_files=["res://.godot/imported/mana_potion_icon.svg-a748c0946003b46a082e0546c17e9f84.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
diff --git a/icons/steel_sword_icon.svg b/icons/steel_sword_icon.svg
new file mode 100644
index 0000000..56749aa
--- /dev/null
+++ b/icons/steel_sword_icon.svg
@@ -0,0 +1,11 @@
+
diff --git a/icons/steel_sword_icon.svg.import b/icons/steel_sword_icon.svg.import
new file mode 100644
index 0000000..68eeaa2
--- /dev/null
+++ b/icons/steel_sword_icon.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bsj8lgen1giiw"
+path="res://.godot/imported/steel_sword_icon.svg-e1e6ede998445ecfd316dac81b83a7c5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icons/steel_sword_icon.svg"
+dest_files=["res://.godot/imported/steel_sword_icon.svg-e1e6ede998445ecfd316dac81b83a7c5.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
diff --git a/icons/wooden_shield_icon.svg b/icons/wooden_shield_icon.svg
new file mode 100644
index 0000000..ca48b76
--- /dev/null
+++ b/icons/wooden_shield_icon.svg
@@ -0,0 +1,17 @@
+
diff --git a/icons/wooden_shield_icon.svg.import b/icons/wooden_shield_icon.svg.import
new file mode 100644
index 0000000..f37f763
--- /dev/null
+++ b/icons/wooden_shield_icon.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dovodjtj2gb2e"
+path="res://.godot/imported/wooden_shield_icon.svg-9243b141ac1930d785b6e64e9bdd401e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icons/wooden_shield_icon.svg"
+dest_files=["res://.godot/imported/wooden_shield_icon.svg-9243b141ac1930d785b6e64e9bdd401e.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
diff --git a/inventory.gd b/inventory.gd
index 17a292e..8e35dc2 100644
--- a/inventory.gd
+++ b/inventory.gd
@@ -1,5 +1,5 @@
# Inventory.gd
-# Verwaltet das Spieler-Inventar mit Items und Gold
+# Verwaltet das Spieler-Inventar mit Items (Equipment + Consumables) und Gold
extends Resource
class_name Inventory
@@ -8,7 +8,7 @@ signal gold_changed(new_amount: int)
const MAX_SLOTS = 20 # Maximale Inventarplätze
-var items: Array[Equipment] = []
+var items: Array = [] # Equipment oder Consumable
var gold: int = 0
func _init():
@@ -16,17 +16,35 @@ func _init():
gold = 0
# Item hinzufügen - gibt true zurück wenn erfolgreich
-func add_item(item: Equipment) -> bool:
+# Consumables werden gestackt wenn möglich
+func add_item(item) -> bool:
+ # Consumable stacking
+ if item is Consumable:
+ for existing in items:
+ if existing is Consumable and existing.item_name == item.item_name:
+ var space = existing.max_stack - existing.stack_size
+ if space > 0:
+ var transfer = mini(item.stack_size, space)
+ existing.stack_size += transfer
+ item.stack_size -= transfer
+ if item.stack_size <= 0:
+ inventory_changed.emit()
+ print("Item gestackt: ", existing.item_name, " x", existing.stack_size)
+ return true
+
if items.size() >= MAX_SLOTS:
print("Inventar voll!")
return false
items.append(item)
inventory_changed.emit()
- print("Item erhalten: ", item.item_name)
+ if item is Consumable:
+ print("Item erhalten: ", item.item_name, " x", item.stack_size)
+ else:
+ print("Item erhalten: ", item.item_name)
return true
# Item an Index entfernen
-func remove_item_at(index: int) -> Equipment:
+func remove_item_at(index: int):
if index < 0 or index >= items.size():
return null
var item = items[index]
@@ -35,7 +53,7 @@ func remove_item_at(index: int) -> Equipment:
return item
# Item direkt entfernen
-func remove_item(item: Equipment) -> bool:
+func remove_item(item) -> bool:
var index = items.find(item)
if index == -1:
return false
@@ -67,7 +85,7 @@ func free_slots() -> int:
return MAX_SLOTS - items.size()
# Item an Index holen
-func get_item(index: int) -> Equipment:
+func get_item(index: int):
if index < 0 or index >= items.size():
return null
return items[index]
diff --git a/inventory_panel.gd b/inventory_panel.gd
index 8285374..480531f 100644
--- a/inventory_panel.gd
+++ b/inventory_panel.gd
@@ -1,8 +1,8 @@
# InventoryPanel.gd
-# UI für das Spieler-Inventar
+# UI für das Spieler-Inventar (Equipment + Consumables)
extends CanvasLayer
-signal item_selected(item: Equipment, index: int)
+signal item_selected(item, index: int)
var panel_visible = false
var player = null
@@ -30,7 +30,8 @@ func toggle():
_refresh_inventory()
func _on_inventory_changed():
- _refresh_inventory()
+ if panel_visible:
+ _refresh_inventory()
func _on_gold_changed(new_amount: int):
gold_label.text = str(new_amount) + " Gold"
@@ -66,18 +67,48 @@ func _create_slot(index: int) -> Panel:
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)
+ var item_icon = null
+ if item is Consumable:
+ item_icon = item.icon
+ elif item is Equipment:
+ item_icon = item.icon
- # Rahmen in Seltenheitsfarbe
- style.border_color = Equipment.get_rarity_color(item.rarity)
+ if item_icon:
+ var icon = TextureRect.new()
+ icon.texture = item_icon
+ icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
+ icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ icon.custom_minimum_size = Vector2(SLOT_SIZE - 4, SLOT_SIZE - 4)
+ icon.position = Vector2(2, 2)
+ slot.add_child(icon)
+ else:
+ # Fallback: Text
+ var label = Label.new()
+ label.text = item.item_name.substr(0, 3)
+ label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+ label.add_theme_font_size_override("font_size", 10)
+ if item is Equipment:
+ label.modulate = Equipment.get_rarity_color(item.rarity)
+ label.anchors_preset = Control.PRESET_FULL_RECT
+ slot.add_child(label)
+
+ # Stack-Count für Consumables
+ if item is Consumable and item.stack_size > 1:
+ var stack_label = Label.new()
+ stack_label.text = str(item.stack_size)
+ stack_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
+ stack_label.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
+ stack_label.add_theme_font_size_override("font_size", 11)
+ stack_label.size = Vector2(SLOT_SIZE - 4, SLOT_SIZE - 4)
+ stack_label.position = Vector2(0, 0)
+ slot.add_child(stack_label)
+
+ # Rahmen
+ if item is Equipment:
+ style.border_color = Equipment.get_rarity_color(item.rarity)
+ elif item is Consumable:
+ style.border_color = Color(0.3, 0.7, 0.3) # Grüner Rand für Consumables
style.set_border_width_all(2)
# Klick-Handler
@@ -88,21 +119,60 @@ func _create_slot(index: int) -> Panel:
return slot
-func _on_slot_clicked(event: InputEvent, index: int, item: Equipment):
+func _on_slot_clicked(event: InputEvent, index: int, item):
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()
+ if item is Equipment:
+ if event.button_index == MOUSE_BUTTON_RIGHT:
+ # Rechtsklick auf Equipment: 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()
+ elif item is Consumable:
+ if event.button_index == MOUSE_BUTTON_RIGHT:
+ # Rechtsklick auf Consumable: Direkt benutzen
+ if player:
+ if player.use_consumable(item):
+ if item.stack_size <= 0:
+ player.inventory.remove_item(item)
+ _refresh_inventory()
+ player._update_action_bar_stacks()
+ elif event.button_index == MOUSE_BUTTON_LEFT and event.shift_pressed:
+ # Shift+Linksklick: Auf nächsten freien Aktionsleisten-Slot legen
+ if player:
+ _assign_consumable_to_bar(item)
-func _get_item_tooltip(item: Equipment) -> String:
+func _assign_consumable_to_bar(item: Consumable):
+ # Nächsten freien Slot ab 2 finden
+ for i in range(2, 9):
+ if player.action_bar_items[i] == null:
+ player.assign_to_action_bar(i, item)
+ print(item.item_name + " auf Slot " + str(i + 1) + " gelegt")
+ return
+ # Kein freier Slot - auf Slot 2 legen
+ player.assign_to_action_bar(2, item)
+ print(item.item_name + " auf Slot 3 gelegt (überschrieben)")
+
+func _get_item_tooltip(item) -> String:
+ if item is Consumable:
+ return _get_consumable_tooltip(item)
+ elif item is Equipment:
+ return _get_equipment_tooltip(item)
+ return item.item_name
+
+func _get_consumable_tooltip(item: Consumable) -> String:
+ var tooltip = item.item_name + "\n"
+ tooltip += "Verbrauchbar\n"
+ tooltip += "---\n"
+ tooltip += item.get_effect_text() + "\n"
+ tooltip += "Cooldown: " + str(item.cooldown) + "s\n"
+ tooltip += "Anzahl: " + str(item.stack_size) + "/" + str(item.max_stack) + "\n"
+ tooltip += "\n[Rechtsklick: Benutzen]\n[Shift+Linksklick: Auf Leiste legen]"
+ return tooltip
+
+func _get_equipment_tooltip(item: Equipment) -> String:
var tooltip = item.item_name + "\n"
tooltip += Equipment.get_slot_name(item.slot) + "\n"
tooltip += "---\n"
diff --git a/loot_entry.gd b/loot_entry.gd
index c84dbaa..875addb 100644
--- a/loot_entry.gd
+++ b/loot_entry.gd
@@ -3,5 +3,5 @@
extends Resource
class_name LootEntry
-@export var item: Equipment
+@export var item: Resource # Equipment oder Consumable
@export_range(0.0, 1.0) var drop_chance: float = 0.1 # 10% Standard
diff --git a/loot_table.gd b/loot_table.gd
index ae688e8..44e6e7e 100644
--- a/loot_table.gd
+++ b/loot_table.gd
@@ -19,6 +19,12 @@ func generate_loot() -> Dictionary:
for entry in possible_drops:
if randf() <= entry.drop_chance:
- result["items"].append(entry.item)
+ # Consumables kopieren damit Stacks unabhängig bleiben
+ if entry.item is Consumable:
+ var copy = entry.item.duplicate()
+ copy.stack_size = 1
+ result["items"].append(copy)
+ else:
+ result["items"].append(entry.item)
return result
diff --git a/loot_tables/goblin_loot.tres b/loot_tables/goblin_loot.tres
index 6f33f9c..8278921 100644
--- a/loot_tables/goblin_loot.tres
+++ b/loot_tables/goblin_loot.tres
@@ -1,10 +1,12 @@
-[gd_resource type="Resource" script_class="LootTable" format=3]
+[gd_resource type="Resource" script_class="LootTable" format=3 uid="uid://83hg0vwdovn8"]
-[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"]
+[ext_resource type="Script" uid="uid://dej1tpamsi71r" path="res://loot_table.gd" id="1"]
+[ext_resource type="Script" uid="uid://cx2w8nkuoylv5" path="res://loot_entry.gd" id="2"]
+[ext_resource type="Resource" uid="uid://vprfv2phlcbc" path="res://equipment/iron_sword.tres" id="3"]
+[ext_resource type="Resource" uid="uid://cnijhxsjmepss" path="res://equipment/leather_chest.tres" id="4"]
+[ext_resource type="Resource" uid="uid://d54jjc24pj3t" path="res://equipment/iron_helm.tres" id="5"]
+[ext_resource type="Resource" path="res://consumables/small_hp_potion.tres" id="6"]
+[ext_resource type="Resource" path="res://consumables/small_mana_potion.tres" id="7"]
[sub_resource type="Resource" id="entry_1"]
script = ExtResource("2")
@@ -14,15 +16,23 @@ 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
+
+[sub_resource type="Resource" id="entry_4"]
+script = ExtResource("2")
+item = ExtResource("6")
+drop_chance = 0.4
+
+[sub_resource type="Resource" id="entry_5"]
+script = ExtResource("2")
+item = ExtResource("7")
+drop_chance = 0.25
[resource]
script = ExtResource("1")
min_gold = 2
max_gold = 8
-possible_drops = [SubResource("entry_1"), SubResource("entry_2"), SubResource("entry_3")]
+possible_drops = Array[ExtResource("2")]([SubResource("entry_1"), SubResource("entry_2"), SubResource("entry_3"), SubResource("entry_4"), SubResource("entry_5")])
diff --git a/loot_window.gd b/loot_window.gd
index 494bb49..9497200 100644
--- a/loot_window.gd
+++ b/loot_window.gd
@@ -49,12 +49,32 @@ func _refresh_display():
var items = current_loot.get("items", [])
for i in range(items.size()):
var item = items[i]
+ var hbox = HBoxContainer.new()
+ hbox.theme_override_constants = {}
+
+ # Icon wenn vorhanden
+ if item.icon:
+ var icon = TextureRect.new()
+ icon.texture = item.icon
+ icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
+ icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ icon.custom_minimum_size = Vector2(24, 24)
+ hbox.add_child(icon)
+
var button = Button.new()
- button.text = item.item_name + " (" + Equipment.get_slot_name(item.slot) + ")"
- button.modulate = Equipment.get_rarity_color(item.rarity)
+ if item is Equipment:
+ button.text = item.item_name + " (" + Equipment.get_slot_name(item.slot) + ")"
+ button.modulate = Equipment.get_rarity_color(item.rarity)
+ elif item is Consumable:
+ var count_text = " x" + str(item.stack_size) if item.stack_size > 1 else ""
+ button.text = item.item_name + count_text
+ button.modulate = Color(0.3, 0.9, 0.3)
+ button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button.pressed.connect(_on_loot_item.bind(i))
button.tooltip_text = _get_item_tooltip(item)
- item_list.add_child(button)
+ hbox.add_child(button)
+
+ item_list.add_child(hbox)
# Wenn kein Loot mehr da, Fenster schließen
if current_loot.get("gold", 0) <= 0 and items.size() == 0:
diff --git a/loot_window.gd.uid b/loot_window.gd.uid
new file mode 100644
index 0000000..478b980
--- /dev/null
+++ b/loot_window.gd.uid
@@ -0,0 +1 @@
+uid://y6v242l8n1ti
diff --git a/main_menu.gd b/main_menu.gd
new file mode 100644
index 0000000..4cf56b6
--- /dev/null
+++ b/main_menu.gd
@@ -0,0 +1,155 @@
+# MainMenu.gd
+# Hauptmenü mit Spielstart und Einstellungen
+extends CanvasLayer
+
+signal start_game
+
+@onready var main_panel = $MainPanel
+@onready var settings_panel = $SettingsPanel
+@onready var play_btn = $MainPanel/VBoxContainer/PlayBtn
+@onready var settings_btn = $MainPanel/VBoxContainer/SettingsBtn
+@onready var quit_btn = $MainPanel/VBoxContainer/QuitBtn
+
+# Settings Controls
+@onready var resolution_option = $SettingsPanel/VBoxContainer/ResolutionRow/ResolutionOption
+@onready var window_mode_option = $SettingsPanel/VBoxContainer/WindowModeRow/WindowModeOption
+@onready var vsync_check = $SettingsPanel/VBoxContainer/VsyncRow/VsyncCheck
+@onready var msaa_option = $SettingsPanel/VBoxContainer/MSAARow/MSAAOption
+@onready var back_btn = $SettingsPanel/VBoxContainer/BackBtn
+
+# Auflösungen
+const RESOLUTIONS = [
+ Vector2i(1280, 720),
+ Vector2i(1366, 768),
+ Vector2i(1600, 900),
+ Vector2i(1920, 1080),
+ Vector2i(2560, 1440),
+ Vector2i(3840, 2160),
+]
+
+func _ready():
+ get_tree().paused = true
+ process_mode = Node.PROCESS_MODE_ALWAYS
+
+ main_panel.visible = true
+ settings_panel.visible = false
+
+ play_btn.pressed.connect(_on_play)
+ settings_btn.pressed.connect(_on_settings)
+ quit_btn.pressed.connect(_on_quit)
+ back_btn.pressed.connect(_on_back)
+
+ _setup_resolution_options()
+ _setup_window_mode_options()
+ _setup_vsync()
+ _setup_msaa()
+
+func _setup_resolution_options():
+ resolution_option.clear()
+ var current_size = DisplayServer.window_get_size()
+ var selected = 0
+ for i in range(RESOLUTIONS.size()):
+ var res = RESOLUTIONS[i]
+ resolution_option.add_item(str(res.x) + " x " + str(res.y), i)
+ if res == current_size:
+ selected = i
+ resolution_option.selected = selected
+ resolution_option.item_selected.connect(_on_resolution_changed)
+
+func _setup_window_mode_options():
+ window_mode_option.clear()
+ window_mode_option.add_item("Fenster", 0)
+ window_mode_option.add_item("Randloses Fenster", 1)
+ window_mode_option.add_item("Vollbild", 2)
+
+ var current_mode = DisplayServer.window_get_mode()
+ match current_mode:
+ DisplayServer.WINDOW_MODE_WINDOWED:
+ window_mode_option.selected = 0
+ DisplayServer.WINDOW_MODE_FULLSCREEN:
+ window_mode_option.selected = 2
+ _:
+ window_mode_option.selected = 0
+
+ window_mode_option.item_selected.connect(_on_window_mode_changed)
+
+func _setup_vsync():
+ var vsync_mode = DisplayServer.window_get_vsync_mode()
+ vsync_check.button_pressed = (vsync_mode != DisplayServer.VSYNC_DISABLED)
+ vsync_check.toggled.connect(_on_vsync_toggled)
+
+func _setup_msaa():
+ msaa_option.clear()
+ msaa_option.add_item("Aus", 0)
+ msaa_option.add_item("2x", 1)
+ msaa_option.add_item("4x", 2)
+ msaa_option.add_item("8x", 3)
+
+ var current_msaa = get_viewport().msaa_3d
+ match current_msaa:
+ Viewport.MSAA_DISABLED:
+ msaa_option.selected = 0
+ Viewport.MSAA_2X:
+ msaa_option.selected = 1
+ Viewport.MSAA_4X:
+ msaa_option.selected = 2
+ Viewport.MSAA_8X:
+ msaa_option.selected = 3
+
+ msaa_option.item_selected.connect(_on_msaa_changed)
+
+func _on_resolution_changed(index: int):
+ var res = RESOLUTIONS[index]
+ DisplayServer.window_set_size(res)
+ # Fenster zentrieren
+ var screen_size = DisplayServer.screen_get_size()
+ var window_pos = (screen_size - res) / 2
+ DisplayServer.window_set_position(window_pos)
+
+func _on_window_mode_changed(index: int):
+ match index:
+ 0: # Fenster
+ DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
+ DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, false)
+ 1: # Randloses Fenster
+ DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
+ DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true)
+ var screen_size = DisplayServer.screen_get_size()
+ DisplayServer.window_set_size(screen_size)
+ DisplayServer.window_set_position(Vector2i.ZERO)
+ 2: # Vollbild
+ DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, false)
+ DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
+
+func _on_vsync_toggled(enabled: bool):
+ if enabled:
+ DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
+ else:
+ DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
+
+func _on_msaa_changed(index: int):
+ match index:
+ 0:
+ get_viewport().msaa_3d = Viewport.MSAA_DISABLED
+ 1:
+ get_viewport().msaa_3d = Viewport.MSAA_2X
+ 2:
+ get_viewport().msaa_3d = Viewport.MSAA_4X
+ 3:
+ get_viewport().msaa_3d = Viewport.MSAA_8X
+
+func _on_play():
+ start_game.emit()
+ get_tree().paused = false
+ queue_free()
+
+func _on_settings():
+ main_panel.visible = false
+ settings_panel.visible = true
+
+func _on_back():
+ settings_panel.visible = false
+ main_panel.visible = true
+
+func _on_quit():
+ get_tree().quit()
diff --git a/main_menu.gd.uid b/main_menu.gd.uid
new file mode 100644
index 0000000..6c0ecdd
--- /dev/null
+++ b/main_menu.gd.uid
@@ -0,0 +1 @@
+uid://bw65mud8idif0
diff --git a/main_menu.tscn b/main_menu.tscn
new file mode 100644
index 0000000..f9048c2
--- /dev/null
+++ b/main_menu.tscn
@@ -0,0 +1,161 @@
+[gd_scene format=3 uid="uid://main_menu"]
+
+[ext_resource type="Script" path="res://main_menu.gd" id="1_menu"]
+
+[node name="MainMenu" type="CanvasLayer"]
+script = ExtResource("1_menu")
+
+[node name="MainPanel" 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="MainPanel"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 30.0
+offset_top = 30.0
+offset_right = -30.0
+offset_bottom = -30.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/separation = 20
+
+[node name="Title" type="Label" parent="MainPanel/VBoxContainer"]
+layout_mode = 2
+theme_override_font_sizes/font_size = 32
+text = "DungeonCrawler"
+horizontal_alignment = 1
+
+[node name="Spacer" type="Control" parent="MainPanel/VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="PlayBtn" type="Button" parent="MainPanel/VBoxContainer"]
+custom_minimum_size = Vector2(0, 50)
+layout_mode = 2
+theme_override_font_sizes/font_size = 20
+text = "Spielen"
+
+[node name="SettingsBtn" type="Button" parent="MainPanel/VBoxContainer"]
+custom_minimum_size = Vector2(0, 50)
+layout_mode = 2
+theme_override_font_sizes/font_size = 20
+text = "Einstellungen"
+
+[node name="QuitBtn" type="Button" parent="MainPanel/VBoxContainer"]
+custom_minimum_size = Vector2(0, 50)
+layout_mode = 2
+theme_override_font_sizes/font_size = 20
+text = "Beenden"
+
+[node name="SettingsPanel" type="Panel" parent="."]
+visible = false
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -250.0
+offset_top = -220.0
+offset_right = 250.0
+offset_bottom = 220.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="SettingsPanel"]
+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="SettingsPanel/VBoxContainer"]
+layout_mode = 2
+theme_override_font_sizes/font_size = 24
+text = "Einstellungen"
+horizontal_alignment = 1
+
+[node name="HSeparator" type="HSeparator" parent="SettingsPanel/VBoxContainer"]
+layout_mode = 2
+
+[node name="ResolutionRow" type="HBoxContainer" parent="SettingsPanel/VBoxContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="SettingsPanel/VBoxContainer/ResolutionRow"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_font_sizes/font_size = 16
+text = "Auflösung"
+
+[node name="ResolutionOption" type="OptionButton" parent="SettingsPanel/VBoxContainer/ResolutionRow"]
+custom_minimum_size = Vector2(200, 0)
+layout_mode = 2
+theme_override_font_sizes/font_size = 14
+
+[node name="WindowModeRow" type="HBoxContainer" parent="SettingsPanel/VBoxContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="SettingsPanel/VBoxContainer/WindowModeRow"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_font_sizes/font_size = 16
+text = "Fenstermodus"
+
+[node name="WindowModeOption" type="OptionButton" parent="SettingsPanel/VBoxContainer/WindowModeRow"]
+custom_minimum_size = Vector2(200, 0)
+layout_mode = 2
+theme_override_font_sizes/font_size = 14
+
+[node name="VsyncRow" type="HBoxContainer" parent="SettingsPanel/VBoxContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="SettingsPanel/VBoxContainer/VsyncRow"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_font_sizes/font_size = 16
+text = "VSync"
+
+[node name="VsyncCheck" type="CheckBox" parent="SettingsPanel/VBoxContainer/VsyncRow"]
+layout_mode = 2
+text = "Aktiviert"
+
+[node name="MSAARow" type="HBoxContainer" parent="SettingsPanel/VBoxContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="SettingsPanel/VBoxContainer/MSAARow"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_font_sizes/font_size = 16
+text = "Anti-Aliasing"
+
+[node name="MSAAOption" type="OptionButton" parent="SettingsPanel/VBoxContainer/MSAARow"]
+custom_minimum_size = Vector2(200, 0)
+layout_mode = 2
+theme_override_font_sizes/font_size = 14
+
+[node name="Spacer" type="Control" parent="SettingsPanel/VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="BackBtn" type="Button" parent="SettingsPanel/VBoxContainer"]
+custom_minimum_size = Vector2(0, 45)
+layout_mode = 2
+theme_override_font_sizes/font_size = 18
+text = "Zurück"
diff --git a/player.gd b/player.gd
index e87a7ca..143296e 100644
--- a/player.gd
+++ b/player.gd
@@ -25,8 +25,16 @@ const MAX_LEVEL_DIFF_MOD = 0.5 # Maximal 50% Modifikation
var max_hp = 100
var current_hp = 100
+var max_resource = 0 # Klassen-Ressource (Mana/Energie/Wut), 0 = keine
+var current_resource = 0
var target = null # Aktuell markierter Gegner
+# Aktionsleiste: Items/Consumables zugewiesen zu Slots (0-8)
+# Slot 0 = Autoattack, Slot 1 = Heavy Strike, Rest frei für Items
+var action_bar_items: Array = [null, null, null, null, null, null, null, null, null]
+var potion_cooldown: float = 0.0
+const POTION_COOLDOWN_TIME = 1.0
+
# Equipment System
var equipment: Dictionary = {
Equipment.Slot.HEAD: null,
@@ -67,8 +75,10 @@ func _ready():
# Stats aus Klasse berechnen
_calculate_stats()
current_hp = max_hp
+ current_resource = max_resource
hud.update_health(current_hp, max_hp)
+ hud.update_resource(current_resource, max_resource, get_resource_name())
hud.update_level(level, current_xp, xp_to_next_level)
hud.set_active_slot(0)
# Icons für Skills setzen
@@ -96,6 +106,7 @@ func _calculate_stats():
intelligence = 10
stamina = 10
max_hp = 100
+ max_resource = 0
return
# Stats = Basis + (Level-1) * Zuwachs pro Level
@@ -107,11 +118,36 @@ func _calculate_stats():
# HP aus Stamina berechnen
max_hp = stamina * CharacterClass.HP_PER_STAMINA
+ # Klassen-Ressource berechnen
+ _calculate_resource()
# Equipment-Boni hinzufügen
_apply_equipment_stats()
- print("Stats berechnet - STR: ", strength, " AGI: ", agility, " INT: ", intelligence, " STA: ", stamina, " ARM: ", armor, " HP: ", max_hp)
+ print("Stats berechnet - STR: ", strength, " AGI: ", agility, " INT: ", intelligence, " STA: ", stamina, " ARM: ", armor, " HP: ", max_hp, " RES: ", max_resource)
+
+# Klassen-Ressource berechnen (Mana aus INT, Energie fix, Wut fix)
+func _calculate_resource():
+ if character_class == null or character_class.resource_type == CharacterClass.ResourceType.NONE:
+ max_resource = 0
+ return
+ match character_class.resource_type:
+ CharacterClass.ResourceType.MANA:
+ max_resource = character_class.base_resource + intelligence * CharacterClass.MANA_PER_INT
+ CharacterClass.ResourceType.ENERGY:
+ max_resource = character_class.base_resource # Fix, skaliert nicht
+ CharacterClass.ResourceType.RAGE:
+ max_resource = character_class.base_resource # Fix, skaliert nicht
+
+# Name der Klassen-Ressource
+func get_resource_name() -> String:
+ if character_class == null:
+ return ""
+ match character_class.resource_type:
+ CharacterClass.ResourceType.MANA: return "Mana"
+ CharacterClass.ResourceType.ENERGY: return "Energie"
+ CharacterClass.ResourceType.RAGE: return "Wut"
+ return ""
# Equipment-Stats auf Charakter anwenden
func _apply_equipment_stats():
@@ -137,8 +173,9 @@ func _apply_equipment_stats():
intelligence += bonus_int
stamina += bonus_sta
- # HP neu berechnen mit Equipment-Stamina
+ # HP und Ressource neu berechnen mit Equipment-Boni
max_hp = stamina * CharacterClass.HP_PER_STAMINA
+ _calculate_resource()
# Equipment anlegen
func equip_item(item: Equipment) -> Equipment:
@@ -202,13 +239,15 @@ func _level_up():
# Stats neu berechnen
_calculate_stats()
- # HP vollständig auffüllen bei Level-Up
+ # HP und Ressource vollständig auffüllen bei Level-Up
current_hp = max_hp
+ current_resource = max_resource
hud.update_health(current_hp, max_hp)
+ hud.update_resource(current_resource, max_resource, get_resource_name())
# Character Panel aktualisieren falls offen
character_panel.update_stats(self)
- print("LEVEL UP! Jetzt Level ", level, " - HP voll aufgefüllt!")
+ print("LEVEL UP! Jetzt Level ", level, " - HP und Ressource voll aufgefüllt!")
# XP-Kurve: Jedes Level braucht mehr XP
func _calculate_xp_for_level(target_level: int) -> int:
@@ -216,6 +255,9 @@ func _calculate_xp_for_level(target_level: int) -> int:
# Handler für HUD-Slot-Klicks
func _on_slot_clicked(slot_index: int):
+ _use_action_slot(slot_index)
+
+func _use_action_slot(slot_index: int):
match slot_index:
0: # Autoattack manuell starten
if target != null and global_cooldown <= 0:
@@ -223,6 +265,14 @@ func _on_slot_clicked(slot_index: int):
perform_autoattack()
1: # Heavy Strike
use_heavy_strike()
+ _: # Slots 2-8: Consumables
+ var item = action_bar_items[slot_index]
+ if item is Consumable:
+ if use_consumable(item):
+ # Item aus Inventar entfernen wenn Stack leer
+ if item.stack_size <= 0:
+ inventory.remove_item(item)
+ _update_action_bar_stacks()
# Schaden am Spieler abziehen und HP-Leiste aktualisieren
func take_damage(amount):
@@ -258,6 +308,55 @@ func heal(amount):
current_hp = clamp(current_hp + amount, 0, max_hp)
hud.update_health(current_hp, max_hp)
+# Ressource wiederherstellen (Mana/Energie/Wut)
+func restore_mana(amount):
+ current_resource = clamp(current_resource + amount, 0, max_resource)
+ hud.update_resource(current_resource, max_resource, get_resource_name())
+
+# Ressource verbrauchen
+func spend_resource(amount) -> bool:
+ if current_resource < amount:
+ print("Nicht genug " + get_resource_name() + "!")
+ return false
+ current_resource = clamp(current_resource - amount, 0, max_resource)
+ hud.update_resource(current_resource, max_resource, get_resource_name())
+ return true
+
+# Consumable benutzen (Trank etc.)
+func use_consumable(consumable: Consumable) -> bool:
+ if potion_cooldown > 0:
+ print("Trank noch im Cooldown!")
+ return false
+ if consumable.use(self):
+ potion_cooldown = consumable.cooldown
+ if consumable.stack_size <= 0:
+ return true # Verbraucht
+ return false
+
+# Consumable auf Aktionsleiste legen
+func assign_to_action_bar(slot_index: int, consumable: Consumable):
+ if slot_index < 2 or slot_index > 8:
+ return # Slot 0+1 sind reserviert für Skills
+ action_bar_items[slot_index] = consumable
+ if consumable and consumable.icon:
+ hud.set_slot_icon_texture(slot_index, consumable.icon)
+ hud.set_slot_stack_count(slot_index, consumable.stack_size)
+ else:
+ hud.clear_slot_icon(slot_index)
+ hud.set_slot_stack_count(slot_index, 0)
+
+# Aktionsleiste Stack-Counts aktualisieren
+func _update_action_bar_stacks():
+ for i in range(2, 9):
+ var item = action_bar_items[i]
+ if item is Consumable:
+ if item.stack_size <= 0:
+ action_bar_items[i] = null
+ hud.clear_slot_icon(i)
+ hud.set_slot_stack_count(i, 0)
+ else:
+ hud.set_slot_stack_count(i, item.stack_size)
+
# Loot empfangen und Fenster anzeigen
func receive_loot(loot: Dictionary, world_pos: Vector3):
loot_window.show_loot(loot, world_pos)
@@ -437,6 +536,8 @@ func _physics_process(delta):
# Skill-Cooldowns herunterzählen
if heavy_strike_cooldown > 0:
heavy_strike_cooldown -= delta
+ if potion_cooldown > 0:
+ potion_cooldown -= delta
# HUD Cooldowns aktualisieren
hud.set_slot_cooldown(0, global_cooldown) # Slot 1: GCD (Autoattack)
@@ -469,18 +570,25 @@ func _physics_process(delta):
use_heavy_strike()
if Input.is_action_just_pressed("action_3"):
hud.set_active_slot(2)
+ _use_action_slot(2)
if Input.is_action_just_pressed("action_4"):
hud.set_active_slot(3)
+ _use_action_slot(3)
if Input.is_action_just_pressed("action_5"):
hud.set_active_slot(4)
+ _use_action_slot(4)
if Input.is_action_just_pressed("action_6"):
hud.set_active_slot(5)
+ _use_action_slot(5)
if Input.is_action_just_pressed("action_7"):
hud.set_active_slot(6)
+ _use_action_slot(6)
if Input.is_action_just_pressed("action_8"):
hud.set_active_slot(7)
+ _use_action_slot(7)
if Input.is_action_just_pressed("action_9"):
hud.set_active_slot(8)
+ _use_action_slot(8)
# TEST: T drücken = 10 Schaden
if Input.is_action_just_pressed("test_damage"):
diff --git a/world.gd b/world.gd
index fd14393..2c5e143 100644
--- a/world.gd
+++ b/world.gd
@@ -3,6 +3,7 @@
extends Node3D
const ENEMY_SCENE = preload("res://enemy.tscn")
+const MAIN_MENU = preload("res://main_menu.tscn")
const CLASS_SELECTION_MENU = preload("res://class_selection_menu.tscn")
const RESPAWN_TIME = 5.0
@@ -16,10 +17,18 @@ const GOBLIN_LOOT = preload("res://loot_tables/goblin_loot.tres")
@onready var player = $Player
func _ready():
- # Klassenauswahl-Menü anzeigen
+ # Hauptmenü anzeigen
+ var main_menu = MAIN_MENU.instantiate()
+ add_child(main_menu)
+ main_menu.start_game.connect(_on_start_game)
+
+# Nach Hauptmenü: Klassenauswahl anzeigen
+func _on_start_game():
var menu = CLASS_SELECTION_MENU.instantiate()
add_child(menu)
menu.class_selected.connect(_on_class_selected)
+ # Spiel wieder pausieren für Klassenauswahl
+ get_tree().paused = true
# Klasse ausgewählt: Spieler initialisieren
func _on_class_selected(character_class: CharacterClass):
diff --git a/world.tscn b/world.tscn
index 6f1de4f..7b91309 100644
--- a/world.tscn
+++ b/world.tscn
@@ -3,7 +3,6 @@
[ext_resource type="PackedScene" uid="uid://dniyuebl8yhtv" path="res://player.tscn" id="1_f3sb7"]
[ext_resource type="Script" uid="uid://cx56h588mfsk0" path="res://world.gd" id="1_tlwt5"]
[ext_resource type="PackedScene" uid="uid://cvojaeanxugfj" path="res://enemy.tscn" id="2_fj7yv"]
-[ext_resource type="Script" uid="uid://bg5qs3pcfp7p7" path="res://enemy.gd" id="4_aqk2v"]
[sub_resource type="BoxShape3D" id="BoxShape3D_fj7yv"]
size = Vector3(200, 0.5, 200)
@@ -16,12 +15,12 @@ size = Vector3(200, 0.5, 200)
[node name="World" type="Node3D" unique_id=2007838514]
script = ExtResource("1_tlwt5")
-[node name="StaticBody3D" type="StaticBody3D" parent="." unique_id=2101916269]
+[node name="Boden" type="StaticBody3D" parent="." unique_id=2101916269]
-[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" unique_id=1873339390]
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Boden" unique_id=1873339390]
shape = SubResource("BoxShape3D_fj7yv")
-[node name="MeshInstance3D" type="MeshInstance3D" parent="StaticBody3D" unique_id=1214783061]
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Boden" unique_id=1214783061]
mesh = SubResource("BoxMesh_tlwt5")
[node name="Player" parent="." unique_id=937297102 instance=ExtResource("1_f3sb7")]
@@ -32,7 +31,6 @@ transform = Transform3D(-45, 0, 0, 0, -45, 0, 0, 0, -45, 0, 0, 0)
[node name="Enemy" parent="." unique_id=332011146 instance=ExtResource("2_fj7yv")]
transform = Transform3D(-1, 0, -8.742278e-08, 0, 1, 0, 8.742278e-08, 0, -1, 0, 0.3, -7.038)
-script = ExtResource("4_aqk2v")
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=827244005]
navigation_mesh = SubResource("NavigationMesh_fj7yv")