update
This commit is contained in:
75
plan.md
75
plan.md
@@ -21,6 +21,7 @@
|
|||||||
- HUD (Instanz von hud.tscn)
|
- HUD (Instanz von hud.tscn)
|
||||||
|
|
||||||
- player.tscn — Spieler
|
- player.tscn — Spieler
|
||||||
|
- Gruppe (player)
|
||||||
- CharacterBody3D (player.gd)
|
- CharacterBody3D (player.gd)
|
||||||
- Kollision (CapsuleShape3D, 1.8m x 0.3m)
|
- Kollision (CapsuleShape3D, 1.8m x 0.3m)
|
||||||
- Mesh (CapsuleMesh)
|
- Mesh (CapsuleMesh)
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
- Camera3D (hinter/über Spieler)
|
- Camera3D (hinter/über Spieler)
|
||||||
- Movement (Node, movement.gd)
|
- Movement (Node, movement.gd)
|
||||||
- Combat (Node, combat.gd)
|
- Combat (Node, combat.gd)
|
||||||
- PlayerClass (Node, player_class.gd)
|
- Role (Node, role.gd)
|
||||||
- Targeting (Node, targeting.gd)
|
- Targeting (Node, targeting.gd)
|
||||||
- Health (Node, health.gd)
|
- Health (Node, health.gd)
|
||||||
- Shield (Node, shield.gd)
|
- Shield (Node, shield.gd)
|
||||||
@@ -40,23 +41,28 @@
|
|||||||
- ShieldBar (ProgressBar, links oben, unter HealthBar)
|
- ShieldBar (ProgressBar, links oben, unter HealthBar)
|
||||||
- RespawnTimer (Label, mitte, Countdown bei Tod)
|
- RespawnTimer (Label, mitte, Countdown bei Tod)
|
||||||
- AbilityBar (HBoxContainer, mitte unten)
|
- AbilityBar (HBoxContainer, mitte unten)
|
||||||
- ClassIcon (Label, Klassen-Symbol: T=Tank, D=Schaden, H=Heiler)
|
- RoleIcon (Label, T=Tank, D=Schaden, H=Heiler)
|
||||||
- Ability 1-5
|
- Abilities(Label, Fähigkeiten 1-4, Passive P)
|
||||||
|
|
||||||
- portal.tscn — Portal (Gegner-Spawner, anvisierbar, Gruppe "targetable")
|
- portal.tscn — Portal (Gegner-Spawner, anvisierbar)
|
||||||
|
- Gruppe (portals)
|
||||||
- StaticBody3D (portal.gd)
|
- StaticBody3D (portal.gd)
|
||||||
- Kollision (CylinderShape3D)
|
- Kollision (CylinderShape3D)
|
||||||
- Mesh (CylinderMesh, Platzhalter)
|
- Mesh (CylinderMesh, blau)
|
||||||
- Health (Node, health.gd)
|
- Health (Node, health.gd)
|
||||||
- HitArea (Area3D, Trefferbereich)
|
- HitArea (Area3D, Trefferbereich)
|
||||||
- CollisionShape3D
|
- CollisionShape3D
|
||||||
- PortalSpawner (Node, portal_spawner.gd)
|
- Spawner (Node, spawner.gd)
|
||||||
- Spawnt 3 Gegner bei 85%, 70%, 55%, 40%, 25%, 10% Leben (einmalig)
|
- DetectionArea (Area3D, 10m Radius, Auto-Targeting bei Betreten)
|
||||||
- Portal = Spawnpunkt der Gegner
|
- CollisionShape3D (SphereShape3D)
|
||||||
- 10m Aggro-Radius um das Portal
|
|
||||||
- Healthbar (Sprite3D + SubViewport, healthbar.gd)
|
- Healthbar (Sprite3D + SubViewport, healthbar.gd)
|
||||||
|
- Phase 1: Angreifbar, HP-Bar, spawnt 3 Gegner bei 85%/70%/55%/40%/25%/10% Leben
|
||||||
|
- Portal = Spawnpunkt, 10m Aggro-Radius
|
||||||
|
- Phase 2 (geplant): Bei 0 HP → Portal wird Gate, teleportiert in Dungeon
|
||||||
|
- Abgesperrter Bereich mit Gegner-Gruppen und Boss
|
||||||
|
|
||||||
- enemy.tscn — Gegner
|
- enemy.tscn — Gegner
|
||||||
|
- Gruppe (enemies)
|
||||||
- CharacterBody3D (enemy.gd)
|
- CharacterBody3D (enemy.gd)
|
||||||
- Kollision (CapsuleShape3D)
|
- Kollision (CapsuleShape3D)
|
||||||
- Mesh (SphereMesh, Platzhalter)
|
- Mesh (SphereMesh, Platzhalter)
|
||||||
@@ -80,41 +86,54 @@
|
|||||||
- player.gd — Kern, verbindet Komponenten
|
- player.gd — Kern, verbindet Komponenten
|
||||||
- camera.gd — LMB freies Umsehen, RMB Kamera + Laufrichtung anpassen
|
- camera.gd — LMB freies Umsehen, RMB Kamera + Laufrichtung anpassen
|
||||||
- movement.gd — Bewegung (WASD relativ zur Kamera), Springen, Schwerkraft
|
- movement.gd — Bewegung (WASD relativ zur Kamera), Springen, Schwerkraft
|
||||||
- combat.gd — Führt Abilities aus, verwaltet Cooldowns (GCD 1s), Fähigkeiten 1-5
|
- combat.gd — Führt Abilities aus, verwaltet Cooldowns (GCD 0.5s), Auto-Attack (10 Schaden, 1s, 20m)
|
||||||
- 1 Single: 10 Schaden, Distanzprüfung, 0s CD, GCD
|
- targeting.gd — Klick/TAB anvisieren (Gruppen "enemies" + "portals"), Kampfmodus bei Gegner-Angriff, Auto-Targeting (Gegner > Portal)
|
||||||
- 2 AOE: 5 Schaden im Bereich, 3s CD, GCD
|
|
||||||
- 3 Utility: Schild sofort auf 100%, 5s CD, kein GCD
|
|
||||||
- 4 Ult: 4x Single + 2x AOE um Ziel, 30s CD, GCD
|
|
||||||
- 5 Passive: 50% mehr Schaden (permanent aktiv, kein CD)
|
|
||||||
- targeting.gd — Klick/TAB anvisieren (Gruppe "targetable"), Kampfmodus bei Gegner-Angriff, Auto-Targeting auf nächsten Gegner
|
|
||||||
- event_bus.gd — Autoload-Singleton, globale Signals
|
- event_bus.gd — Autoload-Singleton, globale Signals
|
||||||
- portal.gd — Portal-Kern, Gruppe "targetable", kein Kampf/Aggro/State
|
- role.gd — Rollenwechsel ALT+1 bis ALT+3 (Tank/Schaden/Heiler), tauscht AbilitySet
|
||||||
- portal_spawner.gd — Spawnt Gegner bei Lebensschwellen, setzt Portal als Spawnpunkt
|
- respawn.gd — Bei Tod: Spieler deaktivieren, 3s Timer, Respawn am Startpunkt, Leben/Schild voll
|
||||||
- enemy.gd — Gegner-Kern, State Machine (Idle, Verfolgen, Angreifen, Zurückkehren), alarmiert Gegner in 3m, Gruppe "enemies" + "targetable"
|
- hud.gd — Reagiert auf Events, aktualisiert HealthBar/ShieldBar/AbilityBar/RespawnTimer
|
||||||
|
- portal.gd — Portal-Kern, Gruppe "portals", stirbt bei 0 HP, kein Kampf/Aggro/State
|
||||||
|
- enemy.gd — Gegner-Kern, State Machine (Idle, Verfolgen, Angreifen, Zurückkehren), alarmiert Gegner in 3m, Gruppe "enemies"
|
||||||
- enemy_movement.gd — Navigation zum Ziel/Spawnpunkt
|
- enemy_movement.gd — Navigation zum Ziel/Spawnpunkt
|
||||||
- enemy_combat.gd — Angriff über Event (damage_requested)
|
- enemy_combat.gd — Angriff über Event (damage_requested)
|
||||||
- enemy_aggro.gd — Aggro-Tabelle (Spieler → Wert), wählt Ziel mit höchstem Aggro
|
- enemy_aggro.gd — Aggro-Tabelle (Spieler → Wert), wählt Ziel mit höchstem Aggro
|
||||||
|
|
||||||
|
## Components (scripts/components/, wiederverwendbar)
|
||||||
|
- health.gd — Leben, Regeneration, Tod bei 0 (liest Base-Werte aus EntityStats)
|
||||||
|
- shield.gd — Schild, Regeneration nach Delay (liest Base-Werte aus EntityStats)
|
||||||
|
- healthbar.gd — Liest Health/Shield (optional) vom Parent, gelber Rand bei Anvisierung, blauer Lebensbalken wenn Spieler Ziel ist (nur bei Gegnern)
|
||||||
|
- spawner.gd — Spawnt Entities bei Lebensschwellen, engagt Spieler im Portal-Radius sofort
|
||||||
|
|
||||||
|
## Stats (Resources)
|
||||||
|
- entity_stats.gd (Resource) — max_health, health_regen, max_shield, shield_regen_delay, shield_regen_time
|
||||||
|
- player_stats.tres — health=100, regen=1/s, shield=50, delay=3s, regen_time=5s
|
||||||
|
- enemy_stats.tres — health=100, regen=0, shield=50, delay=3s, regen_time=5s
|
||||||
|
- portal_stats.tres — health=500, regen=0, shield=0
|
||||||
|
|
||||||
|
## Aggro
|
||||||
- Schaden = Aggro (1:1)
|
- Schaden = Aggro (1:1)
|
||||||
- Heilung = Aggro (0.5x)
|
- Heilung = Aggro (0.5x)
|
||||||
- Tank = Aggro-Multiplikator (2x)
|
- Tank = Aggro-Multiplikator (2x)
|
||||||
- Aggro verfällt -1/s
|
- Aggro verfällt -1/s
|
||||||
|
- Spieler im Portal-Radius: Aggro bleibt bei mindestens 1
|
||||||
- Außerhalb 10m Portal-Radius: Aggro verfällt 1% * 2 je s (1%, 2%, 4%, 8%, ...)
|
- Außerhalb 10m Portal-Radius: Aggro verfällt 1% * 2 je s (1%, 2%, 4%, 8%, ...)
|
||||||
- Ohne Aggro: Gegner kehrt zum Portal zurück, regeneriert 10% Leben/s bis 100%, dann 1%/s
|
- Ohne Aggro: Gegner kehrt zum Portal zurück, regeneriert 10% Leben/s bis 100%, dann 1%/s
|
||||||
- Bei Spieler-Tod → Aggro auf 0
|
- Bei Spieler-Tod → Aggro auf 0
|
||||||
- health.gd — Leben, 1/s Regeneration, Tod bei 0 (wiederverwendbar)
|
|
||||||
- shield.gd — Schild, regeneriert nach 3s ohne Schaden, in 5s voll (wiederverwendbar)
|
|
||||||
- player_class.gd — Klassenwechsel ALT+1 bis ALT+3 (Tank/Schaden/Heiler), tauscht AbilitySet
|
|
||||||
- respawn.gd — Bei Tod: Spieler deaktivieren, 3s Timer, Respawn am Startpunkt, Leben/Schild voll
|
|
||||||
- hud.gd — Reagiert auf Events, aktualisiert HealthBar/ShieldBar/AbilityBar/RespawnTimer
|
|
||||||
- healthbar.gd — Liest Health/Shield (optional) vom Parent, gelber Rand bei Anvisierung, blauer Lebensbalken wenn Spieler Ziel ist (nur bei Gegnern)
|
|
||||||
|
|
||||||
## Abilities (Resources)
|
## Abilities (Resources)
|
||||||
- ability.gd (Resource) — name, type, damage, range, cooldown, uses_gcd, execute()
|
- ability.gd (Resource) — name, type, damage, range, cooldown, uses_gcd, execute()
|
||||||
- ability_set.gd (Resource) — Set von 5 Abilities pro Klasse
|
- ability_set.gd (Resource) — Set von 5 Abilities pro Klasse
|
||||||
- ability_modifier.gd (Resource) — Verändert Ability (Element, Beruf, Prestige)
|
- ability_modifier.gd (Resource) — Verändert Ability (Element, Beruf, Prestige)
|
||||||
- Typen: Single, AOE, Utility, Ult, Passive
|
- Typen: Single, AOE, Utility, Ult, Passive
|
||||||
- Jede Klasse hat ein eigenes AbilitySet
|
- Auto-Attack: 10 Schaden, 1s, automatisch bei anvisiertem Gegner im Kampf
|
||||||
- Beim Klassenwechsel wird das AbilitySet getauscht
|
- Schadens-Klasse:
|
||||||
|
- 1 Single: 30 Schaden, 20m Range, 1s CD, GCD
|
||||||
|
- 2 AOE: 20 Schaden, 5m Range, 3s CD, GCD
|
||||||
|
- 3 Utility: Schild sofort auf 100%, 5s CD, kein GCD
|
||||||
|
- 4 Ult: 5x Single (50 Schaden) + 2x AOE 3m (20 Schaden), 20m Range, 15s CD, GCD
|
||||||
|
- 5 Passive: 50% mehr Schaden (permanent aktiv, kein CD)
|
||||||
|
- Jede Rolle hat ein eigenes AbilitySet
|
||||||
|
- Beim Rollenwechsel wird das AbilitySet getauscht
|
||||||
- Elemente und Modifikatoren verändern Abilities nachträglich
|
- Elemente und Modifikatoren verändern Abilities nachträglich
|
||||||
|
|
||||||
## Events
|
## Events
|
||||||
@@ -126,7 +145,7 @@
|
|||||||
- shield_regenerated(entity) — Schild ist wieder voll
|
- shield_regenerated(entity) — Schild ist wieder voll
|
||||||
- target_changed(player, target) — Spieler hat neues Ziel anvisiert
|
- target_changed(player, target) — Spieler hat neues Ziel anvisiert
|
||||||
- player_respawned(player) — Spieler ist respawnt
|
- player_respawned(player) — Spieler ist respawnt
|
||||||
- class_changed(player, class_name) — Spieler hat Klasse gewechselt
|
- role_changed(player, role_type) — Spieler hat Rolle gewechselt
|
||||||
- health_changed(entity, current, max) — Leben hat sich verändert
|
- health_changed(entity, current, max) — Leben hat sich verändert
|
||||||
- shield_changed(entity, current, max) — Schild hat sich verändert
|
- shield_changed(entity, current, max) — Schild hat sich verändert
|
||||||
- respawn_tick(timer) — Respawn-Countdown Update
|
- respawn_tick(timer) — Respawn-Countdown Update
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
ability_name = "AOE"
|
ability_name = "AOE"
|
||||||
type = 1
|
type = 1
|
||||||
damage = 5.0
|
damage = 20.0
|
||||||
ability_range = 5.0
|
ability_range = 5.0
|
||||||
cooldown = 3.0
|
cooldown = 3.0
|
||||||
icon = "2"
|
icon = "2"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
ability_name = "Single"
|
ability_name = "Single"
|
||||||
damage = 10.0
|
damage = 30.0
|
||||||
ability_range = 20.0
|
ability_range = 20.0
|
||||||
|
cooldown = 1.0
|
||||||
icon = "1"
|
icon = "1"
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ ability_name = "Burst"
|
|||||||
type = 3
|
type = 3
|
||||||
damage = 10.0
|
damage = 10.0
|
||||||
ability_range = 20.0
|
ability_range = 20.0
|
||||||
cooldown = 30.0
|
cooldown = 15.0
|
||||||
|
aoe_radius = 3.0
|
||||||
icon = "4"
|
icon = "4"
|
||||||
|
|||||||
11
resources/stats/enemy_stats.tres
Normal file
11
resources/stats/enemy_stats.tres
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[gd_resource type="Resource" script_class="EntityStats" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/resources/entity_stats.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
max_health = 100.0
|
||||||
|
health_regen = 0.0
|
||||||
|
max_shield = 50.0
|
||||||
|
shield_regen_delay = 3.0
|
||||||
|
shield_regen_time = 5.0
|
||||||
11
resources/stats/player_stats.tres
Normal file
11
resources/stats/player_stats.tres
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[gd_resource type="Resource" script_class="EntityStats" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/resources/entity_stats.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
max_health = 100.0
|
||||||
|
health_regen = 1.0
|
||||||
|
max_shield = 50.0
|
||||||
|
shield_regen_delay = 3.0
|
||||||
|
shield_regen_time = 5.0
|
||||||
9
resources/stats/portal_stats.tres
Normal file
9
resources/stats/portal_stats.tres
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[gd_resource type="Resource" script_class="EntityStats" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/resources/entity_stats.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
max_health = 500.0
|
||||||
|
health_regen = 0.0
|
||||||
|
max_shield = 0.0
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
[ext_resource type="Script" path="res://scripts/enemy/enemy_movement.gd" id="5"]
|
[ext_resource type="Script" path="res://scripts/enemy/enemy_movement.gd" id="5"]
|
||||||
[ext_resource type="Script" path="res://scripts/enemy/enemy_combat.gd" id="6"]
|
[ext_resource type="Script" path="res://scripts/enemy/enemy_combat.gd" id="6"]
|
||||||
[ext_resource type="Script" path="res://scripts/enemy/enemy_aggro.gd" id="7"]
|
[ext_resource type="Script" path="res://scripts/enemy/enemy_aggro.gd" id="7"]
|
||||||
|
[ext_resource type="Resource" path="res://resources/stats/enemy_stats.tres" id="8"]
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||||
radius = 0.4
|
radius = 0.4
|
||||||
@@ -53,9 +54,11 @@ mesh = SubResource("SphereMesh_1")
|
|||||||
|
|
||||||
[node name="Health" type="Node" parent="."]
|
[node name="Health" type="Node" parent="."]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
|
stats = ExtResource("8")
|
||||||
|
|
||||||
[node name="Shield" type="Node" parent="."]
|
[node name="Shield" type="Node" parent="."]
|
||||||
script = ExtResource("3")
|
script = ExtResource("3")
|
||||||
|
stats = ExtResource("8")
|
||||||
|
|
||||||
[node name="HitArea" type="Area3D" parent="."]
|
[node name="HitArea" type="Area3D" parent="."]
|
||||||
collision_layer = 4
|
collision_layer = 4
|
||||||
|
|||||||
@@ -8,10 +8,11 @@
|
|||||||
[ext_resource type="Script" uid="uid://bpfw71oprcvou" path="res://scripts/components/shield.gd" id="6"]
|
[ext_resource type="Script" uid="uid://bpfw71oprcvou" path="res://scripts/components/shield.gd" id="6"]
|
||||||
[ext_resource type="Script" uid="uid://b05nkuryipwny" path="res://scripts/player/targeting.gd" id="8"]
|
[ext_resource type="Script" uid="uid://b05nkuryipwny" path="res://scripts/player/targeting.gd" id="8"]
|
||||||
[ext_resource type="Script" uid="uid://dw3dtax5bx0of" path="res://scripts/player/respawn.gd" id="9"]
|
[ext_resource type="Script" uid="uid://dw3dtax5bx0of" path="res://scripts/player/respawn.gd" id="9"]
|
||||||
[ext_resource type="Script" uid="uid://rus4umqvvqq4" path="res://scripts/player/player_class.gd" id="10"]
|
[ext_resource type="Script" path="res://scripts/player/role.gd" id="10"]
|
||||||
[ext_resource type="Resource" uid="uid://cgxtn7dfs40bh" path="res://resources/ability_sets/tank_set.tres" id="11"]
|
[ext_resource type="Resource" uid="uid://cgxtn7dfs40bh" path="res://resources/ability_sets/tank_set.tres" id="11"]
|
||||||
[ext_resource type="Resource" uid="uid://beodknb6i1pm4" path="res://resources/ability_sets/damage_set.tres" id="12"]
|
[ext_resource type="Resource" uid="uid://beodknb6i1pm4" path="res://resources/ability_sets/damage_set.tres" id="12"]
|
||||||
[ext_resource type="Resource" uid="uid://kcwuhnqy34mj" path="res://resources/ability_sets/healer_set.tres" id="13"]
|
[ext_resource type="Resource" uid="uid://kcwuhnqy34mj" path="res://resources/ability_sets/healer_set.tres" id="13"]
|
||||||
|
[ext_resource type="Resource" path="res://resources/stats/player_stats.tres" id="14"]
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||||
radius = 0.3
|
radius = 0.3
|
||||||
@@ -48,14 +49,16 @@ script = ExtResource("8")
|
|||||||
|
|
||||||
[node name="Health" type="Node" parent="." unique_id=1872357630]
|
[node name="Health" type="Node" parent="." unique_id=1872357630]
|
||||||
script = ExtResource("5")
|
script = ExtResource("5")
|
||||||
|
stats = ExtResource("14")
|
||||||
|
|
||||||
[node name="Shield" type="Node" parent="." unique_id=716948065]
|
[node name="Shield" type="Node" parent="." unique_id=716948065]
|
||||||
script = ExtResource("6")
|
script = ExtResource("6")
|
||||||
|
stats = ExtResource("14")
|
||||||
|
|
||||||
[node name="Respawn" type="Node" parent="." unique_id=1562314386]
|
[node name="Respawn" type="Node" parent="." unique_id=1562314386]
|
||||||
script = ExtResource("9")
|
script = ExtResource("9")
|
||||||
|
|
||||||
[node name="PlayerClass" type="Node" parent="." unique_id=134158295]
|
[node name="Role" type="Node" parent="." unique_id=134158295]
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
tank_set = ExtResource("11")
|
tank_set = ExtResource("11")
|
||||||
damage_set = ExtResource("12")
|
damage_set = ExtResource("12")
|
||||||
|
|||||||
@@ -3,7 +3,12 @@
|
|||||||
[ext_resource type="Script" path="res://scripts/portal/portal.gd" id="1"]
|
[ext_resource type="Script" path="res://scripts/portal/portal.gd" id="1"]
|
||||||
[ext_resource type="Script" path="res://scripts/components/health.gd" id="2"]
|
[ext_resource type="Script" path="res://scripts/components/health.gd" id="2"]
|
||||||
[ext_resource type="Script" path="res://scripts/components/healthbar.gd" id="3"]
|
[ext_resource type="Script" path="res://scripts/components/healthbar.gd" id="3"]
|
||||||
[ext_resource type="Script" path="res://scripts/portal/portal_spawner.gd" id="4"]
|
[ext_resource type="Script" path="res://scripts/components/spawner.gd" id="4"]
|
||||||
|
[ext_resource type="PackedScene" path="res://scenes/enemy/enemy.tscn" id="5"]
|
||||||
|
[ext_resource type="Resource" path="res://resources/stats/portal_stats.tres" id="6"]
|
||||||
|
|
||||||
|
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
|
||||||
|
radius = 10.0
|
||||||
|
|
||||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_1"]
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_1"]
|
||||||
radius = 1.0
|
radius = 1.0
|
||||||
@@ -39,7 +44,7 @@ mesh = SubResource("CylinderMesh_1")
|
|||||||
|
|
||||||
[node name="Health" type="Node" parent="."]
|
[node name="Health" type="Node" parent="."]
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
max_health = 500.0
|
stats = ExtResource("6")
|
||||||
|
|
||||||
[node name="HitArea" type="Area3D" parent="."]
|
[node name="HitArea" type="Area3D" parent="."]
|
||||||
collision_layer = 4
|
collision_layer = 4
|
||||||
@@ -48,8 +53,17 @@ collision_mask = 0
|
|||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="HitArea"]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="HitArea"]
|
||||||
shape = SubResource("CylinderShape3D_2")
|
shape = SubResource("CylinderShape3D_2")
|
||||||
|
|
||||||
[node name="PortalSpawner" type="Node" parent="."]
|
[node name="DetectionArea" type="Area3D" parent="."]
|
||||||
|
collision_layer = 0
|
||||||
|
collision_mask = 1
|
||||||
|
monitoring = true
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="DetectionArea"]
|
||||||
|
shape = SubResource("SphereShape3D_1")
|
||||||
|
|
||||||
|
[node name="Spawner" type="Node" parent="."]
|
||||||
script = ExtResource("4")
|
script = ExtResource("4")
|
||||||
|
spawn_scene = ExtResource("5")
|
||||||
|
|
||||||
[node name="Healthbar" type="Sprite3D" parent="."]
|
[node name="Healthbar" type="Sprite3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0)
|
||||||
@@ -76,3 +90,5 @@ theme_override_styles/fill = SubResource("StyleBoxFlat_health_fill")
|
|||||||
max_value = 500.0
|
max_value = 500.0
|
||||||
value = 500.0
|
value = 500.0
|
||||||
show_percentage = false
|
show_percentage = false
|
||||||
|
|
||||||
|
[connection signal="body_entered" from="DetectionArea" to="." method="_on_detection_area_body_entered"]
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
[gd_scene format=3 uid="uid://dy1icabu2ssbw"]
|
[gd_scene format=3 uid="uid://dy1icabu2ssbw"]
|
||||||
|
|
||||||
[ext_resource type="PackedScene" path="res://scenes/hud/hud.tscn" id="hud"]
|
[ext_resource type="PackedScene" path="res://scenes/hud/hud.tscn" id="hud"]
|
||||||
[ext_resource type="PackedScene" path="res://scenes/portal/portal.tscn" id="portal"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://cdnkbt1f0db7e" path="res://scenes/player/player.tscn" id="player"]
|
[ext_resource type="PackedScene" uid="uid://cdnkbt1f0db7e" path="res://scenes/player/player.tscn" id="player"]
|
||||||
|
[ext_resource type="PackedScene" path="res://scenes/portal/portal.tscn" id="portal"]
|
||||||
|
|
||||||
[sub_resource type="NavigationMesh" id="NavigationMesh_1"]
|
[sub_resource type="NavigationMesh" id="NavigationMesh_1"]
|
||||||
vertices = PackedVector3Array(-9.5, 0.5, -9.5, -9.5, 0.5, 9.5, 9.5, 0.5, 9.5, 9.5, 0.5, -9.5)
|
vertices = PackedVector3Array(-9.5, 0.5, -9.5, -9.5, 0.5, 9.5, 9.5, 0.5, 9.5, 9.5, 0.5, -9.5)
|
||||||
@@ -51,5 +51,5 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7, 1, -7)
|
|||||||
|
|
||||||
[node name="HUD" parent="." unique_id=24362518 instance=ExtResource("hud")]
|
[node name="HUD" parent="." unique_id=24362518 instance=ExtResource("hud")]
|
||||||
|
|
||||||
[node name="Portal" parent="." instance=ExtResource("portal")]
|
[node name="Portal" parent="." unique_id=2086501116 instance=ExtResource("portal")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 5)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 5)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ enum Type { SINGLE, AOE, UTILITY, ULT, PASSIVE }
|
|||||||
@export var ability_range: float = 3.0
|
@export var ability_range: float = 3.0
|
||||||
@export var cooldown: float = 0.0
|
@export var cooldown: float = 0.0
|
||||||
@export var uses_gcd: bool = true
|
@export var uses_gcd: bool = true
|
||||||
|
@export var aoe_radius: float = 0.0
|
||||||
@export var icon: String = ""
|
@export var icon: String = ""
|
||||||
|
|
||||||
func execute(player: Node, targeting: Node) -> bool:
|
func execute(player: Node, targeting: Node) -> bool:
|
||||||
@@ -69,12 +70,13 @@ func _execute_ult(player: Node, targeting: Node, dmg: float) -> bool:
|
|||||||
if not _in_range(player, targeting):
|
if not _in_range(player, targeting):
|
||||||
return false
|
return false
|
||||||
var target: Node3D = targeting.current_target
|
var target: Node3D = targeting.current_target
|
||||||
EventBus.damage_requested.emit(player, target, dmg * 4.0)
|
EventBus.damage_requested.emit(player, target, dmg * 5.0)
|
||||||
|
var aoe_range: float = aoe_radius if aoe_radius > 0 else ability_range
|
||||||
var enemies := player.get_tree().get_nodes_in_group("enemies")
|
var enemies := player.get_tree().get_nodes_in_group("enemies")
|
||||||
for enemy in enemies:
|
for enemy in enemies:
|
||||||
if enemy != target:
|
if enemy != target:
|
||||||
var enemy_dist: float = target.global_position.distance_to(enemy.global_position)
|
var enemy_dist: float = target.global_position.distance_to(enemy.global_position)
|
||||||
if enemy_dist <= ability_range:
|
if enemy_dist <= aoe_range:
|
||||||
EventBus.damage_requested.emit(player, enemy, dmg * 2.0)
|
EventBus.damage_requested.emit(player, enemy, dmg * 2.0)
|
||||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg * 4.0)
|
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg * 5.0)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
@export var max_health := 100.0
|
@export var stats: EntityStats
|
||||||
const HEALTH_REGEN := 1.0
|
var max_health: float
|
||||||
|
var health_regen: float
|
||||||
var current_health: float
|
var current_health: float
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
max_health = stats.max_health
|
||||||
|
health_regen = stats.health_regen
|
||||||
current_health = max_health
|
current_health = max_health
|
||||||
EventBus.damage_requested.connect(_on_damage_requested)
|
EventBus.damage_requested.connect(_on_damage_requested)
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
if current_health > 0 and current_health < max_health:
|
if current_health > 0 and current_health < max_health and health_regen > 0:
|
||||||
current_health = min(current_health + HEALTH_REGEN * delta, max_health)
|
current_health = min(current_health + health_regen * delta, max_health)
|
||||||
EventBus.health_changed.emit(get_parent(), current_health, max_health)
|
EventBus.health_changed.emit(get_parent(), current_health, max_health)
|
||||||
|
|
||||||
func _on_damage_requested(attacker: Node, target: Node, amount: float) -> void:
|
func _on_damage_requested(attacker: Node, target: Node, amount: float) -> void:
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
@export var max_shield := 50.0
|
@export var stats: EntityStats
|
||||||
const REGEN_DELAY := 3.0
|
var max_shield: float
|
||||||
const REGEN_TIME := 5.0
|
var regen_delay: float
|
||||||
|
var regen_time: float
|
||||||
var current_shield: float
|
var current_shield: float
|
||||||
var regen_timer := 0.0
|
var regen_timer := 0.0
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
max_shield = stats.max_shield
|
||||||
|
regen_delay = stats.shield_regen_delay
|
||||||
|
regen_time = stats.shield_regen_time
|
||||||
current_shield = max_shield
|
current_shield = max_shield
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
|
if max_shield <= 0:
|
||||||
|
return
|
||||||
if current_shield < max_shield:
|
if current_shield < max_shield:
|
||||||
regen_timer += delta
|
regen_timer += delta
|
||||||
if regen_timer >= REGEN_DELAY:
|
if regen_timer >= regen_delay:
|
||||||
current_shield += (max_shield / REGEN_TIME) * delta
|
current_shield += (max_shield / regen_time) * delta
|
||||||
if current_shield >= max_shield:
|
if current_shield >= max_shield:
|
||||||
current_shield = max_shield
|
current_shield = max_shield
|
||||||
EventBus.shield_regenerated.emit(get_parent())
|
EventBus.shield_regenerated.emit(get_parent())
|
||||||
@@ -25,7 +31,6 @@ func absorb(amount: float) -> float:
|
|||||||
regen_timer = 0.0
|
regen_timer = 0.0
|
||||||
var absorbed: float = min(amount, current_shield)
|
var absorbed: float = min(amount, current_shield)
|
||||||
current_shield -= absorbed
|
current_shield -= absorbed
|
||||||
print("%s Schild: %s/%s (-%s)" % [get_parent().name, current_shield, max_shield, absorbed])
|
|
||||||
if current_shield <= 0:
|
if current_shield <= 0:
|
||||||
EventBus.shield_broken.emit(get_parent())
|
EventBus.shield_broken.emit(get_parent())
|
||||||
EventBus.shield_changed.emit(get_parent(), current_shield, max_shield)
|
EventBus.shield_changed.emit(get_parent(), current_shield, max_shield)
|
||||||
|
|||||||
44
scripts/components/spawner.gd
Normal file
44
scripts/components/spawner.gd
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
@export var spawn_scene: PackedScene
|
||||||
|
@export var spawn_count := 3
|
||||||
|
@export var thresholds: Array[float] = [0.85, 0.70, 0.55, 0.40, 0.25, 0.10]
|
||||||
|
|
||||||
|
var triggered: Array[bool] = []
|
||||||
|
|
||||||
|
@onready var parent: Node3D = get_parent()
|
||||||
|
@onready var health: Node = get_parent().get_node("Health")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
triggered.resize(thresholds.size())
|
||||||
|
triggered.fill(false)
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if not spawn_scene or health.current_health <= 0:
|
||||||
|
return
|
||||||
|
var ratio: float = health.current_health / health.max_health
|
||||||
|
for i in range(thresholds.size()):
|
||||||
|
if not triggered[i] and ratio <= thresholds[i]:
|
||||||
|
triggered[i] = true
|
||||||
|
_spawn()
|
||||||
|
|
||||||
|
func _spawn() -> void:
|
||||||
|
var spawned: Array = []
|
||||||
|
for j in range(spawn_count):
|
||||||
|
var entity: Node = spawn_scene.instantiate()
|
||||||
|
var offset := Vector3(randf_range(-2, 2), 0, randf_range(-2, 2))
|
||||||
|
parent.get_parent().add_child(entity)
|
||||||
|
entity.global_position = parent.global_position + offset
|
||||||
|
if "spawn_position" in entity:
|
||||||
|
entity.spawn_position = parent.global_position
|
||||||
|
if "portal" in entity:
|
||||||
|
entity.portal = parent
|
||||||
|
spawned.append(entity)
|
||||||
|
var player: Node = get_tree().get_first_node_in_group("player")
|
||||||
|
if player:
|
||||||
|
var dist: float = parent.global_position.distance_to(player.global_position)
|
||||||
|
if dist <= 10.0:
|
||||||
|
for entity in spawned:
|
||||||
|
if entity.has_method("_engage"):
|
||||||
|
entity._engage(player)
|
||||||
|
EventBus.portal_spawn.emit(parent, spawned)
|
||||||
1
scripts/components/spawner.gd.uid
Normal file
1
scripts/components/spawner.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cm2s3xkmuesey
|
||||||
@@ -13,7 +13,6 @@ var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
|||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
spawn_position = global_position
|
spawn_position = global_position
|
||||||
add_to_group("enemies")
|
add_to_group("enemies")
|
||||||
add_to_group("targetable")
|
|
||||||
EventBus.entity_died.connect(_on_entity_died)
|
EventBus.entity_died.connect(_on_entity_died)
|
||||||
|
|
||||||
func _on_entity_died(entity: Node) -> void:
|
func _on_entity_died(entity: Node) -> void:
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ func _process(delta: float) -> void:
|
|||||||
var bonus_decay: float = aggro_table[player] * 0.01 * pow(2, seconds_outside) * delta
|
var bonus_decay: float = aggro_table[player] * 0.01 * pow(2, seconds_outside) * delta
|
||||||
decay += bonus_decay
|
decay += bonus_decay
|
||||||
aggro_table[player] -= decay
|
aggro_table[player] -= decay
|
||||||
|
# Im Portal-Radius: Aggro bleibt bei mindestens 1
|
||||||
|
if not outside_portal and enemy.portal and is_instance_valid(player):
|
||||||
|
var player_dist: float = player.global_position.distance_to(enemy.portal.global_position)
|
||||||
|
if player_dist <= PORTAL_RADIUS and aggro_table[player] < 1.0:
|
||||||
|
aggro_table[player] = 1.0
|
||||||
if aggro_table[player] <= 0:
|
if aggro_table[player] <= 0:
|
||||||
aggro_table.erase(player)
|
aggro_table.erase(player)
|
||||||
|
|
||||||
@@ -43,8 +48,8 @@ func _on_damage_dealt(attacker: Node, target: Node, amount: float) -> void:
|
|||||||
if target != enemy:
|
if target != enemy:
|
||||||
return
|
return
|
||||||
var multiplier := 1.0
|
var multiplier := 1.0
|
||||||
var player_class: Node = attacker.get_node_or_null("PlayerClass")
|
var role: Node = attacker.get_node_or_null("Role")
|
||||||
if player_class and player_class.current_class == 0:
|
if role and role.current_role == 0:
|
||||||
multiplier = 2.0
|
multiplier = 2.0
|
||||||
add_aggro(attacker, amount * multiplier)
|
add_aggro(attacker, amount * multiplier)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ signal shield_broken(entity)
|
|||||||
signal shield_regenerated(entity)
|
signal shield_regenerated(entity)
|
||||||
signal target_changed(player, target)
|
signal target_changed(player, target)
|
||||||
signal player_respawned(player)
|
signal player_respawned(player)
|
||||||
signal class_changed(player, class_type)
|
signal role_changed(player, role_type)
|
||||||
signal damage_requested(attacker, target, amount)
|
signal damage_requested(attacker, target, amount)
|
||||||
signal health_changed(entity, current, max_val)
|
signal health_changed(entity, current, max_val)
|
||||||
signal shield_changed(entity, current, max_val)
|
signal shield_changed(entity, current, max_val)
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
const GCD_TIME := 0.5
|
const GCD_TIME := 0.5
|
||||||
|
const AA_DAMAGE := 10.0
|
||||||
|
const AA_COOLDOWN := 1.0
|
||||||
|
const AA_RANGE := 20.0
|
||||||
|
|
||||||
@onready var player: CharacterBody3D = get_parent()
|
@onready var player: CharacterBody3D = get_parent()
|
||||||
@onready var targeting: Node = get_parent().get_node("Targeting")
|
@onready var targeting: Node = get_parent().get_node("Targeting")
|
||||||
@onready var player_class: Node = get_parent().get_node("PlayerClass")
|
@onready var role: Node = get_parent().get_node("Role")
|
||||||
|
|
||||||
var abilities: Array = []
|
var abilities: Array = []
|
||||||
var cooldowns: Array[float] = [0.0, 0.0, 0.0, 0.0, 0.0]
|
var cooldowns: Array[float] = [0.0, 0.0, 0.0, 0.0, 0.0]
|
||||||
var max_cooldowns: Array[float] = [0.0, 0.0, 0.0, 0.0, 0.0]
|
var max_cooldowns: Array[float] = [0.0, 0.0, 0.0, 0.0, 0.0]
|
||||||
var gcd_timer := 0.0
|
var gcd_timer := 0.0
|
||||||
|
var aa_timer := 0.0
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_load_abilities()
|
_load_abilities()
|
||||||
EventBus.class_changed.connect(_on_class_changed)
|
EventBus.role_changed.connect(_on_role_changed)
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
if gcd_timer > 0:
|
if gcd_timer > 0:
|
||||||
@@ -22,9 +26,26 @@ func _process(delta: float) -> void:
|
|||||||
if cooldowns[i] > 0:
|
if cooldowns[i] > 0:
|
||||||
cooldowns[i] -= delta
|
cooldowns[i] -= delta
|
||||||
EventBus.cooldown_tick.emit(cooldowns, max_cooldowns, gcd_timer)
|
EventBus.cooldown_tick.emit(cooldowns, max_cooldowns, gcd_timer)
|
||||||
|
_auto_attack(delta)
|
||||||
|
|
||||||
|
func _auto_attack(delta: float) -> void:
|
||||||
|
aa_timer -= delta
|
||||||
|
if aa_timer > 0:
|
||||||
|
return
|
||||||
|
if not targeting.in_combat or not targeting.current_target:
|
||||||
|
return
|
||||||
|
if not is_instance_valid(targeting.current_target):
|
||||||
|
return
|
||||||
|
var dist := player.global_position.distance_to(targeting.current_target.global_position)
|
||||||
|
if dist > AA_RANGE:
|
||||||
|
return
|
||||||
|
var dmg := apply_passive(AA_DAMAGE)
|
||||||
|
EventBus.damage_requested.emit(player, targeting.current_target, dmg)
|
||||||
|
print("AA: %s Schaden an %s" % [dmg, targeting.current_target.name])
|
||||||
|
aa_timer = AA_COOLDOWN
|
||||||
|
|
||||||
func _load_abilities() -> void:
|
func _load_abilities() -> void:
|
||||||
var ability_set: AbilitySet = player_class.get_ability_set()
|
var ability_set: AbilitySet = role.get_ability_set()
|
||||||
if ability_set:
|
if ability_set:
|
||||||
abilities = ability_set.abilities
|
abilities = ability_set.abilities
|
||||||
else:
|
else:
|
||||||
@@ -59,5 +80,5 @@ func apply_passive(base_damage: float) -> float:
|
|||||||
return base_damage * (1.0 + ability.damage / 100.0)
|
return base_damage * (1.0 + ability.damage / 100.0)
|
||||||
return base_damage
|
return base_damage
|
||||||
|
|
||||||
func _on_class_changed(_player: Node, _class_type: int) -> void:
|
func _on_role_changed(_player: Node, _role_type: int) -> void:
|
||||||
_load_abilities()
|
_load_abilities()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func _ready() -> void:
|
|||||||
EventBus.shield_changed.connect(_on_shield_changed)
|
EventBus.shield_changed.connect(_on_shield_changed)
|
||||||
EventBus.entity_died.connect(_on_entity_died)
|
EventBus.entity_died.connect(_on_entity_died)
|
||||||
EventBus.player_respawned.connect(_on_player_respawned)
|
EventBus.player_respawned.connect(_on_player_respawned)
|
||||||
EventBus.class_changed.connect(_on_class_changed)
|
EventBus.role_changed.connect(_on_role_changed)
|
||||||
EventBus.respawn_tick.connect(_on_respawn_tick)
|
EventBus.respawn_tick.connect(_on_respawn_tick)
|
||||||
EventBus.cooldown_tick.connect(_on_cooldown_tick)
|
EventBus.cooldown_tick.connect(_on_cooldown_tick)
|
||||||
|
|
||||||
@@ -46,8 +46,8 @@ func _on_player_respawned(_player: Node) -> void:
|
|||||||
func _on_respawn_tick(timer: float) -> void:
|
func _on_respawn_tick(timer: float) -> void:
|
||||||
respawn_label.text = str(ceil(timer))
|
respawn_label.text = str(ceil(timer))
|
||||||
|
|
||||||
func _on_class_changed(_player: Node, class_type: int) -> void:
|
func _on_role_changed(_player: Node, role_type: int) -> void:
|
||||||
match class_type:
|
match role_type:
|
||||||
0: class_icon.text = "T"
|
0: class_icon.text = "T"
|
||||||
1: class_icon.text = "D"
|
1: class_icon.text = "D"
|
||||||
2: class_icon.text = "H"
|
2: class_icon.text = "H"
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
extends Node
|
|
||||||
|
|
||||||
enum PlayerClass { TANK, DAMAGE, HEALER }
|
|
||||||
|
|
||||||
var current_class: int = PlayerClass.DAMAGE
|
|
||||||
|
|
||||||
@export var tank_set: AbilitySet
|
|
||||||
@export var damage_set: AbilitySet
|
|
||||||
@export var healer_set: AbilitySet
|
|
||||||
|
|
||||||
@onready var player: CharacterBody3D = get_parent()
|
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
|
||||||
if event.is_action_pressed("class_tank"):
|
|
||||||
set_class(PlayerClass.TANK)
|
|
||||||
elif event.is_action_pressed("class_damage"):
|
|
||||||
set_class(PlayerClass.DAMAGE)
|
|
||||||
elif event.is_action_pressed("class_healer"):
|
|
||||||
set_class(PlayerClass.HEALER)
|
|
||||||
|
|
||||||
func set_class(new_class: int) -> void:
|
|
||||||
current_class = new_class
|
|
||||||
EventBus.class_changed.emit(player, current_class)
|
|
||||||
|
|
||||||
func get_class_icon() -> String:
|
|
||||||
match current_class:
|
|
||||||
PlayerClass.TANK: return "T"
|
|
||||||
PlayerClass.DAMAGE: return "D"
|
|
||||||
PlayerClass.HEALER: return "H"
|
|
||||||
return ""
|
|
||||||
|
|
||||||
func get_ability_set() -> AbilitySet:
|
|
||||||
match current_class:
|
|
||||||
PlayerClass.TANK: return tank_set
|
|
||||||
PlayerClass.DAMAGE: return damage_set
|
|
||||||
PlayerClass.HEALER: return healer_set
|
|
||||||
return damage_set
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://rus4umqvvqq4
|
|
||||||
37
scripts/player/role.gd
Normal file
37
scripts/player/role.gd
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
enum Role { TANK, DAMAGE, HEALER }
|
||||||
|
|
||||||
|
var current_role: int = Role.DAMAGE
|
||||||
|
|
||||||
|
@export var tank_set: AbilitySet
|
||||||
|
@export var damage_set: AbilitySet
|
||||||
|
@export var healer_set: AbilitySet
|
||||||
|
|
||||||
|
@onready var player: CharacterBody3D = get_parent()
|
||||||
|
|
||||||
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
if event.is_action_pressed("class_tank"):
|
||||||
|
set_role(Role.TANK)
|
||||||
|
elif event.is_action_pressed("class_damage"):
|
||||||
|
set_role(Role.DAMAGE)
|
||||||
|
elif event.is_action_pressed("class_healer"):
|
||||||
|
set_role(Role.HEALER)
|
||||||
|
|
||||||
|
func set_role(new_role: int) -> void:
|
||||||
|
current_role = new_role
|
||||||
|
EventBus.role_changed.emit(player, current_role)
|
||||||
|
|
||||||
|
func get_role_icon() -> String:
|
||||||
|
match current_role:
|
||||||
|
Role.TANK: return "T"
|
||||||
|
Role.DAMAGE: return "D"
|
||||||
|
Role.HEALER: return "H"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
func get_ability_set() -> AbilitySet:
|
||||||
|
match current_role:
|
||||||
|
Role.TANK: return tank_set
|
||||||
|
Role.DAMAGE: return damage_set
|
||||||
|
Role.HEALER: return healer_set
|
||||||
|
return damage_set
|
||||||
1
scripts/player/role.gd.uid
Normal file
1
scripts/player/role.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dhomrampxola4
|
||||||
@@ -49,7 +49,7 @@ func _try_target_under_mouse(mouse_pos: Vector2) -> void:
|
|||||||
set_target(null)
|
set_target(null)
|
||||||
|
|
||||||
func _cycle_target() -> void:
|
func _cycle_target() -> void:
|
||||||
var targets := get_tree().get_nodes_in_group("targetable")
|
var targets := get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("portals")
|
||||||
if targets.is_empty():
|
if targets.is_empty():
|
||||||
set_target(null)
|
set_target(null)
|
||||||
return
|
return
|
||||||
@@ -70,23 +70,26 @@ func _on_enemy_engaged(_enemy: Node, target: Node) -> void:
|
|||||||
if not in_combat:
|
if not in_combat:
|
||||||
in_combat = true
|
in_combat = true
|
||||||
if current_target == null:
|
if current_target == null:
|
||||||
_target_nearest()
|
_auto_target()
|
||||||
|
|
||||||
func _on_damage_dealt(_attacker: Node, target: Node, _amount: float) -> void:
|
func _on_damage_dealt(attacker: Node, target: Node, _amount: float) -> void:
|
||||||
if target == player:
|
if target == player:
|
||||||
combat_timer = COMBAT_TIMEOUT
|
combat_timer = COMBAT_TIMEOUT
|
||||||
if not in_combat:
|
if not in_combat:
|
||||||
in_combat = true
|
in_combat = true
|
||||||
if current_target == null:
|
if current_target == null:
|
||||||
_target_nearest()
|
_auto_target()
|
||||||
|
elif attacker == player and in_combat:
|
||||||
|
combat_timer = COMBAT_TIMEOUT
|
||||||
|
|
||||||
func _on_entity_died(entity: Node) -> void:
|
func _on_entity_died(entity: Node) -> void:
|
||||||
if entity == current_target:
|
if entity == current_target:
|
||||||
set_target(null)
|
set_target(null)
|
||||||
if in_combat:
|
if in_combat:
|
||||||
_target_nearest_except(entity)
|
_auto_target(entity)
|
||||||
|
|
||||||
func _target_nearest_except(exclude: Node = null) -> void:
|
func _auto_target(exclude: Node = null) -> void:
|
||||||
|
# Priorität 1: Nächster Gegner
|
||||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
var enemies := get_tree().get_nodes_in_group("enemies")
|
||||||
var nearest: Node3D = null
|
var nearest: Node3D = null
|
||||||
var nearest_dist: float = INF
|
var nearest_dist: float = INF
|
||||||
@@ -98,16 +101,14 @@ func _target_nearest_except(exclude: Node = null) -> void:
|
|||||||
nearest = enemy
|
nearest = enemy
|
||||||
if nearest:
|
if nearest:
|
||||||
set_target(nearest)
|
set_target(nearest)
|
||||||
|
return
|
||||||
func _target_nearest() -> void:
|
# Priorität 2: Nächstes Portal
|
||||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
var portals := get_tree().get_nodes_in_group("portals")
|
||||||
var nearest: Node3D = null
|
for p in portals:
|
||||||
var nearest_dist: float = INF
|
if is_instance_valid(p) and p != exclude:
|
||||||
for enemy in enemies:
|
var dist: float = player.global_position.distance_to(p.global_position)
|
||||||
if is_instance_valid(enemy):
|
|
||||||
var dist: float = player.global_position.distance_to(enemy.global_position)
|
|
||||||
if dist < nearest_dist:
|
if dist < nearest_dist:
|
||||||
nearest_dist = dist
|
nearest_dist = dist
|
||||||
nearest = enemy
|
nearest = p
|
||||||
if nearest:
|
if nearest:
|
||||||
set_target(nearest)
|
set_target(nearest)
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
extends StaticBody3D
|
extends StaticBody3D
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
add_to_group("targetable")
|
add_to_group("portals")
|
||||||
|
EventBus.entity_died.connect(_on_entity_died)
|
||||||
|
|
||||||
|
func _on_entity_died(entity: Node) -> void:
|
||||||
|
if entity == self:
|
||||||
|
queue_free()
|
||||||
|
|
||||||
|
func _on_detection_area_body_entered(body: Node3D) -> void:
|
||||||
|
if body is CharacterBody3D and body.name == "Player":
|
||||||
|
EventBus.enemy_engaged.emit(self, body)
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
extends Node
|
|
||||||
|
|
||||||
const SPAWN_COUNT := 3
|
|
||||||
var thresholds := [0.85, 0.70, 0.55, 0.40, 0.25, 0.10]
|
|
||||||
var triggered: Array[bool] = [false, false, false, false, false, false]
|
|
||||||
var enemy_scene: PackedScene = preload("res://scenes/enemy/enemy.tscn")
|
|
||||||
|
|
||||||
@onready var portal: StaticBody3D = get_parent()
|
|
||||||
@onready var health: Node = get_parent().get_node("Health")
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
|
||||||
if health.current_health <= 0:
|
|
||||||
return
|
|
||||||
var ratio: float = health.current_health / health.max_health
|
|
||||||
for i in range(thresholds.size()):
|
|
||||||
if not triggered[i] and ratio <= thresholds[i]:
|
|
||||||
triggered[i] = true
|
|
||||||
_spawn_enemies()
|
|
||||||
|
|
||||||
func _spawn_enemies() -> void:
|
|
||||||
var spawned: Array = []
|
|
||||||
for j in range(SPAWN_COUNT):
|
|
||||||
var enemy: CharacterBody3D = enemy_scene.instantiate()
|
|
||||||
var offset := Vector3(randf_range(-2, 2), 0, randf_range(-2, 2))
|
|
||||||
portal.get_parent().add_child(enemy)
|
|
||||||
enemy.global_position = portal.global_position + offset
|
|
||||||
enemy.spawn_position = portal.global_position
|
|
||||||
enemy.portal = portal
|
|
||||||
spawned.append(enemy)
|
|
||||||
EventBus.portal_spawn.emit(portal, spawned)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://begrg74oh76pu
|
|
||||||
8
scripts/resources/entity_stats.gd
Normal file
8
scripts/resources/entity_stats.gd
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
extends Resource
|
||||||
|
class_name EntityStats
|
||||||
|
|
||||||
|
@export var max_health := 100.0
|
||||||
|
@export var health_regen := 0.0
|
||||||
|
@export var max_shield := 0.0
|
||||||
|
@export var shield_regen_delay := 3.0
|
||||||
|
@export var shield_regen_time := 5.0
|
||||||
1
scripts/resources/entity_stats.gd.uid
Normal file
1
scripts/resources/entity_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ij663bdj2cgu
|
||||||
Reference in New Issue
Block a user