- Alle Aktionsleisten-Slots frei belegbar (Skills + Consumables) - Drag & Drop: Items/Skills zwischen Slots verschieben oder rausziehen - Gelber Highlight-Rand beim Hover über Slots während Drag - Drag-Icon auf eigener CanvasLayer (Layer 200) für korrektes Z-Order - Fähigkeiten-Panel (P-Taste): Listet alle Skills, per Drag auf Leiste ziehen - Skills als Strings in action_bar_items gespeichert (generisches System) - Cooldown-Anzeige generisch für alle Slot-Typen - Bugfix: theme_override_constants und Tooltip-Typen in LootWindow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
682 lines
22 KiB
GDScript
682 lines
22 KiB
GDScript
# Player.gd
|
|
# Steuert den Spielercharakter: Bewegung, Kamera, HP, Angriff, Zielauswahl
|
|
extends CharacterBody3D
|
|
|
|
const SPEED = 5.0
|
|
const JUMP_VELOCITY = 4.5
|
|
const GRAVITY = 9.8
|
|
|
|
# Charakter-Klasse und Level-System
|
|
@export var character_class: CharacterClass
|
|
var level: int = 1
|
|
var current_xp: int = 0
|
|
var xp_to_next_level: int = 100 # XP benötigt für Level 2
|
|
|
|
# Aktuelle Stats (berechnet aus Klasse + Level)
|
|
var strength: int = 10
|
|
var agility: int = 10
|
|
var intelligence: int = 10
|
|
var stamina: int = 10
|
|
var armor: int = 0 # Rüstung aus Ausrüstung
|
|
|
|
# Level-Differenz Konstanten
|
|
const LEVEL_DIFF_DAMAGE_MOD = 0.1 # 10% mehr/weniger Schaden pro Level-Differenz
|
|
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: Skills (String) oder Consumables in Slots (0-8)
|
|
# Skills: "autoattack", "heavy_strike" — frei verschiebbar
|
|
var action_bar_items: Array = ["autoattack", "heavy_strike", null, null, null, null, null, null, null]
|
|
|
|
# Alle verfügbaren Skills (für Fähigkeiten-Panel)
|
|
var available_skills: Array = [
|
|
{"id": "autoattack", "name": "Autoattack", "icon": "res://icons/autoattack_icon.svg", "description": "Greift das Ziel automatisch an.\nSchaden: Waffenschaden + Main-Stat"},
|
|
{"id": "heavy_strike", "name": "Heavy Strike", "icon": "res://icons/heavy_strike_icon.svg", "description": "Starker Hieb mit 3s Cooldown.\nSchaden: 10-15 + Main-Stat"},
|
|
]
|
|
var potion_cooldown: float = 0.0
|
|
const POTION_COOLDOWN_TIME = 1.0
|
|
|
|
# Equipment System
|
|
var equipment: Dictionary = {
|
|
Equipment.Slot.HEAD: null,
|
|
Equipment.Slot.CHEST: null,
|
|
Equipment.Slot.HANDS: null,
|
|
Equipment.Slot.LEGS: null,
|
|
Equipment.Slot.FEET: null,
|
|
Equipment.Slot.WEAPON: null,
|
|
Equipment.Slot.OFFHAND: null
|
|
}
|
|
|
|
# Inventar System
|
|
var inventory: Inventory = Inventory.new()
|
|
|
|
# Global Cooldown System (GCD) - gilt für alle Aktionen inkl. Autoattack
|
|
var global_cooldown = 0.0
|
|
const BASE_GCD = 1.5 # Basis-GCD in Sekunden (wird durch Haste modifiziert)
|
|
var haste: float = 0.0 # Angriffsgeschwindigkeits-Bonus (0.1 = 10% schneller)
|
|
|
|
# Autoattack System
|
|
var autoattack_active = false # Ob Autoattack aktiv ist
|
|
|
|
# Skills System - individuelle Cooldowns (zusätzlich zum GCD)
|
|
var heavy_strike_cooldown = 0.0
|
|
const HEAVY_STRIKE_DAMAGE_MIN = 10
|
|
const HEAVY_STRIKE_DAMAGE_MAX = 15
|
|
const HEAVY_STRIKE_COOLDOWN = 3.0
|
|
const HEAVY_STRIKE_RANGE = 4.0
|
|
|
|
@onready var camera_pivot = $CameraPivot
|
|
@onready var camera = $CameraPivot/Camera3D
|
|
@onready var hud = $HUD
|
|
@onready var character_panel = $CharacterPanel
|
|
@onready var inventory_panel = $InventoryPanel
|
|
@onready var loot_window = $LootWindow
|
|
@onready var skill_panel = $SkillPanel
|
|
|
|
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)
|
|
# Aktionsleiste initialisieren (Skills + Items)
|
|
for i in range(9):
|
|
_refresh_action_slot(i)
|
|
|
|
# HUD-Klicks und Drag verbinden
|
|
hud.slot_clicked.connect(_on_slot_clicked)
|
|
hud.slot_drag_removed.connect(_on_slot_drag_removed)
|
|
hud.slot_drag_swapped.connect(_on_slot_drag_swapped)
|
|
|
|
# Inventar Panel initialisieren
|
|
inventory_panel.setup(self)
|
|
|
|
# Loot Window initialisieren
|
|
loot_window.setup(self)
|
|
|
|
# Skill Panel initialisieren
|
|
skill_panel.setup(self)
|
|
|
|
# Gold im HUD aktualisieren wenn sich Gold ändert
|
|
inventory.gold_changed.connect(func(amount): hud.update_gold(amount))
|
|
|
|
# Stats basierend auf Klasse und Level berechnen
|
|
func _calculate_stats():
|
|
if character_class == null:
|
|
# Fallback ohne Klasse
|
|
strength = 10
|
|
agility = 10
|
|
intelligence = 10
|
|
stamina = 10
|
|
max_hp = 100
|
|
max_resource = 0
|
|
return
|
|
|
|
# Stats = Basis + (Level-1) * Zuwachs pro Level
|
|
var levels_gained = level - 1
|
|
strength = character_class.base_strength + int(levels_gained * character_class.strength_per_level)
|
|
agility = character_class.base_agility + int(levels_gained * character_class.agility_per_level)
|
|
intelligence = character_class.base_intelligence + int(levels_gained * character_class.intelligence_per_level)
|
|
stamina = character_class.base_stamina + int(levels_gained * character_class.stamina_per_level)
|
|
|
|
# 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, " 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():
|
|
armor = 0
|
|
haste = 0.0
|
|
var bonus_str = 0
|
|
var bonus_agi = 0
|
|
var bonus_int = 0
|
|
var bonus_sta = 0
|
|
|
|
for slot in equipment.keys():
|
|
var item = equipment[slot]
|
|
if item != null:
|
|
armor += item.armor
|
|
haste += item.haste
|
|
bonus_str += item.strength
|
|
bonus_agi += item.agility
|
|
bonus_int += item.intelligence
|
|
bonus_sta += item.stamina
|
|
|
|
strength += bonus_str
|
|
agility += bonus_agi
|
|
intelligence += bonus_int
|
|
stamina += bonus_sta
|
|
|
|
# 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:
|
|
var old_item = equipment[item.slot]
|
|
equipment[item.slot] = item
|
|
_calculate_stats()
|
|
# HP proportional anpassen
|
|
if max_hp > 0:
|
|
current_hp = mini(current_hp, max_hp)
|
|
hud.update_health(current_hp, max_hp)
|
|
character_panel.update_stats(self)
|
|
print("Ausgerüstet: ", item.item_name, " in Slot ", Equipment.get_slot_name(item.slot))
|
|
return old_item
|
|
|
|
# Equipment ablegen
|
|
func unequip_slot(slot: Equipment.Slot) -> Equipment:
|
|
var old_item = equipment[slot]
|
|
if old_item == null:
|
|
return null
|
|
equipment[slot] = null
|
|
_calculate_stats()
|
|
current_hp = mini(current_hp, max_hp)
|
|
hud.update_health(current_hp, max_hp)
|
|
character_panel.update_stats(self)
|
|
print("Abgelegt: ", old_item.item_name)
|
|
return old_item
|
|
|
|
# Ausgerüstete Waffe holen
|
|
func get_equipped_weapon() -> Equipment:
|
|
return equipment[Equipment.Slot.WEAPON]
|
|
|
|
# Main-Stat für Schadensberechnung holen
|
|
func get_main_stat() -> int:
|
|
if character_class == null:
|
|
return 10
|
|
match character_class.main_stat:
|
|
CharacterClass.MainStat.STRENGTH:
|
|
return strength
|
|
CharacterClass.MainStat.AGILITY:
|
|
return agility
|
|
CharacterClass.MainStat.INTELLIGENCE:
|
|
return intelligence
|
|
return 10
|
|
|
|
# XP erhalten und Level-Up prüfen
|
|
func gain_xp(amount: int):
|
|
current_xp += amount
|
|
print("+" , amount, " XP (", current_xp, "/", xp_to_next_level, ")")
|
|
|
|
while current_xp >= xp_to_next_level:
|
|
_level_up()
|
|
|
|
hud.update_level(level, current_xp, xp_to_next_level)
|
|
|
|
# Level-Up durchführen
|
|
func _level_up():
|
|
current_xp -= xp_to_next_level
|
|
level += 1
|
|
xp_to_next_level = _calculate_xp_for_level(level + 1)
|
|
|
|
# Stats neu berechnen
|
|
_calculate_stats()
|
|
|
|
# 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 und Ressource voll aufgefüllt!")
|
|
|
|
# XP-Kurve: Jedes Level braucht mehr XP
|
|
func _calculate_xp_for_level(target_level: int) -> int:
|
|
return 100 * target_level # Level 2: 100, Level 3: 200, etc.
|
|
|
|
# Handler für HUD-Slot-Klicks
|
|
func _on_slot_clicked(slot_index: int):
|
|
_use_action_slot(slot_index)
|
|
|
|
# Slot aus Aktionsleiste entfernen (rausgezogen) - Item/Skill bleibt verfügbar
|
|
func _on_slot_drag_removed(slot_index: int):
|
|
action_bar_items[slot_index] = null
|
|
hud.clear_slot_icon(slot_index)
|
|
hud.set_slot_stack_count(slot_index, 0)
|
|
print("Slot " + str(slot_index + 1) + " geleert")
|
|
|
|
# Zwei Slots tauschen
|
|
func _on_slot_drag_swapped(from_slot: int, to_slot: int):
|
|
var temp = action_bar_items[from_slot]
|
|
action_bar_items[from_slot] = action_bar_items[to_slot]
|
|
action_bar_items[to_slot] = temp
|
|
_refresh_action_slot(from_slot)
|
|
_refresh_action_slot(to_slot)
|
|
|
|
# Skill per ID auf Slot legen
|
|
func assign_skill_to_action_bar(slot_index: int, skill_id: String):
|
|
action_bar_items[slot_index] = skill_id
|
|
_refresh_action_slot(slot_index)
|
|
print(skill_id + " auf Slot " + str(slot_index + 1) + " gelegt")
|
|
|
|
# Skill-Info anhand ID holen
|
|
func get_skill_info(skill_id: String) -> Dictionary:
|
|
for skill in available_skills:
|
|
if skill["id"] == skill_id:
|
|
return skill
|
|
return {}
|
|
|
|
# Cooldown für einen Slot ermitteln
|
|
func _get_slot_cooldown(slot_index: int) -> float:
|
|
var entry = action_bar_items[slot_index]
|
|
if entry is String:
|
|
match entry:
|
|
"autoattack":
|
|
return global_cooldown
|
|
"heavy_strike":
|
|
return heavy_strike_cooldown
|
|
elif entry is Consumable:
|
|
return potion_cooldown
|
|
return 0.0
|
|
|
|
func _refresh_action_slot(slot_index: int):
|
|
var entry = action_bar_items[slot_index]
|
|
if entry is String:
|
|
# Skill
|
|
var info = get_skill_info(entry)
|
|
if info.size() > 0:
|
|
hud.set_slot_icon(slot_index, info["icon"])
|
|
else:
|
|
hud.clear_slot_icon(slot_index)
|
|
hud.set_slot_stack_count(slot_index, 0)
|
|
elif entry is Consumable and entry.icon:
|
|
hud.set_slot_icon_texture(slot_index, entry.icon)
|
|
hud.set_slot_stack_count(slot_index, entry.stack_size)
|
|
else:
|
|
hud.clear_slot_icon(slot_index)
|
|
hud.set_slot_stack_count(slot_index, 0)
|
|
|
|
func _use_action_slot(slot_index: int):
|
|
var entry = action_bar_items[slot_index]
|
|
if entry is String:
|
|
# Skill ausführen
|
|
match entry:
|
|
"autoattack":
|
|
if target != null and global_cooldown <= 0:
|
|
start_autoattack()
|
|
perform_autoattack()
|
|
"heavy_strike":
|
|
use_heavy_strike()
|
|
elif entry is Consumable:
|
|
if use_consumable(entry):
|
|
if entry.stack_size <= 0:
|
|
inventory.remove_item(entry)
|
|
_update_action_bar_stacks()
|
|
|
|
# Schaden am Spieler abziehen und HP-Leiste aktualisieren
|
|
func take_damage(amount):
|
|
current_hp = clamp(current_hp - amount, 0, max_hp)
|
|
hud.update_health(current_hp, max_hp)
|
|
if current_hp <= 0:
|
|
die()
|
|
|
|
# Schaden mit Rüstung und Level-Differenz berechnen
|
|
func calculate_incoming_damage(raw_damage: int, attacker_level: int, is_melee: bool) -> int:
|
|
var damage = float(raw_damage)
|
|
|
|
# Rüstung reduziert nur Nahkampfschaden
|
|
if is_melee and armor > 0:
|
|
var armor_reduction = float(armor) / (float(armor) + 50.0)
|
|
damage = damage * (1.0 - armor_reduction)
|
|
|
|
# Level-Differenz Modifikator (Gegner höheres Level = mehr Schaden)
|
|
var level_diff = attacker_level - level
|
|
var level_mod = clamp(level_diff * LEVEL_DIFF_DAMAGE_MOD, -MAX_LEVEL_DIFF_MOD, MAX_LEVEL_DIFF_MOD)
|
|
damage = damage * (1.0 + level_mod)
|
|
|
|
return maxi(1, int(damage))
|
|
|
|
# Schaden mit vollem Schadenssystem nehmen
|
|
func take_damage_from(raw_damage: int, attacker_level: int, is_melee: bool = true):
|
|
var final_damage = calculate_incoming_damage(raw_damage, attacker_level, is_melee)
|
|
print("Spieler nimmt Schaden: ", raw_damage, " -> ", final_damage, " (nach Rüstung/Level)")
|
|
take_damage(final_damage)
|
|
|
|
# HP heilen und HP-Leiste aktualisieren
|
|
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)
|
|
|
|
func die():
|
|
print("Spieler gestorben!")
|
|
|
|
# Schaden basierend auf ausgerüsteter Waffe + Main-Stat Skalierung
|
|
func get_attack_damage() -> int:
|
|
var weapon = get_equipped_weapon()
|
|
var base_damage: int
|
|
if weapon == null:
|
|
# Unbewaffneter Schaden klassenabhängig
|
|
if character_class:
|
|
base_damage = randi_range(character_class.unarmed_min_damage, character_class.unarmed_max_damage)
|
|
else:
|
|
base_damage = 1
|
|
else:
|
|
base_damage = randi_range(weapon.min_damage, weapon.max_damage)
|
|
|
|
# Schaden skaliert mit Main-Stat
|
|
var stat_bonus = int(get_main_stat() * CharacterClass.DAMAGE_PER_MAIN_STAT)
|
|
return base_damage + stat_bonus
|
|
|
|
# Aktuellen GCD berechnen (mit Haste-Modifikator)
|
|
func get_current_gcd() -> float:
|
|
var weapon = get_equipped_weapon()
|
|
var base_speed: float
|
|
if weapon == null:
|
|
# Unbewaffnete Angriffsgeschwindigkeit klassenabhängig
|
|
if character_class:
|
|
base_speed = character_class.unarmed_attack_speed
|
|
else:
|
|
base_speed = BASE_GCD
|
|
else:
|
|
base_speed = weapon.attack_speed
|
|
|
|
# Haste reduziert den GCD: GCD = Basis / (1 + Haste)
|
|
# Bei 0.5 Haste (50%): 1.5s / 1.5 = 1.0s
|
|
return base_speed / (1.0 + haste)
|
|
|
|
# DPS berechnen (für Anzeige)
|
|
func get_dps() -> float:
|
|
var weapon = get_equipped_weapon()
|
|
var avg_damage: float
|
|
if weapon == null:
|
|
# Unbewaffneter Durchschnittsschaden klassenabhängig
|
|
if character_class:
|
|
avg_damage = (character_class.unarmed_min_damage + character_class.unarmed_max_damage) / 2.0
|
|
else:
|
|
avg_damage = 1.0
|
|
else:
|
|
avg_damage = (weapon.min_damage + weapon.max_damage) / 2.0
|
|
|
|
var stat_bonus = get_main_stat() * CharacterClass.DAMAGE_PER_MAIN_STAT
|
|
var total_damage = avg_damage + stat_bonus
|
|
var gcd = get_current_gcd()
|
|
|
|
# DPS = Schaden / GCD
|
|
return total_damage / gcd
|
|
|
|
# Reichweite basierend auf ausgerüsteter Waffe (unbewaffnet = 3.0)
|
|
func get_attack_range() -> float:
|
|
var weapon = get_equipped_weapon()
|
|
if weapon == null:
|
|
return 3.0
|
|
return weapon.weapon_range
|
|
|
|
# Ziel markieren — start_attack=true startet sofort die Autoattack
|
|
func set_target(new_target, start_attack: bool = false):
|
|
if target != null and is_instance_valid(target):
|
|
target.hide_health()
|
|
target = new_target
|
|
target.show_health()
|
|
print("Ziel markiert: ", target.name)
|
|
if start_attack:
|
|
start_autoattack()
|
|
if global_cooldown <= 0:
|
|
perform_autoattack()
|
|
|
|
# Ziel komplett aufheben und Autoattack stoppen
|
|
func clear_target():
|
|
if target != null and is_instance_valid(target):
|
|
target.hide_health()
|
|
target = null
|
|
autoattack_active = false
|
|
print("Ziel aufgehoben, Autoattack gestoppt")
|
|
|
|
# Autoattack aktivieren
|
|
func start_autoattack():
|
|
autoattack_active = true
|
|
print("Autoattack aktiviert")
|
|
|
|
# Autoattack deaktivieren
|
|
func stop_autoattack():
|
|
autoattack_active = false
|
|
print("Autoattack deaktiviert")
|
|
|
|
# Führt einen Autoattack aus (wird vom GCD-System aufgerufen)
|
|
func perform_autoattack():
|
|
if target == null or not is_instance_valid(target):
|
|
target = null
|
|
autoattack_active = false
|
|
return
|
|
|
|
var distance = global_position.distance_to(target.global_position)
|
|
if distance <= get_attack_range():
|
|
var dmg = get_attack_damage()
|
|
# Neues Schadenssystem mit Rüstung und Level-Differenz
|
|
if target.has_method("take_damage_from"):
|
|
target.take_damage_from(dmg, level, true) # true = Nahkampf
|
|
else:
|
|
target.take_damage(dmg)
|
|
print("Autoattack: ", dmg, " Schaden (GCD: %.2fs, DPS: %.1f)" % [get_current_gcd(), get_dps()])
|
|
# GCD auslösen basierend auf Waffengeschwindigkeit + Haste
|
|
trigger_global_cooldown()
|
|
|
|
# Global Cooldown auslösen (basierend auf Waffe + Haste)
|
|
func trigger_global_cooldown():
|
|
global_cooldown = get_current_gcd()
|
|
|
|
# Heavy Strike: Starker Angriff mit Cooldown
|
|
func use_heavy_strike():
|
|
if target == null or not is_instance_valid(target):
|
|
print("Kein Ziel für Heavy Strike!")
|
|
return
|
|
|
|
# Nur Skill-eigener Cooldown Check (kein GCD-Check!)
|
|
if heavy_strike_cooldown > 0:
|
|
print("Heavy Strike noch im Cooldown: ", "%.1f" % heavy_strike_cooldown, "s")
|
|
return
|
|
|
|
var distance = global_position.distance_to(target.global_position)
|
|
if distance > HEAVY_STRIKE_RANGE:
|
|
print("Ziel zu weit entfernt für Heavy Strike!")
|
|
return
|
|
|
|
var base_damage = randi_range(HEAVY_STRIKE_DAMAGE_MIN, HEAVY_STRIKE_DAMAGE_MAX)
|
|
var stat_bonus = int(get_main_stat() * CharacterClass.DAMAGE_PER_MAIN_STAT)
|
|
var damage = base_damage + stat_bonus
|
|
# Neues Schadenssystem mit Rüstung und Level-Differenz
|
|
if target.has_method("take_damage_from"):
|
|
target.take_damage_from(damage, level, true) # true = Nahkampf
|
|
else:
|
|
target.take_damage(damage)
|
|
heavy_strike_cooldown = HEAVY_STRIKE_COOLDOWN
|
|
trigger_global_cooldown() # GCD zurücksetzen damit Autoattack nicht sofort feuert
|
|
start_autoattack() # Autoattack nach Skill automatisch aktivieren
|
|
print("Heavy Strike! ", damage, " Rohschaden")
|
|
|
|
# Raycast von der Kamera auf Mausposition — trifft Gegner mit take_damage()
|
|
func _try_select_target(start_attack: bool = false):
|
|
var space_state = get_world_3d().direct_space_state
|
|
var viewport = get_viewport()
|
|
var mouse_pos = viewport.get_mouse_position()
|
|
var ray_origin = camera.project_ray_origin(mouse_pos)
|
|
var ray_end = ray_origin + camera.project_ray_normal(mouse_pos) * 100.0
|
|
var query = PhysicsRayQueryParameters3D.create(ray_origin, ray_end)
|
|
query.exclude = [self]
|
|
var result = space_state.intersect_ray(query)
|
|
if result and result.collider.has_method("take_damage"):
|
|
set_target(result.collider, start_attack)
|
|
elif not start_attack:
|
|
# Nur bei Linksklick ins Leere: Ziel deselektieren und Autoattack stoppen
|
|
# Rechtsklick wird für Kameradrehung verwendet
|
|
clear_target()
|
|
|
|
func _physics_process(delta):
|
|
# Global Cooldown herunterzählen (gilt für alle Aktionen)
|
|
if global_cooldown > 0:
|
|
global_cooldown -= delta
|
|
|
|
# Wenn GCD bereit und Autoattack aktiv, versuche anzugreifen
|
|
if global_cooldown <= 0 and autoattack_active:
|
|
perform_autoattack()
|
|
|
|
# Skill-Cooldowns herunterzählen
|
|
if heavy_strike_cooldown > 0:
|
|
heavy_strike_cooldown -= delta
|
|
if potion_cooldown > 0:
|
|
potion_cooldown -= delta
|
|
|
|
# HUD Cooldowns aktualisieren - generisch pro Slot
|
|
for i in range(9):
|
|
var cd = _get_slot_cooldown(i)
|
|
hud.set_slot_cooldown(i, cd)
|
|
|
|
# Schwerkraft
|
|
if not is_on_floor():
|
|
velocity.y -= GRAVITY * delta
|
|
|
|
# Springen
|
|
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
|
|
velocity.y = JUMP_VELOCITY
|
|
|
|
# Linksklick: nur markieren
|
|
if Input.is_action_just_pressed("select_target"):
|
|
_try_select_target(false)
|
|
|
|
# Rechtsklick: markieren + angreifen
|
|
if Input.is_action_just_pressed("ui_right_mouse"):
|
|
_try_select_target(true)
|
|
|
|
# Aktionsleiste 1-9 — alle generisch über _use_action_slot
|
|
for i in range(9):
|
|
var action_name = "action_" + str(i + 1)
|
|
if Input.is_action_just_pressed(action_name):
|
|
hud.set_active_slot(i)
|
|
_use_action_slot(i)
|
|
|
|
# TEST: T drücken = 10 Schaden
|
|
if Input.is_action_just_pressed("test_damage"):
|
|
take_damage(10)
|
|
|
|
# C drücken = Charakter-Panel öffnen/schließen
|
|
if Input.is_action_just_pressed("toggle_character"):
|
|
character_panel.update_stats(self)
|
|
character_panel.toggle()
|
|
|
|
# I drücken = Inventar öffnen/schließen
|
|
if Input.is_action_just_pressed("toggle_inventory"):
|
|
inventory_panel.toggle()
|
|
|
|
# P drücken = Fähigkeiten-Panel öffnen/schließen
|
|
if Input.is_action_just_pressed("toggle_skills"):
|
|
skill_panel.toggle()
|
|
|
|
# Eingabe
|
|
var input_dir = Vector2.ZERO
|
|
if Input.is_action_pressed("move_forward"):
|
|
input_dir.y -= 1
|
|
if Input.is_action_pressed("move_back"):
|
|
input_dir.y += 1
|
|
if Input.is_action_pressed("move_left"):
|
|
input_dir.x -= 1
|
|
if Input.is_action_pressed("move_right"):
|
|
input_dir.x += 1
|
|
|
|
# Bewegung relativ zur Kamera
|
|
var world_yaw = rotation.y + camera_pivot.rotation.y
|
|
var forward = Vector3(-sin(world_yaw), 0, -cos(world_yaw)).normalized()
|
|
var right = Vector3(cos(world_yaw), 0, -sin(world_yaw)).normalized()
|
|
var direction = (forward * -input_dir.y + right * input_dir.x)
|
|
|
|
velocity.x = direction.x * SPEED
|
|
velocity.z = direction.z * SPEED
|
|
|
|
# RMB gehalten: Spieler schaut in Kamerarichtung
|
|
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
|
rotation.y = world_yaw
|
|
camera_pivot.rotation.y = 0
|
|
|
|
move_and_slide()
|