Magier-Kampfsystem, Castbar, Inventar-Drag und Gegner-Aggro
- Zauberstab als eigener Fernkampf-Skill (20m, magisch, exklusiv mit Autoattack) - Frostblitz mit 1.5s Castzeit und Castbar (mittig über Aktionsleiste) - Cast wird durch Bewegung, Springen oder Schaden unterbrochen - Holzstab als Magier-Startwaffe (+3 INT) - Frostblitz-Icon (SVG) - Skills klassenabhängig: Magier=Zauberstab+Frostblitz, Krieger/Schurke=Heavy Strike - Inventar: Drag & Drop zum Umordnen mit gelbem Highlight - Gegner aggrot sofort bei Schadenstreffer (nicht nur in Aggro-Range) - Inventar: swap_items/move_items Funktionen Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d029a37e7f
commit
8f0ac2227e
13 changed files with 574 additions and 41 deletions
|
|
@ -16,3 +16,6 @@ strength_per_level = 1.0
|
||||||
agility_per_level = 1.5
|
agility_per_level = 1.5
|
||||||
intelligence_per_level = 3.0
|
intelligence_per_level = 3.0
|
||||||
stamina_per_level = 1.5
|
stamina_per_level = 1.5
|
||||||
|
unarmed_min_damage = 2
|
||||||
|
unarmed_max_damage = 5
|
||||||
|
unarmed_attack_speed = 2.0
|
||||||
|
|
|
||||||
7
enemy.gd
7
enemy.gd
|
|
@ -104,10 +104,15 @@ func show_health():
|
||||||
func hide_health():
|
func hide_health():
|
||||||
health_label.visible = false
|
health_label.visible = false
|
||||||
|
|
||||||
# Schaden nehmen und Label aktualisieren (alte Methode für Kompatibilität)
|
# Schaden nehmen und Label aktualisieren
|
||||||
func take_damage(amount):
|
func take_damage(amount):
|
||||||
current_hp -= amount
|
current_hp -= amount
|
||||||
_update_label()
|
_update_label()
|
||||||
|
# Aggro bei Schaden — sofort angreifen
|
||||||
|
if current_state == State.PATROL:
|
||||||
|
current_state = State.CHASE
|
||||||
|
is_waiting = false
|
||||||
|
print("Gegner wurde angegriffen und verfolgt den Spieler!")
|
||||||
if current_hp <= 0:
|
if current_hp <= 0:
|
||||||
die()
|
die()
|
||||||
|
|
||||||
|
|
|
||||||
14
equipment/wooden_staff.tres
Normal file
14
equipment/wooden_staff.tres
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
[gd_resource type="Resource" script_class="Equipment" format=3 uid="uid://wooden_staff_01"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://re0xiie1udfq" path="res://equipment.gd" id="1_equipment"]
|
||||||
|
[ext_resource type="Texture2D" path="res://icons/wooden_staff_icon.svg" id="2_icon"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_equipment")
|
||||||
|
item_name = "Holzstab"
|
||||||
|
intelligence = 3
|
||||||
|
min_damage = 2
|
||||||
|
max_damage = 5
|
||||||
|
attack_speed = 2.0
|
||||||
|
weapon_range = 3.0
|
||||||
|
icon = ExtResource("2_icon")
|
||||||
92
hud.gd
92
hud.gd
|
|
@ -42,6 +42,11 @@ var slot_stack_labels = [] # Label für Stack-Anzahl
|
||||||
var resource_bar: ProgressBar
|
var resource_bar: ProgressBar
|
||||||
var resource_label: Label
|
var resource_label: Label
|
||||||
|
|
||||||
|
# Castbar
|
||||||
|
var castbar: ProgressBar
|
||||||
|
var castbar_label: Label
|
||||||
|
var castbar_container: PanelContainer
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
_create_level_ui()
|
_create_level_ui()
|
||||||
|
|
||||||
|
|
@ -147,10 +152,10 @@ func _input(event):
|
||||||
|
|
||||||
func _end_actionbar_drag():
|
func _end_actionbar_drag():
|
||||||
var drop_slot = get_slot_at_position(get_viewport().get_mouse_position())
|
var drop_slot = get_slot_at_position(get_viewport().get_mouse_position())
|
||||||
if drop_slot >= 2 and drop_slot <= 8 and drop_slot != drag_from_slot:
|
if drop_slot >= 0 and drop_slot <= 8 and drop_slot != drag_from_slot:
|
||||||
# Auf anderen Slot verschoben -> swap
|
# Auf anderen Slot verschoben -> swap
|
||||||
slot_drag_swapped.emit(drag_from_slot, drop_slot)
|
slot_drag_swapped.emit(drag_from_slot, drop_slot)
|
||||||
elif drop_slot < 0 or drop_slot < 2 or drop_slot > 8:
|
elif drop_slot < 0 or drop_slot > 8:
|
||||||
# Außerhalb gedroppt -> aus Leiste entfernen
|
# Außerhalb gedroppt -> aus Leiste entfernen
|
||||||
slot_drag_removed.emit(drag_from_slot)
|
slot_drag_removed.emit(drag_from_slot)
|
||||||
# Aufräumen
|
# Aufräumen
|
||||||
|
|
@ -253,6 +258,9 @@ func _create_level_ui():
|
||||||
gold_label.text = "0 Gold"
|
gold_label.text = "0 Gold"
|
||||||
control.add_child(gold_label)
|
control.add_child(gold_label)
|
||||||
|
|
||||||
|
# Castbar — direkt über der Aktionsleiste positioniert
|
||||||
|
_create_castbar()
|
||||||
|
|
||||||
# Gold aktualisieren
|
# Gold aktualisieren
|
||||||
func update_gold(amount: int):
|
func update_gold(amount: int):
|
||||||
if gold_label:
|
if gold_label:
|
||||||
|
|
@ -340,7 +348,7 @@ func update_drag_hover(mouse_pos: Vector2):
|
||||||
# Alten Highlight entfernen
|
# Alten Highlight entfernen
|
||||||
_clear_drag_highlight()
|
_clear_drag_highlight()
|
||||||
# Neuen Highlight setzen (nur Slots 2-8)
|
# Neuen Highlight setzen (nur Slots 2-8)
|
||||||
if hovered >= 2 and hovered <= 8:
|
if hovered >= 0 and hovered <= 8:
|
||||||
drag_highlight_slot = hovered
|
drag_highlight_slot = hovered
|
||||||
var style = StyleBoxFlat.new()
|
var style = StyleBoxFlat.new()
|
||||||
style.bg_color = Color(0.15, 0.15, 0.15)
|
style.bg_color = Color(0.15, 0.15, 0.15)
|
||||||
|
|
@ -361,3 +369,81 @@ func get_slot_at_position(mouse_pos: Vector2) -> int:
|
||||||
if rect.has_point(mouse_pos):
|
if rect.has_point(mouse_pos):
|
||||||
return i
|
return i
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
func _create_castbar():
|
||||||
|
# Eigene CanvasLayer für die Castbar
|
||||||
|
var castbar_layer = CanvasLayer.new()
|
||||||
|
castbar_layer.name = "CastbarLayer"
|
||||||
|
castbar_layer.layer = 10
|
||||||
|
add_child(castbar_layer)
|
||||||
|
|
||||||
|
castbar_container = PanelContainer.new()
|
||||||
|
castbar_container.name = "CastbarContainer"
|
||||||
|
castbar_container.visible = false
|
||||||
|
castbar_container.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
|
||||||
|
var style = StyleBoxFlat.new()
|
||||||
|
style.bg_color = Color(0.1, 0.1, 0.1, 0.85)
|
||||||
|
style.set_border_width_all(2)
|
||||||
|
style.border_color = Color(0.6, 0.6, 0.6)
|
||||||
|
style.set_corner_radius_all(4)
|
||||||
|
castbar_container.add_theme_stylebox_override("panel", style)
|
||||||
|
|
||||||
|
var vbox = VBoxContainer.new()
|
||||||
|
vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
castbar_container.add_child(vbox)
|
||||||
|
|
||||||
|
castbar_label = Label.new()
|
||||||
|
castbar_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
castbar_label.add_theme_font_size_override("font_size", 13)
|
||||||
|
castbar_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
vbox.add_child(castbar_label)
|
||||||
|
|
||||||
|
castbar = ProgressBar.new()
|
||||||
|
castbar.custom_minimum_size = Vector2(300, 16)
|
||||||
|
castbar.show_percentage = false
|
||||||
|
castbar.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
|
||||||
|
var fill = StyleBoxFlat.new()
|
||||||
|
fill.bg_color = Color(1.0, 0.7, 0.0, 1.0)
|
||||||
|
fill.set_corner_radius_all(2)
|
||||||
|
castbar.add_theme_stylebox_override("fill", fill)
|
||||||
|
|
||||||
|
var bg = StyleBoxFlat.new()
|
||||||
|
bg.bg_color = Color(0.2, 0.2, 0.2, 1.0)
|
||||||
|
bg.set_corner_radius_all(2)
|
||||||
|
castbar.add_theme_stylebox_override("background", bg)
|
||||||
|
|
||||||
|
vbox.add_child(castbar)
|
||||||
|
castbar_layer.add_child(castbar_container)
|
||||||
|
|
||||||
|
# Castbar anzeigen
|
||||||
|
func show_castbar(spell_name: String, cast_time: float):
|
||||||
|
if castbar_container == null:
|
||||||
|
return
|
||||||
|
# Spell-Name übersetzen
|
||||||
|
var display_name = spell_name
|
||||||
|
match spell_name:
|
||||||
|
"frostbolt": display_name = "Frostblitz"
|
||||||
|
castbar_label.text = display_name + " (" + "%.1f" % cast_time + "s)"
|
||||||
|
castbar.max_value = cast_time
|
||||||
|
castbar.value = 0
|
||||||
|
# Mittig über der Aktionsleiste positionieren
|
||||||
|
var viewport_size = get_viewport().get_visible_rect().size
|
||||||
|
castbar_container.position = Vector2(viewport_size.x / 2 - 160, viewport_size.y - 120)
|
||||||
|
castbar_container.visible = true
|
||||||
|
|
||||||
|
# Castbar Fortschritt aktualisieren
|
||||||
|
func update_castbar(elapsed: float, total: float):
|
||||||
|
if castbar == null:
|
||||||
|
return
|
||||||
|
castbar.value = elapsed
|
||||||
|
var remaining = total - elapsed
|
||||||
|
if remaining < 0:
|
||||||
|
remaining = 0
|
||||||
|
castbar_label.text = castbar_label.text.split("(")[0].strip_edges() + " (%.1f" % remaining + "s)"
|
||||||
|
|
||||||
|
# Castbar verstecken
|
||||||
|
func hide_castbar():
|
||||||
|
if castbar_container:
|
||||||
|
castbar_container.visible = false
|
||||||
|
|
|
||||||
14
icons/frostbolt_icon.svg
Normal file
14
icons/frostbolt_icon.svg
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<!-- Eisstrahl -->
|
||||||
|
<polygon points="10,32 32,24 54,32 32,40" fill="#66CCFF" stroke="#3399DD" stroke-width="1.5"/>
|
||||||
|
<!-- Eiskristall-Spitze -->
|
||||||
|
<polygon points="54,32 62,32 58,26 58,38" fill="#99DDFF" stroke="#3399DD" stroke-width="1"/>
|
||||||
|
<!-- Frost-Partikel -->
|
||||||
|
<circle cx="20" cy="28" r="3" fill="white" opacity="0.7"/>
|
||||||
|
<circle cx="38" cy="36" r="2.5" fill="white" opacity="0.6"/>
|
||||||
|
<circle cx="28" cy="22" r="2" fill="white" opacity="0.5"/>
|
||||||
|
<!-- Schneeflocke Mitte -->
|
||||||
|
<line x1="32" y1="28" x2="32" y2="36" stroke="white" stroke-width="1.5" opacity="0.8"/>
|
||||||
|
<line x1="28" y1="30" x2="36" y2="34" stroke="white" stroke-width="1.5" opacity="0.8"/>
|
||||||
|
<line x1="28" y1="34" x2="36" y2="30" stroke="white" stroke-width="1.5" opacity="0.8"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 822 B |
43
icons/frostbolt_icon.svg.import
Normal file
43
icons/frostbolt_icon.svg.import
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://d3lnef1xxgol3"
|
||||||
|
path="res://.godot/imported/frostbolt_icon.svg-97e9c6299dcf36b8065da5f6427efafe.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://icons/frostbolt_icon.svg"
|
||||||
|
dest_files=["res://.godot/imported/frostbolt_icon.svg-97e9c6299dcf36b8065da5f6427efafe.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/wooden_staff_icon.svg
Normal file
14
icons/wooden_staff_icon.svg
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<!-- Stab -->
|
||||||
|
<line x1="20" y1="56" x2="38" y2="12" stroke="#8B6914" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<!-- Kristall oben -->
|
||||||
|
<polygon points="38,12 44,6 42,16 48,10" fill="#66CCFF" stroke="#3399DD" stroke-width="1"/>
|
||||||
|
<polygon points="38,12 32,6 36,16 30,10" fill="#99DDFF" stroke="#3399DD" stroke-width="1"/>
|
||||||
|
<!-- Glühen -->
|
||||||
|
<circle cx="38" cy="10" r="6" fill="#66CCFF" opacity="0.3"/>
|
||||||
|
<circle cx="38" cy="10" r="3" fill="white" opacity="0.5"/>
|
||||||
|
<!-- Wicklung -->
|
||||||
|
<line x1="25" y1="44" x2="29" y2="42" stroke="#C0A040" stroke-width="2"/>
|
||||||
|
<line x1="26" y1="40" x2="30" y2="38" stroke="#C0A040" stroke-width="2"/>
|
||||||
|
<line x1="27" y1="36" x2="31" y2="34" stroke="#C0A040" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 787 B |
43
icons/wooden_staff_icon.svg.import
Normal file
43
icons/wooden_staff_icon.svg.import
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dts32a6fpdc84"
|
||||||
|
path="res://.godot/imported/wooden_staff_icon.svg-9178c7c24ac6ded32a37ea646c50397f.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://icons/wooden_staff_icon.svg"
|
||||||
|
dest_files=["res://.godot/imported/wooden_staff_icon.svg-9178c7c24ac6ded32a37ea646c50397f.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
|
||||||
33
inventory.gd
33
inventory.gd
|
|
@ -90,6 +90,39 @@ func get_item(index: int):
|
||||||
return null
|
return null
|
||||||
return items[index]
|
return items[index]
|
||||||
|
|
||||||
|
# Zwei Items im Inventar tauschen
|
||||||
|
func swap_items(index_a: int, index_b: int):
|
||||||
|
if index_a < 0 or index_b < 0:
|
||||||
|
return
|
||||||
|
if index_a >= items.size() or index_b >= items.size():
|
||||||
|
return
|
||||||
|
var temp = items[index_a]
|
||||||
|
items[index_a] = items[index_b]
|
||||||
|
items[index_b] = temp
|
||||||
|
inventory_changed.emit()
|
||||||
|
|
||||||
|
# Item an bestimmten Index verschieben (von aktuellem Platz)
|
||||||
|
func move_item(from_index: int, to_index: int):
|
||||||
|
if from_index < 0 or from_index >= items.size():
|
||||||
|
return
|
||||||
|
if to_index < 0 or to_index >= MAX_SLOTS:
|
||||||
|
return
|
||||||
|
if from_index == to_index:
|
||||||
|
return
|
||||||
|
if to_index < items.size():
|
||||||
|
# Ziel belegt -> tauschen
|
||||||
|
swap_items(from_index, to_index)
|
||||||
|
else:
|
||||||
|
# Ziel leer -> Item verschieben
|
||||||
|
var item = items[from_index]
|
||||||
|
items.remove_at(from_index)
|
||||||
|
# Index anpassen falls nötig
|
||||||
|
if to_index >= items.size():
|
||||||
|
items.append(item)
|
||||||
|
else:
|
||||||
|
items.insert(to_index, item)
|
||||||
|
inventory_changed.emit()
|
||||||
|
|
||||||
# Anzahl Items
|
# Anzahl Items
|
||||||
func item_count() -> int:
|
func item_count() -> int:
|
||||||
return items.size()
|
return items.size()
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,10 @@ var player = null
|
||||||
|
|
||||||
# Drag & Drop
|
# Drag & Drop
|
||||||
var dragging = false
|
var dragging = false
|
||||||
var drag_item: Consumable = null
|
var drag_item = null # Equipment oder Consumable
|
||||||
|
var drag_from_index: int = -1 # Inventar-Index von dem gedraggt wird
|
||||||
var drag_icon: TextureRect = null
|
var drag_icon: TextureRect = null
|
||||||
|
var drag_highlight_slot: int = -1 # Aktuell hervorgehobener Inventar-Slot
|
||||||
|
|
||||||
@onready var panel = $Panel
|
@onready var panel = $Panel
|
||||||
@onready var gold_label = $Panel/VBoxContainer/Header/GoldLabel
|
@onready var gold_label = $Panel/VBoxContainer/Header/GoldLabel
|
||||||
|
|
@ -68,6 +70,9 @@ func _create_slot(index: int) -> Panel:
|
||||||
style.set_border_width_all(1)
|
style.set_border_width_all(1)
|
||||||
slot.add_theme_stylebox_override("panel", style)
|
slot.add_theme_stylebox_override("panel", style)
|
||||||
|
|
||||||
|
# Alle Slots brauchen gui_input für Drop-Erkennung
|
||||||
|
slot.gui_input.connect(_on_slot_input.bind(index))
|
||||||
|
|
||||||
# Item vorhanden?
|
# Item vorhanden?
|
||||||
if player.inventory and index < player.inventory.item_count():
|
if player.inventory and index < player.inventory.item_count():
|
||||||
var item = player.inventory.get_item(index)
|
var item = player.inventory.get_item(index)
|
||||||
|
|
@ -85,6 +90,7 @@ func _create_slot(index: int) -> Panel:
|
||||||
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||||
icon.custom_minimum_size = Vector2(SLOT_SIZE - 4, SLOT_SIZE - 4)
|
icon.custom_minimum_size = Vector2(SLOT_SIZE - 4, SLOT_SIZE - 4)
|
||||||
icon.position = Vector2(2, 2)
|
icon.position = Vector2(2, 2)
|
||||||
|
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
slot.add_child(icon)
|
slot.add_child(icon)
|
||||||
else:
|
else:
|
||||||
# Fallback: Text
|
# Fallback: Text
|
||||||
|
|
@ -96,6 +102,7 @@ func _create_slot(index: int) -> Panel:
|
||||||
if item is Equipment:
|
if item is Equipment:
|
||||||
label.modulate = Equipment.get_rarity_color(item.rarity)
|
label.modulate = Equipment.get_rarity_color(item.rarity)
|
||||||
label.anchors_preset = Control.PRESET_FULL_RECT
|
label.anchors_preset = Control.PRESET_FULL_RECT
|
||||||
|
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
slot.add_child(label)
|
slot.add_child(label)
|
||||||
|
|
||||||
# Stack-Count für Consumables
|
# Stack-Count für Consumables
|
||||||
|
|
@ -107,25 +114,30 @@ func _create_slot(index: int) -> Panel:
|
||||||
stack_label.add_theme_font_size_override("font_size", 11)
|
stack_label.add_theme_font_size_override("font_size", 11)
|
||||||
stack_label.size = Vector2(SLOT_SIZE - 4, SLOT_SIZE - 4)
|
stack_label.size = Vector2(SLOT_SIZE - 4, SLOT_SIZE - 4)
|
||||||
stack_label.position = Vector2(0, 0)
|
stack_label.position = Vector2(0, 0)
|
||||||
|
stack_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
slot.add_child(stack_label)
|
slot.add_child(stack_label)
|
||||||
|
|
||||||
# Rahmen
|
# Rahmen
|
||||||
if item is Equipment:
|
if item is Equipment:
|
||||||
style.border_color = Equipment.get_rarity_color(item.rarity)
|
style.border_color = Equipment.get_rarity_color(item.rarity)
|
||||||
elif item is Consumable:
|
elif item is Consumable:
|
||||||
style.border_color = Color(0.3, 0.7, 0.3) # Grüner Rand für Consumables
|
style.border_color = Color(0.3, 0.7, 0.3)
|
||||||
style.set_border_width_all(2)
|
style.set_border_width_all(2)
|
||||||
|
|
||||||
# Klick-Handler
|
|
||||||
slot.gui_input.connect(_on_slot_clicked.bind(index, item))
|
|
||||||
|
|
||||||
# Tooltip
|
# Tooltip
|
||||||
slot.tooltip_text = _get_item_tooltip(item)
|
slot.tooltip_text = _get_item_tooltip(item)
|
||||||
|
|
||||||
return slot
|
return slot
|
||||||
|
|
||||||
func _on_slot_clicked(event: InputEvent, index: int, item):
|
func _on_slot_input(event: InputEvent, index: int):
|
||||||
if event is InputEventMouseButton and event.pressed:
|
if event is InputEventMouseButton and event.pressed:
|
||||||
|
var item = null
|
||||||
|
if player.inventory and index < player.inventory.item_count():
|
||||||
|
item = player.inventory.get_item(index)
|
||||||
|
|
||||||
|
if item == null:
|
||||||
|
return
|
||||||
|
|
||||||
if item is Equipment:
|
if item is Equipment:
|
||||||
if event.button_index == MOUSE_BUTTON_RIGHT:
|
if event.button_index == MOUSE_BUTTON_RIGHT:
|
||||||
# Rechtsklick auf Equipment: Anlegen
|
# Rechtsklick auf Equipment: Anlegen
|
||||||
|
|
@ -135,6 +147,9 @@ func _on_slot_clicked(event: InputEvent, index: int, item):
|
||||||
if old_item:
|
if old_item:
|
||||||
player.inventory.add_item(old_item)
|
player.inventory.add_item(old_item)
|
||||||
_refresh_inventory()
|
_refresh_inventory()
|
||||||
|
elif event.button_index == MOUSE_BUTTON_LEFT:
|
||||||
|
# Linksklick: Drag starten
|
||||||
|
_start_drag(item, index)
|
||||||
elif item is Consumable:
|
elif item is Consumable:
|
||||||
if event.button_index == MOUSE_BUTTON_RIGHT:
|
if event.button_index == MOUSE_BUTTON_RIGHT:
|
||||||
# Rechtsklick auf Consumable: Direkt benutzen
|
# Rechtsklick auf Consumable: Direkt benutzen
|
||||||
|
|
@ -146,15 +161,18 @@ func _on_slot_clicked(event: InputEvent, index: int, item):
|
||||||
player._update_action_bar_stacks()
|
player._update_action_bar_stacks()
|
||||||
elif event.button_index == MOUSE_BUTTON_LEFT:
|
elif event.button_index == MOUSE_BUTTON_LEFT:
|
||||||
# Linksklick: Drag starten
|
# Linksklick: Drag starten
|
||||||
_start_drag(item)
|
_start_drag(item, index)
|
||||||
|
|
||||||
# Drag & Drop System
|
# Drag & Drop System
|
||||||
func _start_drag(item: Consumable):
|
func _start_drag(item, index: int):
|
||||||
dragging = true
|
dragging = true
|
||||||
drag_item = item
|
drag_item = item
|
||||||
|
drag_from_index = index
|
||||||
# Icon am Mauszeiger erstellen
|
# Icon am Mauszeiger erstellen
|
||||||
|
var tex = item.icon if item.icon else null
|
||||||
drag_icon = TextureRect.new()
|
drag_icon = TextureRect.new()
|
||||||
drag_icon.texture = item.icon
|
if tex:
|
||||||
|
drag_icon.texture = tex
|
||||||
drag_icon.custom_minimum_size = Vector2(40, 40)
|
drag_icon.custom_minimum_size = Vector2(40, 40)
|
||||||
drag_icon.size = Vector2(40, 40)
|
drag_icon.size = Vector2(40, 40)
|
||||||
drag_icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
|
drag_icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
|
||||||
|
|
@ -174,9 +192,12 @@ func _start_drag(item: Consumable):
|
||||||
func _process(_delta):
|
func _process(_delta):
|
||||||
if dragging and drag_icon:
|
if dragging and drag_icon:
|
||||||
drag_icon.position = get_viewport().get_mouse_position() - Vector2(20, 20)
|
drag_icon.position = get_viewport().get_mouse_position() - Vector2(20, 20)
|
||||||
|
var mouse_pos = get_viewport().get_mouse_position()
|
||||||
# HUD Slots highlighten
|
# HUD Slots highlighten
|
||||||
if player and player.hud:
|
if player and player.hud:
|
||||||
player.hud.update_drag_hover(get_viewport().get_mouse_position())
|
player.hud.update_drag_hover(mouse_pos)
|
||||||
|
# Inventar-Slots highlighten
|
||||||
|
_update_inventory_hover(mouse_pos)
|
||||||
|
|
||||||
func _input(event):
|
func _input(event):
|
||||||
if not dragging:
|
if not dragging:
|
||||||
|
|
@ -188,20 +209,80 @@ func _input(event):
|
||||||
func _end_drag():
|
func _end_drag():
|
||||||
if not dragging:
|
if not dragging:
|
||||||
return
|
return
|
||||||
# Prüfen ob über einem Action-Slot
|
var mouse_pos = get_viewport().get_mouse_position()
|
||||||
|
|
||||||
|
# Prüfen ob über einem Inventar-Slot
|
||||||
|
var inv_slot = _get_inventory_slot_at(mouse_pos)
|
||||||
|
if inv_slot >= 0 and inv_slot != drag_from_index:
|
||||||
|
# Innerhalb Inventar verschieben/tauschen
|
||||||
|
player.inventory.move_item(drag_from_index, inv_slot)
|
||||||
|
else:
|
||||||
|
# Prüfen ob über einem Action-Slot (nur Consumables)
|
||||||
|
if player and player.hud:
|
||||||
|
var action_slot = player.hud.get_slot_at_position(mouse_pos)
|
||||||
|
if action_slot >= 0 and action_slot <= 8 and drag_item:
|
||||||
|
if drag_item is Consumable:
|
||||||
|
player.assign_to_action_bar(action_slot, drag_item)
|
||||||
|
print(drag_item.item_name + " auf Aktionsleiste Slot " + str(action_slot + 1) + " gelegt")
|
||||||
|
|
||||||
|
# HUD Highlight deaktivieren
|
||||||
if player and player.hud:
|
if player and player.hud:
|
||||||
var slot_index = player.hud.get_slot_at_position(get_viewport().get_mouse_position())
|
|
||||||
if slot_index >= 2 and slot_index <= 8 and drag_item:
|
|
||||||
player.assign_to_action_bar(slot_index, drag_item)
|
|
||||||
print(drag_item.item_name + " auf Slot " + str(slot_index + 1) + " gelegt")
|
|
||||||
player.hud.set_drag_active(false)
|
player.hud.set_drag_active(false)
|
||||||
|
# Inventar Highlight entfernen
|
||||||
|
_clear_inventory_highlight()
|
||||||
# Aufräumen
|
# Aufräumen
|
||||||
if drag_icon:
|
if drag_icon:
|
||||||
var drag_layer = drag_icon.get_parent()
|
var drag_layer = drag_icon.get_parent()
|
||||||
drag_layer.queue_free() # Entfernt DragLayer + Icon
|
drag_layer.queue_free()
|
||||||
drag_icon = null
|
drag_icon = null
|
||||||
dragging = false
|
dragging = false
|
||||||
drag_item = null
|
drag_item = null
|
||||||
|
drag_from_index = -1
|
||||||
|
|
||||||
|
# Inventar-Slot unter Mausposition finden
|
||||||
|
func _get_inventory_slot_at(mouse_pos: Vector2) -> int:
|
||||||
|
for i in range(item_grid.get_child_count()):
|
||||||
|
var slot = item_grid.get_child(i)
|
||||||
|
var rect = slot.get_global_rect()
|
||||||
|
if rect.has_point(mouse_pos):
|
||||||
|
return i
|
||||||
|
return -1
|
||||||
|
|
||||||
|
# Inventar-Slot Highlight während Drag
|
||||||
|
func _update_inventory_hover(mouse_pos: Vector2):
|
||||||
|
var hovered = _get_inventory_slot_at(mouse_pos)
|
||||||
|
if hovered == drag_highlight_slot:
|
||||||
|
return
|
||||||
|
_clear_inventory_highlight()
|
||||||
|
if hovered >= 0 and hovered != drag_from_index:
|
||||||
|
drag_highlight_slot = hovered
|
||||||
|
var slot = item_grid.get_child(hovered)
|
||||||
|
var style = StyleBoxFlat.new()
|
||||||
|
style.bg_color = Color(0.15, 0.15, 0.15)
|
||||||
|
style.border_color = Color(1, 0.9, 0, 1) # Gelber Rand
|
||||||
|
style.set_border_width_all(3)
|
||||||
|
slot.add_theme_stylebox_override("panel", style)
|
||||||
|
|
||||||
|
func _clear_inventory_highlight():
|
||||||
|
if drag_highlight_slot >= 0 and drag_highlight_slot < item_grid.get_child_count():
|
||||||
|
# Style zurücksetzen
|
||||||
|
var slot = item_grid.get_child(drag_highlight_slot)
|
||||||
|
var item = null
|
||||||
|
if player.inventory and drag_highlight_slot < player.inventory.item_count():
|
||||||
|
item = player.inventory.get_item(drag_highlight_slot)
|
||||||
|
var style = StyleBoxFlat.new()
|
||||||
|
style.bg_color = Color(0.15, 0.15, 0.15)
|
||||||
|
if item is Equipment:
|
||||||
|
style.border_color = Equipment.get_rarity_color(item.rarity)
|
||||||
|
style.set_border_width_all(2)
|
||||||
|
elif item is Consumable:
|
||||||
|
style.border_color = Color(0.3, 0.7, 0.3)
|
||||||
|
style.set_border_width_all(2)
|
||||||
|
else:
|
||||||
|
style.border_color = Color(0.3, 0.3, 0.3)
|
||||||
|
style.set_border_width_all(1)
|
||||||
|
slot.add_theme_stylebox_override("panel", style)
|
||||||
|
drag_highlight_slot = -1
|
||||||
|
|
||||||
func _get_item_tooltip(item) -> String:
|
func _get_item_tooltip(item) -> String:
|
||||||
if item is Consumable:
|
if item is Consumable:
|
||||||
|
|
@ -217,7 +298,7 @@ func _get_consumable_tooltip(item: Consumable) -> String:
|
||||||
tooltip += item.get_effect_text() + "\n"
|
tooltip += item.get_effect_text() + "\n"
|
||||||
tooltip += "Cooldown: " + str(item.cooldown) + "s\n"
|
tooltip += "Cooldown: " + str(item.cooldown) + "s\n"
|
||||||
tooltip += "Anzahl: " + str(item.stack_size) + "/" + str(item.max_stack) + "\n"
|
tooltip += "Anzahl: " + str(item.stack_size) + "/" + str(item.max_stack) + "\n"
|
||||||
tooltip += "\n[Rechtsklick: Benutzen]\n[Shift+Linksklick: Auf Leiste legen]"
|
tooltip += "\n[Rechtsklick: Benutzen]\n[Linksklick: Ziehen]"
|
||||||
return tooltip
|
return tooltip
|
||||||
|
|
||||||
func _get_equipment_tooltip(item: Equipment) -> String:
|
func _get_equipment_tooltip(item: Equipment) -> String:
|
||||||
|
|
|
||||||
219
player.gd
219
player.gd
|
|
@ -30,13 +30,19 @@ var current_resource = 0
|
||||||
var target = null # Aktuell markierter Gegner
|
var target = null # Aktuell markierter Gegner
|
||||||
|
|
||||||
# Aktionsleiste: Skills (String) oder Consumables in Slots (0-8)
|
# Aktionsleiste: Skills (String) oder Consumables in Slots (0-8)
|
||||||
# Skills: "autoattack", "heavy_strike" — frei verschiebbar
|
var action_bar_items: Array = [null, null, null, null, null, null, null, null, null]
|
||||||
var action_bar_items: Array = ["autoattack", "heavy_strike", null, null, null, null, null, null, null]
|
|
||||||
|
|
||||||
# Alle verfügbaren Skills (für Fähigkeiten-Panel)
|
# Alle verfügbaren Skills (für Fähigkeiten-Panel) — wird klassenabhängig befüllt
|
||||||
var available_skills: Array = [
|
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"},
|
# Skill-Definitionen pro Klasse
|
||||||
|
const AUTOATTACK_SKILL = {"id": "autoattack", "name": "Autoattack", "icon": "res://icons/autoattack_icon.svg", "description": "Greift das Ziel im Nahkampf an.\nSchaden: Waffenschaden + Main-Stat"}
|
||||||
|
const MELEE_SKILLS = [
|
||||||
|
{"id": "heavy_strike", "name": "Heavy Strike", "icon": "res://icons/heavy_strike_icon.svg", "description": "Starker Hieb mit 3s Cooldown.\nSchaden: 10-15 + Main-Stat\nReichweite: 4.0"},
|
||||||
|
]
|
||||||
|
const MAGE_SKILLS = [
|
||||||
|
{"id": "wand", "name": "Zauberstab", "icon": "res://icons/autoattack_icon.svg", "description": "Magischer Fernkampfangriff.\nSchaden: Waffenschaden + INT\nReichweite: 20.0\nIgnoriert Rüstung\nDeaktiviert Autoattack"},
|
||||||
|
{"id": "frostbolt", "name": "Frostblitz", "icon": "res://icons/frostbolt_icon.svg", "description": "Magischer Fernkampfangriff mit Castzeit.\nSchaden: 12-20 + INT\nManakosten: 20\nReichweite: 20.0\nCastzeit: 1.5s\nCooldown: 2.5s"},
|
||||||
]
|
]
|
||||||
var potion_cooldown: float = 0.0
|
var potion_cooldown: float = 0.0
|
||||||
const POTION_COOLDOWN_TIME = 1.0
|
const POTION_COOLDOWN_TIME = 1.0
|
||||||
|
|
@ -63,6 +69,10 @@ var haste: float = 0.0 # Angriffsgeschwindigkeits-Bonus (0.1 = 10% schneller)
|
||||||
# Autoattack System
|
# Autoattack System
|
||||||
var autoattack_active = false # Ob Autoattack aktiv ist
|
var autoattack_active = false # Ob Autoattack aktiv ist
|
||||||
|
|
||||||
|
# Zauberstab System (Magier-Fernkampf, exklusiv mit Autoattack)
|
||||||
|
var wand_active = false
|
||||||
|
const WAND_RANGE = 20.0
|
||||||
|
|
||||||
# Skills System - individuelle Cooldowns (zusätzlich zum GCD)
|
# Skills System - individuelle Cooldowns (zusätzlich zum GCD)
|
||||||
var heavy_strike_cooldown = 0.0
|
var heavy_strike_cooldown = 0.0
|
||||||
const HEAVY_STRIKE_DAMAGE_MIN = 10
|
const HEAVY_STRIKE_DAMAGE_MIN = 10
|
||||||
|
|
@ -70,6 +80,20 @@ const HEAVY_STRIKE_DAMAGE_MAX = 15
|
||||||
const HEAVY_STRIKE_COOLDOWN = 3.0
|
const HEAVY_STRIKE_COOLDOWN = 3.0
|
||||||
const HEAVY_STRIKE_RANGE = 4.0
|
const HEAVY_STRIKE_RANGE = 4.0
|
||||||
|
|
||||||
|
var frostbolt_cooldown = 0.0
|
||||||
|
const FROSTBOLT_DAMAGE_MIN = 12
|
||||||
|
const FROSTBOLT_DAMAGE_MAX = 20
|
||||||
|
const FROSTBOLT_COOLDOWN = 2.5
|
||||||
|
const FROSTBOLT_RANGE = 20.0
|
||||||
|
const FROSTBOLT_MANA_COST = 20
|
||||||
|
const FROSTBOLT_CAST_TIME = 1.5
|
||||||
|
|
||||||
|
# Cast-System
|
||||||
|
var is_casting = false
|
||||||
|
var cast_time_remaining = 0.0
|
||||||
|
var cast_time_total = 0.0
|
||||||
|
var cast_spell_id = "" # Welcher Zauber gecastet wird
|
||||||
|
|
||||||
@onready var camera_pivot = $CameraPivot
|
@onready var camera_pivot = $CameraPivot
|
||||||
@onready var camera = $CameraPivot/Camera3D
|
@onready var camera = $CameraPivot/Camera3D
|
||||||
@onready var hud = $HUD
|
@onready var hud = $HUD
|
||||||
|
|
@ -88,6 +112,8 @@ func _ready():
|
||||||
hud.update_resource(current_resource, max_resource, get_resource_name())
|
hud.update_resource(current_resource, max_resource, get_resource_name())
|
||||||
hud.update_level(level, current_xp, xp_to_next_level)
|
hud.update_level(level, current_xp, xp_to_next_level)
|
||||||
hud.set_active_slot(0)
|
hud.set_active_slot(0)
|
||||||
|
# Skills klassenabhängig aufbauen
|
||||||
|
_init_class_skills()
|
||||||
# Aktionsleiste initialisieren (Skills + Items)
|
# Aktionsleiste initialisieren (Skills + Items)
|
||||||
for i in range(9):
|
for i in range(9):
|
||||||
_refresh_action_slot(i)
|
_refresh_action_slot(i)
|
||||||
|
|
@ -138,6 +164,21 @@ func _calculate_stats():
|
||||||
|
|
||||||
print("Stats berechnet - STR: ", strength, " AGI: ", agility, " INT: ", intelligence, " STA: ", stamina, " ARM: ", armor, " HP: ", max_hp, " RES: ", max_resource)
|
print("Stats berechnet - STR: ", strength, " AGI: ", agility, " INT: ", intelligence, " STA: ", stamina, " ARM: ", armor, " HP: ", max_hp, " RES: ", max_resource)
|
||||||
|
|
||||||
|
# Skills klassenabhängig aufbauen
|
||||||
|
func _init_class_skills():
|
||||||
|
available_skills = [AUTOATTACK_SKILL.duplicate()]
|
||||||
|
if character_class and character_class.resource_type == CharacterClass.ResourceType.MANA:
|
||||||
|
# Magier: Autoattack + Zauberstab + Frostblitz
|
||||||
|
available_skills.append_array(MAGE_SKILLS.duplicate(true))
|
||||||
|
action_bar_items[0] = "wand"
|
||||||
|
action_bar_items[1] = "frostbolt"
|
||||||
|
action_bar_items[2] = "autoattack"
|
||||||
|
else:
|
||||||
|
# Krieger/Schurke: Autoattack + Heavy Strike
|
||||||
|
available_skills.append_array(MELEE_SKILLS.duplicate(true))
|
||||||
|
action_bar_items[0] = "autoattack"
|
||||||
|
action_bar_items[1] = "heavy_strike"
|
||||||
|
|
||||||
# Klassen-Ressource berechnen (Mana aus INT, Energie fix, Wut fix)
|
# Klassen-Ressource berechnen (Mana aus INT, Energie fix, Wut fix)
|
||||||
func _calculate_resource():
|
func _calculate_resource():
|
||||||
if character_class == null or character_class.resource_type == CharacterClass.ResourceType.NONE:
|
if character_class == null or character_class.resource_type == CharacterClass.ResourceType.NONE:
|
||||||
|
|
@ -304,8 +345,12 @@ func _get_slot_cooldown(slot_index: int) -> float:
|
||||||
match entry:
|
match entry:
|
||||||
"autoattack":
|
"autoattack":
|
||||||
return global_cooldown
|
return global_cooldown
|
||||||
|
"wand":
|
||||||
|
return global_cooldown
|
||||||
"heavy_strike":
|
"heavy_strike":
|
||||||
return heavy_strike_cooldown
|
return heavy_strike_cooldown
|
||||||
|
"frostbolt":
|
||||||
|
return frostbolt_cooldown
|
||||||
elif entry is Consumable:
|
elif entry is Consumable:
|
||||||
return potion_cooldown
|
return potion_cooldown
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
@ -334,10 +379,18 @@ func _use_action_slot(slot_index: int):
|
||||||
match entry:
|
match entry:
|
||||||
"autoattack":
|
"autoattack":
|
||||||
if target != null and global_cooldown <= 0:
|
if target != null and global_cooldown <= 0:
|
||||||
|
wand_active = false # Zauberstab deaktivieren
|
||||||
start_autoattack()
|
start_autoattack()
|
||||||
perform_autoattack()
|
perform_autoattack()
|
||||||
|
"wand":
|
||||||
|
if target != null and global_cooldown <= 0:
|
||||||
|
autoattack_active = false # Autoattack deaktivieren
|
||||||
|
start_wand()
|
||||||
|
perform_wand_attack()
|
||||||
"heavy_strike":
|
"heavy_strike":
|
||||||
use_heavy_strike()
|
use_heavy_strike()
|
||||||
|
"frostbolt":
|
||||||
|
use_frostbolt()
|
||||||
elif entry is Consumable:
|
elif entry is Consumable:
|
||||||
if use_consumable(entry):
|
if use_consumable(entry):
|
||||||
if entry.stack_size <= 0:
|
if entry.stack_size <= 0:
|
||||||
|
|
@ -348,6 +401,8 @@ func _use_action_slot(slot_index: int):
|
||||||
func take_damage(amount):
|
func take_damage(amount):
|
||||||
current_hp = clamp(current_hp - amount, 0, max_hp)
|
current_hp = clamp(current_hp - amount, 0, max_hp)
|
||||||
hud.update_health(current_hp, max_hp)
|
hud.update_health(current_hp, max_hp)
|
||||||
|
if is_casting:
|
||||||
|
_cancel_cast()
|
||||||
if current_hp <= 0:
|
if current_hp <= 0:
|
||||||
die()
|
die()
|
||||||
|
|
||||||
|
|
@ -488,7 +543,7 @@ func get_dps() -> float:
|
||||||
# DPS = Schaden / GCD
|
# DPS = Schaden / GCD
|
||||||
return total_damage / gcd
|
return total_damage / gcd
|
||||||
|
|
||||||
# Reichweite basierend auf ausgerüsteter Waffe (unbewaffnet = 3.0)
|
# Reichweite basierend auf ausgerüsteter Waffe (Nahkampf)
|
||||||
func get_attack_range() -> float:
|
func get_attack_range() -> float:
|
||||||
var weapon = get_equipped_weapon()
|
var weapon = get_equipped_weapon()
|
||||||
if weapon == null:
|
if weapon == null:
|
||||||
|
|
@ -503,9 +558,7 @@ func set_target(new_target, start_attack: bool = false):
|
||||||
target.show_health()
|
target.show_health()
|
||||||
print("Ziel markiert: ", target.name)
|
print("Ziel markiert: ", target.name)
|
||||||
if start_attack:
|
if start_attack:
|
||||||
start_autoattack()
|
_start_default_attack()
|
||||||
if global_cooldown <= 0:
|
|
||||||
perform_autoattack()
|
|
||||||
|
|
||||||
# Ziel komplett aufheben und Autoattack stoppen
|
# Ziel komplett aufheben und Autoattack stoppen
|
||||||
func clear_target():
|
func clear_target():
|
||||||
|
|
@ -513,7 +566,19 @@ func clear_target():
|
||||||
target.hide_health()
|
target.hide_health()
|
||||||
target = null
|
target = null
|
||||||
autoattack_active = false
|
autoattack_active = false
|
||||||
print("Ziel aufgehoben, Autoattack gestoppt")
|
wand_active = false
|
||||||
|
print("Ziel aufgehoben, Angriff gestoppt")
|
||||||
|
|
||||||
|
# Standard-Angriff starten (Rechtsklick): Magier=Zauberstab, Rest=Autoattack
|
||||||
|
func _start_default_attack():
|
||||||
|
if character_class and character_class.resource_type == CharacterClass.ResourceType.MANA:
|
||||||
|
start_wand()
|
||||||
|
if global_cooldown <= 0:
|
||||||
|
perform_wand_attack()
|
||||||
|
else:
|
||||||
|
start_autoattack()
|
||||||
|
if global_cooldown <= 0:
|
||||||
|
perform_autoattack()
|
||||||
|
|
||||||
# Autoattack aktivieren
|
# Autoattack aktivieren
|
||||||
func start_autoattack():
|
func start_autoattack():
|
||||||
|
|
@ -525,6 +590,34 @@ func stop_autoattack():
|
||||||
autoattack_active = false
|
autoattack_active = false
|
||||||
print("Autoattack deaktiviert")
|
print("Autoattack deaktiviert")
|
||||||
|
|
||||||
|
# Zauberstab aktivieren (deaktiviert Autoattack)
|
||||||
|
func start_wand():
|
||||||
|
wand_active = true
|
||||||
|
autoattack_active = false
|
||||||
|
print("Zauberstab aktiviert")
|
||||||
|
|
||||||
|
# Zauberstab deaktivieren
|
||||||
|
func stop_wand():
|
||||||
|
wand_active = false
|
||||||
|
print("Zauberstab deaktiviert")
|
||||||
|
|
||||||
|
# Zauberstab-Angriff ausführen (Fernkampf, magisch)
|
||||||
|
func perform_wand_attack():
|
||||||
|
if target == null or not is_instance_valid(target):
|
||||||
|
target = null
|
||||||
|
wand_active = false
|
||||||
|
return
|
||||||
|
|
||||||
|
var distance = global_position.distance_to(target.global_position)
|
||||||
|
if distance <= WAND_RANGE:
|
||||||
|
var dmg = get_attack_damage()
|
||||||
|
if target.has_method("take_damage_from"):
|
||||||
|
target.take_damage_from(dmg, level, false) # Magisch, ignoriert Rüstung
|
||||||
|
else:
|
||||||
|
target.take_damage(dmg)
|
||||||
|
print("Zauberstab: ", dmg, " magischer Schaden")
|
||||||
|
trigger_global_cooldown()
|
||||||
|
|
||||||
# Führt einen Autoattack aus (wird vom GCD-System aufgerufen)
|
# Führt einen Autoattack aus (wird vom GCD-System aufgerufen)
|
||||||
func perform_autoattack():
|
func perform_autoattack():
|
||||||
if target == null or not is_instance_valid(target):
|
if target == null or not is_instance_valid(target):
|
||||||
|
|
@ -535,13 +628,11 @@ func perform_autoattack():
|
||||||
var distance = global_position.distance_to(target.global_position)
|
var distance = global_position.distance_to(target.global_position)
|
||||||
if distance <= get_attack_range():
|
if distance <= get_attack_range():
|
||||||
var dmg = get_attack_damage()
|
var dmg = get_attack_damage()
|
||||||
# Neues Schadenssystem mit Rüstung und Level-Differenz
|
|
||||||
if target.has_method("take_damage_from"):
|
if target.has_method("take_damage_from"):
|
||||||
target.take_damage_from(dmg, level, true) # true = Nahkampf
|
target.take_damage_from(dmg, level, true) # Nahkampf
|
||||||
else:
|
else:
|
||||||
target.take_damage(dmg)
|
target.take_damage(dmg)
|
||||||
print("Autoattack: ", dmg, " Schaden (GCD: %.2fs, DPS: %.1f)" % [get_current_gcd(), get_dps()])
|
print("Autoattack: ", dmg, " Schaden (GCD: %.2fs, DPS: %.1f)" % [get_current_gcd(), get_dps()])
|
||||||
# GCD auslösen basierend auf Waffengeschwindigkeit + Haste
|
|
||||||
trigger_global_cooldown()
|
trigger_global_cooldown()
|
||||||
|
|
||||||
# Global Cooldown auslösen (basierend auf Waffe + Haste)
|
# Global Cooldown auslösen (basierend auf Waffe + Haste)
|
||||||
|
|
@ -577,6 +668,82 @@ func use_heavy_strike():
|
||||||
start_autoattack() # Autoattack nach Skill automatisch aktivieren
|
start_autoattack() # Autoattack nach Skill automatisch aktivieren
|
||||||
print("Heavy Strike! ", damage, " Rohschaden")
|
print("Heavy Strike! ", damage, " Rohschaden")
|
||||||
|
|
||||||
|
# Frostblitz: Cast starten
|
||||||
|
func use_frostbolt():
|
||||||
|
if is_casting:
|
||||||
|
return # Bereits am Casten
|
||||||
|
if target == null or not is_instance_valid(target):
|
||||||
|
print("Kein Ziel für Frostblitz!")
|
||||||
|
return
|
||||||
|
if frostbolt_cooldown > 0:
|
||||||
|
print("Frostblitz noch im Cooldown: ", "%.1f" % frostbolt_cooldown, "s")
|
||||||
|
return
|
||||||
|
if current_resource < FROSTBOLT_MANA_COST:
|
||||||
|
print("Nicht genug Mana für Frostblitz! (", current_resource, "/", FROSTBOLT_MANA_COST, ")")
|
||||||
|
return
|
||||||
|
var distance = global_position.distance_to(target.global_position)
|
||||||
|
if distance > FROSTBOLT_RANGE:
|
||||||
|
print("Ziel zu weit entfernt für Frostblitz!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Cast starten
|
||||||
|
_start_cast("frostbolt", FROSTBOLT_CAST_TIME)
|
||||||
|
print("Frostblitz wird gewirkt... (", FROSTBOLT_CAST_TIME, "s)")
|
||||||
|
|
||||||
|
# Frostblitz: Schaden anwenden nach erfolgreichem Cast
|
||||||
|
func _finish_frostbolt():
|
||||||
|
if target == null or not is_instance_valid(target):
|
||||||
|
print("Ziel verloren!")
|
||||||
|
return
|
||||||
|
var distance = global_position.distance_to(target.global_position)
|
||||||
|
if distance > FROSTBOLT_RANGE:
|
||||||
|
print("Ziel zu weit entfernt!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Mana abziehen
|
||||||
|
spend_resource(FROSTBOLT_MANA_COST)
|
||||||
|
|
||||||
|
var base_damage = randi_range(FROSTBOLT_DAMAGE_MIN, FROSTBOLT_DAMAGE_MAX)
|
||||||
|
var stat_bonus = int(intelligence * CharacterClass.DAMAGE_PER_MAIN_STAT)
|
||||||
|
var damage = base_damage + stat_bonus
|
||||||
|
if target.has_method("take_damage_from"):
|
||||||
|
target.take_damage_from(damage, level, false)
|
||||||
|
else:
|
||||||
|
target.take_damage(damage)
|
||||||
|
frostbolt_cooldown = FROSTBOLT_COOLDOWN
|
||||||
|
trigger_global_cooldown()
|
||||||
|
start_wand() # Zauberstab nach Cast weiter aktiv
|
||||||
|
print("Frostblitz! ", damage, " magischer Schaden (", FROSTBOLT_MANA_COST, " Mana)")
|
||||||
|
|
||||||
|
# Cast-System
|
||||||
|
func _start_cast(spell_id: String, cast_time: float):
|
||||||
|
is_casting = true
|
||||||
|
cast_spell_id = spell_id
|
||||||
|
cast_time_total = cast_time
|
||||||
|
cast_time_remaining = cast_time
|
||||||
|
autoattack_active = false # Autoattack pausieren während Cast
|
||||||
|
hud.show_castbar(spell_id, cast_time)
|
||||||
|
|
||||||
|
func _cancel_cast():
|
||||||
|
if not is_casting:
|
||||||
|
return
|
||||||
|
is_casting = false
|
||||||
|
cast_spell_id = ""
|
||||||
|
cast_time_remaining = 0.0
|
||||||
|
hud.hide_castbar()
|
||||||
|
print("Zauber unterbrochen!")
|
||||||
|
|
||||||
|
func _finish_cast():
|
||||||
|
var spell = cast_spell_id
|
||||||
|
is_casting = false
|
||||||
|
cast_spell_id = ""
|
||||||
|
cast_time_remaining = 0.0
|
||||||
|
hud.hide_castbar()
|
||||||
|
# Fertigen Zauber ausführen
|
||||||
|
match spell:
|
||||||
|
"frostbolt":
|
||||||
|
_finish_frostbolt()
|
||||||
|
|
||||||
# Raycast von der Kamera auf Mausposition — trifft Gegner mit take_damage()
|
# Raycast von der Kamera auf Mausposition — trifft Gegner mit take_damage()
|
||||||
func _try_select_target(start_attack: bool = false):
|
func _try_select_target(start_attack: bool = false):
|
||||||
var space_state = get_world_3d().direct_space_state
|
var space_state = get_world_3d().direct_space_state
|
||||||
|
|
@ -595,17 +762,29 @@ func _try_select_target(start_attack: bool = false):
|
||||||
clear_target()
|
clear_target()
|
||||||
|
|
||||||
func _physics_process(delta):
|
func _physics_process(delta):
|
||||||
|
# Cast-System
|
||||||
|
if is_casting:
|
||||||
|
cast_time_remaining -= delta
|
||||||
|
hud.update_castbar(cast_time_total - cast_time_remaining, cast_time_total)
|
||||||
|
if cast_time_remaining <= 0:
|
||||||
|
_finish_cast()
|
||||||
|
|
||||||
# Global Cooldown herunterzählen (gilt für alle Aktionen)
|
# Global Cooldown herunterzählen (gilt für alle Aktionen)
|
||||||
if global_cooldown > 0:
|
if global_cooldown > 0:
|
||||||
global_cooldown -= delta
|
global_cooldown -= delta
|
||||||
|
|
||||||
# Wenn GCD bereit und Autoattack aktiv, versuche anzugreifen
|
# Wenn GCD bereit und nicht am Casten
|
||||||
if global_cooldown <= 0 and autoattack_active:
|
if global_cooldown <= 0 and not is_casting:
|
||||||
perform_autoattack()
|
if wand_active:
|
||||||
|
perform_wand_attack()
|
||||||
|
elif autoattack_active:
|
||||||
|
perform_autoattack()
|
||||||
|
|
||||||
# Skill-Cooldowns herunterzählen
|
# Skill-Cooldowns herunterzählen
|
||||||
if heavy_strike_cooldown > 0:
|
if heavy_strike_cooldown > 0:
|
||||||
heavy_strike_cooldown -= delta
|
heavy_strike_cooldown -= delta
|
||||||
|
if frostbolt_cooldown > 0:
|
||||||
|
frostbolt_cooldown -= delta
|
||||||
if potion_cooldown > 0:
|
if potion_cooldown > 0:
|
||||||
potion_cooldown -= delta
|
potion_cooldown -= delta
|
||||||
|
|
||||||
|
|
@ -621,6 +800,8 @@ func _physics_process(delta):
|
||||||
# Springen
|
# Springen
|
||||||
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
|
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
|
||||||
velocity.y = JUMP_VELOCITY
|
velocity.y = JUMP_VELOCITY
|
||||||
|
if is_casting:
|
||||||
|
_cancel_cast()
|
||||||
|
|
||||||
# Linksklick: nur markieren
|
# Linksklick: nur markieren
|
||||||
if Input.is_action_just_pressed("select_target"):
|
if Input.is_action_just_pressed("select_target"):
|
||||||
|
|
@ -665,6 +846,10 @@ func _physics_process(delta):
|
||||||
if Input.is_action_pressed("move_right"):
|
if Input.is_action_pressed("move_right"):
|
||||||
input_dir.x += 1
|
input_dir.x += 1
|
||||||
|
|
||||||
|
# Bewegung unterbricht Cast
|
||||||
|
if is_casting and input_dir.length() > 0:
|
||||||
|
_cancel_cast()
|
||||||
|
|
||||||
# Bewegung relativ zur Kamera
|
# Bewegung relativ zur Kamera
|
||||||
var world_yaw = rotation.y + camera_pivot.rotation.y
|
var world_yaw = rotation.y + camera_pivot.rotation.y
|
||||||
var forward = Vector3(-sin(world_yaw), 0, -cos(world_yaw)).normalized()
|
var forward = Vector3(-sin(world_yaw), 0, -cos(world_yaw)).normalized()
|
||||||
|
|
|
||||||
1
skill_panel.gd.uid
Normal file
1
skill_panel.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://gf03d1ewoxcf
|
||||||
17
world.gd
17
world.gd
|
|
@ -8,7 +8,8 @@ const CLASS_SELECTION_MENU = preload("res://class_selection_menu.tscn")
|
||||||
const RESPAWN_TIME = 5.0
|
const RESPAWN_TIME = 5.0
|
||||||
|
|
||||||
# Startausrüstung
|
# Startausrüstung
|
||||||
const STARTER_WEAPON = preload("res://equipment/iron_sword.tres")
|
const STARTER_SWORD = preload("res://equipment/iron_sword.tres")
|
||||||
|
const STARTER_STAFF = preload("res://equipment/wooden_staff.tres")
|
||||||
const STARTER_CHEST = preload("res://equipment/leather_chest.tres")
|
const STARTER_CHEST = preload("res://equipment/leather_chest.tres")
|
||||||
|
|
||||||
# Loot Tables
|
# Loot Tables
|
||||||
|
|
@ -34,13 +35,23 @@ func _on_start_game():
|
||||||
func _on_class_selected(character_class: CharacterClass):
|
func _on_class_selected(character_class: CharacterClass):
|
||||||
player.character_class = character_class
|
player.character_class = character_class
|
||||||
|
|
||||||
# Startausrüstung geben
|
# Skills klassenabhängig aufbauen
|
||||||
player.equip_item(STARTER_WEAPON)
|
player._init_class_skills()
|
||||||
|
for i in range(9):
|
||||||
|
player._refresh_action_slot(i)
|
||||||
|
|
||||||
|
# Startausrüstung klassenabhängig
|
||||||
|
if character_class.resource_type == CharacterClass.ResourceType.MANA:
|
||||||
|
player.equip_item(STARTER_STAFF)
|
||||||
|
else:
|
||||||
|
player.equip_item(STARTER_SWORD)
|
||||||
player.equip_item(STARTER_CHEST)
|
player.equip_item(STARTER_CHEST)
|
||||||
|
|
||||||
player._calculate_stats()
|
player._calculate_stats()
|
||||||
player.current_hp = player.max_hp
|
player.current_hp = player.max_hp
|
||||||
|
player.current_resource = player.max_resource
|
||||||
player.hud.update_health(player.current_hp, player.max_hp)
|
player.hud.update_health(player.current_hp, player.max_hp)
|
||||||
|
player.hud.update_resource(player.current_resource, player.max_resource, player.get_resource_name())
|
||||||
print("Klasse gewählt: ", character_class.class_name_de)
|
print("Klasse gewählt: ", character_class.class_name_de)
|
||||||
|
|
||||||
# Jetzt Gegner initialisieren
|
# Jetzt Gegner initialisieren
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue