update
This commit is contained in:
62
CLAUDE.md
62
CLAUDE.md
@@ -14,51 +14,75 @@ Der User kommuniziert auf Deutsch. Code und Variablen auf Englisch. Kommentare n
|
||||
## Architektur
|
||||
- **Zwischen Szenen**: Kommunikation über EventBus (Autoload). Szenen kennen sich nicht.
|
||||
- **Innerhalb einer Szene**: Modulare Skripte als Child-Nodes, Zugriff auf Geschwister-Nodes erlaubt.
|
||||
- **Gruppen**: "player", "enemies", "portals"
|
||||
- **Autoloads**: EventBus (Signals), GameState (Spielerzustand zwischen Szenenwechseln)
|
||||
- **Gruppen**: "player", "enemies", "portals", "boss"
|
||||
- **Resources** für statische Konfiguration (Stats, Abilities), **Nodes** für laufenden Zustand
|
||||
- Portale sind keine Gegner — eigene Gruppe "portals", kein State/Aggro/Combat
|
||||
|
||||
## Projektstruktur
|
||||
- `scenes/` — .tscn Dateien (world, player, enemy, portal, hud)
|
||||
- `scripts/player/` — Spieler-Skripte (player, camera, movement, combat, targeting, role, respawn)
|
||||
- `scripts/enemy/` — Gegner-Skripte (enemy, enemy_movement, enemy_combat, enemy_aggro)
|
||||
- `scripts/portal/` — Portal-Skripte (portal)
|
||||
- `scenes/` — .tscn Dateien
|
||||
- `world.tscn` — Hauptszene (100x100m, Taverne in Mitte)
|
||||
- `player/player.tscn` — Spieler
|
||||
- `enemy/enemy.tscn` — Gegner
|
||||
- `enemy/boss.tscn` — Boss (eigene Szene, erbt von Enemy)
|
||||
- `portal/portal.tscn` — Portal (Gegner-Spawner)
|
||||
- `portal/gate.tscn` — Gate (Teleporter, konfigurierbar: Dungeon-Eingang oder Exit)
|
||||
- `dungeon/dungeon.tscn` — Dungeon (15x90m Schlauch, 4 Gegnergruppen + Boss)
|
||||
- `hud/hud.tscn` — HUD
|
||||
- `scripts/player/` — Spieler-Skripte (player, camera, movement, combat, targeting, role, respawn, hud)
|
||||
- `scripts/enemy/` — Gegner-Skripte (enemy, enemy_movement, enemy_combat, enemy_aggro, boss)
|
||||
- `scripts/portal/` — Portal + Gate (portal, gate)
|
||||
- `scripts/dungeon/` — Dungeon-Logik (dungeon_manager)
|
||||
- `scripts/world/` — Welt-Logik (portal_spawner)
|
||||
- `scripts/components/` — Wiederverwendbare Komponenten (health, shield, healthbar, spawner)
|
||||
- `scripts/abilities/` — Ability-System (ability, ability_set)
|
||||
- `scripts/resources/` — Resource-Klassen (entity_stats)
|
||||
- `resources/stats/` — Stats .tres (player_stats, enemy_stats, portal_stats)
|
||||
- `resources/abilities/` — Ability .tres (single_attack, aoe_attack, utility_shield_reset, ult_burst, passive_damage_boost)
|
||||
- `scripts/event_bus.gd` — Globale Signals
|
||||
- `scripts/game_state.gd` — Spielerzustand zwischen Szenenwechseln
|
||||
- `resources/stats/` — Stats .tres (player_stats, enemy_stats, portal_stats, boss_stats)
|
||||
- `resources/abilities/` — Ability .tres pro Rolle (single_attack, tank_single, healer_single, etc.)
|
||||
- `resources/ability_sets/` — AbilitySet .tres pro Rolle (tank_set, damage_set, healer_set)
|
||||
|
||||
## Planungsdokument
|
||||
`mmo/plan.md` enthält die vollständige Projektstruktur: Szenenbaum, Szenen mit Nodes, Skripte, Components, Stats, Aggro-Regeln, Abilities und Events. Dieses Dokument ist die Wahrheit für den Soll-Zustand.
|
||||
`plan.md` enthält die vollständige Projektstruktur: Szenenbaum, Szenen mit Nodes, Skripte, Components, Stats, Aggro-Regeln, Abilities und Events. Dieses Dokument ist die Wahrheit für den Soll-Zustand.
|
||||
|
||||
## Design-Dokumente
|
||||
Unter `~/Documents/2026/projekte/mmo/` liegen die originalen Design-Docs:
|
||||
Unter `~/Documents/2026/projekte/mmo/infosammlung/` liegen die originalen Design-Docs:
|
||||
- `story.md` — Gameplay-Loop, Szenarien, Steuerung
|
||||
- `idden.md` — Alle Ideen, Kernphilosophie, Technik
|
||||
- `Szenarien.md` — Ressourcenanfragen, Dungeons, Gemeinschaft, Endgame
|
||||
- `Level 1.md` bis `Level 3.md` — Systeme nach Priorität
|
||||
|
||||
## Core Loop
|
||||
1. Portal spawnt auf der Karte
|
||||
1. Portale spawnen dynamisch auf der Karte (PortalSpawner, max 3, 20-40m vom Zentrum)
|
||||
2. Spieler greift Portal an → Gegner spawnen bei Lebensschwellen (85%/70%/55%/40%/25%/10%)
|
||||
3. Spieler bekämpft Gegner mit Abilities (Single, AOE, Utility, Ult) + Auto-Attack
|
||||
4. Portal bei 0 HP → Phase 2 (geplant: wird Gate zu Dungeon)
|
||||
5. Tod → 3s Respawn am Startpunkt
|
||||
4. Portal bei 0 HP → Gate spawnt, Gegner werden entfernt
|
||||
5. Spieler betritt Gate → Dungeon (separate Szene, 4 Gegnergruppen + Boss)
|
||||
6. Spieler kann zwischen Welt und Dungeon hin und her (beide Gates aktiv solange Boss lebt)
|
||||
7. Boss stirbt → 2s Delay → Spieler wird zur Taverne teleportiert, Gates verschwinden
|
||||
8. Tod → 3s Respawn bei Taverne
|
||||
|
||||
## Kampfsystem
|
||||
- Auto-Attack: 10 Schaden, 1s, automatisch bei anvisiertem Gegner im Kampf
|
||||
- 5 Abilities pro Rolle: Single, AOE, Utility, Ult, Passive
|
||||
- Auto-Attack: Rollenspezifisch (D: 10 Schaden/10m, T: 5 Schaden/3m, H: 1 Heilung/20m), 0.5s CD
|
||||
- 5 Abilities pro Rolle: Single, AOE, Utility, Ult, Passive — jede Rolle hat eigene Werte
|
||||
- GCD 0.5s (gilt für 1, 2, 4), Utility ignoriert GCD
|
||||
- Cooldown-Overlay in AbilityBar (von oben nach unten)
|
||||
- Heilung: Heiler heilt sich selbst (Singleplayer), is_heal Flag auf Ability
|
||||
- Passive: Schadens-Boost (D), Schild-Boost (T), Heal-Boost (H) — je 50%
|
||||
- Targeting: Klick, TAB (cyclet "enemies" + "portals"), Auto-Target (Gegner > Portal)
|
||||
- Aggro-System: 1:1 Schaden, Tank 2x, verfällt -1/s, exponentiell außerhalb Portal-Radius
|
||||
- Aggro-System: 1:1 Schaden, Tank 2x, Heilung 0.5x, verfällt -1/s, exponentiell außerhalb Portal-Radius
|
||||
|
||||
## Rollen
|
||||
- Tank (T), Schaden (D), Heiler (H) — wechselbar mit ALT+1/2/3
|
||||
- Aktuell alle gleiche Abilities, später unterschiedlich
|
||||
- Jede Rolle hat eigenes AbilitySet (Resource)
|
||||
- Jede Rolle hat eigenes AbilitySet mit unterschiedlichen Werten und Mechaniken
|
||||
- Tank: Weniger Schaden, Nahkampf, Schild-Ult (300%), Schild-Passive
|
||||
- Heiler: Heilt statt schadet (Single, AOE, Ult), Heal-Passive, AOE macht Schaden
|
||||
|
||||
## Szenenwechsel
|
||||
- GameState Autoload speichert Spielerzustand (HP, Shield, Rolle) zwischen Szenen
|
||||
- Gate (Eingang): save_player → Dungeon laden
|
||||
- Gate (Exit): save_player, returning_from_dungeon → Welt laden, Spieler bei Gate-Position
|
||||
- Boss-Tod: dungeon_cleared → Welt laden, Spieler bei Taverne, Gates weg
|
||||
- PortalSpawner stellt Gate wieder her wenn portal_position gesetzt und Boss noch lebt
|
||||
|
||||
## Workflow mit dem User
|
||||
- **plan.md ist zentral** — User will Änderungen zuerst in plan.md dokumentiert haben, dann implementieren
|
||||
|
||||
46
infosammlung/Level 1.md
Normal file
46
infosammlung/Level 1.md
Normal file
@@ -0,0 +1,46 @@
|
||||
Hauptsysteme
|
||||
- Welt
|
||||
- Spieler
|
||||
- Gegner
|
||||
- NPC
|
||||
- Dungeons
|
||||
- HUD
|
||||
- Fähigkeiten
|
||||
- Klassen
|
||||
Zusatzsysteme
|
||||
- Berufe
|
||||
- Quest
|
||||
- Items
|
||||
- Inventar
|
||||
- Equipment
|
||||
|
||||
Nebensysteme
|
||||
- Audio
|
||||
- Licht
|
||||
- Transport
|
||||
- Tageszeit
|
||||
- Wetter
|
||||
- Presitige
|
||||
- Einstellungen
|
||||
- Achivments
|
||||
- Events
|
||||
- Tutorial
|
||||
- Speichersystem
|
||||
- Game Over
|
||||
- Admin-Tools
|
||||
- Narrative
|
||||
- Wirtschaft
|
||||
- Karte
|
||||
|
||||
Mehrspieler
|
||||
- Multiplayer
|
||||
- Chat
|
||||
- Gruppe
|
||||
- Handel
|
||||
- Offline-Fortschritt
|
||||
|
||||
---
|
||||
Nicht
|
||||
- Gilden - Jeder ist in Teil der Community
|
||||
- Housing - Fokus auf Tarverne und Zentrum
|
||||
- Kosmetik - Aussehen durch Crafting und Fortschritt bestimmt
|
||||
60
infosammlung/Level 2.md
Normal file
60
infosammlung/Level 2.md
Normal file
@@ -0,0 +1,60 @@
|
||||
Hauptsysteme
|
||||
- Welt
|
||||
- Spieler
|
||||
- Gegner
|
||||
- NPC
|
||||
- Dungeons
|
||||
- HUD
|
||||
- KI
|
||||
- Kampf
|
||||
- Fähigkeiten
|
||||
- Eigenschaften
|
||||
- Klassen
|
||||
- Berufe
|
||||
- Quest
|
||||
- Items
|
||||
- Inventar
|
||||
- Equipment
|
||||
- Loot
|
||||
- Fortschritt
|
||||
|
||||
Nebensysteme
|
||||
- Audio
|
||||
- Licht
|
||||
- Transport
|
||||
- Tageszeit
|
||||
- Wetter
|
||||
- Presitige
|
||||
- Einstellungen
|
||||
- Achivments
|
||||
- Events
|
||||
- Tutorial
|
||||
- Speichersystem
|
||||
- Game Over
|
||||
- Admin-Tools
|
||||
- Narrative
|
||||
- Wirtschaft
|
||||
- Karte
|
||||
|
||||
Mehrspieler
|
||||
- Multiplayer
|
||||
- Chat
|
||||
- Gruppe
|
||||
- Handel
|
||||
- Offline-Fortschritt
|
||||
|
||||
---
|
||||
Nicht
|
||||
- Gilden - Jeder ist in Teil der Community
|
||||
- Housing - Fokus auf Tarverne und Zentrum
|
||||
- Kosmetik - Aussehen durch Crafting und Fortschritt bestimmt
|
||||
|
||||
Alex / Sanni
|
||||
Billi / Kai
|
||||
Lea / Christopf
|
||||
|
||||
Dösi
|
||||
Nina / Igor
|
||||
Basti
|
||||
Vincent
|
||||
Bennet
|
||||
110
infosammlung/Level 3.md
Normal file
110
infosammlung/Level 3.md
Normal file
@@ -0,0 +1,110 @@
|
||||
Hauptsysteme
|
||||
- Welt
|
||||
- Boden, Hügel, Gebäude, Natur
|
||||
- Spieler
|
||||
- Mesh, Textur, Steuerung, Animation
|
||||
- Verwandlung - Kampf und Normale Gestalt
|
||||
- Gestaltanpassung - Primärattribut > Rest
|
||||
- Elemente verändern Aussehen
|
||||
- Gegner
|
||||
- Mesh, Textur, Animation
|
||||
- Skalierung - Passend sich dem Level an
|
||||
- NPC
|
||||
- Mesh, Textur, Animation
|
||||
- Fallback - Ein NPC je mögliche Spieler Position
|
||||
- KI
|
||||
- Gegner KI
|
||||
- NPC KI
|
||||
- Kampf
|
||||
- Schadensberechnung
|
||||
- Angriff
|
||||
- Bei Tod respawn in der Stadt
|
||||
- AFK - Unbesiegbar, Castet automatisch Fähigkeiten mit halber Stärke, wenn in Gruppe
|
||||
- Dungeons
|
||||
- Gebietgenerierung
|
||||
- Instanzierung - Eine Instanz, tauchen zufällig auf
|
||||
- Break - Dungon öffnet nach einer Weile, Elite Monster starten eine Invasion
|
||||
- Invasion - Kein Respawn
|
||||
- Loot - 50% Gruppenleistung und 50% Eigenleistung
|
||||
- HUD
|
||||
- Leben, Schild, Name, Stufe
|
||||
- Fähigkeiten
|
||||
- Minimap
|
||||
- Anpassung
|
||||
- Fähigkeiten
|
||||
- Arten - Single, AOE, Utility, Ulti, Passive
|
||||
- Elemente manipulieren Fähigkeiten
|
||||
- Eigenschaften
|
||||
- Leben, Schild
|
||||
- Attribute
|
||||
- Automatische verbesserung mit Aktionen
|
||||
- Klassen
|
||||
- Klassenauswahl
|
||||
- Arten - Verteidigung, Nahkampf, Fernkampf, Heilung, Unterstützung
|
||||
- Wechsel - Rollenwechsel geht immer
|
||||
- Berufe
|
||||
- Übersicht, Annehmen, Wechseln
|
||||
- Arten - Abendteurer, Quester, Schmied, Baumeister, Forscher, Archologe
|
||||
- Abendteurer - Monster bekämpfen
|
||||
- Forschung - Unbekannte Objekte identifizieren
|
||||
- Crafting - Objekte erstellen oder verbessern, Ressourcen abbauen
|
||||
- Bauen - Objekte erstellen oder verbessern, Ressourcen abbauen
|
||||
- Entdecken - Unbekannte Objekte finden
|
||||
- Quest
|
||||
- Öffnen, Annehmen, Abbrechen, Schließen
|
||||
- Erstellen - Quester kann eigene Quests erstellen
|
||||
- Schwarzes Brett - Aktuelle Aufgaben von NPCs oder Spielern
|
||||
- Items
|
||||
- Mesh, Textur
|
||||
- Inventar
|
||||
- Items aufnehmen und ablegen
|
||||
- Größe unendlich
|
||||
- Equipment
|
||||
- Loot
|
||||
- Fortschritt
|
||||
- Aktionen geben Erfahrung
|
||||
- Globaler Fortschritt - Jede Aktion gibt einen Teil an jeden Spieler
|
||||
- Globale Freischaltung - Jede Freischaltung gilt für alle Spieler
|
||||
|
||||
Nebensysteme
|
||||
- Audio
|
||||
- Musik, Sounds
|
||||
- Licht
|
||||
- Lokal, Global
|
||||
- Transport
|
||||
- Extremschnell auf Wegen
|
||||
- Tageszeit
|
||||
- Tag, Abend, Nacht
|
||||
- Wetter
|
||||
- Sonne, Regen, Schnee
|
||||
- Presitige
|
||||
- Einstellungen
|
||||
- Achivments
|
||||
- Spielfortschritt, Entdecken, Maxlevel
|
||||
- Events
|
||||
- Tutorial
|
||||
- Keins, Gameplay selbsterklärend gestalten
|
||||
- Speichersystem
|
||||
- Speichern, Laden
|
||||
- Game Over
|
||||
- Tarverne wird zerstört
|
||||
- Belohnung - Teil vom Fortschritt gibt Erharung zum Prestige
|
||||
- Admin-Tools
|
||||
- Narrative
|
||||
- Geschichte durch Welt / NPC / Berichte erzählen
|
||||
- Wirtschaft
|
||||
- Karte
|
||||
|
||||
Mehrspieler
|
||||
- Multiplayer
|
||||
- Client, Server, Lokalität
|
||||
- Chat
|
||||
- Lokaler Chat
|
||||
- Globaler Chat
|
||||
- Gruppen Chat
|
||||
- Gruppe
|
||||
- Spieler in der nähe bilden automatisch eine Gruppe
|
||||
- Gruppensuche
|
||||
- Handel
|
||||
- Einfaches Handelfenster
|
||||
- Offline-Fortschritt
|
||||
26
infosammlung/Planung.md
Normal file
26
infosammlung/Planung.md
Normal file
@@ -0,0 +1,26 @@
|
||||
Baum
|
||||
- Welt
|
||||
- Spieler
|
||||
|
||||
Welt - Node3D
|
||||
- Boden - MeshInstance3D
|
||||
- PlaneMesh 10mx10m
|
||||
- StandardMaterial3D mit NoiseTexture2D als Albedo
|
||||
- NoiseTexture2D: FastNoiseLite, Gradient dunkelgrün zu hellgrün, seamless
|
||||
- UV-Skalierung für Wiederholung
|
||||
- DirectionalLight3D — Sonnenlicht mit Schatten, 45° Winkel
|
||||
- Camera3D — 30° Winkel nach unten geneigt
|
||||
|
||||
Spieler - CharacterBody3D
|
||||
- Kollision - CollisionShape3D mit CapsuleShape3D
|
||||
- Mesh - MeshInstance3D mit CapsuleMesh
|
||||
- Kamera
|
||||
- CameraPivot - Node3D, Position am Kopf des Spielers
|
||||
- Camera3D - Position hinter/über dem Spieler
|
||||
- Stript - LMB Kamera und Laufrichtung bewegen, RMB Kamera bewegen
|
||||
- Steuerung
|
||||
- Skript - WASD Bewegung relativ zur Kamera, Leertaste Springen
|
||||
|
||||
Gegner - CharacterBody3D
|
||||
- Kollision - CollisionShape3D
|
||||
- Mesh - MeshInstance3D mit CapsuleMesh, Rot
|
||||
38
infosammlung/Szenarien.md
Normal file
38
infosammlung/Szenarien.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Szenarien
|
||||
Ressourcenanfragen
|
||||
- Heiler will Zauber verbessern -> Ressourcen fehlen -> Anfrage erstellen
|
||||
- Quester sieht die Anfrage > Quester nimmt sie an und erstellt eine Quest
|
||||
- Abenteurer sieht eine Quest -> Nimmt Quest an und besorgt die Ressource
|
||||
- Abendteuert gibt Ressource an Quester -> Bekommt Erfahrung, Gold und Ruf
|
||||
- Quester gibt Ressource -> Bekommt Erfahrung, Gold und Ruf
|
||||
- Heiler bekommt Ressource -> Zahl Geld -> Verbessert die Fähigkieten
|
||||
|
||||
Dungeons
|
||||
- Portal taucht auf -> Wird eingestufft und auf der Karte angezeigt
|
||||
- Abedteurer können das Portal betreten und es leeren -> Erfahrung und Ressourcen
|
||||
- Miner können Ressourcen abbauen -> Verkaufen für Erfahrung und Gold
|
||||
- Archologen können Portal untersuchen -> Forschung verbessern
|
||||
- Dungen wird nicht besiegt -> Dungeon bricht und greift das Dorf an
|
||||
- Wird das Hauptgebäude zerstört -> Kein Respawn Möglich -> Dorf stirbt
|
||||
|
||||
Gemeinschaft
|
||||
- Bewohner erledigen aufgaben -> Ort entwickelt sich und wird stärker
|
||||
- Je Stärker der Ort, desto schwer sind die Aufgaben
|
||||
- Stärkere Spieler fokussieren sich auf schwere Aufgabe
|
||||
- Anfänger auf einfache Aufgaben
|
||||
- Gemeinschaft bekommt Vorteile durch jeden Spieler
|
||||
- Wird ein Spieler besser dann wird die Geminschaft besser
|
||||
|
||||
Endgame
|
||||
- Gemeinschaft ist fertig ausgebaut
|
||||
- Durchschnitt der Spieler hat ein bestimmtes Niveau erreicht
|
||||
- "Spiel wird einfach"
|
||||
- Letzte Invasion -> Portale tauchen auf -> Greifen dorf an -> Spieler verteidigen sich
|
||||
- Kein Respawn -> Am Ende alle Gegner oder Spiele sind besiegt
|
||||
- Sieg für Spieler -> Prestige Fähigkeiten
|
||||
- Sieg für Gegner -> Dorf stirbt -> Game Over
|
||||
|
||||
Loop
|
||||
- Dorf beitretten -> Gemeinschaft entsteht -> Gemeinschaft wächst
|
||||
- Gemeinschaft überlebt Invasion -> Prestige und Reset
|
||||
- Neues Dorf --> ...
|
||||
70
infosammlung/idden.md
Normal file
70
infosammlung/idden.md
Normal file
@@ -0,0 +1,70 @@
|
||||
## Alle Ideen – Übersicht
|
||||
|
||||
**Kernphilosophie**
|
||||
|
||||
- Spiel gegen die drei Urängste: soziale Zurückweisung, Überforderung, Versagen
|
||||
- Angst nicht entfernen, sondern durch positive Gefühle ersetzen
|
||||
- Zielgruppe: einsame Menschen, denen eine Brücke zur echten Welt gebaut wird
|
||||
- Stilles Designprinzip, nicht offen als "Anti-Einsamkeits-Spiel" kommuniziert
|
||||
|
||||
**Lokalität**
|
||||
|
||||
- Spieler werden nach geographischer Nähe im gleichen Layer gruppiert
|
||||
- Dynamischer Radius: bei geringer Dichte wird er vergrößert bis global
|
||||
- Globaler Server als Opt-out für Spieler, die Anonymität wollen
|
||||
- Ziel: Online-Freundschaften können natürlich zu echten werden
|
||||
- Treffen ist selbstorganisiert, kein eingebautes Feature
|
||||
|
||||
**Spieldesign**
|
||||
|
||||
- Jede Aktion hilft automatisch anderen, auch Solospielen
|
||||
- Keine formellen Gruppen – jeder in einem Bereich ist automatisch Teil des Geschehens
|
||||
- Dungeons spawnen als Invasionen auf der Karte mit verschiedenen Schwierigkeiten
|
||||
- Boss skaliert dynamisch mit Spieleranzahl
|
||||
- Versagen eines Einzelnen schadet nie der Gruppe
|
||||
- Kein Blame, kein DPS-Meter, kein Kick-System
|
||||
|
||||
**Dynamische Gemeinschaft**
|
||||
|
||||
- Spieler können Abenteurer oder "NPCs" sein – Schmied, Questgeber, Händler, Gastwirt
|
||||
- Stadt-Spieler und Abenteurer sind voneinander abhängig
|
||||
- Offline-Automatismen: Läden und Quests laufen weiter wenn der Spieler offline ist
|
||||
- Die Welt fühlt sich immer belebt an
|
||||
|
||||
**Progressionssystem**
|
||||
|
||||
- Berufungs-Zähler wie in Plunderer – jede Tätigkeit hat einen sichtbaren Rang
|
||||
- Individuelle Ränge stärken die gesamte Gemeinschaft
|
||||
- Regionale Gesamtstärke schaltet bessere Inhalte frei
|
||||
- Kein Neid, da jeder Aufstieg allen hilft
|
||||
- Natürliche Spezialisierung durch offene Nischen
|
||||
|
||||
**Technik**
|
||||
|
||||
- Godot 4 als Client
|
||||
- Dedizierter Server mit API-Architektur
|
||||
- Client sendet Intentionen, Server validiert
|
||||
- Offline-Automatismen laufen serverseitig
|
||||
- Anti-Cheat by Design: selbst Cheater helfen der Gemeinschaft
|
||||
|
||||
**Monetarisierung**
|
||||
|
||||
- Free-to-Play mit kosmetischen Items
|
||||
- Cosmetics werden wertvoller durch Lokalität und soziale Bindung
|
||||
|
||||
Hier die neuen Punkte aus unserer Diskussion:
|
||||
|
||||
- Jede Spielerrolle hat ein NPC-Pendant als Fallback – das Spiel funktioniert solo, wird aber mit Spielern besser
|
||||
- Invasionen skalieren mit Anzahl und Stärke der Spieler
|
||||
- Solo-Invasion ist die einfachste Stufe, stärkste Invasionen nur auf öffentlichen Servern
|
||||
- Soziale Angst abbauen, aber Verlustangst als Motivator nutzen
|
||||
- Gemeinsame Vorbereitung auf die Endinvasion ist das Endgame-Ziel
|
||||
- Prestige-Verbesserungen erweitern den Spielstil statt nur Werte zu erhöhen
|
||||
- Prestige schaltet einzigartige Fähigkeiten frei, darunter Wiederbelebung
|
||||
- Normales Spiel hat normalen Respawn
|
||||
- Endinvasion: kein normaler Respawn, nur Prestige-Spieler können wiederbeleben
|
||||
- Prestige-Spieler werden im Finale zum Sicherheitsnetz der Gemeinschaft
|
||||
- Kernloop: Bedeutung aufbauen → alles riskieren → Triumph → Prestige-Reset → neu beginnen mit erweiterten Fähigkeiten
|
||||
- Anfangs Godot 2D für den Prototyp für Core Loops testing
|
||||
- Später Godot 3D mit Blender Objekten mit wenigen Polygonen
|
||||
- Kleine Karte Anfangs
|
||||
58
infosammlung/story.md
Normal file
58
infosammlung/story.md
Normal file
@@ -0,0 +1,58 @@
|
||||
Shortstory
|
||||
- Dorf in der Mitte
|
||||
- Portale spawnen außen mit Monstern und einem Boss
|
||||
- Nach 1 Tag bricht das Portal und Monter greifen Dorf an
|
||||
- Spieler besigen Monster und bekommen Essenz
|
||||
- Teil der gewonnen Essenz wird an alle Spieler verteilt
|
||||
- Spieler können Kampfrollen annehmen Tank, Schaden, Heiler
|
||||
- Spieler haben 5 Fähigkeiten: Single, AOE, Utility, Ult, Passive
|
||||
- Fähigkeiten unterscheiden sich je nach Kampfrolle
|
||||
- Spieler können Elemente verwenden, sie verändern die Fähigkeiten
|
||||
- Spieler können Berufe annehmen: Quester, Crafter, Forscher, Archologe, Handwerker
|
||||
- Berufe verbessern den Kampf und Verteidigung
|
||||
- Nach 7 Tagen kommt eine Invasion mit starken Gegnern
|
||||
- Beim Sieg bekommt man Presitigeessenz, die man für dauerhafte Verbesserungen verwenden kann
|
||||
- Wird das Dorf zerstört, dann heißt es Game Over
|
||||
|
||||
Allgemein
|
||||
- Eine Welt startet mit einem Dorfplatz und einem Helfer.
|
||||
- Der Helfer soll neuen Spielern helfen.
|
||||
- Die Welt besteht aus einem Bereich.
|
||||
- In dem Berech erscheinen Portale, diese sind auf der Karte zu sehen.
|
||||
- In dem Portal sind Monster und ein Boss.
|
||||
- Nach einem Tag bricht das Portal und die Monster greifen das Dorf an.
|
||||
- Stirbt der Helfer, dann heißt es Game Over.
|
||||
- Stirbt ein Spieler, dann wird er am Dorfplatz wiederbelebt.
|
||||
- Spieler können Monster für Loot und Erfahrung bekämpfen.
|
||||
- Jeder Spieler in der Welt bekommt einen kleinen Teil vom Loot.
|
||||
- Mit Loot kann man Inhalte freischalten.
|
||||
- Inhalte sind sowasw wie Beruffreischaltung, Fähigkeitenverbesserungen, Elementverbesserungen, Items
|
||||
- Berufe sind Abenteurer - Monster bekämpfen, Archologe - Neues finden, Forscher - Neues identifizieren, Handwerker - Gebäude erstellen und verbessern, Crafter - Items erstellen und verbessern, Quester - Aufgaben verwalten
|
||||
- Elemente wie Feuer, Eis, Finternis, Gift, ... verändern wie Fähigkeiten funktionieren
|
||||
- Wenn man Monster bekämpft bekommt man automatisch Attribute je nach der Role - Tank, Schaden, Heiler
|
||||
- Der Beruf hat die Stufen F,E,D,C,B,A,S,S+, je höher die Stufe, desto mehr kann man machen
|
||||
- Die Attribute, Elemente und der höchste Berufsrang verändern das Aussehen
|
||||
- Nach 7 Tagen kommt eine Invasion mit starken Gegnern, dabei gibt es kein Respawn, wird der Helfer getötet dann heißt es Game Over, man bekommt % der geschafften Invasion als Prestige gutgeschrieben
|
||||
- Besiegt man die Invasion, dann endet das Spiel mit 100% und einem sicheren Prestigepunkt
|
||||
|
||||
Szenarien
|
||||
- Spiel start
|
||||
- Menü - Einzelspiel, Koop, Online, Einstellungen, Quit
|
||||
- Einzelspiel - Neues Spiel, Laden
|
||||
- Neues Spiel - Name eingeben, dann startet das Spiel
|
||||
|
||||
- Neues Spieler erscheit
|
||||
- HUD links-oben - Leben, Schild, Rang
|
||||
- HUD mitte-unten - Kampfhaltung, Fähigkeiten
|
||||
- HUD rechts-oben - Minimap
|
||||
- Fähigkeiten - Single, AOE, Utility, Ult, Passive (1 bis 5)
|
||||
- Kapfhaltung - Tank, Schaden, Heiler, Neutral (ALT+1 bis ALT+4)
|
||||
|
||||
- Laufen mit WASD
|
||||
- Leertaste springen
|
||||
- Linke Maustaste Kamera bewegen und Laufrichtung mitziehn
|
||||
- Rechte Maustaste nur Kamera bewegen
|
||||
- Welt - Boden, Hügel, Natur
|
||||
- Weg - Bewegung +300%
|
||||
- Dorf - Nur Tarverne
|
||||
|
||||
252
plan.md
252
plan.md
@@ -1,8 +1,9 @@
|
||||
# Projektstruktur
|
||||
## Szenenbaum
|
||||
- Welt
|
||||
- Taverne
|
||||
- Player
|
||||
- Portal
|
||||
- Portale (dynamisch)
|
||||
- Gegner
|
||||
- HUD
|
||||
|
||||
@@ -10,23 +11,26 @@
|
||||
- Zwischen Szenen: Kommunikation über EventBus (Szenen kennen sich nicht)
|
||||
- Innerhalb einer Szene: Modulare Skripte, Zugriff auf Geschwister-Nodes erlaubt
|
||||
|
||||
## Szenen
|
||||
- world.tscn — Hauptszene
|
||||
## Welt
|
||||
- world.tscn — Hauptszene (200x200m)
|
||||
- NavigationRegion3D (Wegfindung für Gegner)
|
||||
- Boden (MeshInstance3D, 20x20m PlaneMesh, Gras-Noise-Textur)
|
||||
- Boden (MeshInstance3D, 200x200m PlaneMesh, Gras-Noise-Textur)
|
||||
- Kollision (StaticBody3D, WorldBoundaryShape3D)
|
||||
- Licht (DirectionalLight3D, 45°, Schatten)
|
||||
- Spieler (Instanz von player.tscn)
|
||||
- Portal (Instanz von portal.tscn)
|
||||
- Taverne (StaticBody3D, BoxMesh, Mitte der Karte, Spieler-Spawn/Respawn)
|
||||
- Spieler (Instanz von player.tscn, Spawn bei Taverne)
|
||||
- PortalSpawner (Node, portal_spawner.gd)
|
||||
- HUD (Instanz von hud.tscn)
|
||||
- portal_spawner.gd — Spawnt Portale dynamisch in der Portalzone (außerhalb Dorfradius)
|
||||
- Timer-basiert, zufällige Position, max. Anzahl begrenzt
|
||||
|
||||
- player.tscn — Spieler
|
||||
## Spieler
|
||||
- player.tscn — CharacterBody3D
|
||||
- Gruppe (player)
|
||||
- CharacterBody3D (player.gd)
|
||||
- Kollision (CapsuleShape3D, 1.8m x 0.3m)
|
||||
- Mesh (CapsuleMesh)
|
||||
- CameraPivot (Node3D, Kopfhöhe, camera.gd)
|
||||
- Camera3D (hinter/über Spieler)
|
||||
- Camera3D (hinter/über Spieler)w
|
||||
- Movement (Node, movement.gd)
|
||||
- Combat (Node, combat.gd)
|
||||
- Role (Node, role.gd)
|
||||
@@ -34,36 +38,51 @@
|
||||
- Health (Node, health.gd)
|
||||
- Shield (Node, shield.gd)
|
||||
- Respawn (Node, respawn.gd)
|
||||
- player.gd — Kern, verbindet Komponenten
|
||||
- camera.gd — LMB freies Umsehen, RMB Kamera + Laufrichtung anpassen
|
||||
- movement.gd — Bewegung (WASD relativ zur Kamera), Springen, Schwerkraft
|
||||
- respawn.gd — Bei Tod: Spieler deaktivieren, 3s Timer, Respawn bei Taverne, Leben/Schild voll
|
||||
- player_stats.tres — health=100, regen=1/s, shield=50, delay=3s, regen_time=5s
|
||||
|
||||
- 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)
|
||||
- RoleIcon (Label, T=Tank, D=Schaden, H=Heiler)
|
||||
- Abilities(Label, Fähigkeiten 1-4, Passive P)
|
||||
## Kampf
|
||||
- combat.gd — Führt Abilities aus, verwaltet Cooldowns (GCD 0.5s), Auto-Attack (0.5s)
|
||||
- targeting.gd — Klick/TAB anvisieren (Gruppen "enemies" + "portals"), Kampfmodus bei Gegner-Angriff, Auto-Targeting (Gegner > Portal)
|
||||
- role.gd — Rollenwechsel ALT+1 bis ALT+3 (Tank/Schaden/Heiler), tauscht AbilitySet
|
||||
|
||||
- portal.tscn — Portal (Gegner-Spawner, anvisierbar)
|
||||
- Gruppe (portals)
|
||||
- StaticBody3D (portal.gd)
|
||||
- Kollision (CylinderShape3D)
|
||||
- Mesh (CylinderMesh, blau)
|
||||
- Health (Node, health.gd)
|
||||
- HitArea (Area3D, Trefferbereich)
|
||||
- CollisionShape3D
|
||||
- Spawner (Node, spawner.gd)
|
||||
- DetectionArea (Area3D, 10m Radius, Auto-Targeting bei Betreten)
|
||||
- CollisionShape3D (SphereShape3D)
|
||||
- Healthbar (Sprite3D + SubViewport, healthbar.gd)
|
||||
- Phase 1: Angreifbar, HP-Bar, spawnt 3 Gegner bei 85%/70%/55%/40%/25%/10% Leben
|
||||
- Portal = Spawnpunkt, 10m Aggro-Radius
|
||||
- Phase 2 (geplant): Bei 0 HP → Portal wird Gate, teleportiert in Dungeon
|
||||
- Abgesperrter Bereich mit Gegner-Gruppen und Boss
|
||||
## Abilities
|
||||
- ability.gd (Resource) — name, type, damage, range, cooldown, uses_gcd, execute()
|
||||
- ability_set.gd (Resource) — Set von 5 Abilities pro Rolle
|
||||
- ability_modifier.gd (Resource) — Verändert Ability (Element, Beruf, Prestige)
|
||||
- Typen: Single, AOE, Utility, Ult, Passive
|
||||
- Auto-Attack: Automatisch bei anvisiertem Gegner im Kampf
|
||||
- Schadens-Klasse:
|
||||
- AA: 10 Schaden, 10m
|
||||
- 1 Single: 30 Schaden, 20m Range, 2s CD, GCD
|
||||
- 2 AOE: 20 Schaden, 5m Range, 3s CD, GCD
|
||||
- 3 Utility: Schild sofort auf 100%, 5s CD, kein GCD
|
||||
- 4 Ult: 5x Single (50 Schaden) + 2x AOE 3m (20 Schaden), 20m Range, 15s CD, GCD
|
||||
- 5 Passive: 50% mehr Schaden Aura, 50m (permanent aktiv, kein CD)
|
||||
- Tank-Klasse:
|
||||
- AA: 5 Schaden, 3m
|
||||
- 1 Single: 15 Schaden, 3m Range, 2s CD, GCD
|
||||
- 2 AOE: 10 Schaden, 10m Range, 3s CD, GCD
|
||||
- 3 Utility: Schild sofort auf 100%, 5s CD, kein GCD
|
||||
- 4 Ult: Schild 300%, 20s CD, GCD
|
||||
- 5 Passive: 50% mehr Schild Aura, 50m (permanent aktiv, kein CD)
|
||||
- Heiler-Klasse: Kein Autotarget, Anvisierter Spieler wird geheilt, ist keiner anvisiert dann Selfheal
|
||||
- AA: 1 Heilung, 20m
|
||||
- 1 Single: 15 Heilung, 20m Range, 2s CD, GCD
|
||||
- 2 AOE: 10 Heilung, 20m Range, 3s CD, GCD
|
||||
- 3 Utility: Schild sofort auf 100%, 5s CD, kein GCD
|
||||
- 4 Ult: 25 Heal Single + 10 AOE Heal 3m radius, 20m Range, 15s CD, GCD
|
||||
- 5 Passive: 50% mehr Heal Aura, 50m (permanent aktiv, kein CD)
|
||||
- Jede Rolle hat ein eigenes AbilitySet
|
||||
- Beim Rollenwechsel wird das AbilitySet getauscht
|
||||
- Elemente und Modifikatoren verändern Abilities nachträglich
|
||||
|
||||
- enemy.tscn — Gegner
|
||||
## Gegner
|
||||
- enemy.tscn — CharacterBody3D
|
||||
- Gruppe (enemies)
|
||||
- CharacterBody3D (enemy.gd)
|
||||
- Kollision (CapsuleShape3D)
|
||||
- Mesh (SphereMesh, Platzhalter)
|
||||
- Health (Node, health.gd)
|
||||
@@ -81,74 +100,119 @@
|
||||
- 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, verwaltet Cooldowns (GCD 0.5s), Auto-Attack (10 Schaden, 1s, 20m)
|
||||
- targeting.gd — Klick/TAB anvisieren (Gruppen "enemies" + "portals"), Kampfmodus bei Gegner-Angriff, Auto-Targeting (Gegner > Portal)
|
||||
- event_bus.gd — Autoload-Singleton, globale Signals
|
||||
- role.gd — Rollenwechsel 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
|
||||
- portal.gd — Portal-Kern, Gruppe "portals", stirbt bei 0 HP, kein Kampf/Aggro/State
|
||||
- enemy.gd — Gegner-Kern, State Machine (Idle, Verfolgen, Angreifen, Zurückkehren), alarmiert Gegner in 3m, Gruppe "enemies"
|
||||
- enemy_movement.gd — Navigation zum Ziel/Spawnpunkt
|
||||
- enemy_combat.gd — Angriff über Event (damage_requested)
|
||||
- enemy_aggro.gd — Aggro-Tabelle (Spieler → Wert), wählt Ziel mit höchstem Aggro
|
||||
- enemy_stats.tres — health=100, regen=0, shield=50, delay=3s, regen_time=5s
|
||||
- Aggro-Regeln:
|
||||
- Schaden = Aggro (1:1)
|
||||
- Heilung = Aggro (0.5x)
|
||||
- Tank = Aggro-Multiplikator (2x)
|
||||
- Aggro verfällt -1/s
|
||||
- Spieler im Portal-Radius: Aggro bleibt bei mindestens 1
|
||||
- Außerhalb 10m Portal-Radius: Aggro verfällt 1% * 2 je s (1%, 2%, 4%, 8%, ...)
|
||||
- Ohne Aggro: Gegner kehrt zum Portal zurück, regeneriert 10% Leben/s bis 100%, dann 1%/s
|
||||
- Bei Spieler-Tod → Aggro auf 0
|
||||
|
||||
## Components (scripts/components/, wiederverwendbar)
|
||||
## Portal
|
||||
- portal.tscn — StaticBody3D
|
||||
- Gruppe (portals)
|
||||
- Kollision (CylinderShape3D)
|
||||
- Mesh (CylinderMesh, blau)
|
||||
- Health (Node, health.gd)
|
||||
- HitArea (Area3D, Trefferbereich)
|
||||
- CollisionShape3D
|
||||
- Spawner (Node, spawner.gd)
|
||||
- DetectionArea (Area3D, 10m Radius, Auto-Targeting bei Betreten)
|
||||
- CollisionShape3D (SphereShape3D)
|
||||
- Healthbar (Sprite3D + SubViewport, healthbar.gd)
|
||||
- portal.gd — Portal-Kern, Gruppe "portals", bei 0 HP → spawnt Gate, zerstört Gegner und sich
|
||||
- spawner.gd — Spawnt Entities bei Lebensschwellen, engagt Spieler im Portal-Radius sofort
|
||||
- portal_stats.tres — health=500, regen=1%/s, shield=0
|
||||
- Phase 1: Angreifbar, HP-Bar, spawnt 3 Gegner bei 85%/70%/55%/40%/25%/10% Leben
|
||||
- Portal = Spawnpunkt, 10m Aggro-Radius
|
||||
|
||||
## Gate
|
||||
- gate.tscn — StaticBody3D (keine Kollision)
|
||||
- Mesh (CylinderMesh, grün, leuchtend)
|
||||
- GateArea (Area3D, 3m Radius, Spieler-Erkennung)
|
||||
- CollisionShape3D (SphereShape3D)
|
||||
- gate.gd — Konfigurierbar: target_scene, is_exit
|
||||
- Eingangs-Gate: Prüft nach 0.5s ob Spieler bereits in GateArea steht, speichert Portal-Position
|
||||
- Exit-Gate: 1s Aktivierungsdelay (verhindert Re-Triggern bei Spawn)
|
||||
- Beide Gates bestehen solange Boss lebt, verschwinden bei dungeon_cleared
|
||||
|
||||
## Dungeon
|
||||
- dungeon.tscn — Geschlossener Raum (15x90m, Wände, dunkles Licht)
|
||||
- NavigationRegion3D (Wegfindung)
|
||||
- Boden (PlaneMesh, dunkle Textur)
|
||||
- 4 Wände (StaticBody3D + BoxMesh, 3m hoch)
|
||||
- Spieler (Instanz von player.tscn, Position 0/1/-5)
|
||||
- HUD (Instanz von hud.tscn)
|
||||
- Gegnergruppen bei (15x15, 15x30, 15x45, 15x60), je 4 Gegner
|
||||
- Boss (Instanz von boss.tscn)
|
||||
- Exit-Gate (Instanz von gate.tscn, is_exit=true, Ecke bei -6/0/-4)
|
||||
- DungeonManager (Node, dungeon_manager.gd)
|
||||
- dungeon_manager.gd — Stellt Spieler wieder her, erkennt Boss-Tod, 2s Delay, dungeon_cleared, zurück zur Taverne
|
||||
|
||||
## Boss
|
||||
- boss.tscn — CharacterBody3D (boss.gd)
|
||||
- Gruppe (enemies, boss)
|
||||
- Kollision (CapsuleShape3D, größer als Enemy)
|
||||
- Mesh (SphereMesh, lila, 1.5x Größe)
|
||||
- Health (Node, health.gd, boss_stats.tres)
|
||||
- Shield (Node, shield.gd, boss_stats.tres)
|
||||
- HitArea, DetectionArea, NavigationAgent3D, EnemyMovement, EnemyCombat, EnemyAggro
|
||||
- Healthbar (Sprite3D + SubViewport, healthbar.gd)
|
||||
- boss.gd — Erbt von enemy.gd, Gruppe "boss"
|
||||
- boss_stats.tres — health=500, regen=0, shield=100, delay=5s, regen_time=8s
|
||||
|
||||
## GameState
|
||||
- game_state.gd — Autoload, speichert Spielerzustand zwischen Szenenwechseln
|
||||
- save_player(player), restore_player(player), clear_player(), clear()
|
||||
- returning_from_dungeon — Spieler kommt aus Dungeon, spawnt bei Gate-Position
|
||||
- dungeon_cleared — Boss tot, Gates werden entfernt, Spieler spawnt bei Taverne
|
||||
- portal_position (Vector3, Position des Gates für Rückkehr + Wiederherstellung)
|
||||
|
||||
## Gemeinsame Komponenten
|
||||
- health.gd — Leben, Regeneration, Tod bei 0 (liest Base-Werte aus EntityStats)
|
||||
- shield.gd — Schild, Regeneration nach Delay (liest Base-Werte aus EntityStats)
|
||||
- healthbar.gd — Liest Health/Shield (optional) vom Parent, gelber Rand bei Anvisierung, blauer Lebensbalken wenn Spieler Ziel ist (nur bei Gegnern)
|
||||
- spawner.gd — Spawnt Entities bei Lebensschwellen, engagt Spieler im Portal-Radius sofort
|
||||
|
||||
## Stats (Resources)
|
||||
- entity_stats.gd (Resource) — max_health, health_regen, max_shield, shield_regen_delay, shield_regen_time
|
||||
- player_stats.tres — health=100, regen=1/s, shield=50, delay=3s, regen_time=5s
|
||||
- enemy_stats.tres — health=100, regen=0, shield=50, delay=3s, regen_time=5s
|
||||
- portal_stats.tres — health=500, regen=0, shield=0
|
||||
|
||||
## Aggro
|
||||
- Schaden = Aggro (1:1)
|
||||
- Heilung = Aggro (0.5x)
|
||||
- Tank = Aggro-Multiplikator (2x)
|
||||
- Aggro verfällt -1/s
|
||||
- Spieler im Portal-Radius: Aggro bleibt bei mindestens 1
|
||||
- Außerhalb 10m Portal-Radius: Aggro verfällt 1% * 2 je s (1%, 2%, 4%, 8%, ...)
|
||||
- Ohne Aggro: Gegner kehrt zum Portal zurück, regeneriert 10% Leben/s bis 100%, dann 1%/s
|
||||
- Bei Spieler-Tod → Aggro auf 0
|
||||
|
||||
## Abilities (Resources)
|
||||
- ability.gd (Resource) — name, type, damage, range, cooldown, uses_gcd, 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
|
||||
- Auto-Attack: 10 Schaden, 1s, automatisch bei anvisiertem Gegner im Kampf
|
||||
- Schadens-Klasse:
|
||||
- 1 Single: 30 Schaden, 20m Range, 1s CD, GCD
|
||||
- 2 AOE: 20 Schaden, 5m Range, 3s CD, GCD
|
||||
- 3 Utility: Schild sofort auf 100%, 5s CD, kein GCD
|
||||
- 4 Ult: 5x Single (50 Schaden) + 2x AOE 3m (20 Schaden), 20m Range, 15s CD, GCD
|
||||
- 5 Passive: 50% mehr Schaden (permanent aktiv, kein CD)
|
||||
- Jede Rolle hat ein eigenes AbilitySet
|
||||
- Beim Rollenwechsel wird das AbilitySet getauscht
|
||||
- Elemente und Modifikatoren verändern Abilities nachträglich
|
||||
## HUD
|
||||
- hud.tscn — CanvasLayer (hud.gd)
|
||||
- HealthBar (ProgressBar, links oben, Label "50/100")
|
||||
- ShieldBar (ProgressBar, links oben, unter HealthBar, Label "25/50")
|
||||
- RespawnTimer (Label, mitte, Countdown bei Tod)
|
||||
- AbilityBar (HBoxContainer, mitte unten)
|
||||
- RoleIcon (Label, T=Tank, D=Schaden, H=Heiler)
|
||||
- Abilities (Label, Fähigkeiten 1-4, Passive P)
|
||||
- hud.gd — Reagiert auf Events, aktualisiert HealthBar/ShieldBar/AbilityBar/RespawnTimer
|
||||
|
||||
## 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
|
||||
- role_changed(player, role_type) — Spieler hat Rolle 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
|
||||
- cooldown_tick(cooldowns, max_cooldowns, gcd_timer) — Cooldown-Update für HUD
|
||||
- portal_spawn(portal, enemies) — Portal hat Gegner gespawnt
|
||||
- event_bus.gd — Autoload-Singleton, globale Signals
|
||||
- Kampf:
|
||||
- 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:
|
||||
- entity_died(entity) — Entity ist gestorben
|
||||
- health_changed(entity, current, max) — Leben hat sich verändert
|
||||
- shield_changed(entity, current, max) — Schild hat sich verändert
|
||||
- shield_broken(entity) — Schild ist auf 0 gefallen
|
||||
- shield_regenerated(entity) — Schild ist wieder voll
|
||||
- Spieler:
|
||||
- target_changed(player, target) — Spieler hat neues Ziel anvisiert
|
||||
- player_respawned(player) — Spieler ist respawnt
|
||||
- role_changed(player, role_type) — Spieler hat Rolle gewechselt
|
||||
- respawn_tick(timer) — Respawn-Countdown Update
|
||||
- cooldown_tick(cooldowns, max_cooldowns, gcd_timer) — Cooldown-Update für HUD
|
||||
- Gegner:
|
||||
- enemy_engaged(enemy, target) — Gegner hat Spieler anvisiert
|
||||
- Portal:
|
||||
- portal_spawn(portal, enemies) — Portal hat Gegner gespawnt
|
||||
- portal_defeated(portal) — Portal besiegt, wird Gate
|
||||
- Dungeon:
|
||||
- dungeon_cleared() — Boss tot, Dungeon gesäubert
|
||||
|
||||
@@ -18,6 +18,11 @@ config/icon="res://icon.svg"
|
||||
[autoload]
|
||||
|
||||
EventBus="*res://scripts/event_bus.gd"
|
||||
GameState="*res://scripts/game_state.gd"
|
||||
|
||||
[dotnet]
|
||||
|
||||
project/assembly_name="mmo"
|
||||
|
||||
[input]
|
||||
|
||||
|
||||
13
resources/abilities/healer_aoe.tres
Normal file
13
resources/abilities/healer_aoe.tres
Normal file
@@ -0,0 +1,13 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://m1kgk2uugnex"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Circle of Healing"
|
||||
type = 1
|
||||
damage = 10.0
|
||||
ability_range = 20.0
|
||||
cooldown = 3.0
|
||||
icon = "2"
|
||||
is_heal = true
|
||||
13
resources/abilities/healer_passive.tres
Normal file
13
resources/abilities/healer_passive.tres
Normal file
@@ -0,0 +1,13 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Heal Aura"
|
||||
type = 4
|
||||
damage = 50.0
|
||||
ability_range = 0.0
|
||||
uses_gcd = false
|
||||
icon = "P"
|
||||
passive_stat = "heal"
|
||||
12
resources/abilities/healer_single.tres
Normal file
12
resources/abilities/healer_single.tres
Normal file
@@ -0,0 +1,12 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://cqw1jy6kqvmnj"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Heal"
|
||||
damage = 15.0
|
||||
ability_range = 20.0
|
||||
cooldown = 2.0
|
||||
icon = "1"
|
||||
is_heal = true
|
||||
14
resources/abilities/healer_ult.tres
Normal file
14
resources/abilities/healer_ult.tres
Normal file
@@ -0,0 +1,14 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://d04nu1leyki16"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Divine Light"
|
||||
type = 3
|
||||
damage = 25.0
|
||||
ability_range = 20.0
|
||||
cooldown = 15.0
|
||||
aoe_radius = 3.0
|
||||
icon = "4"
|
||||
is_heal = true
|
||||
@@ -7,5 +7,5 @@ script = ExtResource("1")
|
||||
ability_name = "Single"
|
||||
damage = 30.0
|
||||
ability_range = 20.0
|
||||
cooldown = 1.0
|
||||
cooldown = 2.0
|
||||
icon = "1"
|
||||
|
||||
12
resources/abilities/tank_aoe.tres
Normal file
12
resources/abilities/tank_aoe.tres
Normal file
@@ -0,0 +1,12 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Tank AOE"
|
||||
type = 1
|
||||
damage = 10.0
|
||||
ability_range = 10.0
|
||||
cooldown = 3.0
|
||||
icon = "2"
|
||||
13
resources/abilities/tank_passive.tres
Normal file
13
resources/abilities/tank_passive.tres
Normal file
@@ -0,0 +1,13 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Shield Aura"
|
||||
type = 4
|
||||
damage = 50.0
|
||||
ability_range = 0.0
|
||||
uses_gcd = false
|
||||
icon = "P"
|
||||
passive_stat = "shield"
|
||||
11
resources/abilities/tank_single.tres
Normal file
11
resources/abilities/tank_single.tres
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Tank Strike"
|
||||
damage = 15.0
|
||||
ability_range = 3.0
|
||||
cooldown = 2.0
|
||||
icon = "1"
|
||||
11
resources/abilities/tank_ult.tres
Normal file
11
resources/abilities/tank_ult.tres
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
ability_name = "Fortress"
|
||||
type = 2
|
||||
damage = 300.0
|
||||
cooldown = 20.0
|
||||
icon = "4"
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
[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"]
|
||||
[ext_resource type="Resource" uid="uid://dwvc8b3cmce8l" path="res://resources/abilities/single_attack.tres" id="2"]
|
||||
[ext_resource type="Resource" uid="uid://bpx3l13iuynfv" path="res://resources/abilities/aoe_attack.tres" id="3"]
|
||||
[ext_resource type="Resource" uid="uid://du0hyuuj26ea0" path="res://resources/abilities/utility_shield_reset.tres" id="4"]
|
||||
[ext_resource type="Resource" uid="uid://s32wvlww2ls2" path="res://resources/abilities/ult_burst.tres" id="5"]
|
||||
[ext_resource type="Resource" uid="uid://dadpl32yujwhe" 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")])
|
||||
aa_damage = 25.0
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
[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"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/healer_single.tres" id="2"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/healer_aoe.tres" id="3"]
|
||||
[ext_resource type="Resource" uid="uid://du0hyuuj26ea0" path="res://resources/abilities/utility_shield_reset.tres" id="4"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/healer_ult.tres" id="5"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/healer_passive.tres" id="6"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
abilities = Array[ExtResource("1_ability")]([ExtResource("2"), ExtResource("3"), ExtResource("4"), ExtResource("5"), ExtResource("6")])
|
||||
aa_damage = 1.0
|
||||
aa_range = 20.0
|
||||
aa_is_heal = true
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
[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"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/tank_single.tres" id="2"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/tank_aoe.tres" id="3"]
|
||||
[ext_resource type="Resource" uid="uid://du0hyuuj26ea0" path="res://resources/abilities/utility_shield_reset.tres" id="4"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/tank_ult.tres" id="5"]
|
||||
[ext_resource type="Resource" path="res://resources/abilities/tank_passive.tres" id="6"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
abilities = Array[ExtResource("1_ability")]([ExtResource("2"), ExtResource("3"), ExtResource("4"), ExtResource("5"), ExtResource("6")])
|
||||
aa_damage = 5.0
|
||||
aa_range = 3.0
|
||||
|
||||
11
resources/stats/boss_stats.tres
Normal file
11
resources/stats/boss_stats.tres
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="EntityStats" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/resources/entity_stats.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
max_health = 500.0
|
||||
health_regen = 0.0
|
||||
max_shield = 100.0
|
||||
shield_regen_delay = 5.0
|
||||
shield_regen_time = 8.0
|
||||
157
scenes/dungeon/dungeon.tscn
Normal file
157
scenes/dungeon/dungeon.tscn
Normal file
@@ -0,0 +1,157 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://scenes/player/player.tscn" id="player"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/hud/hud.tscn" id="hud"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/enemy/enemy.tscn" id="enemy"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/enemy/boss.tscn" id="boss"]
|
||||
[ext_resource type="Script" path="res://scripts/dungeon/dungeon_manager.gd" id="dungeon_manager"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/portal/gate.tscn" id="gate"]
|
||||
|
||||
[sub_resource type="NavigationMesh" id="NavigationMesh_1"]
|
||||
vertices = PackedVector3Array(-7.0, 0.5, -7.0, -7.0, 0.5, 87.0, 7.0, 0.5, 87.0, 7.0, 0.5, -7.0)
|
||||
polygons = [PackedInt32Array(3, 2, 0), PackedInt32Array(0, 2, 1)]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_floor"]
|
||||
albedo_color = Color(0.2, 0.18, 0.15, 1)
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_1"]
|
||||
material = SubResource("StandardMaterial3D_floor")
|
||||
size = Vector2(15, 90)
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_1"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_wall"]
|
||||
albedo_color = Color(0.25, 0.22, 0.2, 1)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_north_south"]
|
||||
material = SubResource("StandardMaterial3D_wall")
|
||||
size = Vector3(15, 3, 0.5)
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_north_south"]
|
||||
size = Vector3(15, 3, 0.5)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_east_west"]
|
||||
material = SubResource("StandardMaterial3D_wall")
|
||||
size = Vector3(0.5, 3, 90)
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_east_west"]
|
||||
size = Vector3(0.5, 3, 90)
|
||||
|
||||
[node name="Dungeon" type="Node3D"]
|
||||
|
||||
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="."]
|
||||
navigation_mesh = SubResource("NavigationMesh_1")
|
||||
|
||||
[node name="Boden" type="MeshInstance3D" parent="NavigationRegion3D"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 40)
|
||||
mesh = SubResource("PlaneMesh_1")
|
||||
|
||||
[node name="BodenCollision" type="StaticBody3D" parent="."]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="BodenCollision"]
|
||||
shape = SubResource("WorldBoundaryShape3D_1")
|
||||
|
||||
[node name="WallSouth" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, -5.25)
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="WallSouth"]
|
||||
mesh = SubResource("BoxMesh_north_south")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WallSouth"]
|
||||
shape = SubResource("BoxShape3D_north_south")
|
||||
|
||||
[node name="WallNorth" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 85.25)
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="WallNorth"]
|
||||
mesh = SubResource("BoxMesh_north_south")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WallNorth"]
|
||||
shape = SubResource("BoxShape3D_north_south")
|
||||
|
||||
[node name="WallEast" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.75, 1.5, 40)
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="WallEast"]
|
||||
mesh = SubResource("BoxMesh_east_west")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WallEast"]
|
||||
shape = SubResource("BoxShape3D_east_west")
|
||||
|
||||
[node name="WallWest" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.75, 1.5, 40)
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="WallWest"]
|
||||
mesh = SubResource("BoxMesh_east_west")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WallWest"]
|
||||
shape = SubResource("BoxShape3D_east_west")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 10, 40)
|
||||
light_energy = 0.6
|
||||
shadow_enabled = true
|
||||
|
||||
[node name="Player" parent="." instance=ExtResource("player")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, -3)
|
||||
|
||||
[node name="HUD" parent="." instance=ExtResource("hud")]
|
||||
|
||||
[node name="Enemy1a" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0, 15)
|
||||
|
||||
[node name="Enemy1b" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 15)
|
||||
|
||||
[node name="Enemy1c" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 15)
|
||||
|
||||
[node name="Enemy1d" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 0, 15)
|
||||
|
||||
[node name="Enemy2a" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0, 30)
|
||||
|
||||
[node name="Enemy2b" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 30)
|
||||
|
||||
[node name="Enemy2c" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 30)
|
||||
|
||||
[node name="Enemy2d" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 0, 30)
|
||||
|
||||
[node name="Enemy3a" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0, 45)
|
||||
|
||||
[node name="Enemy3b" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 45)
|
||||
|
||||
[node name="Enemy3c" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 45)
|
||||
|
||||
[node name="Enemy3d" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 0, 45)
|
||||
|
||||
[node name="Enemy4a" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0, 60)
|
||||
|
||||
[node name="Enemy4b" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 60)
|
||||
|
||||
[node name="Enemy4c" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 60)
|
||||
|
||||
[node name="Enemy4d" parent="." instance=ExtResource("enemy")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 0, 60)
|
||||
|
||||
[node name="Boss" parent="." instance=ExtResource("boss")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 75)
|
||||
|
||||
[node name="ExitGate" parent="." instance=ExtResource("gate")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0, -4)
|
||||
target_scene = "res://scenes/world.tscn"
|
||||
is_exit = true
|
||||
|
||||
[node name="DungeonManager" type="Node" parent="."]
|
||||
script = ExtResource("dungeon_manager")
|
||||
128
scenes/enemy/boss.tscn
Normal file
128
scenes/enemy/boss.tscn
Normal file
@@ -0,0 +1,128 @@
|
||||
[gd_scene load_steps=6 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/enemy/boss.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/components/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"]
|
||||
[ext_resource type="Script" path="res://scripts/enemy/enemy_aggro.gd" id="7"]
|
||||
[ext_resource type="Resource" path="res://resources/stats/boss_stats.tres" id="8"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||
radius = 0.6
|
||||
height = 2.0
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_2"]
|
||||
radius = 0.6
|
||||
height = 2.0
|
||||
|
||||
[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_health_fill_aggro"]
|
||||
bg_color = Color(0.2, 0.4, 0.9, 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.6, 0.1, 0.6, 1)
|
||||
|
||||
[sub_resource type="SphereMesh" id="SphereMesh_1"]
|
||||
radius = 0.75
|
||||
height = 1.5
|
||||
material = SubResource("StandardMaterial3D_1")
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
|
||||
radius = 8.0
|
||||
|
||||
[node name="Boss" type="CharacterBody3D"]
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
shape = SubResource("CapsuleShape3D_1")
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1.5, 0, 0, 0, 1.5, 0, 0, 0, 1.5, 0, 0, 0)
|
||||
mesh = SubResource("SphereMesh_1")
|
||||
|
||||
[node name="Health" type="Node" parent="."]
|
||||
script = ExtResource("2")
|
||||
stats = ExtResource("8")
|
||||
|
||||
[node name="Shield" type="Node" parent="."]
|
||||
script = ExtResource("3")
|
||||
stats = ExtResource("8")
|
||||
|
||||
[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="EnemyAggro" type="Node" parent="."]
|
||||
script = ExtResource("7")
|
||||
|
||||
[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, 2.0, 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 = 500.0
|
||||
value = 500.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 = 100.0
|
||||
value = 100.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"]
|
||||
@@ -48,6 +48,18 @@ max_value = 100.0
|
||||
value = 100.0
|
||||
show_percentage = false
|
||||
|
||||
[node name="HealthLabel" type="Label" parent="HealthBar"]
|
||||
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 = 12
|
||||
text = "100/100"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="ShieldBar" type="ProgressBar" parent="."]
|
||||
offset_left = 10.0
|
||||
offset_top = 35.0
|
||||
@@ -59,6 +71,18 @@ max_value = 50.0
|
||||
value = 50.0
|
||||
show_percentage = false
|
||||
|
||||
[node name="ShieldLabel" type="Label" parent="ShieldBar"]
|
||||
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 = 12
|
||||
text = "50/50"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="RespawnTimer" type="Label" parent="."]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
|
||||
36
scenes/portal/gate.tscn
Normal file
36
scenes/portal/gate.tscn
Normal file
@@ -0,0 +1,36 @@
|
||||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/portal/gate.gd" id="1"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
|
||||
albedo_color = Color(0.1, 0.9, 0.3, 1)
|
||||
emission_enabled = true
|
||||
emission = Color(0.1, 0.9, 0.3, 1)
|
||||
emission_energy_multiplier = 0.5
|
||||
|
||||
[sub_resource type="CylinderMesh" id="CylinderMesh_1"]
|
||||
top_radius = 1.0
|
||||
bottom_radius = 1.0
|
||||
height = 2.0
|
||||
material = SubResource("StandardMaterial3D_1")
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
|
||||
radius = 3.0
|
||||
|
||||
[node name="Gate" type="StaticBody3D"]
|
||||
script = ExtResource("1")
|
||||
collision_layer = 0
|
||||
collision_mask = 0
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("CylinderMesh_1")
|
||||
|
||||
[node name="GateArea" type="Area3D" parent="."]
|
||||
collision_layer = 0
|
||||
collision_mask = 1
|
||||
monitoring = true
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="GateArea"]
|
||||
shape = SubResource("SphereShape3D_1")
|
||||
|
||||
[connection signal="body_entered" from="GateArea" to="." method="_on_gate_area_body_entered"]
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
[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"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/portal/portal.tscn" id="portal"]
|
||||
[ext_resource type="Script" uid="uid://cskx6o07iukwh" path="res://scripts/world/portal_spawner.gd" id="portal_spawner"]
|
||||
|
||||
[sub_resource type="NavigationMesh" id="NavigationMesh_1"]
|
||||
vertices = PackedVector3Array(-9.5, 0.5, -9.5, -9.5, 0.5, 9.5, 9.5, 0.5, 9.5, 9.5, 0.5, -9.5)
|
||||
vertices = PackedVector3Array(-49.5, 0.5, -49.5, -49.5, 0.5, 49.5, 49.5, 0.5, 49.5, 49.5, 0.5, -49.5)
|
||||
polygons = [PackedInt32Array(3, 2, 0), PackedInt32Array(0, 2, 1)]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_1"]
|
||||
@@ -19,37 +19,56 @@ noise = SubResource("FastNoiseLite_1")
|
||||
color_ramp = SubResource("Gradient_1")
|
||||
seamless = true
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ground"]
|
||||
albedo_texture = SubResource("NoiseTexture2D_1")
|
||||
uv1_scale = Vector3(3, 3, 1)
|
||||
uv1_scale = Vector3(15, 15, 1)
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_1"]
|
||||
material = SubResource("StandardMaterial3D_1")
|
||||
size = Vector2(20, 20)
|
||||
material = SubResource("StandardMaterial3D_ground")
|
||||
size = Vector2(100, 100)
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_1"]
|
||||
|
||||
[node name="World" type="Node3D" unique_id=1834775183]
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_tavern"]
|
||||
albedo_color = Color(0.45, 0.3, 0.15, 1)
|
||||
|
||||
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=561649622]
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_tavern"]
|
||||
material = SubResource("StandardMaterial3D_tavern")
|
||||
size = Vector3(5, 3, 5)
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_tavern"]
|
||||
size = Vector3(5, 3, 5)
|
||||
|
||||
[node name="World" type="Node3D" unique_id=1865233338]
|
||||
|
||||
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="." unique_id=1265843679]
|
||||
navigation_mesh = SubResource("NavigationMesh_1")
|
||||
|
||||
[node name="Boden" type="MeshInstance3D" parent="NavigationRegion3D" unique_id=1129907783]
|
||||
[node name="Boden" type="MeshInstance3D" parent="NavigationRegion3D" unique_id=593226019]
|
||||
mesh = SubResource("PlaneMesh_1")
|
||||
|
||||
[node name="BodenCollision" type="StaticBody3D" parent="." unique_id=391155617]
|
||||
[node name="BodenCollision" type="StaticBody3D" parent="." unique_id=1112667638]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="BodenCollision" unique_id=809444866]
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="BodenCollision" unique_id=621554623]
|
||||
shape = SubResource("WorldBoundaryShape3D_1")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1132129079]
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1797472817]
|
||||
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="Taverne" type="StaticBody3D" parent="." unique_id=1978646562]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
||||
|
||||
[node name="HUD" parent="." unique_id=24362518 instance=ExtResource("hud")]
|
||||
[node name="Mesh" type="MeshInstance3D" parent="Taverne" unique_id=2043279810]
|
||||
mesh = SubResource("BoxMesh_tavern")
|
||||
|
||||
[node name="Portal" parent="." unique_id=2086501116 instance=ExtResource("portal")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 5)
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Taverne" unique_id=2108564286]
|
||||
shape = SubResource("BoxShape3D_tavern")
|
||||
|
||||
[node name="Player" parent="." unique_id=585018813 instance=ExtResource("player")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, -5)
|
||||
|
||||
[node name="HUD" parent="." unique_id=763693646 instance=ExtResource("hud")]
|
||||
|
||||
[node name="PortalSpawner" type="Node" parent="." unique_id=2100621428]
|
||||
script = ExtResource("portal_spawner")
|
||||
|
||||
@@ -11,9 +11,12 @@ enum Type { SINGLE, AOE, UTILITY, ULT, PASSIVE }
|
||||
@export var uses_gcd: bool = true
|
||||
@export var aoe_radius: float = 0.0
|
||||
@export var icon: String = ""
|
||||
@export var is_heal: bool = false
|
||||
@export var passive_stat: String = "damage"
|
||||
|
||||
func execute(player: Node, targeting: Node) -> bool:
|
||||
var dmg: float = _get_modified_damage(player, damage)
|
||||
var stat: String = "heal" if is_heal else "damage"
|
||||
var dmg: float = _get_modified_damage(player, damage, stat)
|
||||
match type:
|
||||
Type.SINGLE:
|
||||
return _execute_single(player, targeting, dmg)
|
||||
@@ -25,28 +28,44 @@ func execute(player: Node, targeting: Node) -> bool:
|
||||
return _execute_ult(player, targeting, dmg)
|
||||
return false
|
||||
|
||||
func _get_modified_damage(player: Node, base: float) -> float:
|
||||
func _get_modified_damage(player: Node, base: float, stat: String = "damage") -> float:
|
||||
var combat: Node = player.get_node("Combat")
|
||||
return combat.apply_passive(base)
|
||||
return combat.apply_passive(base, stat)
|
||||
|
||||
func _in_range(player: Node, targeting: Node) -> bool:
|
||||
if ability_range <= 0:
|
||||
return true
|
||||
var target: Node3D = targeting.current_target
|
||||
if not target or not is_instance_valid(target):
|
||||
if is_heal:
|
||||
return true
|
||||
if not is_instance_valid(targeting.current_target):
|
||||
return false
|
||||
var dist: float = player.global_position.distance_to(target.global_position)
|
||||
var dist: float = player.global_position.distance_to(targeting.current_target.global_position)
|
||||
return dist <= ability_range
|
||||
|
||||
func _execute_single(player: Node, targeting: Node, dmg: float) -> bool:
|
||||
if is_heal:
|
||||
EventBus.heal_requested.emit(player, player, dmg)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
||||
return true
|
||||
if not _in_range(player, targeting):
|
||||
return false
|
||||
var target: Node3D = targeting.current_target
|
||||
EventBus.damage_requested.emit(player, target, dmg)
|
||||
if not is_instance_valid(targeting.current_target):
|
||||
return false
|
||||
EventBus.damage_requested.emit(player, targeting.current_target, dmg)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
||||
return true
|
||||
|
||||
func _execute_aoe(player: Node, dmg: float) -> bool:
|
||||
if is_heal:
|
||||
EventBus.heal_requested.emit(player, player, dmg)
|
||||
var players := player.get_tree().get_nodes_in_group("player")
|
||||
for p in players:
|
||||
if p != player and is_instance_valid(p):
|
||||
var dist: float = player.global_position.distance_to(p.global_position)
|
||||
if dist <= ability_range:
|
||||
EventBus.heal_requested.emit(player, p, dmg)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
||||
return true
|
||||
var hit := false
|
||||
var enemies := player.get_tree().get_nodes_in_group("enemies")
|
||||
for enemy in enemies:
|
||||
@@ -61,20 +80,38 @@ func _execute_aoe(player: Node, dmg: float) -> bool:
|
||||
func _execute_utility(player: Node) -> bool:
|
||||
var shield: Node = player.get_node_or_null("Shield")
|
||||
if shield:
|
||||
if damage > 0:
|
||||
shield.current_shield = shield.max_shield * (damage / 100.0)
|
||||
else:
|
||||
if shield.current_shield >= shield.max_shield:
|
||||
return false
|
||||
shield.current_shield = shield.max_shield
|
||||
EventBus.shield_changed.emit(player, shield.current_shield, shield.max_shield)
|
||||
return true
|
||||
return false
|
||||
|
||||
func _execute_ult(player: Node, targeting: Node, dmg: float) -> bool:
|
||||
if is_heal:
|
||||
EventBus.heal_requested.emit(player, player, dmg)
|
||||
var players := player.get_tree().get_nodes_in_group("player")
|
||||
var aoe_range: float = aoe_radius if aoe_radius > 0 else ability_range
|
||||
for p in players:
|
||||
if p != player and is_instance_valid(p):
|
||||
var dist: float = player.global_position.distance_to(p.global_position)
|
||||
if dist <= aoe_range:
|
||||
EventBus.heal_requested.emit(player, p, dmg * 0.4)
|
||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
||||
return true
|
||||
if not _in_range(player, targeting):
|
||||
return false
|
||||
if not is_instance_valid(targeting.current_target):
|
||||
return false
|
||||
var target: Node3D = targeting.current_target
|
||||
EventBus.damage_requested.emit(player, target, dmg * 5.0)
|
||||
var aoe_range: float = aoe_radius if aoe_radius > 0 else ability_range
|
||||
var enemies := player.get_tree().get_nodes_in_group("enemies")
|
||||
for enemy in enemies:
|
||||
if enemy != target:
|
||||
if enemy != target and is_instance_valid(enemy):
|
||||
var enemy_dist: float = target.global_position.distance_to(enemy.global_position)
|
||||
if enemy_dist <= aoe_range:
|
||||
EventBus.damage_requested.emit(player, enemy, dmg * 2.0)
|
||||
|
||||
@@ -2,3 +2,6 @@ extends Resource
|
||||
class_name AbilitySet
|
||||
|
||||
@export var abilities: Array[Ability] = []
|
||||
@export var aa_damage: float = 10.0
|
||||
@export var aa_range: float = 10.0
|
||||
@export var aa_is_heal: bool = false
|
||||
|
||||
@@ -10,6 +10,7 @@ func _ready() -> void:
|
||||
health_regen = stats.health_regen
|
||||
current_health = max_health
|
||||
EventBus.damage_requested.connect(_on_damage_requested)
|
||||
EventBus.heal_requested.connect(_on_heal_requested)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if current_health > 0 and current_health < max_health and health_regen > 0:
|
||||
@@ -34,3 +35,11 @@ func take_damage(amount: float, attacker: Node) -> void:
|
||||
EventBus.health_changed.emit(get_parent(), current_health, max_health)
|
||||
if current_health <= 0:
|
||||
EventBus.entity_died.emit(get_parent())
|
||||
|
||||
func heal(amount: float) -> void:
|
||||
current_health = min(current_health + amount, max_health)
|
||||
EventBus.health_changed.emit(get_parent(), current_health, max_health)
|
||||
|
||||
func _on_heal_requested(healer: Node, target: Node, amount: float) -> void:
|
||||
if target == get_parent():
|
||||
heal(amount)
|
||||
|
||||
@@ -7,11 +7,15 @@ var regen_time: float
|
||||
var current_shield: float
|
||||
var regen_timer := 0.0
|
||||
|
||||
var base_max_shield: float
|
||||
|
||||
func _ready() -> void:
|
||||
max_shield = stats.max_shield
|
||||
base_max_shield = max_shield
|
||||
regen_delay = stats.shield_regen_delay
|
||||
regen_time = stats.shield_regen_time
|
||||
current_shield = max_shield
|
||||
EventBus.role_changed.connect(_on_role_changed)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if max_shield <= 0:
|
||||
@@ -25,6 +29,22 @@ func _process(delta: float) -> void:
|
||||
EventBus.shield_regenerated.emit(get_parent())
|
||||
EventBus.shield_changed.emit(get_parent(), current_shield, max_shield)
|
||||
|
||||
func _on_role_changed(_player: Node, _role_type: int) -> void:
|
||||
if get_parent() != _player:
|
||||
return
|
||||
var role: Node = get_parent().get_node_or_null("Role")
|
||||
if not role:
|
||||
return
|
||||
var ability_set: AbilitySet = role.get_ability_set()
|
||||
if not ability_set:
|
||||
return
|
||||
max_shield = base_max_shield
|
||||
for ability in ability_set.abilities:
|
||||
if ability and ability.type == Ability.Type.PASSIVE and ability.passive_stat == "shield":
|
||||
max_shield = base_max_shield * (1.0 + ability.damage / 100.0)
|
||||
current_shield = min(current_shield, max_shield)
|
||||
EventBus.shield_changed.emit(get_parent(), current_shield, max_shield)
|
||||
|
||||
func absorb(amount: float) -> float:
|
||||
if current_shield <= 0:
|
||||
return amount
|
||||
|
||||
16
scripts/dungeon/dungeon_manager.gd
Normal file
16
scripts/dungeon/dungeon_manager.gd
Normal file
@@ -0,0 +1,16 @@
|
||||
extends Node
|
||||
|
||||
func _ready() -> void:
|
||||
var player: Node = get_tree().get_first_node_in_group("player")
|
||||
if player:
|
||||
GameState.restore_player(player)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity.is_in_group("boss"):
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
GameState.dungeon_cleared = true
|
||||
GameState.returning_from_dungeon = false
|
||||
GameState.clear_player()
|
||||
EventBus.dungeon_cleared.emit()
|
||||
get_tree().change_scene_to_file("res://scenes/world.tscn")
|
||||
1
scripts/dungeon/dungeon_manager.gd.uid
Normal file
1
scripts/dungeon/dungeon_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://drn4h1lxx5t1j
|
||||
5
scripts/enemy/boss.gd
Normal file
5
scripts/enemy/boss.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends "res://scripts/enemy/enemy.gd"
|
||||
|
||||
func _ready() -> void:
|
||||
super._ready()
|
||||
add_to_group("boss")
|
||||
1
scripts/enemy/boss.gd.uid
Normal file
1
scripts/enemy/boss.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bkehq3dqyp2yd
|
||||
@@ -10,6 +10,7 @@ var seconds_outside := 0.0
|
||||
func _ready() -> void:
|
||||
EventBus.damage_dealt.connect(_on_damage_dealt)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
EventBus.heal_requested.connect(_on_heal_requested)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var outside_portal := false
|
||||
@@ -53,6 +54,12 @@ func _on_damage_dealt(attacker: Node, target: Node, amount: float) -> void:
|
||||
multiplier = 2.0
|
||||
add_aggro(attacker, amount * multiplier)
|
||||
|
||||
func _on_heal_requested(healer: Node, _target: Node, amount: float) -> void:
|
||||
if not healer.is_in_group("player"):
|
||||
return
|
||||
if healer in aggro_table:
|
||||
add_aggro(healer, amount * 0.5)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
aggro_table.erase(entity)
|
||||
|
||||
|
||||
@@ -15,3 +15,6 @@ signal respawn_tick(timer)
|
||||
signal enemy_engaged(enemy, target)
|
||||
signal cooldown_tick(cooldowns, max_cooldowns, gcd_timer)
|
||||
signal portal_spawn(portal, enemies)
|
||||
signal heal_requested(healer, target, amount)
|
||||
signal portal_defeated(portal)
|
||||
signal dungeon_cleared()
|
||||
|
||||
42
scripts/game_state.gd
Normal file
42
scripts/game_state.gd
Normal file
@@ -0,0 +1,42 @@
|
||||
extends Node
|
||||
|
||||
var player_health: float = -1.0
|
||||
var player_max_health: float = -1.0
|
||||
var player_shield: float = -1.0
|
||||
var player_max_shield: float = -1.0
|
||||
var player_role: int = 1
|
||||
var portal_position: Vector3 = Vector3.ZERO
|
||||
var returning_from_dungeon := false
|
||||
var dungeon_cleared := false
|
||||
|
||||
func save_player(player: Node) -> void:
|
||||
var health: Node = player.get_node("Health")
|
||||
var shield: Node = player.get_node("Shield")
|
||||
var role: Node = player.get_node("Role")
|
||||
player_health = health.current_health
|
||||
player_max_health = health.max_health
|
||||
player_shield = shield.current_shield
|
||||
player_max_shield = shield.max_shield
|
||||
player_role = role.current_role
|
||||
|
||||
func restore_player(player: Node) -> void:
|
||||
if player_health < 0:
|
||||
return
|
||||
var health: Node = player.get_node("Health")
|
||||
var shield: Node = player.get_node("Shield")
|
||||
var role: Node = player.get_node("Role")
|
||||
health.current_health = player_health
|
||||
shield.current_shield = player_shield
|
||||
role.set_role(player_role)
|
||||
EventBus.health_changed.emit(player, health.current_health, health.max_health)
|
||||
EventBus.shield_changed.emit(player, shield.current_shield, shield.max_shield)
|
||||
|
||||
func clear_player() -> void:
|
||||
player_health = -1.0
|
||||
player_shield = -1.0
|
||||
|
||||
func clear() -> void:
|
||||
clear_player()
|
||||
portal_position = Vector3.ZERO
|
||||
returning_from_dungeon = false
|
||||
dungeon_cleared = false
|
||||
1
scripts/game_state.gd.uid
Normal file
1
scripts/game_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cp2vadwcd12sm
|
||||
@@ -1,9 +1,7 @@
|
||||
extends Node
|
||||
|
||||
const GCD_TIME := 0.5
|
||||
const AA_DAMAGE := 10.0
|
||||
const AA_COOLDOWN := 1.0
|
||||
const AA_RANGE := 20.0
|
||||
const AA_COOLDOWN := 0.5
|
||||
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
@onready var targeting: Node = get_parent().get_node("Targeting")
|
||||
@@ -36,12 +34,23 @@ func _auto_attack(delta: float) -> void:
|
||||
return
|
||||
if not is_instance_valid(targeting.current_target):
|
||||
return
|
||||
var dist := player.global_position.distance_to(targeting.current_target.global_position)
|
||||
if dist > AA_RANGE:
|
||||
var ability_set: AbilitySet = role.get_ability_set()
|
||||
if not ability_set:
|
||||
return
|
||||
var dmg := apply_passive(AA_DAMAGE)
|
||||
var aa_damage: float = ability_set.aa_damage
|
||||
var aa_range: float = ability_set.aa_range
|
||||
var aa_is_heal: bool = ability_set.aa_is_heal
|
||||
var dmg: float = apply_passive(aa_damage, "heal" if aa_is_heal else "damage")
|
||||
if aa_is_heal:
|
||||
EventBus.heal_requested.emit(player, player, dmg)
|
||||
print("AA Heal: %s an %s" % [dmg, player.name])
|
||||
else:
|
||||
var dist := player.global_position.distance_to(targeting.current_target.global_position)
|
||||
if dist > aa_range:
|
||||
return
|
||||
var target_name: String = targeting.current_target.name
|
||||
EventBus.damage_requested.emit(player, targeting.current_target, dmg)
|
||||
print("AA: %s Schaden an %s" % [dmg, targeting.current_target.name])
|
||||
print("AA: %s Schaden an %s" % [dmg, target_name])
|
||||
aa_timer = AA_COOLDOWN
|
||||
|
||||
func _load_abilities() -> void:
|
||||
@@ -74,11 +83,11 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
gcd_timer = GCD_TIME
|
||||
return
|
||||
|
||||
func apply_passive(base_damage: float) -> float:
|
||||
func apply_passive(base: float, stat: String = "damage") -> 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
|
||||
if ability and ability.type == Ability.Type.PASSIVE and ability.passive_stat == stat:
|
||||
return base * (1.0 + ability.damage / 100.0)
|
||||
return base
|
||||
|
||||
func _on_role_changed(_player: Node, _role_type: int) -> void:
|
||||
_load_abilities()
|
||||
|
||||
@@ -3,7 +3,9 @@ extends CanvasLayer
|
||||
const GCD_TIME := 0.5
|
||||
|
||||
@onready var health_bar: ProgressBar = $HealthBar
|
||||
@onready var health_label: Label = $HealthBar/HealthLabel
|
||||
@onready var shield_bar: ProgressBar = $ShieldBar
|
||||
@onready var shield_label: Label = $ShieldBar/ShieldLabel
|
||||
@onready var respawn_label: Label = $RespawnTimer
|
||||
@onready var class_icon: Label = $AbilityBar/ClassIcon/Label
|
||||
@onready var ability_panels: Array = [
|
||||
@@ -30,11 +32,13 @@ 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
|
||||
health_label.text = "%d/%d" % [current, max_val]
|
||||
|
||||
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
|
||||
shield_label.text = "%d/%d" % [current, max_val]
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity.name == "Player":
|
||||
|
||||
@@ -2,3 +2,7 @@ extends CharacterBody3D
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("player")
|
||||
if GameState.returning_from_dungeon:
|
||||
GameState.restore_player(self)
|
||||
global_position = GameState.portal_position + Vector3(0, 1, -5)
|
||||
GameState.returning_from_dungeon = false
|
||||
|
||||
@@ -8,7 +8,7 @@ var spawn_position: Vector3
|
||||
@onready var player: CharacterBody3D = get_parent()
|
||||
|
||||
func _ready() -> void:
|
||||
spawn_position = player.global_position
|
||||
spawn_position = Vector3(0, 1, -5)
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
|
||||
34
scripts/portal/gate.gd
Normal file
34
scripts/portal/gate.gd
Normal file
@@ -0,0 +1,34 @@
|
||||
extends StaticBody3D
|
||||
|
||||
@export var target_scene: String = "res://scenes/dungeon/dungeon.tscn"
|
||||
@export var is_exit: bool = false
|
||||
|
||||
var active := false
|
||||
|
||||
func _ready() -> void:
|
||||
if not is_exit:
|
||||
if GameState.dungeon_cleared:
|
||||
queue_free()
|
||||
return
|
||||
get_tree().create_timer(0.5).timeout.connect(_check_overlapping)
|
||||
else:
|
||||
get_tree().create_timer(1.0).timeout.connect(func() -> void: active = true)
|
||||
|
||||
func _check_overlapping() -> void:
|
||||
active = true
|
||||
for body in $GateArea.get_overlapping_bodies():
|
||||
_on_gate_area_body_entered(body)
|
||||
|
||||
func _on_gate_area_body_entered(body: Node3D) -> void:
|
||||
if not active:
|
||||
return
|
||||
if body is CharacterBody3D and body.name == "Player":
|
||||
GameState.save_player(body)
|
||||
if is_exit:
|
||||
GameState.returning_from_dungeon = true
|
||||
else:
|
||||
GameState.portal_position = global_position
|
||||
call_deferred("_change_scene")
|
||||
|
||||
func _change_scene() -> void:
|
||||
get_tree().change_scene_to_file(target_scene)
|
||||
1
scripts/portal/gate.gd.uid
Normal file
1
scripts/portal/gate.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ctci5mc3cd2ck
|
||||
@@ -1,11 +1,22 @@
|
||||
extends StaticBody3D
|
||||
|
||||
const GATE_SCENE: PackedScene = preload("res://scenes/portal/gate.tscn")
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("portals")
|
||||
EventBus.entity_died.connect(_on_entity_died)
|
||||
|
||||
func _on_entity_died(entity: Node) -> void:
|
||||
if entity == self:
|
||||
if entity != self:
|
||||
return
|
||||
var gate: Node3D = GATE_SCENE.instantiate()
|
||||
gate.global_position = global_position
|
||||
get_parent().add_child(gate)
|
||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
||||
for enemy in enemies:
|
||||
if is_instance_valid(enemy):
|
||||
enemy.queue_free()
|
||||
EventBus.portal_defeated.emit(self)
|
||||
queue_free()
|
||||
|
||||
func _on_detection_area_body_entered(body: Node3D) -> void:
|
||||
|
||||
45
scripts/world/portal_spawner.gd
Normal file
45
scripts/world/portal_spawner.gd
Normal file
@@ -0,0 +1,45 @@
|
||||
extends Node
|
||||
|
||||
const PORTAL_SCENE: PackedScene = preload("res://scenes/portal/portal.tscn")
|
||||
const GATE_SCENE: PackedScene = preload("res://scenes/portal/gate.tscn")
|
||||
const SPAWN_INTERVAL := 30.0
|
||||
const MAX_PORTALS := 3
|
||||
const MIN_DISTANCE := 20.0
|
||||
const MAX_DISTANCE := 40.0
|
||||
|
||||
var portals: Array[Node] = []
|
||||
var timer := 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
if GameState.portal_position != Vector3.ZERO and not GameState.dungeon_cleared:
|
||||
call_deferred("_restore_gate")
|
||||
else:
|
||||
if GameState.dungeon_cleared:
|
||||
GameState.clear()
|
||||
call_deferred("_spawn_portal")
|
||||
|
||||
func _restore_gate() -> void:
|
||||
var gate: Node3D = GATE_SCENE.instantiate()
|
||||
get_parent().add_child(gate)
|
||||
gate.global_position = GameState.portal_position
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
timer += delta
|
||||
if timer >= SPAWN_INTERVAL:
|
||||
timer = 0.0
|
||||
_cleanup_dead()
|
||||
if portals.size() < MAX_PORTALS:
|
||||
_spawn_portal()
|
||||
|
||||
func _spawn_portal() -> void:
|
||||
var angle: float = randf() * TAU
|
||||
var distance: float = randf_range(MIN_DISTANCE, MAX_DISTANCE)
|
||||
var pos := Vector3(cos(angle) * distance, 0, sin(angle) * distance)
|
||||
var portal: Node3D = PORTAL_SCENE.instantiate()
|
||||
get_parent().add_child(portal)
|
||||
portal.global_position = pos
|
||||
portals.append(portal)
|
||||
print("Portal gespawnt bei: %s" % pos)
|
||||
|
||||
func _cleanup_dead() -> void:
|
||||
portals = portals.filter(func(p: Node) -> bool: return is_instance_valid(p))
|
||||
1
scripts/world/portal_spawner.gd.uid
Normal file
1
scripts/world/portal_spawner.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cskx6o07iukwh
|
||||
Reference in New Issue
Block a user