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")