Consumable-System, Klassen-Ressourcen, Hauptmenü und Item-Icons
- Consumable-System: Tränke (HP/Mana) mit Stacking, Rechtsklick-Benutzung, Aktionsleisten-Zuweisung - Klassen-Ressourcen: ResourceType (NONE/MANA/RAGE/ENERGY) pro Klasse statt universelles Mana - Hauptmenü: Einstellungen für Auflösung, Fenstermodus, VSync, MSAA - Item-Icons: SVG-Icons für alle Equipment-Items und Tränke - Character Panel: Icon-Grid mit Hover-Tooltips statt Textanzeige - HUD: Ressourcen-Leiste mit klassenabhängiger Farbe - Loot: Consumable-Support in LootTable/LootWindow - Dokumentation vollständig aktualisiert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
113
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
55
consumable.gd
Normal file
|
|
@ -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
|
||||
1
consumable.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://b25cvd8swhsg3
|
||||
14
consumables/small_hp_potion.tres
Normal file
|
|
@ -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
|
||||
14
consumables/small_mana_potion.tres
Normal file
|
|
@ -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
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
89
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
|
||||
|
|
|
|||
13
icons/hp_potion_icon.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Flasche -->
|
||||
<path d="M24 20 L24 12 L40 12 L40 20 L46 32 L46 50 Q46 56 40 56 L24 56 Q18 56 18 50 L18 32 Z" fill="#CC2222" stroke="#8B1515" stroke-width="2"/>
|
||||
<!-- Flaschenhals -->
|
||||
<rect x="26" y="8" width="12" height="6" fill="#A08060" stroke="#806040" stroke-width="1" rx="1"/>
|
||||
<!-- Korken -->
|
||||
<rect x="27" y="5" width="10" height="5" fill="#B09070" rx="2"/>
|
||||
<!-- Highlight -->
|
||||
<path d="M22 34 Q22 28 26 24 L26 34 Z" fill="rgba(255,255,255,0.3)"/>
|
||||
<!-- Kreuz -->
|
||||
<rect x="29" y="32" width="6" height="18" fill="white" rx="1"/>
|
||||
<rect x="23" y="38" width="18" height="6" fill="white" rx="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 686 B |
43
icons/hp_potion_icon.svg.import
Normal file
|
|
@ -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
|
||||
13
icons/iron_helm_icon.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Helm Kuppel -->
|
||||
<path d="M16 38 Q16 12 32 10 Q48 12 48 38 Z" fill="#909090" stroke="#707070" stroke-width="2"/>
|
||||
<!-- Helmrand -->
|
||||
<path d="M14 38 L50 38" stroke="#606060" stroke-width="4" stroke-linecap="round"/>
|
||||
<!-- Nasenschutz -->
|
||||
<rect x="30" y="28" width="4" height="14" fill="#808080" rx="1"/>
|
||||
<!-- Nieten -->
|
||||
<circle cx="20" cy="38" r="2" fill="#505050"/>
|
||||
<circle cx="44" cy="38" r="2" fill="#505050"/>
|
||||
<!-- Highlight -->
|
||||
<path d="M24 16 Q30 14 32 18" fill="none" stroke="#B0B0B0" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 604 B |
43
icons/iron_helm_icon.svg.import
Normal file
|
|
@ -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
|
||||
11
icons/iron_sword_icon.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Klinge -->
|
||||
<line x1="32" y1="8" x2="32" y2="42" stroke="#A0A0A0" stroke-width="6" stroke-linecap="round"/>
|
||||
<line x1="32" y1="8" x2="32" y2="42" stroke="#C0C0C0" stroke-width="3" stroke-linecap="round"/>
|
||||
<!-- Parierstange -->
|
||||
<line x1="22" y1="42" x2="42" y2="42" stroke="#8B7355" stroke-width="5" stroke-linecap="round"/>
|
||||
<!-- Griff -->
|
||||
<line x1="32" y1="42" x2="32" y2="54" stroke="#6B4226" stroke-width="5" stroke-linecap="round"/>
|
||||
<!-- Knauf -->
|
||||
<circle cx="32" cy="56" r="3" fill="#A0A0A0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 587 B |
43
icons/iron_sword_icon.svg.import
Normal file
|
|
@ -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
|
||||
14
icons/leather_chest_icon.svg
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Rumpf -->
|
||||
<path d="M20 18 L44 18 L46 50 L18 50 Z" fill="#8B6914" stroke="#6B4E0A" stroke-width="2"/>
|
||||
<!-- Schultern -->
|
||||
<path d="M14 18 L20 18 L20 28 L14 24 Z" fill="#7A5C10" stroke="#6B4E0A" stroke-width="1.5"/>
|
||||
<path d="M44 18 L50 18 L50 24 L44 28 Z" fill="#7A5C10" stroke="#6B4E0A" stroke-width="1.5"/>
|
||||
<!-- Kragen -->
|
||||
<path d="M24 18 L32 14 L40 18" fill="none" stroke="#6B4E0A" stroke-width="2"/>
|
||||
<!-- Nähte -->
|
||||
<line x1="32" y1="18" x2="32" y2="50" stroke="#6B4E0A" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<!-- Gürtel -->
|
||||
<rect x="18" y="38" width="28" height="4" fill="#5A3E08" rx="1"/>
|
||||
<rect x="30" y="37" width="4" height="6" fill="#C0A030" rx="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 759 B |
43
icons/leather_chest_icon.svg.import
Normal file
|
|
@ -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
|
||||
12
icons/mana_potion_icon.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Flasche -->
|
||||
<path d="M24 20 L24 12 L40 12 L40 20 L46 32 L46 50 Q46 56 40 56 L24 56 Q18 56 18 50 L18 32 Z" fill="#2244CC" stroke="#152288" stroke-width="2"/>
|
||||
<!-- Flaschenhals -->
|
||||
<rect x="26" y="8" width="12" height="6" fill="#A08060" stroke="#806040" stroke-width="1" rx="1"/>
|
||||
<!-- Korken -->
|
||||
<rect x="27" y="5" width="10" height="5" fill="#B09070" rx="2"/>
|
||||
<!-- Highlight -->
|
||||
<path d="M22 34 Q22 28 26 24 L26 34 Z" fill="rgba(255,255,255,0.3)"/>
|
||||
<!-- Stern -->
|
||||
<polygon points="32,30 34,38 42,38 36,43 38,51 32,46 26,51 28,43 22,38 30,38" fill="white" opacity="0.9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 663 B |
43
icons/mana_potion_icon.svg.import
Normal file
|
|
@ -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
|
||||
11
icons/steel_sword_icon.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Klinge -->
|
||||
<line x1="32" y1="6" x2="32" y2="40" stroke="#7090B0" stroke-width="7" stroke-linecap="round"/>
|
||||
<line x1="32" y1="6" x2="32" y2="40" stroke="#A0C0E0" stroke-width="3" stroke-linecap="round"/>
|
||||
<!-- Parierstange -->
|
||||
<line x1="20" y1="40" x2="44" y2="40" stroke="#4060A0" stroke-width="5" stroke-linecap="round"/>
|
||||
<!-- Griff -->
|
||||
<line x1="32" y1="40" x2="32" y2="53" stroke="#3B2516" stroke-width="5" stroke-linecap="round"/>
|
||||
<!-- Knauf -->
|
||||
<circle cx="32" cy="56" r="4" fill="#4060A0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 587 B |
43
icons/steel_sword_icon.svg.import
Normal file
|
|
@ -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
|
||||
17
icons/wooden_shield_icon.svg
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Schild Form -->
|
||||
<path d="M16 14 L48 14 L48 36 L32 56 L16 36 Z" fill="#8B6914" stroke="#5A3E08" stroke-width="2.5"/>
|
||||
<!-- Holzmaserung -->
|
||||
<line x1="26" y1="14" x2="26" y2="44" stroke="#7A5C10" stroke-width="1"/>
|
||||
<line x1="38" y1="14" x2="38" y2="44" stroke="#7A5C10" stroke-width="1"/>
|
||||
<!-- Metallbeschlag Mitte -->
|
||||
<circle cx="32" cy="30" r="6" fill="none" stroke="#A0A0A0" stroke-width="2.5"/>
|
||||
<circle cx="32" cy="30" r="2" fill="#A0A0A0"/>
|
||||
<!-- Metallrand oben -->
|
||||
<line x1="16" y1="14" x2="48" y2="14" stroke="#808080" stroke-width="3"/>
|
||||
<!-- Nieten -->
|
||||
<circle cx="20" cy="18" r="1.5" fill="#707070"/>
|
||||
<circle cx="44" cy="18" r="1.5" fill="#707070"/>
|
||||
<circle cx="20" cy="34" r="1.5" fill="#707070"/>
|
||||
<circle cx="44" cy="34" r="1.5" fill="#707070"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 857 B |
43
icons/wooden_shield_icon.svg.import
Normal file
|
|
@ -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
|
||||
32
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]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")])
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
1
loot_window.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://y6v242l8n1ti
|
||||
155
main_menu.gd
Normal file
|
|
@ -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()
|
||||
1
main_menu.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bw65mud8idif0
|
||||
161
main_menu.tscn
Normal file
|
|
@ -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"
|
||||
116
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"):
|
||||
|
|
|
|||
11
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):
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||