commit aa2c1825340fafc0aa7cf73736ad091512b55c0d Author: Marek Date: Sun Mar 29 16:05:31 2026 +0200 last init 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