From aa2c1825340fafc0aa7cf73736ad091512b55c0d Mon Sep 17 00:00:00 2001 From: Marek Date: Sun, 29 Mar 2026 16:05:31 +0200 Subject: [PATCH] last init --- .editorconfig | 4 + .gitattributes | 2 + .gitignore | 3 + icon.svg | 1 + icon.svg.import | 43 +++++ plan.md | 107 +++++++++++ project.godot | 96 ++++++++++ resources/abilities/aoe_attack.tres | 11 ++ resources/abilities/passive_damage_boost.tres | 11 ++ resources/abilities/single_attack.tres | 11 ++ resources/abilities/ult_burst.tres | 11 ++ resources/abilities/utility_shield_reset.tres | 11 ++ resources/ability_sets/damage_set.tres | 13 ++ resources/ability_sets/healer_set.tres | 13 ++ resources/ability_sets/tank_set.tres | 13 ++ scenes/enemy/enemy.tscn | 117 +++++++++++ scenes/hud/hud.tscn | 181 ++++++++++++++++++ scenes/player/player.tscn | 62 ++++++ scenes/world.tscn | 64 +++++++ scripts/abilities/ability.gd | 66 +++++++ scripts/abilities/ability.gd.uid | 1 + scripts/abilities/ability_set.gd | 4 + scripts/abilities/ability_set.gd.uid | 1 + scripts/components/health.gd | 33 ++++ scripts/components/health.gd.uid | 1 + scripts/components/shield.gd | 32 ++++ scripts/components/shield.gd.uid | 1 + scripts/enemy/enemy.gd | 38 ++++ scripts/enemy/enemy.gd.uid | 1 + scripts/enemy/enemy_combat.gd | 26 +++ scripts/enemy/enemy_combat.gd.uid | 1 + scripts/enemy/enemy_healthbar.gd | 22 +++ scripts/enemy/enemy_healthbar.gd.uid | 1 + scripts/enemy/enemy_movement.gd | 52 +++++ scripts/enemy/enemy_movement.gd.uid | 1 + scripts/event_bus.gd | 15 ++ scripts/event_bus.gd.uid | 1 + scripts/player/camera.gd | 30 +++ scripts/player/camera.gd.uid | 1 + scripts/player/combat.gd | 35 ++++ scripts/player/combat.gd.uid | 1 + scripts/player/hud.gd | 43 +++++ scripts/player/hud.gd.uid | 1 + scripts/player/movement.gd | 35 ++++ scripts/player/movement.gd.uid | 1 + scripts/player/player.gd | 1 + scripts/player/player.gd.uid | 1 + scripts/player/player_class.gd | 37 ++++ scripts/player/player_class.gd.uid | 1 + scripts/player/respawn.gd | 46 +++++ scripts/player/respawn.gd.uid | 1 + scripts/player/targeting.gd | 113 +++++++++++ scripts/player/targeting.gd.uid | 1 + 53 files changed, 1419 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 icon.svg create mode 100644 icon.svg.import create mode 100644 plan.md create mode 100644 project.godot create mode 100644 resources/abilities/aoe_attack.tres create mode 100644 resources/abilities/passive_damage_boost.tres create mode 100644 resources/abilities/single_attack.tres create mode 100644 resources/abilities/ult_burst.tres create mode 100644 resources/abilities/utility_shield_reset.tres create mode 100644 resources/ability_sets/damage_set.tres create mode 100644 resources/ability_sets/healer_set.tres create mode 100644 resources/ability_sets/tank_set.tres create mode 100644 scenes/enemy/enemy.tscn create mode 100644 scenes/hud/hud.tscn create mode 100644 scenes/player/player.tscn create mode 100644 scenes/world.tscn create mode 100644 scripts/abilities/ability.gd create mode 100644 scripts/abilities/ability.gd.uid create mode 100644 scripts/abilities/ability_set.gd create mode 100644 scripts/abilities/ability_set.gd.uid create mode 100644 scripts/components/health.gd create mode 100644 scripts/components/health.gd.uid create mode 100644 scripts/components/shield.gd create mode 100644 scripts/components/shield.gd.uid create mode 100644 scripts/enemy/enemy.gd create mode 100644 scripts/enemy/enemy.gd.uid create mode 100644 scripts/enemy/enemy_combat.gd create mode 100644 scripts/enemy/enemy_combat.gd.uid create mode 100644 scripts/enemy/enemy_healthbar.gd create mode 100644 scripts/enemy/enemy_healthbar.gd.uid create mode 100644 scripts/enemy/enemy_movement.gd create mode 100644 scripts/enemy/enemy_movement.gd.uid create mode 100644 scripts/event_bus.gd create mode 100644 scripts/event_bus.gd.uid create mode 100644 scripts/player/camera.gd create mode 100644 scripts/player/camera.gd.uid create mode 100644 scripts/player/combat.gd create mode 100644 scripts/player/combat.gd.uid create mode 100644 scripts/player/hud.gd create mode 100644 scripts/player/hud.gd.uid create mode 100644 scripts/player/movement.gd create mode 100644 scripts/player/movement.gd.uid create mode 100644 scripts/player/player.gd create mode 100644 scripts/player/player.gd.uid create mode 100644 scripts/player/player_class.gd create mode 100644 scripts/player/player_class.gd.uid create mode 100644 scripts/player/respawn.gd create mode 100644 scripts/player/respawn.gd.uid create mode 100644 scripts/player/targeting.gd create mode 100644 scripts/player/targeting.gd.uid diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f28239b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*] +charset = utf-8 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0af181c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Godot 4+ specific ignores +.godot/ +/android/ diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..c6bbb7d --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..c85faa4 --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xpc0cepkwm7e" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..5eb4821 --- /dev/null +++ b/plan.md @@ -0,0 +1,107 @@ +# Projektstruktur +## Szenenbaum +- Welt + - Player + - Gegner + - HUD + +## Architektur +- Zwischen Szenen: Kommunikation über EventBus (Szenen kennen sich nicht) +- Innerhalb einer Szene: Modulare Skripte, Zugriff auf Geschwister-Nodes erlaubt + +## Szenen +- world.tscn — Hauptszene + - NavigationRegion3D (Wegfindung für Gegner) + - Boden (MeshInstance3D, 20x20m PlaneMesh, Gras-Noise-Textur) + - Kollision (StaticBody3D, WorldBoundaryShape3D) + - Licht (DirectionalLight3D, 45°, Schatten) + - Spieler (Instanz von player.tscn) + - HUD (Instanz von hud.tscn) + +- player.tscn — Spieler + - CharacterBody3D (player.gd) + - Kollision (CapsuleShape3D, 1.8m x 0.3m) + - Mesh (CapsuleMesh) + - CameraPivot (Node3D, Kopfhöhe, camera.gd) + - Camera3D (hinter/über Spieler) + - Movement (Node, movement.gd) + - Combat (Node, combat.gd) + - PlayerClass (Node, player_class.gd) + - Targeting (Node, targeting.gd) + - Health (Node, health.gd) + - Shield (Node, shield.gd) + - Respawn (Node, respawn.gd) + +- hud.tscn — HUD (eigene Szene, kommuniziert nur über Events) + - CanvasLayer (hud.gd) + - HealthBar (ProgressBar, links oben) + - ShieldBar (ProgressBar, links oben, unter HealthBar) + - RespawnTimer (Label, mitte, Countdown bei Tod) + - AbilityBar (HBoxContainer, mitte unten) + - ClassIcon (Label, Klassen-Symbol: T=Tank, D=Schaden, H=Heiler) + - Ability 1-5 + +- enemy.tscn — Gegner + - CharacterBody3D (enemy.gd) + - Kollision (CapsuleShape3D) + - Mesh (SphereMesh, Platzhalter) + - Health (Node, health.gd) + - Shield (Node, shield.gd) + - HitArea (Area3D, Trefferbereich des Gegners) + - CollisionShape3D (CapsuleShape3D) + - DetectionArea (Area3D, Erkennungsradius) + - CollisionShape3D (SphereShape3D) + - NavigationAgent3D (Wegfindung) + - EnemyMovement (Node, enemy_movement.gd) + - EnemyCombat (Node, enemy_combat.gd) + - Healthbar (Sprite3D + SubViewport, über dem Gegner, enemy_healthbar.gd) + - SubViewport + - Border (ColorRect, gelb, sichtbar bei Anvisierung) + - HealthBar (ProgressBar, grün) + - ShieldBar (ProgressBar, blau) + +## Skripte +- player.gd — Kern, verbindet Komponenten +- camera.gd — LMB freies Umsehen, RMB Kamera + Laufrichtung anpassen +- movement.gd — Bewegung (WASD relativ zur Kamera), Springen, Schwerkraft +- combat.gd — Führt Abilities aus, Fähigkeiten 1-5 + - 1 Single: Anvisiertes Ziel angreifen (10 Schaden, Distanzprüfung) + - 2 AOE: Alle Gegner im Bereich (5 Schaden, halber Single-Schaden) + - 3 Utility: Schild-Regeneration sofort zurücksetzen + - 4 Ult: 4x Schaden an anvisiertem Ziel + 2x AOE-Schaden um das Ziel herum + - 5 Passive: 50% mehr Schaden (permanent aktiv) +- targeting.gd — Klick/TAB anvisieren, Kampfmodus bei Gegner-Angriff, Auto-Targeting auf nächsten Gegner +- event_bus.gd — Autoload-Singleton, globale Signals +- enemy.gd — Gegner-Kern, State Machine (Idle, Verfolgen, Angreifen, Zurückkehren) +- enemy_movement.gd — Navigation zum Ziel/Spawnpunkt +- enemy_combat.gd — Angriff über Event (damage_requested) +- 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 +- enemy_healthbar.gd — Liest Health/Shield vom Gegner, aktualisiert Balken über dem Gegner, gelber Rand bei Anvisierung + +## Abilities (Resources) +- ability.gd (Resource) — name, type, damage, range, execute() +- ability_set.gd (Resource) — Set von 5 Abilities pro Klasse +- ability_modifier.gd (Resource) — Verändert Ability (Element, Beruf, Prestige) +- Typen: Single, AOE, Utility, Ult, Passive +- Jede Klasse hat ein eigenes AbilitySet +- Beim Klassenwechsel wird das AbilitySet getauscht +- Elemente und Modifikatoren verändern Abilities nachträglich + +## Events +- attack_executed(attacker, position, direction, damage) — Angriff wurde ausgeführt +- damage_dealt(attacker, target, damage) — Schaden wurde verteilt +- damage_requested(attacker, target, amount) — Schaden zwischen Szenen anfordern +- entity_died(entity) — Entity ist gestorben +- shield_broken(entity) — Schild ist auf 0 gefallen +- shield_regenerated(entity) — Schild ist wieder voll +- target_changed(player, target) — Spieler hat neues Ziel anvisiert +- player_respawned(player) — Spieler ist respawnt +- class_changed(player, class_name) — Spieler hat Klasse gewechselt +- health_changed(entity, current, max) — Leben hat sich verändert +- shield_changed(entity, current, max) — Schild hat sich verändert +- respawn_tick(timer) — Respawn-Countdown Update +- enemy_engaged(enemy, target) — Gegner hat Spieler anvisiert diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..a1b7f1b --- /dev/null +++ b/project.godot @@ -0,0 +1,96 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="mmo" +run/main_scene="res://scenes/world.tscn" +config/features=PackedStringArray("4.6", "Forward Plus") +config/icon="res://icon.svg" + +[autoload] + +EventBus="*res://scripts/event_bus.gd" + +[input] + +move_forward={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +] +} +move_back={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +] +} +move_left={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) +] +} +move_right={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) +] +} +ability_1={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":49,"key_label":0,"unicode":49,"location":0,"echo":false,"script":null) +] +} +ability_2={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":50,"key_label":0,"unicode":50,"location":0,"echo":false,"script":null) +] +} +ability_3={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":51,"key_label":0,"unicode":51,"location":0,"echo":false,"script":null) +] +} +ability_4={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":52,"key_label":0,"unicode":52,"location":0,"echo":false,"script":null) +] +} +ability_5={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":53,"key_label":0,"unicode":53,"location":0,"echo":false,"script":null) +] +} +target_next={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +class_tank={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":49,"key_label":0,"unicode":49,"location":0,"echo":false,"script":null) +] +} +class_damage={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":50,"key_label":0,"unicode":50,"location":0,"echo":false,"script":null) +] +} +class_healer={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":51,"key_label":0,"unicode":51,"location":0,"echo":false,"script":null) +] +} + +[physics] + +3d/physics_engine="Jolt Physics" + +[rendering] + +rendering_device/driver.windows="d3d12" diff --git a/resources/abilities/aoe_attack.tres b/resources/abilities/aoe_attack.tres new file mode 100644 index 0000000..06a8022 --- /dev/null +++ b/resources/abilities/aoe_attack.tres @@ -0,0 +1,11 @@ +[gd_resource type="Resource" script_class="Ability" load_steps=2 format=3] + +[ext_resource type="Script" path="res://scripts/abilities/ability.gd" id="1"] + +[resource] +script = ExtResource("1") +ability_name = "AOE" +type = 1 +damage = 5.0 +ability_range = 5.0 +icon = "2" diff --git a/resources/abilities/passive_damage_boost.tres b/resources/abilities/passive_damage_boost.tres new file mode 100644 index 0000000..b391ddc --- /dev/null +++ b/resources/abilities/passive_damage_boost.tres @@ -0,0 +1,11 @@ +[gd_resource type="Resource" script_class="Ability" load_steps=2 format=3] + +[ext_resource type="Script" path="res://scripts/abilities/ability.gd" id="1"] + +[resource] +script = ExtResource("1") +ability_name = "Damage Boost" +type = 4 +damage = 50.0 +ability_range = 0.0 +icon = "5" diff --git a/resources/abilities/single_attack.tres b/resources/abilities/single_attack.tres new file mode 100644 index 0000000..788f352 --- /dev/null +++ b/resources/abilities/single_attack.tres @@ -0,0 +1,11 @@ +[gd_resource type="Resource" script_class="Ability" load_steps=2 format=3] + +[ext_resource type="Script" path="res://scripts/abilities/ability.gd" id="1"] + +[resource] +script = ExtResource("1") +ability_name = "Single" +type = 0 +damage = 10.0 +ability_range = 3.0 +icon = "1" diff --git a/resources/abilities/ult_burst.tres b/resources/abilities/ult_burst.tres new file mode 100644 index 0000000..089db12 --- /dev/null +++ b/resources/abilities/ult_burst.tres @@ -0,0 +1,11 @@ +[gd_resource type="Resource" script_class="Ability" load_steps=2 format=3] + +[ext_resource type="Script" path="res://scripts/abilities/ability.gd" id="1"] + +[resource] +script = ExtResource("1") +ability_name = "Burst" +type = 3 +damage = 10.0 +ability_range = 5.0 +icon = "4" diff --git a/resources/abilities/utility_shield_reset.tres b/resources/abilities/utility_shield_reset.tres new file mode 100644 index 0000000..9ed38c7 --- /dev/null +++ b/resources/abilities/utility_shield_reset.tres @@ -0,0 +1,11 @@ +[gd_resource type="Resource" script_class="Ability" load_steps=2 format=3] + +[ext_resource type="Script" path="res://scripts/abilities/ability.gd" id="1"] + +[resource] +script = ExtResource("1") +ability_name = "Shield Reset" +type = 2 +damage = 0.0 +ability_range = 0.0 +icon = "3" diff --git a/resources/ability_sets/damage_set.tres b/resources/ability_sets/damage_set.tres new file mode 100644 index 0000000..339a652 --- /dev/null +++ b/resources/ability_sets/damage_set.tres @@ -0,0 +1,13 @@ +[gd_resource type="Resource" script_class="AbilitySet" format=3 uid="uid://beodknb6i1pm4"] + +[ext_resource type="Script" uid="uid://voedgs25cwrb" path="res://scripts/abilities/ability_set.gd" id="1"] +[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1_ability"] +[ext_resource type="Resource" path="res://resources/abilities/single_attack.tres" id="2"] +[ext_resource type="Resource" path="res://resources/abilities/aoe_attack.tres" id="3"] +[ext_resource type="Resource" path="res://resources/abilities/utility_shield_reset.tres" id="4"] +[ext_resource type="Resource" path="res://resources/abilities/ult_burst.tres" id="5"] +[ext_resource type="Resource" path="res://resources/abilities/passive_damage_boost.tres" id="6"] + +[resource] +script = ExtResource("1") +abilities = Array[ExtResource("1_ability")]([ExtResource("2"), ExtResource("3"), ExtResource("4"), ExtResource("5"), ExtResource("6")]) diff --git a/resources/ability_sets/healer_set.tres b/resources/ability_sets/healer_set.tres new file mode 100644 index 0000000..6e67c4a --- /dev/null +++ b/resources/ability_sets/healer_set.tres @@ -0,0 +1,13 @@ +[gd_resource type="Resource" script_class="AbilitySet" format=3 uid="uid://kcwuhnqy34mj"] + +[ext_resource type="Script" uid="uid://voedgs25cwrb" path="res://scripts/abilities/ability_set.gd" id="1"] +[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1_ability"] +[ext_resource type="Resource" path="res://resources/abilities/single_attack.tres" id="2"] +[ext_resource type="Resource" path="res://resources/abilities/aoe_attack.tres" id="3"] +[ext_resource type="Resource" path="res://resources/abilities/utility_shield_reset.tres" id="4"] +[ext_resource type="Resource" path="res://resources/abilities/ult_burst.tres" id="5"] +[ext_resource type="Resource" path="res://resources/abilities/passive_damage_boost.tres" id="6"] + +[resource] +script = ExtResource("1") +abilities = Array[ExtResource("1_ability")]([ExtResource("2"), ExtResource("3"), ExtResource("4"), ExtResource("5"), ExtResource("6")]) diff --git a/resources/ability_sets/tank_set.tres b/resources/ability_sets/tank_set.tres new file mode 100644 index 0000000..6705b44 --- /dev/null +++ b/resources/ability_sets/tank_set.tres @@ -0,0 +1,13 @@ +[gd_resource type="Resource" script_class="AbilitySet" format=3 uid="uid://cgxtn7dfs40bh"] + +[ext_resource type="Script" uid="uid://voedgs25cwrb" path="res://scripts/abilities/ability_set.gd" id="1"] +[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1_ability"] +[ext_resource type="Resource" path="res://resources/abilities/single_attack.tres" id="2"] +[ext_resource type="Resource" path="res://resources/abilities/aoe_attack.tres" id="3"] +[ext_resource type="Resource" path="res://resources/abilities/utility_shield_reset.tres" id="4"] +[ext_resource type="Resource" path="res://resources/abilities/ult_burst.tres" id="5"] +[ext_resource type="Resource" path="res://resources/abilities/passive_damage_boost.tres" id="6"] + +[resource] +script = ExtResource("1") +abilities = Array[ExtResource("1_ability")]([ExtResource("2"), ExtResource("3"), ExtResource("4"), ExtResource("5"), ExtResource("6")]) diff --git a/scenes/enemy/enemy.tscn b/scenes/enemy/enemy.tscn new file mode 100644 index 0000000..b6b88a8 --- /dev/null +++ b/scenes/enemy/enemy.tscn @@ -0,0 +1,117 @@ +[gd_scene load_steps=6 format=3] + +[ext_resource type="Script" path="res://scripts/enemy/enemy.gd" id="1"] +[ext_resource type="Script" path="res://scripts/components/health.gd" id="2"] +[ext_resource type="Script" path="res://scripts/components/shield.gd" id="3"] +[ext_resource type="Script" path="res://scripts/enemy/enemy_healthbar.gd" id="4"] +[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"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"] +radius = 0.4 +height = 1.5 + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_2"] +radius = 0.4 +height = 1.5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_bg"] +bg_color = Color(0.3, 0.1, 0.1, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_fill"] +bg_color = Color(0.2, 0.8, 0.2, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_bg"] +bg_color = Color(0.1, 0.1, 0.3, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_fill"] +bg_color = Color(0.2, 0.5, 0.9, 1) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"] +albedo_color = Color(0.8, 0.1, 0.1, 1) + +[sub_resource type="SphereMesh" id="SphereMesh_1"] +radius = 0.5 +height = 1.0 +material = SubResource("StandardMaterial3D_1") + +[sub_resource type="SphereShape3D" id="SphereShape3D_1"] +radius = 8.0 + +[node name="Enemy" type="CharacterBody3D"] +script = ExtResource("1") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +shape = SubResource("CapsuleShape3D_1") + +[node name="Mesh" type="MeshInstance3D" parent="."] +mesh = SubResource("SphereMesh_1") + +[node name="Health" type="Node" parent="."] +script = ExtResource("2") + +[node name="Shield" type="Node" parent="."] +script = ExtResource("3") + +[node name="HitArea" type="Area3D" parent="."] +collision_layer = 4 +collision_mask = 0 + +[node name="CollisionShape3D" type="CollisionShape3D" parent="HitArea"] +shape = SubResource("CapsuleShape3D_2") + +[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."] + +[node name="EnemyMovement" type="Node" parent="."] +script = ExtResource("5") + +[node name="EnemyCombat" type="Node" parent="."] +script = ExtResource("6") + +[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="Healthbar" type="Sprite3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0) +billboard = 1 +pixel_size = 0.01 +script = ExtResource("4") + +[node name="SubViewport" type="SubViewport" parent="Healthbar"] +transparent_bg = true +size = Vector2i(104, 29) + +[node name="Border" type="ColorRect" parent="Healthbar/SubViewport"] +offset_right = 104.0 +offset_bottom = 29.0 +color = Color(1, 0.9, 0.2, 1) + +[node name="HealthBar" type="ProgressBar" parent="Healthbar/SubViewport"] +offset_left = 2.0 +offset_top = 2.0 +offset_right = 102.0 +offset_bottom = 12.0 +theme_override_styles/background = SubResource("StyleBoxFlat_health_bg") +theme_override_styles/fill = SubResource("StyleBoxFlat_health_fill") +max_value = 100.0 +value = 100.0 +show_percentage = false + +[node name="ShieldBar" type="ProgressBar" parent="Healthbar/SubViewport"] +offset_left = 2.0 +offset_top = 15.0 +offset_right = 102.0 +offset_bottom = 27.0 +theme_override_styles/background = SubResource("StyleBoxFlat_shield_bg") +theme_override_styles/fill = SubResource("StyleBoxFlat_shield_fill") +max_value = 50.0 +value = 50.0 +show_percentage = false + +[connection signal="body_entered" from="DetectionArea" to="." method="_on_detection_area_body_entered"] +[connection signal="body_exited" from="DetectionArea" to="." method="_on_detection_area_body_exited"] diff --git a/scenes/hud/hud.tscn b/scenes/hud/hud.tscn new file mode 100644 index 0000000..ee3777d --- /dev/null +++ b/scenes/hud/hud.tscn @@ -0,0 +1,181 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="res://scripts/player/hud.gd" id="1"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ability_active"] +bg_color = Color(0.2, 0.2, 0.2, 0.8) +border_width_bottom = 2 +border_width_left = 2 +border_width_right = 2 +border_width_top = 2 +border_color = Color(0.8, 0.8, 0.8, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ability_round"] +bg_color = Color(0.2, 0.2, 0.2, 0.8) +border_width_bottom = 2 +border_width_left = 2 +border_width_right = 2 +border_width_top = 2 +border_color = Color(0.8, 0.8, 0.8, 1) +corner_radius_top_left = 22 +corner_radius_top_right = 22 +corner_radius_bottom_right = 22 +corner_radius_bottom_left = 22 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_bg"] +bg_color = Color(0.3, 0.1, 0.1, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_fill"] +bg_color = Color(0.2, 0.8, 0.2, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_bg"] +bg_color = Color(0.1, 0.1, 0.3, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_fill"] +bg_color = Color(0.2, 0.5, 0.9, 1) + +[node name="HUD" type="CanvasLayer"] +script = ExtResource("1") + +[node name="HealthBar" type="ProgressBar" parent="."] +offset_left = 10.0 +offset_top = 10.0 +offset_right = 210.0 +offset_bottom = 30.0 +theme_override_styles/background = SubResource("StyleBoxFlat_health_bg") +theme_override_styles/fill = SubResource("StyleBoxFlat_health_fill") +max_value = 100.0 +value = 100.0 +show_percentage = false + +[node name="ShieldBar" type="ProgressBar" parent="."] +offset_left = 10.0 +offset_top = 35.0 +offset_right = 210.0 +offset_bottom = 55.0 +theme_override_styles/background = SubResource("StyleBoxFlat_shield_bg") +theme_override_styles/fill = SubResource("StyleBoxFlat_shield_fill") +max_value = 50.0 +value = 50.0 +show_percentage = false + +[node name="RespawnTimer" type="Label" parent="."] +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -50.0 +offset_top = -30.0 +offset_right = 50.0 +offset_bottom = 30.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_font_sizes/font_size = 48 +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="AbilityBar" type="HBoxContainer" parent="."] +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -130.0 +offset_top = -60.0 +offset_right = 130.0 +offset_bottom = -10.0 +grow_horizontal = 2 +grow_vertical = 0 +theme_override_constants/separation = 5 + +[node name="ClassIcon" type="Panel" parent="AbilityBar"] +custom_minimum_size = Vector2(45, 45) +theme_override_styles/panel = SubResource("StyleBoxFlat_ability_round") + +[node name="Label" type="Label" parent="AbilityBar/ClassIcon"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_font_sizes/font_size = 20 +text = "D" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Ability1" type="Panel" parent="AbilityBar"] +custom_minimum_size = Vector2(45, 45) +theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active") + +[node name="Label" type="Label" parent="AbilityBar/Ability1"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "1" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Ability2" type="Panel" parent="AbilityBar"] +custom_minimum_size = Vector2(45, 45) +theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active") + +[node name="Label" type="Label" parent="AbilityBar/Ability2"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "2" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Ability3" type="Panel" parent="AbilityBar"] +custom_minimum_size = Vector2(45, 45) +theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active") + +[node name="Label" type="Label" parent="AbilityBar/Ability3"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "3" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Ability4" type="Panel" parent="AbilityBar"] +custom_minimum_size = Vector2(45, 45) +theme_override_styles/panel = SubResource("StyleBoxFlat_ability_active") + +[node name="Label" type="Label" parent="AbilityBar/Ability4"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "4" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Ability5" type="Panel" parent="AbilityBar"] +custom_minimum_size = Vector2(45, 45) +theme_override_styles/panel = SubResource("StyleBoxFlat_ability_round") + +[node name="Label" type="Label" parent="AbilityBar/Ability5"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "P" +horizontal_alignment = 1 +vertical_alignment = 1 diff --git a/scenes/player/player.tscn b/scenes/player/player.tscn new file mode 100644 index 0000000..8c0d7d2 --- /dev/null +++ b/scenes/player/player.tscn @@ -0,0 +1,62 @@ +[gd_scene format=3 uid="uid://cdnkbt1f0db7e"] + +[ext_resource type="Script" uid="uid://bfpt2p7uucfyb" path="res://scripts/player/player.gd" id="1"] +[ext_resource type="Script" uid="uid://cohjyjge1kqxb" path="res://scripts/player/camera.gd" id="2"] +[ext_resource type="Script" uid="uid://fg87dh8fbc8" path="res://scripts/player/movement.gd" id="3"] +[ext_resource type="Script" uid="uid://d15til6fsxw5b" path="res://scripts/player/combat.gd" id="4"] +[ext_resource type="Script" path="res://scripts/components/health.gd" id="5"] +[ext_resource type="Script" path="res://scripts/components/shield.gd" id="6"] +[ext_resource type="Script" path="res://scripts/player/targeting.gd" id="8"] +[ext_resource type="Script" path="res://scripts/player/respawn.gd" id="9"] +[ext_resource type="Script" path="res://scripts/player/player_class.gd" id="10"] +[ext_resource type="Resource" path="res://resources/ability_sets/tank_set.tres" id="11"] +[ext_resource type="Resource" path="res://resources/ability_sets/damage_set.tres" id="12"] +[ext_resource type="Resource" path="res://resources/ability_sets/healer_set.tres" id="13"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"] +radius = 0.3 +height = 1.8 + +[sub_resource type="CapsuleMesh" id="CapsuleMesh_1"] +radius = 0.3 +height = 1.8 + +[node name="Player" type="CharacterBody3D" unique_id=1350215040] +script = ExtResource("1") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=33887999] +shape = SubResource("CapsuleShape3D_1") + +[node name="Mesh" type="MeshInstance3D" parent="." unique_id=1346931672] +mesh = SubResource("CapsuleMesh_1") + +[node name="CameraPivot" type="Node3D" parent="." unique_id=1292689540] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0) +script = ExtResource("2") + +[node name="Camera3D" type="Camera3D" parent="CameraPivot" unique_id=1225820651] +transform = Transform3D(1, 0, 0, 0, 0.966, 0.259, 0, -0.259, 0.966, 0, 2, 5) + +[node name="Movement" type="Node" parent="." unique_id=654979387] +script = ExtResource("3") + +[node name="Combat" type="Node" parent="." unique_id=1754235583] +script = ExtResource("4") + +[node name="Targeting" type="Node" parent="."] +script = ExtResource("8") + +[node name="Health" type="Node" parent="."] +script = ExtResource("5") + +[node name="Shield" type="Node" parent="."] +script = ExtResource("6") + +[node name="Respawn" type="Node" parent="."] +script = ExtResource("9") + +[node name="PlayerClass" type="Node" parent="."] +script = ExtResource("10") +tank_set = ExtResource("11") +damage_set = ExtResource("12") +healer_set = ExtResource("13") diff --git a/scenes/world.tscn b/scenes/world.tscn new file mode 100644 index 0000000..8bb599a --- /dev/null +++ b/scenes/world.tscn @@ -0,0 +1,64 @@ +[gd_scene format=3 uid="uid://dy1icabu2ssbw"] + +[ext_resource type="PackedScene" path="res://scenes/enemy/enemy.tscn" id="enemy"] +[ext_resource type="PackedScene" path="res://scenes/hud/hud.tscn" id="hud"] +[ext_resource type="PackedScene" uid="uid://cdnkbt1f0db7e" path="res://scenes/player/player.tscn" id="player"] + +[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) +polygons = [PackedInt32Array(3, 2, 0), PackedInt32Array(0, 2, 1)] + +[sub_resource type="Gradient" id="Gradient_1"] +colors = PackedColorArray(0.15, 0.35, 0.05, 1, 0.3, 0.55, 0.1, 1) + +[sub_resource type="FastNoiseLite" id="FastNoiseLite_1"] +frequency = 0.05 + +[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_1"] +noise = SubResource("FastNoiseLite_1") +color_ramp = SubResource("Gradient_1") +seamless = true + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"] +albedo_texture = SubResource("NoiseTexture2D_1") +uv1_scale = Vector3(3, 3, 1) + +[sub_resource type="PlaneMesh" id="PlaneMesh_1"] +material = SubResource("StandardMaterial3D_1") +size = Vector2(20, 20) + +[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_1"] + +[node name="World" type="Node3D" unique_id=1834775183] + +[node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=561649622] +navigation_mesh = SubResource("NavigationMesh_1") + +[node name="Boden" type="MeshInstance3D" parent="NavigationRegion3D" unique_id=1129907783] +mesh = SubResource("PlaneMesh_1") + +[node name="BodenCollision" type="StaticBody3D" parent="." unique_id=391155617] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="BodenCollision" unique_id=809444866] +shape = SubResource("WorldBoundaryShape3D_1") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1132129079] +transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 10, 0) +shadow_enabled = true + +[node name="Player" parent="." unique_id=190642073 instance=ExtResource("player")] +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="Enemy1" parent="." unique_id=1152011787 instance=ExtResource("enemy")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0.75, 5) + +[node name="Enemy2" parent="." unique_id=877649363 instance=ExtResource("enemy")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7, 0.75, 5) + +[node name="Enemy3" parent="." unique_id=1831736748 instance=ExtResource("enemy")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0.75, 7) + +[node name="Enemy4" parent="." unique_id=1818643429 instance=ExtResource("enemy")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7, 0.75, 7) diff --git a/scripts/abilities/ability.gd b/scripts/abilities/ability.gd new file mode 100644 index 0000000..eeadaf1 --- /dev/null +++ b/scripts/abilities/ability.gd @@ -0,0 +1,66 @@ +extends Resource +class_name Ability + +enum Type { SINGLE, AOE, UTILITY, ULT, PASSIVE } + +@export var ability_name: String = "" +@export var type: Type = Type.SINGLE +@export var damage: float = 0.0 +@export var ability_range: float = 3.0 +@export var icon: String = "" + +func execute(player: Node, targeting: Node) -> void: + var dmg: float = _get_modified_damage(player, damage) + match type: + Type.SINGLE: + _execute_single(player, targeting, dmg) + Type.AOE: + _execute_aoe(player, dmg) + Type.UTILITY: + _execute_utility(player) + Type.ULT: + _execute_ult(player, targeting, dmg) + +func _get_modified_damage(player: Node, base: float) -> float: + var combat: Node = player.get_node("Combat") + return combat.apply_passive(base) + +func _execute_single(player: Node, targeting: Node, dmg: float) -> void: + var target: Node3D = targeting.current_target + if not target or not is_instance_valid(target): + return + var dist: float = player.global_position.distance_to(target.global_position) + if dist > ability_range: + return + EventBus.damage_requested.emit(player, target, dmg) + EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg) + +func _execute_aoe(player: Node, dmg: float) -> void: + var enemies := player.get_tree().get_nodes_in_group("enemies") + for enemy in enemies: + var dist: float = player.global_position.distance_to(enemy.global_position) + if dist <= ability_range: + EventBus.damage_requested.emit(player, enemy, dmg) + EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg) + +func _execute_utility(player: Node) -> void: + var shield: Node = player.get_node_or_null("Shield") + if shield: + shield.current_shield = shield.max_shield + EventBus.shield_changed.emit(player, shield.current_shield, shield.max_shield) + +func _execute_ult(player: Node, targeting: Node, dmg: float) -> void: + var target: Node3D = targeting.current_target + if not target or not is_instance_valid(target): + return + var dist: float = player.global_position.distance_to(target.global_position) + if dist > ability_range: + return + EventBus.damage_requested.emit(player, target, dmg * 4.0) + var enemies := player.get_tree().get_nodes_in_group("enemies") + for enemy in enemies: + if enemy != target: + var enemy_dist: float = target.global_position.distance_to(enemy.global_position) + if enemy_dist <= ability_range: + 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) diff --git a/scripts/abilities/ability.gd.uid b/scripts/abilities/ability.gd.uid new file mode 100644 index 0000000..a2c213e --- /dev/null +++ b/scripts/abilities/ability.gd.uid @@ -0,0 +1 @@ +uid://c03xbbf3yhfl3 diff --git a/scripts/abilities/ability_set.gd b/scripts/abilities/ability_set.gd new file mode 100644 index 0000000..58ab09e --- /dev/null +++ b/scripts/abilities/ability_set.gd @@ -0,0 +1,4 @@ +extends Resource +class_name AbilitySet + +@export var abilities: Array[Ability] = [] diff --git a/scripts/abilities/ability_set.gd.uid b/scripts/abilities/ability_set.gd.uid new file mode 100644 index 0000000..9a11572 --- /dev/null +++ b/scripts/abilities/ability_set.gd.uid @@ -0,0 +1 @@ +uid://voedgs25cwrb diff --git a/scripts/components/health.gd b/scripts/components/health.gd new file mode 100644 index 0000000..dbe7429 --- /dev/null +++ b/scripts/components/health.gd @@ -0,0 +1,33 @@ +extends Node + +@export var max_health := 100.0 +const HEALTH_REGEN := 1.0 +var current_health: float + +func _ready() -> void: + current_health = max_health + EventBus.damage_requested.connect(_on_damage_requested) + +func _process(delta: float) -> void: + if current_health > 0 and current_health < max_health: + current_health = min(current_health + HEALTH_REGEN * delta, max_health) + EventBus.health_changed.emit(get_parent(), current_health, max_health) + +func _on_damage_requested(attacker: Node, target: Node, amount: float) -> void: + if target != get_parent(): + return + var remaining: float = amount + var shield: Node = get_parent().get_node_or_null("Shield") + if shield: + remaining = shield.absorb(remaining) + EventBus.damage_dealt.emit(attacker, get_parent(), amount) + if remaining > 0: + take_damage(remaining, attacker) + +func take_damage(amount: float, attacker: Node) -> void: + current_health -= amount + if current_health <= 0: + current_health = 0 + EventBus.health_changed.emit(get_parent(), current_health, max_health) + if current_health <= 0: + EventBus.entity_died.emit(get_parent()) diff --git a/scripts/components/health.gd.uid b/scripts/components/health.gd.uid new file mode 100644 index 0000000..82be2a2 --- /dev/null +++ b/scripts/components/health.gd.uid @@ -0,0 +1 @@ +uid://b053b4fkkeaod diff --git a/scripts/components/shield.gd b/scripts/components/shield.gd new file mode 100644 index 0000000..570a573 --- /dev/null +++ b/scripts/components/shield.gd @@ -0,0 +1,32 @@ +extends Node + +@export var max_shield := 50.0 +const REGEN_DELAY := 3.0 +const REGEN_TIME := 5.0 +var current_shield: float +var regen_timer := 0.0 + +func _ready() -> void: + current_shield = max_shield + +func _process(delta: float) -> void: + if current_shield < max_shield: + regen_timer += delta + if regen_timer >= REGEN_DELAY: + current_shield += (max_shield / REGEN_TIME) * delta + if current_shield >= max_shield: + current_shield = max_shield + EventBus.shield_regenerated.emit(get_parent()) + EventBus.shield_changed.emit(get_parent(), current_shield, max_shield) + +func absorb(amount: float) -> float: + if current_shield <= 0: + return amount + regen_timer = 0.0 + var absorbed: float = min(amount, current_shield) + current_shield -= absorbed + print("%s Schild: %s/%s (-%s)" % [get_parent().name, current_shield, max_shield, absorbed]) + if current_shield <= 0: + EventBus.shield_broken.emit(get_parent()) + EventBus.shield_changed.emit(get_parent(), current_shield, max_shield) + return amount - absorbed diff --git a/scripts/components/shield.gd.uid b/scripts/components/shield.gd.uid new file mode 100644 index 0000000..add605d --- /dev/null +++ b/scripts/components/shield.gd.uid @@ -0,0 +1 @@ +uid://bpfw71oprcvou diff --git a/scripts/enemy/enemy.gd b/scripts/enemy/enemy.gd new file mode 100644 index 0000000..4aaa4ba --- /dev/null +++ b/scripts/enemy/enemy.gd @@ -0,0 +1,38 @@ +extends CharacterBody3D + +enum State { IDLE, CHASE, ATTACK, RETURN } + +var state: int = State.IDLE +var target: Node3D = null +var spawn_position: Vector3 +var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity") + +@onready var health: Node = $Health + +func _ready() -> void: + spawn_position = global_position + add_to_group("enemies") + EventBus.entity_died.connect(_on_entity_died) + +func _on_entity_died(entity: Node) -> void: + if entity == self: + queue_free() + elif entity == target: + target = null + state = State.RETURN + +func _physics_process(delta: float) -> void: + if not is_on_floor(): + velocity.y -= gravity * delta + move_and_slide() + +func _on_detection_area_body_entered(body: Node3D) -> void: + if body is CharacterBody3D and body.name == "Player": + target = body + state = State.CHASE + EventBus.enemy_engaged.emit(self, body) + +func _on_detection_area_body_exited(body: Node3D) -> void: + if body == target and state == State.CHASE: + state = State.RETURN + target = null diff --git a/scripts/enemy/enemy.gd.uid b/scripts/enemy/enemy.gd.uid new file mode 100644 index 0000000..8a49421 --- /dev/null +++ b/scripts/enemy/enemy.gd.uid @@ -0,0 +1 @@ +uid://bwi75jx0agktd diff --git a/scripts/enemy/enemy_combat.gd b/scripts/enemy/enemy_combat.gd new file mode 100644 index 0000000..d1dc6f1 --- /dev/null +++ b/scripts/enemy/enemy_combat.gd @@ -0,0 +1,26 @@ +extends Node + +const ATTACK_RANGE := 2.0 +const ATTACK_COOLDOWN := 1.5 +const ATTACK_DAMAGE := 5.0 + +var attack_timer := 0.0 + +@onready var enemy: CharacterBody3D = get_parent() + +func _physics_process(delta: float) -> void: + attack_timer -= delta + if enemy.state != enemy.State.ATTACK: + return + if not is_instance_valid(enemy.target): + enemy.state = enemy.State.RETURN + return + var dist := enemy.global_position.distance_to(enemy.target.global_position) + if dist > ATTACK_RANGE: + enemy.state = enemy.State.CHASE + return + if attack_timer <= 0: + attack_timer = ATTACK_COOLDOWN + EventBus.damage_requested.emit(enemy, enemy.target, ATTACK_DAMAGE) + enemy.velocity.x = 0 + enemy.velocity.z = 0 diff --git a/scripts/enemy/enemy_combat.gd.uid b/scripts/enemy/enemy_combat.gd.uid new file mode 100644 index 0000000..bbca79f --- /dev/null +++ b/scripts/enemy/enemy_combat.gd.uid @@ -0,0 +1 @@ +uid://ct4u62xalrjyo diff --git a/scripts/enemy/enemy_healthbar.gd b/scripts/enemy/enemy_healthbar.gd new file mode 100644 index 0000000..13098af --- /dev/null +++ b/scripts/enemy/enemy_healthbar.gd @@ -0,0 +1,22 @@ +extends Sprite3D + +@onready var viewport: SubViewport = $SubViewport +@onready var health_bar: ProgressBar = $SubViewport/HealthBar +@onready var shield_bar: ProgressBar = $SubViewport/ShieldBar +@onready var border: ColorRect = $SubViewport/Border +@onready var health: Node = get_parent().get_node("Health") +@onready var shield: Node = get_parent().get_node("Shield") + +func _ready() -> void: + texture = viewport.get_texture() + health_bar.max_value = health.max_health + shield_bar.max_value = shield.max_shield + border.visible = false + EventBus.target_changed.connect(_on_target_changed) + +func _process(_delta: float) -> void: + health_bar.value = health.current_health + shield_bar.value = shield.current_shield + +func _on_target_changed(_player: Node, target: Node) -> void: + border.visible = (target == get_parent()) diff --git a/scripts/enemy/enemy_healthbar.gd.uid b/scripts/enemy/enemy_healthbar.gd.uid new file mode 100644 index 0000000..4524b26 --- /dev/null +++ b/scripts/enemy/enemy_healthbar.gd.uid @@ -0,0 +1 @@ +uid://dwqx03nfypa7u diff --git a/scripts/enemy/enemy_movement.gd b/scripts/enemy/enemy_movement.gd new file mode 100644 index 0000000..23dad80 --- /dev/null +++ b/scripts/enemy/enemy_movement.gd @@ -0,0 +1,52 @@ +extends Node + +const SPEED := 3.0 +const LEASH_RANGE := 15.0 +const ATTACK_RANGE := 2.0 + +@onready var enemy: CharacterBody3D = get_parent() +@onready var nav_agent: NavigationAgent3D = get_parent().get_node("NavigationAgent3D") + +func _physics_process(_delta: float) -> void: + match enemy.state: + enemy.State.IDLE: + enemy.velocity.x = 0 + enemy.velocity.z = 0 + enemy.State.CHASE: + _chase() + enemy.State.RETURN: + _return_to_spawn() + +func _chase() -> void: + if not is_instance_valid(enemy.target): + enemy.state = enemy.State.RETURN + return + var dist_to_spawn := enemy.global_position.distance_to(enemy.spawn_position) + if dist_to_spawn > LEASH_RANGE: + enemy.state = enemy.State.RETURN + enemy.target = null + return + var dist_to_target := enemy.global_position.distance_to(enemy.target.global_position) + if dist_to_target <= ATTACK_RANGE: + enemy.state = enemy.State.ATTACK + return + nav_agent.target_position = enemy.target.global_position + var next_pos := nav_agent.get_next_path_position() + var direction := (next_pos - enemy.global_position).normalized() + direction.y = 0 + enemy.velocity.x = direction.x * SPEED + enemy.velocity.z = direction.z * SPEED + +func _return_to_spawn() -> void: + var dist := enemy.global_position.distance_to(enemy.spawn_position) + if dist < 1.0: + enemy.state = enemy.State.IDLE + enemy.velocity.x = 0 + enemy.velocity.z = 0 + return + nav_agent.target_position = enemy.spawn_position + var next_pos := nav_agent.get_next_path_position() + var direction := (next_pos - enemy.global_position).normalized() + direction.y = 0 + enemy.velocity.x = direction.x * SPEED + enemy.velocity.z = direction.z * SPEED diff --git a/scripts/enemy/enemy_movement.gd.uid b/scripts/enemy/enemy_movement.gd.uid new file mode 100644 index 0000000..08cbb99 --- /dev/null +++ b/scripts/enemy/enemy_movement.gd.uid @@ -0,0 +1 @@ +uid://tnx6rbnnngn diff --git a/scripts/event_bus.gd b/scripts/event_bus.gd new file mode 100644 index 0000000..b0085d1 --- /dev/null +++ b/scripts/event_bus.gd @@ -0,0 +1,15 @@ +extends Node + +signal attack_executed(attacker, position, direction, damage) +signal damage_dealt(attacker, target, damage) +signal entity_died(entity) +signal shield_broken(entity) +signal shield_regenerated(entity) +signal target_changed(player, target) +signal player_respawned(player) +signal class_changed(player, class_type) +signal damage_requested(attacker, target, amount) +signal health_changed(entity, current, max_val) +signal shield_changed(entity, current, max_val) +signal respawn_tick(timer) +signal enemy_engaged(enemy, target) diff --git a/scripts/event_bus.gd.uid b/scripts/event_bus.gd.uid new file mode 100644 index 0000000..5fb252b --- /dev/null +++ b/scripts/event_bus.gd.uid @@ -0,0 +1 @@ +uid://g7a7xkg1pgb4 diff --git a/scripts/player/camera.gd b/scripts/player/camera.gd new file mode 100644 index 0000000..a01980b --- /dev/null +++ b/scripts/player/camera.gd @@ -0,0 +1,30 @@ +extends Node3D + +const SENSITIVITY := 0.003 +const PITCH_MIN := -80.0 +const PITCH_MAX := 80.0 + +var pitch := -0.3 +var camera_yaw := 0.0 +var player_yaw := -2.356 + +func _ready() -> void: + get_parent().rotation.y = player_yaw + rotation = Vector3(pitch, camera_yaw, 0) + +func _unhandled_input(event: InputEvent) -> void: + if event is InputEventMouseMotion: + var lmb := Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) + var rmb := Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) + + if lmb or rmb: + pitch -= event.relative.y * SENSITIVITY + pitch = clamp(pitch, deg_to_rad(PITCH_MIN), deg_to_rad(PITCH_MAX)) + + if rmb: + player_yaw -= event.relative.x * SENSITIVITY + get_parent().rotation.y = player_yaw + else: + camera_yaw -= event.relative.x * SENSITIVITY + + rotation = Vector3(pitch, camera_yaw, 0) diff --git a/scripts/player/camera.gd.uid b/scripts/player/camera.gd.uid new file mode 100644 index 0000000..7319a55 --- /dev/null +++ b/scripts/player/camera.gd.uid @@ -0,0 +1 @@ +uid://cohjyjge1kqxb diff --git a/scripts/player/combat.gd b/scripts/player/combat.gd new file mode 100644 index 0000000..aced7b4 --- /dev/null +++ b/scripts/player/combat.gd @@ -0,0 +1,35 @@ +extends Node + +@onready var player: CharacterBody3D = get_parent() +@onready var targeting: Node = get_parent().get_node("Targeting") +@onready var player_class: Node = get_parent().get_node("PlayerClass") + +var abilities: Array = [] + +func _ready() -> void: + _load_abilities() + EventBus.class_changed.connect(_on_class_changed) + +func _load_abilities() -> void: + var ability_set: AbilitySet = player_class.get_ability_set() + if ability_set: + abilities = ability_set.abilities + else: + abilities = [] + +func _unhandled_input(event: InputEvent) -> void: + for i in range(min(abilities.size(), 5)): + if event.is_action_pressed("ability_%s" % (i + 1)) and abilities[i]: + if abilities[i].type == Ability.Type.PASSIVE: + return + abilities[i].execute(player, targeting) + return + +func apply_passive(base_damage: float) -> float: + for ability in abilities: + if ability and ability.type == Ability.Type.PASSIVE: + return base_damage * (1.0 + ability.damage / 100.0) + return base_damage + +func _on_class_changed(_player: Node, _class_type: int) -> void: + _load_abilities() diff --git a/scripts/player/combat.gd.uid b/scripts/player/combat.gd.uid new file mode 100644 index 0000000..1b28400 --- /dev/null +++ b/scripts/player/combat.gd.uid @@ -0,0 +1 @@ +uid://d15til6fsxw5b diff --git a/scripts/player/hud.gd b/scripts/player/hud.gd new file mode 100644 index 0000000..e77d149 --- /dev/null +++ b/scripts/player/hud.gd @@ -0,0 +1,43 @@ +extends CanvasLayer + +@onready var health_bar: ProgressBar = $HealthBar +@onready var shield_bar: ProgressBar = $ShieldBar +@onready var respawn_label: Label = $RespawnTimer +@onready var class_icon: Label = $AbilityBar/ClassIcon/Label + +var player_node: Node = null + +func _ready() -> void: + respawn_label.visible = false + EventBus.health_changed.connect(_on_health_changed) + EventBus.shield_changed.connect(_on_shield_changed) + EventBus.entity_died.connect(_on_entity_died) + EventBus.player_respawned.connect(_on_player_respawned) + EventBus.class_changed.connect(_on_class_changed) + EventBus.respawn_tick.connect(_on_respawn_tick) + +func _on_health_changed(entity: Node, current: float, max_val: float) -> void: + if entity.name == "Player": + health_bar.max_value = max_val + health_bar.value = current + +func _on_shield_changed(entity: Node, current: float, max_val: float) -> void: + if entity.name == "Player": + shield_bar.max_value = max_val + shield_bar.value = current + +func _on_entity_died(entity: Node) -> void: + if entity.name == "Player": + respawn_label.visible = true + +func _on_player_respawned(_player: Node) -> void: + respawn_label.visible = false + +func _on_respawn_tick(timer: float) -> void: + respawn_label.text = str(ceil(timer)) + +func _on_class_changed(_player: Node, class_type: int) -> void: + match class_type: + 0: class_icon.text = "T" + 1: class_icon.text = "D" + 2: class_icon.text = "H" diff --git a/scripts/player/hud.gd.uid b/scripts/player/hud.gd.uid new file mode 100644 index 0000000..56b12aa --- /dev/null +++ b/scripts/player/hud.gd.uid @@ -0,0 +1 @@ +uid://c4jhr8k4uwoy7 diff --git a/scripts/player/movement.gd b/scripts/player/movement.gd new file mode 100644 index 0000000..16a3f2a --- /dev/null +++ b/scripts/player/movement.gd @@ -0,0 +1,35 @@ +extends Node + +const SPEED := 5.0 +const JUMP_VELOCITY := 4.5 + +var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity") + +@onready var player: CharacterBody3D = get_parent() + +func _physics_process(delta: float) -> void: + if not player.is_on_floor(): + player.velocity.y -= gravity * delta + + if Input.is_action_just_pressed("ui_accept") and player.is_on_floor(): + player.velocity.y = JUMP_VELOCITY + + var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back") + var camera_pivot := player.get_node("CameraPivot") as Node3D + var forward := -camera_pivot.global_transform.basis.z + forward.y = 0 + forward = forward.normalized() + var right := camera_pivot.global_transform.basis.x + right.y = 0 + right = right.normalized() + + var direction := (forward * -input_dir.y + right * input_dir.x).normalized() + + if direction: + player.velocity.x = direction.x * SPEED + player.velocity.z = direction.z * SPEED + else: + player.velocity.x = move_toward(player.velocity.x, 0, SPEED) + player.velocity.z = move_toward(player.velocity.z, 0, SPEED) + + player.move_and_slide() diff --git a/scripts/player/movement.gd.uid b/scripts/player/movement.gd.uid new file mode 100644 index 0000000..2e48f19 --- /dev/null +++ b/scripts/player/movement.gd.uid @@ -0,0 +1 @@ +uid://fg87dh8fbc8 diff --git a/scripts/player/player.gd b/scripts/player/player.gd new file mode 100644 index 0000000..27b2d44 --- /dev/null +++ b/scripts/player/player.gd @@ -0,0 +1 @@ +extends CharacterBody3D diff --git a/scripts/player/player.gd.uid b/scripts/player/player.gd.uid new file mode 100644 index 0000000..a695d0e --- /dev/null +++ b/scripts/player/player.gd.uid @@ -0,0 +1 @@ +uid://bfpt2p7uucfyb diff --git a/scripts/player/player_class.gd b/scripts/player/player_class.gd new file mode 100644 index 0000000..955799d --- /dev/null +++ b/scripts/player/player_class.gd @@ -0,0 +1,37 @@ +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 diff --git a/scripts/player/player_class.gd.uid b/scripts/player/player_class.gd.uid new file mode 100644 index 0000000..fada3de --- /dev/null +++ b/scripts/player/player_class.gd.uid @@ -0,0 +1 @@ +uid://rus4umqvvqq4 diff --git a/scripts/player/respawn.gd b/scripts/player/respawn.gd new file mode 100644 index 0000000..0720cb4 --- /dev/null +++ b/scripts/player/respawn.gd @@ -0,0 +1,46 @@ +extends Node + +const RESPAWN_TIME := 3.0 +var respawn_timer := 0.0 +var is_dead := false +var spawn_position: Vector3 + +@onready var player: CharacterBody3D = get_parent() + +func _ready() -> void: + spawn_position = player.global_position + EventBus.entity_died.connect(_on_entity_died) + +func _process(delta: float) -> void: + if is_dead: + respawn_timer -= delta + EventBus.respawn_tick.emit(respawn_timer) + if respawn_timer <= 0: + _respawn() + +func _on_entity_died(entity: Node) -> void: + if entity == player and not is_dead: + is_dead = true + respawn_timer = RESPAWN_TIME + player.velocity = Vector3.ZERO + player.get_node("Mesh").visible = false + player.get_node("CollisionShape3D").disabled = true + player.get_node("Movement").set_physics_process(false) + player.get_node("Combat").set_process_unhandled_input(false) + player.get_node("Targeting").set_process_unhandled_input(false) + +func _respawn() -> void: + is_dead = false + player.global_position = spawn_position + player.get_node("Mesh").visible = true + player.get_node("CollisionShape3D").disabled = false + player.get_node("Movement").set_physics_process(true) + player.get_node("Combat").set_process_unhandled_input(true) + player.get_node("Targeting").set_process_unhandled_input(true) + var health_node: Node = player.get_node("Health") + var shield_node: Node = player.get_node("Shield") + health_node.current_health = health_node.max_health + shield_node.current_shield = shield_node.max_shield + EventBus.health_changed.emit(player, health_node.current_health, health_node.max_health) + EventBus.shield_changed.emit(player, shield_node.current_shield, shield_node.max_shield) + EventBus.player_respawned.emit(player) diff --git a/scripts/player/respawn.gd.uid b/scripts/player/respawn.gd.uid new file mode 100644 index 0000000..06060cd --- /dev/null +++ b/scripts/player/respawn.gd.uid @@ -0,0 +1 @@ +uid://dw3dtax5bx0of diff --git a/scripts/player/targeting.gd b/scripts/player/targeting.gd new file mode 100644 index 0000000..8c8736b --- /dev/null +++ b/scripts/player/targeting.gd @@ -0,0 +1,113 @@ +extends Node + +const TARGET_RANGE := 20.0 +const COMBAT_TIMEOUT := 3.0 + +var current_target: Node3D = null +var mouse_press_pos: Vector2 = Vector2.ZERO +var in_combat := false +var combat_timer := 0.0 + +@onready var player: CharacterBody3D = get_parent() +@onready var camera: Camera3D = get_parent().get_node("CameraPivot/Camera3D") + +func _ready() -> void: + EventBus.damage_dealt.connect(_on_damage_dealt) + EventBus.entity_died.connect(_on_entity_died) + EventBus.enemy_engaged.connect(_on_enemy_engaged) + +func _process(delta: float) -> void: + if in_combat: + combat_timer -= delta + if combat_timer <= 0: + in_combat = false + +func _unhandled_input(event: InputEvent) -> void: + if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: + if event.pressed: + mouse_press_pos = event.position + else: + var drag: float = event.position.distance_to(mouse_press_pos) + if drag < 5.0: + _try_target_under_mouse(event.position) + if event.is_action_pressed("target_next"): + _cycle_target() + +func _try_target_under_mouse(mouse_pos: Vector2) -> void: + var from := camera.project_ray_origin(mouse_pos) + var to := from + camera.project_ray_normal(mouse_pos) * TARGET_RANGE + var space := player.get_world_3d().direct_space_state + var query := PhysicsRayQueryParameters3D.create(from, to) + query.collision_mask = 4 + query.collide_with_areas = true + query.collide_with_bodies = false + var result := space.intersect_ray(query) + if result: + var hit_target := result.collider.get_parent() as Node3D + set_target(hit_target) + else: + set_target(null) + +func _cycle_target() -> void: + var enemies := get_tree().get_nodes_in_group("enemies") + if enemies.is_empty(): + set_target(null) + return + if current_target == null or current_target not in enemies: + set_target(enemies[0]) + return + var idx := enemies.find(current_target) + var next_idx := (idx + 1) % enemies.size() + set_target(enemies[next_idx]) + +func set_target(target: Node3D) -> void: + current_target = target + EventBus.target_changed.emit(player, target) + +func _on_enemy_engaged(_enemy: Node, target: Node) -> void: + if target == player: + combat_timer = COMBAT_TIMEOUT + if not in_combat: + in_combat = true + if current_target == null: + _target_nearest() + +func _on_damage_dealt(_attacker: Node, target: Node, _amount: float) -> void: + if target == player: + combat_timer = COMBAT_TIMEOUT + if not in_combat: + in_combat = true + if current_target == null: + _target_nearest() + +func _on_entity_died(entity: Node) -> void: + if entity == current_target: + set_target(null) + if in_combat: + _target_nearest_except(entity) + +func _target_nearest_except(exclude: Node = null) -> void: + var enemies := get_tree().get_nodes_in_group("enemies") + var nearest: Node3D = null + var nearest_dist: float = INF + for enemy in enemies: + if is_instance_valid(enemy) and enemy != exclude: + var dist: float = player.global_position.distance_to(enemy.global_position) + if dist < nearest_dist: + nearest_dist = dist + nearest = enemy + if nearest: + set_target(nearest) + +func _target_nearest() -> void: + var enemies := get_tree().get_nodes_in_group("enemies") + var nearest: Node3D = null + var nearest_dist: float = INF + for enemy in enemies: + if is_instance_valid(enemy): + var dist: float = player.global_position.distance_to(enemy.global_position) + if dist < nearest_dist: + nearest_dist = dist + nearest = enemy + if nearest: + set_target(nearest) diff --git a/scripts/player/targeting.gd.uid b/scripts/player/targeting.gd.uid new file mode 100644 index 0000000..5bf1d42 --- /dev/null +++ b/scripts/player/targeting.gd.uid @@ -0,0 +1 @@ +uid://b05nkuryipwny