Compare commits
10 Commits
d6715e9c3f
...
f1d34ebf1d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1d34ebf1d | ||
|
|
3488856b91 | ||
|
|
73af6abeb7 | ||
|
|
c7e6f8f4b5 | ||
|
|
47f4fe3d90 | ||
|
|
e76c66eda6 | ||
|
|
b236cd52cb | ||
|
|
336124ff83 | ||
|
|
971ffc8da2 | ||
|
|
04749104a0 |
89
CLAUDE.md
89
CLAUDE.md
@@ -12,53 +12,78 @@ Der User kommuniziert auf Deutsch. Code und Variablen auf Englisch. Kommentare n
|
|||||||
- Keine Debug-Prints im finalen Code (nur temporär zum Testen)
|
- Keine Debug-Prints im finalen Code (nur temporär zum Testen)
|
||||||
|
|
||||||
## Architektur
|
## Architektur
|
||||||
- **Zwischen Szenen**: Kommunikation über EventBus (Autoload). Szenen kennen sich nicht.
|
- **Stats (Model)**: Autoload, zentrale Datenhaltung aller Entity-Attribute. Basiswerte aus Resources.
|
||||||
- **Innerhalb einer Szene**: Modulare Skripte als Child-Nodes, Zugriff auf Geschwister-Nodes erlaubt.
|
- **Systeme (Controller)**: Scene-Nodes in world.tscn/dungeon.tscn, lesen/schreiben über Stats.
|
||||||
- **Gruppen**: "player", "enemies", "portals"
|
- **Szenen (Views)**: Rendern, Input senden, Events empfangen. Kein Gameplay-State.
|
||||||
- **Resources** für statische Konfiguration (Stats, Abilities), **Nodes** für laufenden Zustand
|
- **EventBus (Signals)**: Autoload, Kommunikation zwischen Szenen und Systemen.
|
||||||
- Portale sind keine Gegner — eigene Gruppe "portals", kein State/Aggro/Combat
|
- **Event-Flow**: Szene → Input → EventBus → System → Stats → EventBus → Szene
|
||||||
|
- **Zwischen Szenen**: Kommunikation über EventBus. Szenen kennen sich nicht.
|
||||||
|
- **Innerhalb einer Szene**: Zugriff auf Geschwister-Nodes erlaubt.
|
||||||
|
- **Autoloads**: EventBus (Signals), Stats (Entity-Daten), GameState (Szene + Position)
|
||||||
|
- **Gruppen**: "player", "enemies", "portals", "boss", "cooldown_system"
|
||||||
|
- **Resources** für Basiswerte (Stats, Abilities), **Stats Autoload** für Laufzeitwerte
|
||||||
|
|
||||||
## Projektstruktur
|
## Projektstruktur
|
||||||
- `scenes/` — .tscn Dateien (world, player, enemy, portal, hud)
|
Drei Grundordner nach Verantwortung. Resources liegen bei ihren Skripten.
|
||||||
- `scripts/player/` — Spieler-Skripte (player, camera, movement, combat, targeting, role, respawn)
|
- `scenes/` — Darstellung + Input
|
||||||
- `scripts/enemy/` — Gegner-Skripte (enemy, enemy_movement, enemy_combat, enemy_aggro)
|
- `player/` — Spieler + player_stats + role/ (Rollen + Abilities)
|
||||||
- `scripts/portal/` — Portal-Skripte (portal)
|
- `enemy/` — Gegner + enemy_stats + boss/ (Boss + boss_stats)
|
||||||
- `scripts/components/` — Wiederverwendbare Komponenten (health, shield, healthbar, spawner)
|
- `portal/` — Portal + Gate + portal_stats
|
||||||
- `scripts/abilities/` — Ability-System (ability, ability_set)
|
- `dungeon/` — Dungeon + dungeon_manager
|
||||||
- `scripts/resources/` — Resource-Klassen (entity_stats)
|
- `hud/` — HUD
|
||||||
- `resources/stats/` — Stats .tres (player_stats, enemy_stats, portal_stats)
|
- `world/` — Hauptszene + portal_spawner
|
||||||
- `resources/abilities/` — Ability .tres (single_attack, aoe_attack, utility_shield_reset, ult_burst, passive_damage_boost)
|
- `effect_icon_factory.gd` — Shared Utility (Effekt-Icons)
|
||||||
- `resources/ability_sets/` — AbilitySet .tres pro Rolle (tank_set, damage_set, healer_set)
|
- `healthbar*.gd` — Healthbar-Komponenten (health, shield, status, effects)
|
||||||
|
- `systems/` — Spiellogik
|
||||||
|
- 12 Systeme (health, shield, ability, auto_attack, cooldown, enemy_ai, respawn, spawn, effect, element, aura, buff_calc)
|
||||||
|
- `effect.gd` — Effect Resource (Buff/Debuff/Aura Daten)
|
||||||
|
- `aggro/` — AggroSystem (system, tracker, decay, events) + aggro_config
|
||||||
|
- `autoloads/` — Globaler Zustand
|
||||||
|
- event_bus, game_state
|
||||||
|
- `stats/` — stats + base_stats
|
||||||
|
|
||||||
## Planungsdokument
|
## 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:
|
|
||||||
- `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
|
## 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%)
|
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
|
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)
|
4. Portal bei 0 HP → Gate spawnt, Gegner werden entfernt
|
||||||
5. Tod → 3s Respawn am Startpunkt
|
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
|
## Kampfsystem
|
||||||
- Auto-Attack: 10 Schaden, 1s, automatisch bei anvisiertem Gegner im Kampf
|
- 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
|
- 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
|
- 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%, als Aura (50m Radius)
|
||||||
- Targeting: Klick, TAB (cyclet "enemies" + "portals"), Auto-Target (Gegner > Portal)
|
- 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
|
## Rollen
|
||||||
- Tank (T), Schaden (D), Heiler (H) — wechselbar mit ALT+1/2/3
|
- Tank (T), Schaden (D), Heiler (H) — wechselbar mit ALT+1/2/3
|
||||||
- Aktuell alle gleiche Abilities, später unterschiedlich
|
- Jede Rolle hat eigenes AbilitySet mit unterschiedlichen Werten und Mechaniken
|
||||||
- Jede Rolle hat eigenes AbilitySet (Resource)
|
- Tank: Weniger Schaden, Nahkampf, Schild-Ult (300%), Schild-Passive
|
||||||
|
- Heiler: Heilt statt schadet (Single, AOE, Ult), Heal-Passive, AOE macht Schaden
|
||||||
|
|
||||||
|
## Effekte & Elemente
|
||||||
|
- **EffectSystem**: Verwaltet Buffs, Debuffs, Auras auf allen Entities. Kein Stacking (gleicher Name → Refresh)
|
||||||
|
- **Effect Resource**: effect_name, type (BUFF/DEBUFF/AURA), stat, value, duration (-1=permanent), is_multiplier, aura_radius, tick_interval, element
|
||||||
|
- **Auras**: Passive-Abilities werden als AURA-Effekte erstellt. Propagieren Buffs auf Spieler im aura_radius. Verlässt man Radius → Buff sofort weg
|
||||||
|
- **ElementSystem**: Verwaltet Elementar-Zustände auf Enemies + Portalen. Aktuell: Feuer (DoT 3/Tick, 2s Interval, 6s)
|
||||||
|
- **Abilities**: `element` Feld (0=NONE, 1=FIRE). Schadens-Abilities der Damage-Rolle haben element=1 (Feuer)
|
||||||
|
- **Effekt-Icons**: Auf Healthbars (Enemies, Boss, Portal) und Spieler-HUD. Aura=blau, Buff=grün, Debuff=rot
|
||||||
|
|
||||||
|
## Szenenwechsel
|
||||||
|
- Stats Autoload cached Spieler-Werte automatisch bei Szenenwechsel
|
||||||
|
- GameState speichert Rolle + Position
|
||||||
|
- Gate (Eingang): save_player → Dungeon laden
|
||||||
|
- Gate (Exit): returning_from_dungeon → Welt laden, Spieler bei Gate-Position
|
||||||
|
- Boss-Tod: dungeon_cleared → Welt laden, Cache geleert, Spieler bei Taverne mit vollen HP
|
||||||
|
|
||||||
## Workflow mit dem User
|
## Workflow mit dem User
|
||||||
- **plan.md ist zentral** — User will Änderungen zuerst in plan.md dokumentiert haben, dann implementieren
|
- **plan.md ist zentral** — User will Änderungen zuerst in plan.md dokumentiert haben, dann implementieren
|
||||||
|
|||||||
64
autoloads/boss_stats.gd
Normal file
64
autoloads/boss_stats.gd
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
var entities: Dictionary = {}
|
||||||
|
|
||||||
|
func register(entity: Node, base: EnemyStats) -> void:
|
||||||
|
entities[entity] = {
|
||||||
|
"base": base,
|
||||||
|
"health": base.max_health,
|
||||||
|
"max_health": base.max_health,
|
||||||
|
"health_regen": base.health_regen,
|
||||||
|
"shield": base.max_shield,
|
||||||
|
"max_shield": base.max_shield,
|
||||||
|
"shield_regen_delay": base.shield_regen_delay,
|
||||||
|
"shield_regen_time": base.shield_regen_time,
|
||||||
|
"shield_regen_timer": 0.0,
|
||||||
|
"alive": true,
|
||||||
|
"buff_damage": 1.0,
|
||||||
|
"buff_heal": 1.0,
|
||||||
|
"buff_shield": 1.0,
|
||||||
|
"state": 0,
|
||||||
|
"target": null,
|
||||||
|
"spawn_position": Vector3.ZERO,
|
||||||
|
"portal": null,
|
||||||
|
"attack_timer": 0.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
func deregister(entity: Node) -> void:
|
||||||
|
entities.erase(entity)
|
||||||
|
|
||||||
|
func get_stat(entity: Node, key: String) -> Variant:
|
||||||
|
if entity in entities:
|
||||||
|
return entities[entity].get(key)
|
||||||
|
return null
|
||||||
|
|
||||||
|
func set_stat(entity: Node, key: String, value: Variant) -> void:
|
||||||
|
if entity in entities:
|
||||||
|
entities[entity][key] = value
|
||||||
|
|
||||||
|
func get_base(entity: Node) -> EnemyStats:
|
||||||
|
if entity in entities:
|
||||||
|
return entities[entity]["base"]
|
||||||
|
return null
|
||||||
|
|
||||||
|
func is_alive(entity: Node) -> bool:
|
||||||
|
if entity in entities:
|
||||||
|
return entities[entity]["alive"]
|
||||||
|
return false
|
||||||
|
|
||||||
|
func set_health(entity: Node, value: float) -> void:
|
||||||
|
if entity not in entities:
|
||||||
|
return
|
||||||
|
entities[entity]["health"] = value
|
||||||
|
var max_health: float = entities[entity]["max_health"]
|
||||||
|
EventBus.health_changed.emit(entity, value, max_health)
|
||||||
|
if value <= 0 and entities[entity]["alive"]:
|
||||||
|
entities[entity]["alive"] = false
|
||||||
|
EventBus.entity_died.emit(entity)
|
||||||
|
|
||||||
|
func set_shield(entity: Node, value: float) -> void:
|
||||||
|
if entity not in entities:
|
||||||
|
return
|
||||||
|
entities[entity]["shield"] = value
|
||||||
|
var max_shield: float = entities[entity]["max_shield"]
|
||||||
|
EventBus.shield_changed.emit(entity, value, max_shield)
|
||||||
1
autoloads/boss_stats.gd.uid
Normal file
1
autoloads/boss_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dbr02t7pt4vcn
|
||||||
64
autoloads/enemy_stats.gd
Normal file
64
autoloads/enemy_stats.gd
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
var entities: Dictionary = {}
|
||||||
|
|
||||||
|
func register(entity: Node, base: EnemyStats) -> void:
|
||||||
|
entities[entity] = {
|
||||||
|
"base": base,
|
||||||
|
"health": base.max_health,
|
||||||
|
"max_health": base.max_health,
|
||||||
|
"health_regen": base.health_regen,
|
||||||
|
"shield": base.max_shield,
|
||||||
|
"max_shield": base.max_shield,
|
||||||
|
"shield_regen_delay": base.shield_regen_delay,
|
||||||
|
"shield_regen_time": base.shield_regen_time,
|
||||||
|
"shield_regen_timer": 0.0,
|
||||||
|
"alive": true,
|
||||||
|
"buff_damage": 1.0,
|
||||||
|
"buff_heal": 1.0,
|
||||||
|
"buff_shield": 1.0,
|
||||||
|
"state": 0,
|
||||||
|
"target": null,
|
||||||
|
"spawn_position": Vector3.ZERO,
|
||||||
|
"portal": null,
|
||||||
|
"attack_timer": 0.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
func deregister(entity: Node) -> void:
|
||||||
|
entities.erase(entity)
|
||||||
|
|
||||||
|
func get_stat(entity: Node, key: String) -> Variant:
|
||||||
|
if entity in entities:
|
||||||
|
return entities[entity].get(key)
|
||||||
|
return null
|
||||||
|
|
||||||
|
func set_stat(entity: Node, key: String, value: Variant) -> void:
|
||||||
|
if entity in entities:
|
||||||
|
entities[entity][key] = value
|
||||||
|
|
||||||
|
func get_base(entity: Node) -> EnemyStats:
|
||||||
|
if entity in entities:
|
||||||
|
return entities[entity]["base"]
|
||||||
|
return null
|
||||||
|
|
||||||
|
func is_alive(entity: Node) -> bool:
|
||||||
|
if entity in entities:
|
||||||
|
return entities[entity]["alive"]
|
||||||
|
return false
|
||||||
|
|
||||||
|
func set_health(entity: Node, value: float) -> void:
|
||||||
|
if entity not in entities:
|
||||||
|
return
|
||||||
|
entities[entity]["health"] = value
|
||||||
|
var max_health: float = entities[entity]["max_health"]
|
||||||
|
EventBus.health_changed.emit(entity, value, max_health)
|
||||||
|
if value <= 0 and entities[entity]["alive"]:
|
||||||
|
entities[entity]["alive"] = false
|
||||||
|
EventBus.entity_died.emit(entity)
|
||||||
|
|
||||||
|
func set_shield(entity: Node, value: float) -> void:
|
||||||
|
if entity not in entities:
|
||||||
|
return
|
||||||
|
entities[entity]["shield"] = value
|
||||||
|
var max_shield: float = entities[entity]["max_shield"]
|
||||||
|
EventBus.shield_changed.emit(entity, value, max_shield)
|
||||||
1
autoloads/enemy_stats.gd.uid
Normal file
1
autoloads/enemy_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bvxn6y15tvidu
|
||||||
52
autoloads/event_bus.gd
Normal file
52
autoloads/event_bus.gd
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
# Intentionen (Input → System)
|
||||||
|
signal ability_use(player, ability_index)
|
||||||
|
signal role_change_requested(player, role)
|
||||||
|
signal target_requested(player, target)
|
||||||
|
signal enemy_detected(enemy, player)
|
||||||
|
signal enemy_lost(enemy, player)
|
||||||
|
signal portal_entered(portal, player)
|
||||||
|
|
||||||
|
# Kampf
|
||||||
|
signal attack_executed(attacker, position, direction, damage)
|
||||||
|
signal damage_dealt(attacker, target, damage)
|
||||||
|
signal damage_requested(attacker, target, amount)
|
||||||
|
signal heal_requested(healer, target, amount)
|
||||||
|
|
||||||
|
# Entity
|
||||||
|
signal entity_died(entity)
|
||||||
|
signal health_changed(entity, current, max_val)
|
||||||
|
signal shield_changed(entity, current, max_val)
|
||||||
|
signal shield_broken(entity)
|
||||||
|
signal shield_regenerated(entity)
|
||||||
|
|
||||||
|
# Spieler
|
||||||
|
signal target_changed(player, target)
|
||||||
|
signal player_respawned(player)
|
||||||
|
signal role_changed(player, role_type)
|
||||||
|
signal respawn_tick(timer)
|
||||||
|
signal cooldown_tick(cooldowns, max_cooldowns, gcd_timer)
|
||||||
|
|
||||||
|
# Buff
|
||||||
|
signal buff_changed(entity, stat, value)
|
||||||
|
|
||||||
|
# Gegner
|
||||||
|
signal enemy_engaged(enemy, target)
|
||||||
|
|
||||||
|
# Portal
|
||||||
|
signal portal_spawn(portal, enemies)
|
||||||
|
signal portal_defeated(portal)
|
||||||
|
|
||||||
|
# Dungeon
|
||||||
|
signal dungeon_cleared()
|
||||||
|
|
||||||
|
# Effects
|
||||||
|
signal effect_requested(target, effect, source)
|
||||||
|
signal effect_applied(target, effect)
|
||||||
|
signal effect_expired(target, effect)
|
||||||
|
|
||||||
|
# Elements
|
||||||
|
signal element_damage_dealt(attacker, target, amount, element)
|
||||||
|
signal element_applied(target, element)
|
||||||
|
signal element_reaction(target, element_a, element_b, reaction_name)
|
||||||
145
autoloads/player_stats.gd
Normal file
145
autoloads/player_stats.gd
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
enum Role { TANK, DAMAGE, HEALER }
|
||||||
|
|
||||||
|
# Basis (aus Resource geladen)
|
||||||
|
var base: PlayerStats
|
||||||
|
var speed := 5.0
|
||||||
|
var jump_velocity := 4.5
|
||||||
|
var target_range := 20.0
|
||||||
|
var combat_timeout := 3.0
|
||||||
|
var respawn_time := 3.0
|
||||||
|
var gcd_time := 0.5
|
||||||
|
var aa_cooldown := 0.5
|
||||||
|
|
||||||
|
# Laufzeit
|
||||||
|
var health := 100.0
|
||||||
|
var max_health := 100.0
|
||||||
|
var health_regen := 0.0
|
||||||
|
var shield := 0.0
|
||||||
|
var max_shield := 0.0
|
||||||
|
var shield_regen_delay := 3.0
|
||||||
|
var shield_regen_time := 5.0
|
||||||
|
var shield_regen_timer := 0.0
|
||||||
|
var alive := true
|
||||||
|
|
||||||
|
# Buffs
|
||||||
|
var buff_damage := 1.0
|
||||||
|
var buff_heal := 1.0
|
||||||
|
var buff_shield := 1.0
|
||||||
|
|
||||||
|
# Rolle
|
||||||
|
var current_role: int = Role.DAMAGE
|
||||||
|
var ability_set: AbilitySet = null
|
||||||
|
|
||||||
|
# Kampf
|
||||||
|
var target: Node3D = null
|
||||||
|
var in_combat := false
|
||||||
|
var combat_timer := 0.0
|
||||||
|
|
||||||
|
# Cooldowns
|
||||||
|
var cooldowns: Array[float] = []
|
||||||
|
var max_cooldowns: Array[float] = []
|
||||||
|
var gcd := 0.0
|
||||||
|
var aa_timer := 0.0
|
||||||
|
|
||||||
|
# Szenenwechsel
|
||||||
|
var portal_position := Vector3.ZERO
|
||||||
|
var returning_from_dungeon := false
|
||||||
|
var dungeon_cleared := false
|
||||||
|
|
||||||
|
# Cache für Szenenwechsel
|
||||||
|
var _cache: Dictionary = {}
|
||||||
|
|
||||||
|
func init_from_resource(res: PlayerStats) -> void:
|
||||||
|
base = res
|
||||||
|
speed = res.speed
|
||||||
|
jump_velocity = res.jump_velocity
|
||||||
|
target_range = res.target_range
|
||||||
|
combat_timeout = res.combat_timeout
|
||||||
|
respawn_time = res.respawn_time
|
||||||
|
gcd_time = res.gcd_time
|
||||||
|
aa_cooldown = res.aa_cooldown
|
||||||
|
if _cache.is_empty():
|
||||||
|
health = res.max_health
|
||||||
|
max_health = res.max_health
|
||||||
|
health_regen = res.health_regen
|
||||||
|
shield = res.max_shield
|
||||||
|
max_shield = res.max_shield
|
||||||
|
shield_regen_delay = res.shield_regen_delay
|
||||||
|
shield_regen_time = res.shield_regen_time
|
||||||
|
shield_regen_timer = 0.0
|
||||||
|
alive = true
|
||||||
|
buff_damage = 1.0
|
||||||
|
buff_heal = 1.0
|
||||||
|
buff_shield = 1.0
|
||||||
|
else:
|
||||||
|
_restore_cache()
|
||||||
|
cooldowns.resize(5)
|
||||||
|
cooldowns.fill(0.0)
|
||||||
|
max_cooldowns.resize(5)
|
||||||
|
max_cooldowns.fill(0.0)
|
||||||
|
gcd = 0.0
|
||||||
|
aa_timer = 0.0
|
||||||
|
|
||||||
|
func set_health(value: float) -> void:
|
||||||
|
health = value
|
||||||
|
EventBus.health_changed.emit(self, health, max_health)
|
||||||
|
if health <= 0 and alive:
|
||||||
|
alive = false
|
||||||
|
EventBus.entity_died.emit(self)
|
||||||
|
|
||||||
|
func set_shield(value: float) -> void:
|
||||||
|
shield = value
|
||||||
|
EventBus.shield_changed.emit(self, shield, max_shield)
|
||||||
|
|
||||||
|
func set_role(role: int) -> void:
|
||||||
|
current_role = role
|
||||||
|
EventBus.role_changed.emit(self, current_role)
|
||||||
|
|
||||||
|
func set_target(new_target: Node3D) -> void:
|
||||||
|
target = new_target
|
||||||
|
EventBus.target_changed.emit(self, target)
|
||||||
|
|
||||||
|
func respawn() -> void:
|
||||||
|
health = max_health
|
||||||
|
shield = max_shield
|
||||||
|
alive = true
|
||||||
|
EventBus.health_changed.emit(self, health, max_health)
|
||||||
|
EventBus.shield_changed.emit(self, shield, max_shield)
|
||||||
|
EventBus.player_respawned.emit(self)
|
||||||
|
|
||||||
|
func save_cache() -> void:
|
||||||
|
_cache = {
|
||||||
|
"health": health,
|
||||||
|
"max_health": max_health,
|
||||||
|
"health_regen": health_regen,
|
||||||
|
"shield": shield,
|
||||||
|
"max_shield": max_shield,
|
||||||
|
"shield_regen_delay": shield_regen_delay,
|
||||||
|
"shield_regen_time": shield_regen_time,
|
||||||
|
"alive": alive,
|
||||||
|
"buff_damage": buff_damage,
|
||||||
|
"buff_heal": buff_heal,
|
||||||
|
"buff_shield": buff_shield,
|
||||||
|
}
|
||||||
|
|
||||||
|
func clear_cache() -> void:
|
||||||
|
_cache.clear()
|
||||||
|
portal_position = Vector3.ZERO
|
||||||
|
returning_from_dungeon = false
|
||||||
|
dungeon_cleared = false
|
||||||
|
|
||||||
|
func _restore_cache() -> void:
|
||||||
|
health = _cache.get("health", max_health)
|
||||||
|
max_health = _cache.get("max_health", max_health)
|
||||||
|
health_regen = _cache.get("health_regen", 0.0)
|
||||||
|
shield = _cache.get("shield", 0.0)
|
||||||
|
max_shield = _cache.get("max_shield", 0.0)
|
||||||
|
shield_regen_delay = _cache.get("shield_regen_delay", 3.0)
|
||||||
|
shield_regen_time = _cache.get("shield_regen_time", 5.0)
|
||||||
|
alive = _cache.get("alive", true)
|
||||||
|
buff_damage = _cache.get("buff_damage", 1.0)
|
||||||
|
buff_heal = _cache.get("buff_heal", 1.0)
|
||||||
|
buff_shield = _cache.get("buff_shield", 1.0)
|
||||||
|
_cache.clear()
|
||||||
1
autoloads/player_stats.gd.uid
Normal file
1
autoloads/player_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://blmuqkl3aro5w
|
||||||
45
autoloads/portal_stats.gd
Normal file
45
autoloads/portal_stats.gd
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
var entities: Dictionary = {}
|
||||||
|
|
||||||
|
func register(entity: Node, base: PortalStats) -> void:
|
||||||
|
var thresholds: Array[float] = base.thresholds.duplicate()
|
||||||
|
var triggered: Array[bool] = []
|
||||||
|
triggered.resize(thresholds.size())
|
||||||
|
triggered.fill(false)
|
||||||
|
entities[entity] = {
|
||||||
|
"base": base,
|
||||||
|
"health": base.max_health,
|
||||||
|
"max_health": base.max_health,
|
||||||
|
"alive": true,
|
||||||
|
"spawn_count": base.spawn_count,
|
||||||
|
"thresholds": thresholds,
|
||||||
|
"triggered": triggered,
|
||||||
|
}
|
||||||
|
|
||||||
|
func deregister(entity: Node) -> void:
|
||||||
|
entities.erase(entity)
|
||||||
|
|
||||||
|
func get_stat(entity: Node, key: String) -> Variant:
|
||||||
|
if entity in entities:
|
||||||
|
return entities[entity].get(key)
|
||||||
|
return null
|
||||||
|
|
||||||
|
func set_stat(entity: Node, key: String, value: Variant) -> void:
|
||||||
|
if entity in entities:
|
||||||
|
entities[entity][key] = value
|
||||||
|
|
||||||
|
func is_alive(entity: Node) -> bool:
|
||||||
|
if entity in entities:
|
||||||
|
return entities[entity]["alive"]
|
||||||
|
return false
|
||||||
|
|
||||||
|
func set_health(entity: Node, value: float) -> void:
|
||||||
|
if entity not in entities:
|
||||||
|
return
|
||||||
|
entities[entity]["health"] = value
|
||||||
|
var max_health: float = entities[entity]["max_health"]
|
||||||
|
EventBus.health_changed.emit(entity, value, max_health)
|
||||||
|
if value <= 0 and entities[entity]["alive"]:
|
||||||
|
entities[entity]["alive"] = false
|
||||||
|
EventBus.entity_died.emit(entity)
|
||||||
1
autoloads/portal_stats.gd.uid
Normal file
1
autoloads/portal_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://doullpjapcsk1
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
extends Resource
|
extends Resource
|
||||||
class_name EntityStats
|
class_name BaseStats
|
||||||
|
|
||||||
@export var max_health := 100.0
|
@export var max_health := 100.0
|
||||||
@export var health_regen := 0.0
|
@export var health_regen := 0.0
|
||||||
1
autoloads/stats/base_stats.gd.uid
Normal file
1
autoloads/stats/base_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cet184f878lb8
|
||||||
26
comminication.md
Normal file
26
comminication.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Player
|
||||||
|
- movement -> position_changed(player, vector) -> movement_system
|
||||||
|
- attack -> attack_used(player, target) -> attack_system
|
||||||
|
- ability -> ability_used(player, target) -> ability_system
|
||||||
|
- role -> changed_role(player, role) -> role_system
|
||||||
|
- targeting -> target_requested(player, target) -> targeting_system
|
||||||
|
- camera -> no events
|
||||||
|
|
||||||
|
# Enemy
|
||||||
|
- detection -> player_detected(enemy, player) -> aggro_system
|
||||||
|
- detection -> player_lost(enemy, player) -> aggro_system
|
||||||
|
|
||||||
|
# HUD
|
||||||
|
- on_health_changed(player, value) -> health
|
||||||
|
- on_shield_changed(player, value) -> shield
|
||||||
|
|
||||||
|
# Systems
|
||||||
|
- movement_system <- on_position_changed(player, vector)
|
||||||
|
- attack_system <- attack_used(player, target)
|
||||||
|
- ability_system <- ability_used(player, target)
|
||||||
|
- damage_calculator_system ->
|
||||||
|
- health_system -> damage_dealt(player, damage) -> PlayerStats()
|
||||||
|
|
||||||
|
# Autoloads
|
||||||
|
- PlayerStats -> - health -> on_health_changed(player, value)
|
||||||
|
- PlayerStats -> - health -> on_health_changed(player, value)
|
||||||
63
features.md
Normal file
63
features.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Player
|
||||||
|
- Movement - WASD laufen, Leertaste springen
|
||||||
|
- Bewegung korrigiert Camera in Laufrichtung
|
||||||
|
- Camera - 3rd Person, hinter Spieler, bewegung RMB/LMB + ziehen
|
||||||
|
- Beim ziehen der Kamera bewegt sich der zeiger nicht
|
||||||
|
- Targeting - Automatisch den nächsten Gegner bei Kampfbegin anvisieren, im Sichtradius priorisieren
|
||||||
|
- Targetwechsel per Tab oder Mausklick auf den Gegner
|
||||||
|
- Automatisches Target nur, wenn kein Target anvisiert wird
|
||||||
|
- Autoattack - Automatische angriffe im Kampf auf das Target
|
||||||
|
- Tank (1dmg + 1shield, 3m Range, GCD)
|
||||||
|
- Damage (2dmg, 20m Range, GCD)
|
||||||
|
- Heal (1 heal auf lowsten player, 20m Range, GCD)
|
||||||
|
- Role - Wechsel per ALT + 1 (Tank), + 2 (Damage), + 3 (Healer)
|
||||||
|
- Stats werden beim Wechsel nicht zurückgesetzt (HP, Schild, CDs, ...) sondern der größere genommen
|
||||||
|
- Ability - Wechsel per 1 (Single), 2 (AOE), 3 (Utility), 4 (Ult)
|
||||||
|
- Passive (P) ist eine Aura
|
||||||
|
- Single (2s CD), AOE (5s CD), Utitlty (15s CD), Ult (30s)
|
||||||
|
- Basis-Tank:
|
||||||
|
- Single (10dmg+10shield, 3m range)
|
||||||
|
- AOE (5dmg 10m radius, 5shield for each enemy hitted, 3m range)
|
||||||
|
- Utility (no function)
|
||||||
|
- Ult (no function)
|
||||||
|
- Passive (+50% Shield, 50m range)
|
||||||
|
- Basis-Damage:
|
||||||
|
- Single (20dmg, 20m range)
|
||||||
|
- AOE (10dmg 5m radius, 20m range)
|
||||||
|
- Utility (no function)
|
||||||
|
- Ult (no function)
|
||||||
|
- Passive (+50% damage, 50m range)
|
||||||
|
- Basis-Heal:
|
||||||
|
- Single (20heal, 20m range)
|
||||||
|
- AOE (10heal 20m radius, 20m range)
|
||||||
|
- Utility (no function)
|
||||||
|
- Ult (no function)
|
||||||
|
- Passive (+50% heal, 50m range)
|
||||||
|
- Basisstats - Health (100), Shield (100)
|
||||||
|
- Death - Respawn after 3s at tarvern with full hp/shield
|
||||||
|
- Shield - Used before Health, 10%/s regen after 5s no dmg get
|
||||||
|
- Aggro - Tank 4x
|
||||||
|
- Gegner Aggro verteilt sich zu 0.5x an Gegner im 10m range
|
||||||
|
- Nach 5s ohne schaden bekommen oder verursachen endet der kampf
|
||||||
|
- Gegner Aggro sinkt um 2^(s) für jede Sekunden aus dem Kampf
|
||||||
|
# Enemy
|
||||||
|
- KI-States:
|
||||||
|
- Chase - Gegner hat Aggro und läuft auf den Spieler
|
||||||
|
- Attack - Gegner greift Spieler an
|
||||||
|
- Return - Gegner hat Aggro verloren, zurück zum Spawn mit 10%/s HP reg, kein aggrozuwachs bis beim spawn
|
||||||
|
# Protal
|
||||||
|
- Spawn:
|
||||||
|
# Dungeon
|
||||||
|
-
|
||||||
|
# HUD
|
||||||
|
- Health - Links-oben, grün, hp/max text
|
||||||
|
- Shield - Links-oben unter Health, blau, shield/max text
|
||||||
|
- Health und Shield schwarzer Rand zusammen
|
||||||
|
- Abilities - Unten Zentral, 4 Rechteck mit 1-4 text, 1 Kreis mit P text
|
||||||
|
- Role - Unten zentral links von Abilites, Kreis mit T/S/H
|
||||||
|
- Role und Abilites schwarzer Rand
|
||||||
|
- Role bestimmen Hintergrund von Abilites / Role, T - blau, S - Rot, H - Grün
|
||||||
|
- Aura, Buff, Debuff - rechts oben, in der Reihenfolge, schwarzer Rand
|
||||||
|
- Hintergrund Aura - blau, Buff - Gründ, Debuff - Rot
|
||||||
|
# Welt
|
||||||
|
- Globaler Cooldown 0.5s
|
||||||
189
flow.md
Normal file
189
flow.md
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
# Datenflows
|
||||||
|
|
||||||
|
## 1. Kampf-Flow (Schaden)
|
||||||
|
|
||||||
|
```
|
||||||
|
Spieler drückt 1-4 Gegner im ATTACK-State
|
||||||
|
│ │
|
||||||
|
combat.gd enemy_ai_system.gd
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
ability_use_requested ──► AbilitySystem ◄── _process (Auto-Attack)
|
||||||
|
│
|
||||||
|
damage_requested(attacker, target, amount)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
HealthSystem
|
||||||
|
├── ShieldSystem.absorb() → Schild reduzieren
|
||||||
|
│ └── shield_changed / shield_broken
|
||||||
|
├── damage_dealt ──► AggroSystem (Aggro aufbauen)
|
||||||
|
├── health_changed ──► Healthbar, HUD, SpawnSystem
|
||||||
|
└── falls HP=0: entity_died
|
||||||
|
│
|
||||||
|
┌───────────┼───────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
AggroSystem RespawnSystem SpawnSystem
|
||||||
|
(Tabelle (Spieler: (Portal:
|
||||||
|
aufräumen) 3s → Taverne) Daten löschen)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Kampf-Flow (Heilung)
|
||||||
|
|
||||||
|
```
|
||||||
|
Heiler drückt 1/2/4 (is_heal) oder Auto-Attack (aa_is_heal)
|
||||||
|
│
|
||||||
|
AbilitySystem
|
||||||
|
│
|
||||||
|
heal_requested(healer, target, amount)
|
||||||
|
│
|
||||||
|
├──► HealthSystem → HP erhöhen → health_changed → HUD
|
||||||
|
└──► AggroSystem → 0.5x Aggro auf ALLE Gegner die Heiler kennen
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Aggro-Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
damage_dealt ──► AggroSystem._on_damage_dealt()
|
||||||
|
│ Tank-Rolle → 2x Multiplikator
|
||||||
|
▼
|
||||||
|
aggro_tables[enemy][player] += amount
|
||||||
|
|
||||||
|
heal_requested ──► AggroSystem._on_heal_requested()
|
||||||
|
│ 0.5x auf ALLE Gegner mit Heiler in Tabelle
|
||||||
|
▼
|
||||||
|
aggro_tables[enemy][healer] += amount * 0.5
|
||||||
|
|
||||||
|
enemy_detected ──► AggroSystem._on_enemy_detected()
|
||||||
|
│ +1 Aggro, State → CHASE
|
||||||
|
└──► _alert_nearby() → Nachbarn im alert_radius auch aktivieren
|
||||||
|
|
||||||
|
_process(delta):
|
||||||
|
├── Aggro decay: -aggro_decay/s
|
||||||
|
├── Außerhalb Portal-Radius: exponentieller Decay (+1%·2^sekunden)
|
||||||
|
├── Innerhalb Portal-Radius: Minimum 1.0 Aggro
|
||||||
|
└── _update_target(): Höchster Aggro-Wert → enemy.target + State
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Portal → Dungeon Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Portal wird angegriffen
|
||||||
|
│
|
||||||
|
health_changed ──► SpawnSystem
|
||||||
|
│ └── HP-Schwellen (85/70/55/40/25/10%) → Gegner spawnen
|
||||||
|
│ └── portal_spawn Signal
|
||||||
|
▼
|
||||||
|
Portal HP = 0 → entity_died
|
||||||
|
│
|
||||||
|
portal.gd: _on_entity_died()
|
||||||
|
├── Mesh → grün (Gate-Look)
|
||||||
|
├── Gegner mit Portal löschen
|
||||||
|
└── Gate instanzieren an Portal-Position
|
||||||
|
└── portal_defeated Signal
|
||||||
|
|
||||||
|
Spieler betritt Gate
|
||||||
|
│
|
||||||
|
gate.gd: _on_gate_area_body_entered()
|
||||||
|
├── GameState.save_player() → Rolle speichern
|
||||||
|
├── Stats.deregister() → player_cache befüllen
|
||||||
|
└── change_scene → dungeon.tscn
|
||||||
|
│
|
||||||
|
dungeon_manager.gd _ready()
|
||||||
|
├── Stats.register() → player_cache wiederherstellen
|
||||||
|
└── GameState.restore_player() → Rolle setzen
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Dungeon → Welt Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Boss stirbt → entity_died
|
||||||
|
│
|
||||||
|
dungeon_manager.gd: _on_entity_died()
|
||||||
|
├── 2s warten
|
||||||
|
├── GameState.dungeon_cleared = true
|
||||||
|
├── GameState.clear() → Stats.clear_player_cache()
|
||||||
|
├── dungeon_cleared Signal
|
||||||
|
└── change_scene → world.tscn
|
||||||
|
│
|
||||||
|
Spieler spawnt bei Taverne (0, 1, -5)
|
||||||
|
volle HP + Schild (durch clear_player_cache)
|
||||||
|
|
||||||
|
Exit-Gate (zurück zur Welt ohne Boss-Kill):
|
||||||
|
├── GameState.returning_from_dungeon = true
|
||||||
|
└── change_scene → world.tscn
|
||||||
|
└── Spieler bei portal_position, Stats aus Cache
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Rollen-Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Spieler drückt ALT+1/2/3
|
||||||
|
│
|
||||||
|
role.gd: set_role()
|
||||||
|
│
|
||||||
|
role_changed(player, role_type)
|
||||||
|
│
|
||||||
|
├──► BuffSystem: Passive-Buffs berechnen
|
||||||
|
│ ├── buff_damage/buff_heal/buff_shield in Stats setzen
|
||||||
|
│ ├── max_shield anpassen (Tank-Passive)
|
||||||
|
│ └── buff_changed + shield_changed Signals
|
||||||
|
│
|
||||||
|
└──► CooldownSystem: Alle Cooldowns zurücksetzen
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Cooldown-Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
AbilitySystem führt Ability aus
|
||||||
|
│
|
||||||
|
CooldownSystem.set_cooldown(player, index, cd, gcd)
|
||||||
|
│
|
||||||
|
_process(delta): Alle Timer runterzählen
|
||||||
|
│
|
||||||
|
cooldown_tick(cds, max_cds, gcd_timer) ──► HUD (Ability-Icons updaten)
|
||||||
|
│
|
||||||
|
AbilitySystem fragt vor Ausführung:
|
||||||
|
├── is_ready(player, index) — Ability-CD abgelaufen?
|
||||||
|
├── is_gcd_ready(player) — GCD abgelaufen?
|
||||||
|
└── is_aa_ready(player) — Auto-Attack bereit?
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Respawn-Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
entity_died (Spieler)
|
||||||
|
│
|
||||||
|
RespawnSystem._on_entity_died()
|
||||||
|
├── Mesh/Collision/Input deaktivieren
|
||||||
|
└── dead_players[player] = 3.0s
|
||||||
|
|
||||||
|
_process(delta):
|
||||||
|
├── Timer runterzählen
|
||||||
|
├── respawn_tick → HUD (Countdown anzeigen)
|
||||||
|
└── Timer ≤ 0: _respawn()
|
||||||
|
├── Position → Taverne (0, 1, -5)
|
||||||
|
├── HP/Schild auf Max
|
||||||
|
├── Mesh/Collision/Input reaktivieren
|
||||||
|
├── health_changed + shield_changed
|
||||||
|
└── player_respawned
|
||||||
|
```
|
||||||
|
|
||||||
|
## Signalübersicht
|
||||||
|
|
||||||
|
| Signal | Sender | Empfänger |
|
||||||
|
|---|---|---|
|
||||||
|
| `ability_use_requested` | Combat | AbilitySystem |
|
||||||
|
| `damage_requested` | AbilitySystem, EnemyAI | HealthSystem |
|
||||||
|
| `heal_requested` | AbilitySystem | HealthSystem, AggroSystem |
|
||||||
|
| `damage_dealt` | HealthSystem | AggroSystem, Targeting |
|
||||||
|
| `health_changed` | HealthSystem, RespawnSystem | SpawnSystem, Healthbar, HUD |
|
||||||
|
| `shield_changed` | ShieldSystem, AbilitySystem, BuffSystem, RespawnSystem | Healthbar, HUD |
|
||||||
|
| `entity_died` | HealthSystem | AggroSystem, RespawnSystem, SpawnSystem, Targeting, DungeonManager, Portal |
|
||||||
|
| `role_changed` | Role | BuffSystem, CooldownSystem |
|
||||||
|
| `enemy_detected` | Enemy (DetectionArea) | AggroSystem |
|
||||||
|
| `enemy_engaged` | AggroSystem | Targeting |
|
||||||
|
| `cooldown_tick` | CooldownSystem | HUD |
|
||||||
|
| `respawn_tick` | RespawnSystem | HUD |
|
||||||
|
| `dungeon_cleared` | DungeonManager | Gate |
|
||||||
|
| `portal_defeated` | Portal | — |
|
||||||
|
| `player_respawned` | RespawnSystem | — |
|
||||||
424
plan.md
424
plan.md
@@ -1,154 +1,332 @@
|
|||||||
# Projektstruktur
|
# Projektstruktur
|
||||||
|
|
||||||
|
## Ordnerstruktur
|
||||||
|
Drei Grundordner nach Verantwortung. Resources liegen bei ihren Skripten.
|
||||||
|
```
|
||||||
|
scenes/ — Darstellung + Input
|
||||||
|
effect_icon_factory.gd — Shared Utility (statisch, Effekt-Icon-Erstellung)
|
||||||
|
healthbar.gd — Health-Anzeige + Viewport-Setup
|
||||||
|
healthbar_shield.gd — Shield-Anzeige
|
||||||
|
healthbar_status.gd — Target-Border + Aggro-Farbwechsel
|
||||||
|
healthbar_effects.gd — Effekt-Icons auf Healthbar
|
||||||
|
player/ — Spieler + player_stats
|
||||||
|
role/ — Rollenwechsel + Ability/AbilitySet-Klassen
|
||||||
|
damage/ — set.tres + abilities/
|
||||||
|
tank/ — set.tres + abilities/
|
||||||
|
healer/ — set.tres + abilities/
|
||||||
|
enemy/ — Gegner + enemy_stats
|
||||||
|
boss/ — Boss + boss_stats
|
||||||
|
portal/ — Portal + Gate + portal_stats
|
||||||
|
dungeon/ — Dungeon + dungeon_manager
|
||||||
|
hud/ — HUD (4 Skripte: vitals, respawn, abilities, effects)
|
||||||
|
world/ — Hauptszene + portal_spawner
|
||||||
|
systems/ — Spiellogik
|
||||||
|
aggro/ — AggroSystem (system, tracker, decay, events) + aggro_config
|
||||||
|
effect.gd — Effect Resource (Buff/Debuff/Aura Daten)
|
||||||
|
11× *_system.gd — health, shield, ability, auto_attack, cooldown, enemy_ai, respawn, spawn, effect, element, buff_calc
|
||||||
|
aura_system.gd — Aura-Propagierung (Child von EffectSystem)
|
||||||
|
autoloads/ — Globaler Zustand
|
||||||
|
event_bus.gd
|
||||||
|
game_state.gd
|
||||||
|
stats/ — stats.gd + base_stats.gd
|
||||||
|
```
|
||||||
|
|
||||||
## Szenenbaum
|
## Szenenbaum
|
||||||
- Welt
|
- Welt
|
||||||
|
- Systems (12 Systeme als Child-Nodes)
|
||||||
|
- Taverne
|
||||||
- Player
|
- Player
|
||||||
- Portal
|
- Portale (dynamisch)
|
||||||
- Gegner
|
- Gegner
|
||||||
- HUD
|
- HUD
|
||||||
|
|
||||||
## Architektur
|
## Architektur
|
||||||
- Zwischen Szenen: Kommunikation über EventBus (Szenen kennen sich nicht)
|
- Resources (stats/, roles/) — Datenmodelle + Basiswerte, werden später zu Go-Structs + DB
|
||||||
- Innerhalb einer Szene: Modulare Skripte, Zugriff auf Geschwister-Nodes erlaubt
|
- Autoload (Stats) — Laufzeit-Datenhaltung, wird später zu Server-API-Client
|
||||||
|
- Systeme — Spiellogik, lesen/schreiben über Stats
|
||||||
|
- Szenen — Views, rendern + Input, kein Gameplay-State
|
||||||
|
- EventBus — Signale zwischen Szenen und Systemen
|
||||||
|
- Flow: Szene → Input → EventBus → System → Stats → EventBus → Szene
|
||||||
|
- Szenen kennen sich nicht
|
||||||
|
- Innerhalb einer Szene: direkter Zugriff auf Geschwister erlaubt
|
||||||
|
|
||||||
## Szenen
|
# Autoload
|
||||||
- world.tscn — Hauptszene
|
|
||||||
- NavigationRegion3D (Wegfindung für Gegner)
|
## EventBus (autoload/event_bus.gd)
|
||||||
- Boden (MeshInstance3D, 20x20m PlaneMesh, Gras-Noise-Textur)
|
- Intentionen (Input → System):
|
||||||
|
- ability_use_requested(player, ability_index)
|
||||||
|
- enemy_detected(enemy, player)
|
||||||
|
- Kampf:
|
||||||
|
- attack_executed(attacker, position, direction, damage)
|
||||||
|
- damage_dealt(attacker, target, damage)
|
||||||
|
- damage_requested(attacker, target, amount)
|
||||||
|
- heal_requested(healer, target, amount)
|
||||||
|
- Entity:
|
||||||
|
- entity_died(entity)
|
||||||
|
- health_changed(entity, current, max)
|
||||||
|
- shield_changed(entity, current, max)
|
||||||
|
- shield_broken(entity)
|
||||||
|
- shield_regenerated(entity)
|
||||||
|
- Spieler:
|
||||||
|
- target_changed(player, target)
|
||||||
|
- player_respawned(player)
|
||||||
|
- role_changed(player, role_type)
|
||||||
|
- respawn_tick(timer)
|
||||||
|
- cooldown_tick(cooldowns, max_cooldowns, gcd_timer)
|
||||||
|
- Buff:
|
||||||
|
- buff_changed(entity, stat, value)
|
||||||
|
- Gegner:
|
||||||
|
- enemy_engaged(enemy, target)
|
||||||
|
- Portal:
|
||||||
|
- portal_spawn(portal, enemies)
|
||||||
|
- portal_defeated(portal)
|
||||||
|
- Dungeon:
|
||||||
|
- dungeon_cleared()
|
||||||
|
- Effects:
|
||||||
|
- effect_requested(target, effect, source)
|
||||||
|
- effect_applied(target, effect)
|
||||||
|
- effect_expired(target, effect)
|
||||||
|
- Elements:
|
||||||
|
- element_damage_dealt(attacker, target, amount, element)
|
||||||
|
- element_applied(target, element)
|
||||||
|
- element_reaction(target, element_a, element_b, reaction_name)
|
||||||
|
|
||||||
|
## Stats (autoload/stats.gd)
|
||||||
|
- Speichert aktuelle Attribute aller Entities
|
||||||
|
- Basiswerte aus BaseStats Resource
|
||||||
|
- player_cache für Szenenwechsel
|
||||||
|
|
||||||
|
## GameState (autoload/game_state.gd)
|
||||||
|
- Speichert Rolle + Position zwischen Szenenwechseln
|
||||||
|
|
||||||
|
# Resources
|
||||||
|
|
||||||
|
## resources/stats/
|
||||||
|
### BaseStats (base_stats.gd)
|
||||||
|
- max_health, health_regen, max_shield, shield_regen_delay, shield_regen_time
|
||||||
|
### PlayerStats (player_stats.gd, extends BaseStats)
|
||||||
|
- speed, jump_velocity, target_range, combat_timeout, respawn_time, gcd_time, aa_cooldown
|
||||||
|
### EnemyStats (enemy_stats.gd, extends BaseStats)
|
||||||
|
- speed, attack_range, attack_cooldown, attack_damage, regen_fast, regen_slow, aggro_decay, portal_radius, alert_radius
|
||||||
|
### BossStats (boss_stats.gd, extends EnemyStats)
|
||||||
|
### PortalStats (portal_stats.gd, extends BaseStats)
|
||||||
|
- spawn_count, thresholds
|
||||||
|
|
||||||
|
## resources/roles/
|
||||||
|
### Ability (ability.gd)
|
||||||
|
- ability_name, type, damage, ability_range, cooldown, uses_gcd, aoe_radius, icon, is_heal, passive_stat, element
|
||||||
|
- Typen: Single, AOE, Utility, Ult, Passive
|
||||||
|
### AbilitySet (ability_set.gd)
|
||||||
|
- abilities (Array[Ability]), aa_damage, aa_range, aa_is_heal
|
||||||
|
### AbilityModifier (geplant, Zukunft)
|
||||||
|
- Verändert Ability (Beruf, Prestige)
|
||||||
|
### Rollen-Ordner (damage/, tank/, healer/)
|
||||||
|
- set.tres — AbilitySet der Rolle
|
||||||
|
- abilities/ — 5 Ability .tres pro Rolle (single, aoe, utility, ult, passive)
|
||||||
|
|
||||||
|
# Systeme (systems/)
|
||||||
|
- In jeder Root-Szene instanziert (world.tscn, dungeon.tscn)
|
||||||
|
- Entities registrieren/deregistrieren sich bei Stats
|
||||||
|
- Systeme lesen/schreiben über Stats
|
||||||
|
|
||||||
|
### HealthSystem (health_system.gd)
|
||||||
|
- Leben und Lebensregeneration berechnen, Tod bei 0 HP
|
||||||
|
- Listener: damage_requested, heal_requested
|
||||||
|
- Event: health_changed, entity_died
|
||||||
|
### ShieldSystem (shield_system.gd)
|
||||||
|
- Schild und Schildregeneration berechnen
|
||||||
|
- absorb(entity, amount) → remaining damage
|
||||||
|
- Event: shield_changed, shield_broken, shield_regenerated
|
||||||
|
### RespawnSystem (respawn_system.gd)
|
||||||
|
- Respawn bei Taverne mit vollen Leben und Schild
|
||||||
|
- Listener: entity_died
|
||||||
|
- Event: respawn_tick, player_respawned
|
||||||
|
### AbilitySystem (ability_system.gd)
|
||||||
|
- Ability-Ausführung (Single, AOE, Utility, Ult)
|
||||||
|
- Listener: ability_use_requested
|
||||||
|
- Event: attack_executed, damage_requested, heal_requested
|
||||||
|
### AutoAttackSystem (auto_attack_system.gd)
|
||||||
|
- Auto-Attack-Logik, läuft jeden Frame in _process
|
||||||
|
- Liest CombatState.in_combat + Targeting.current_target
|
||||||
|
- Event: damage_requested, heal_requested
|
||||||
|
### EffectSystem (effect_system.gd)
|
||||||
|
- Verwaltet Buffs, Debuffs und Auras auf Entities
|
||||||
|
- Effect Resource (effect.gd): effect_name, type (BUFF/DEBUFF/AURA), stat, value, duration, is_multiplier, aura_radius, tick_interval, element
|
||||||
|
- State: active_effects Dictionary[Node, Array[Dict]] (effect, source, remaining, tick_timer)
|
||||||
|
- Kein Stacking: gleicher effect_name auf Entity → wird refreshed statt gestackt
|
||||||
|
- Passive-Abilities werden als AURA-Effekte erstellt (role_changed → permanente Auras)
|
||||||
|
- _process: Dauer ticken, abgelaufene entfernen, DoT/HoT-Ticks
|
||||||
|
- Listener: role_changed, entity_died, effect_requested
|
||||||
|
- Event: effect_applied, effect_expired
|
||||||
|
- Children:
|
||||||
|
- AuraSystem (aura_system.gd) — Aura-Propagierung im Radius, Buff-Refresh
|
||||||
|
- BuffCalcSystem (buff_calc_system.gd) — Multiplier aggregieren → Stats + Shield updaten
|
||||||
|
- Event: buff_changed, shield_changed
|
||||||
|
### ElementSystem (element_system.gd)
|
||||||
|
- Verwaltet Element-Zustände auf Entities und löst Elementareffekte aus
|
||||||
|
- Element Enum: NONE, FIRE (erweiterbar)
|
||||||
|
- State: applied_elements Dictionary[Node, int]
|
||||||
|
- Nur Entities in Gruppen "enemies" oder "portals" erhalten Elemente
|
||||||
|
- Bei Element-Schaden: Element auf Ziel anwenden, passenden Effekt erstellen
|
||||||
|
- Feuer: DoT (3 Schaden/Tick, 2s Interval, 6s Dauer)
|
||||||
|
- Bei zwei verschiedenen Elementen: Reaktion auslösen (Zukunft)
|
||||||
|
- Listener: element_damage_dealt, entity_died, effect_expired
|
||||||
|
- Event: element_applied, element_reaction
|
||||||
|
### CooldownSystem (cooldown_system.gd)
|
||||||
|
- Cooldown-Tracking, GCD, AA-Timer per Entity
|
||||||
|
- register/deregister per Entity, direkte Funktionsaufrufe vom AbilitySystem
|
||||||
|
- Event: cooldown_tick
|
||||||
|
### AggroSystem (systems/aggro/)
|
||||||
|
- Systemweite Werte in AggroConfig Resource (resources/stats/aggro_config.tres)
|
||||||
|
- aggro_system.gd — Parent, Config halten, Children verdrahten
|
||||||
|
- aggro_tracker.gd — Aggro-Tabellen, Players-in-Range, Zielwahl, Radius-Helper
|
||||||
|
- aggro_decay.gd — Combat-Timer, Decay-Berechnung, Spread, Alert
|
||||||
|
- aggro_events.gd — Signal-Handler (damage_dealt, heal_requested, entity_died, enemy_detected, enemy_lost)
|
||||||
|
- Event: enemy_engaged
|
||||||
|
### EnemyAISystem (enemy_ai_system.gd)
|
||||||
|
- ATTACK-State: Range-Check, Timer, Schaden
|
||||||
|
- Iteriert Enemies in _physics_process
|
||||||
|
- Event: damage_requested
|
||||||
|
### SpawnSystem (spawn_system.gd)
|
||||||
|
- Portal-HP-Schwellen-Spawning
|
||||||
|
- Listener: health_changed, entity_died
|
||||||
|
- Event: portal_spawn
|
||||||
|
|
||||||
|
# Szenen
|
||||||
|
- Szenen sind Views — rendern, Input senden, Events empfangen
|
||||||
|
- Kein Gameplay-State in Szenen (liegt in Stats Autoload)
|
||||||
|
- Entities registrieren sich bei Stats in _ready(), deregistrieren in _exit_tree()
|
||||||
|
- Stats cached Spieler-Werte automatisch bei Szenenwechsel (player_cache)
|
||||||
|
|
||||||
|
## Welt (world/)
|
||||||
|
- world.tscn — Hauptszene (100x100m)
|
||||||
|
- Systems (alle 11 Systeme als Child-Nodes)
|
||||||
|
- NavigationRegion3D
|
||||||
|
- Boden (MeshInstance3D, 100x100m PlaneMesh)
|
||||||
- Kollision (StaticBody3D, WorldBoundaryShape3D)
|
- Kollision (StaticBody3D, WorldBoundaryShape3D)
|
||||||
- Licht (DirectionalLight3D, 45°, Schatten)
|
- Licht (DirectionalLight3D, 45°, Schatten)
|
||||||
|
- Taverne (StaticBody3D, BoxMesh, Mitte der Karte)
|
||||||
- Spieler (Instanz von player.tscn)
|
- Spieler (Instanz von player.tscn)
|
||||||
- Portal (Instanz von portal.tscn)
|
|
||||||
- HUD (Instanz von hud.tscn)
|
- HUD (Instanz von hud.tscn)
|
||||||
|
- PortalSpawner (Node, portal_spawner.gd)
|
||||||
|
|
||||||
- player.tscn — Spieler
|
## Spieler (player/)
|
||||||
|
- player.tscn — CharacterBody3D
|
||||||
- Gruppe (player)
|
- Gruppe (player)
|
||||||
- CharacterBody3D (player.gd)
|
|
||||||
- Kollision (CapsuleShape3D, 1.8m x 0.3m)
|
- Kollision (CapsuleShape3D, 1.8m x 0.3m)
|
||||||
- Mesh (CapsuleMesh)
|
- Mesh (CapsuleMesh)
|
||||||
- CameraPivot (Node3D, Kopfhöhe, camera.gd)
|
- CameraPivot (Node3D, camera.gd)
|
||||||
- Camera3D (hinter/über Spieler)
|
- Camera3D
|
||||||
- Movement (Node, movement.gd)
|
- Movement (Node, movement.gd) — WASD + Springen, liest Werte von Stats
|
||||||
- Combat (Node, combat.gd)
|
- Combat (Node, combat.gd) — Input-Handler, emittiert ability_use_requested
|
||||||
- Role (Node, role.gd)
|
- CombatState (Node, combat_state.gd) — in_combat-Tracking, Combat-Timer
|
||||||
- Targeting (Node, targeting.gd)
|
- Role (Node, role.gd) — Rollenwechsel ALT+1/2/3, emittiert role_changed (auch bei _ready)
|
||||||
- Health (Node, health.gd)
|
- Targeting (Node, targeting.gd) — Klick/TAB/Auto-Target, emittiert target_changed
|
||||||
- Shield (Node, shield.gd)
|
- player.gd — Registriert bei Stats mit PlayerStats Resource, Sichtbarkeit bei Tod/Respawn
|
||||||
- Respawn (Node, respawn.gd)
|
- camera.gd — LMB freies Umsehen, RMB Kamera + Laufrichtung
|
||||||
|
|
||||||
- hud.tscn — HUD (eigene Szene, kommuniziert nur über Events)
|
## Gegner (enemy/)
|
||||||
- CanvasLayer (hud.gd)
|
- enemy.tscn — CharacterBody3D
|
||||||
- HealthBar (ProgressBar, links oben)
|
- Gruppe (enemies)
|
||||||
- ShieldBar (ProgressBar, links oben, unter HealthBar)
|
- Kollision (CapsuleShape3D)
|
||||||
- RespawnTimer (Label, mitte, Countdown bei Tod)
|
- Mesh (SphereMesh)
|
||||||
- AbilityBar (HBoxContainer, mitte unten)
|
- HitArea (Area3D)
|
||||||
- RoleIcon (Label, T=Tank, D=Schaden, H=Heiler)
|
- DetectionArea (Area3D, emittiert enemy_detected)
|
||||||
- Abilities(Label, Fähigkeiten 1-4, Passive P)
|
- NavigationAgent3D
|
||||||
|
- EnemyMovement (Node, enemy_movement.gd) — Empfängt Bewegungsbefehle
|
||||||
|
- Healthbar (Sprite3D + SubViewport, healthbar.gd) — Health-Anzeige
|
||||||
|
- HealthbarShield (Node, healthbar_shield.gd) — Shield-Anzeige
|
||||||
|
- HealthbarStatus (Node, healthbar_status.gd) — Target-Border + Aggro-Farbe
|
||||||
|
- HealthbarEffects (Node, healthbar_effects.gd) — Effekt-Icons
|
||||||
|
- enemy.gd — Registriert bei Stats mit EnemyStats Resource, Detection-Area Signal
|
||||||
|
- Aggro-Regeln (Werte in AggroConfig Resource):
|
||||||
|
- Aufbau:
|
||||||
|
- Schaden = Aggro (1:1), Tank 2x Multiplikator
|
||||||
|
- Heilung = 0.5x Aggro auf alle Gegner die Heiler kennen
|
||||||
|
- Aggro-Spread: 50% des Aggro an Gegner im alert_radius (10m)
|
||||||
|
- Detection-Area (10m): +1 Aggro, Alert an Nachbarn im alert_radius
|
||||||
|
- Kampfstatus:
|
||||||
|
- Spieler in DetectionArea → immer im Kampf (kein Decay)
|
||||||
|
- Spieler verlässt DetectionArea → 5s Combat-Timeout, dann Decay
|
||||||
|
- Schaden verursachen setzt Combat-Timer zurück
|
||||||
|
- Abbau (nach Combat-Timeout):
|
||||||
|
- Basis: -aggro_decay/s (default 1.0)
|
||||||
|
- Exponentieller Decay basierend auf Zeit seit Kampfende (1%·2^sekunden)
|
||||||
|
- Ohne Aggro: Gegner kehrt zum Portal zurück, regeneriert
|
||||||
|
- Bei Spieler-Tod → Aggro auf 0
|
||||||
|
|
||||||
- portal.tscn — Portal (Gegner-Spawner, anvisierbar)
|
## Boss (enemy/)
|
||||||
|
- boss.tscn — wie enemy.tscn aber größer (Mesh lila, 1.5x)
|
||||||
|
- Gruppe (enemies, boss)
|
||||||
|
- boss.gd — Erbt von enemy.gd, registriert bei Stats mit BossStats Resource
|
||||||
|
|
||||||
|
## Portal (portal/)
|
||||||
|
- portal.tscn — StaticBody3D
|
||||||
- Gruppe (portals)
|
- Gruppe (portals)
|
||||||
- StaticBody3D (portal.gd)
|
|
||||||
- Kollision (CylinderShape3D)
|
- Kollision (CylinderShape3D)
|
||||||
- Mesh (CylinderMesh, blau)
|
- Mesh (CylinderMesh, blau)
|
||||||
- Health (Node, health.gd)
|
- HitArea (Area3D)
|
||||||
- HitArea (Area3D, Trefferbereich)
|
- DetectionArea (Area3D, Auto-Targeting bei Betreten)
|
||||||
- CollisionShape3D
|
|
||||||
- Spawner (Node, spawner.gd)
|
|
||||||
- DetectionArea (Area3D, 10m Radius, Auto-Targeting bei Betreten)
|
|
||||||
- CollisionShape3D (SphereShape3D)
|
|
||||||
- Healthbar (Sprite3D + SubViewport, healthbar.gd)
|
- Healthbar (Sprite3D + SubViewport, healthbar.gd)
|
||||||
- Phase 1: Angreifbar, HP-Bar, spawnt 3 Gegner bei 85%/70%/55%/40%/25%/10% Leben
|
- HealthbarShield (Node, healthbar_shield.gd)
|
||||||
- Portal = Spawnpunkt, 10m Aggro-Radius
|
- HealthbarStatus (Node, healthbar_status.gd)
|
||||||
- Phase 2 (geplant): Bei 0 HP → Portal wird Gate, teleportiert in Dungeon
|
- HealthbarEffects (Node, healthbar_effects.gd)
|
||||||
- Abgesperrter Bereich mit Gegner-Gruppen und Boss
|
- portal.gd — Registriert bei Stats mit PortalStats Resource
|
||||||
|
- Spawnt Gegner bei HP-Schwellen (→ SpawnSystem)
|
||||||
|
|
||||||
- enemy.tscn — Gegner
|
## Gate (portal/)
|
||||||
- Gruppe (enemies)
|
- gate.tscn — StaticBody3D (keine Kollision)
|
||||||
- CharacterBody3D (enemy.gd)
|
- Mesh (CylinderMesh, grün, leuchtend)
|
||||||
- Kollision (CapsuleShape3D)
|
- GateArea (Area3D, 3m Radius)
|
||||||
- Mesh (SphereMesh, Platzhalter)
|
- gate.gd — Konfigurierbar: target_scene, is_exit
|
||||||
- Health (Node, health.gd)
|
- Beide Gates bestehen solange Boss lebt, verschwinden bei dungeon_cleared
|
||||||
- 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)
|
|
||||||
- EnemyAggro (Node, enemy_aggro.gd)
|
|
||||||
- Healthbar (Sprite3D + SubViewport, über dem Gegner, healthbar.gd)
|
|
||||||
- SubViewport
|
|
||||||
- Border (ColorRect, gelb, sichtbar bei Anvisierung)
|
|
||||||
- HealthBar (ProgressBar, grün)
|
|
||||||
- ShieldBar (ProgressBar, blau)
|
|
||||||
|
|
||||||
## Skripte
|
## Dungeon (dungeon/)
|
||||||
- player.gd — Kern, verbindet Komponenten
|
- dungeon.tscn — Geschlossener Raum (15x90m, Wände, dunkles Licht)
|
||||||
- camera.gd — LMB freies Umsehen, RMB Kamera + Laufrichtung anpassen
|
- Systems (alle 12 Systeme, temporär bis Welt parallel läuft)
|
||||||
- movement.gd — Bewegung (WASD relativ zur Kamera), Springen, Schwerkraft
|
- NavigationRegion3D
|
||||||
- combat.gd — Führt Abilities aus, verwaltet Cooldowns (GCD 0.5s), Auto-Attack (10 Schaden, 1s, 20m)
|
- Boden, 4 Wände (StaticBody3D + BoxMesh, 3m hoch)
|
||||||
- targeting.gd — Klick/TAB anvisieren (Gruppen "enemies" + "portals"), Kampfmodus bei Gegner-Angriff, Auto-Targeting (Gegner > Portal)
|
- Spieler (Instanz von player.tscn)
|
||||||
- event_bus.gd — Autoload-Singleton, globale Signals
|
- HUD (Instanz von hud.tscn)
|
||||||
- role.gd — Rollenwechsel ALT+1 bis ALT+3 (Tank/Schaden/Heiler), tauscht AbilitySet
|
- Gegnergruppen (4x4 Gegner)
|
||||||
- respawn.gd — Bei Tod: Spieler deaktivieren, 3s Timer, Respawn am Startpunkt, Leben/Schild voll
|
- Boss (Instanz von boss.tscn)
|
||||||
- hud.gd — Reagiert auf Events, aktualisiert HealthBar/ShieldBar/AbilityBar/RespawnTimer
|
- Exit-Gate (Instanz von gate.tscn, is_exit=true)
|
||||||
- portal.gd — Portal-Kern, Gruppe "portals", stirbt bei 0 HP, kein Kampf/Aggro/State
|
- DungeonManager (Node, dungeon_manager.gd)
|
||||||
- enemy.gd — Gegner-Kern, State Machine (Idle, Verfolgen, Angreifen, Zurückkehren), alarmiert Gegner in 3m, Gruppe "enemies"
|
- Eigene Systems bis Welt parallel läuft (geplant: Reparenting)
|
||||||
- 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
|
|
||||||
|
|
||||||
## Components (scripts/components/, wiederverwendbar)
|
## HUD (hud/)
|
||||||
- health.gd — Leben, Regeneration, Tod bei 0 (liest Base-Werte aus EntityStats)
|
- hud.tscn — CanvasLayer (kein Root-Skript)
|
||||||
- shield.gd — Schild, Regeneration nach Delay (liest Base-Werte aus EntityStats)
|
- HealthBar (ProgressBar, Label)
|
||||||
- healthbar.gd — Liest Health/Shield (optional) vom Parent, gelber Rand bei Anvisierung, blauer Lebensbalken wenn Spieler Ziel ist (nur bei Gegnern)
|
- ShieldBar (ProgressBar, Label)
|
||||||
- spawner.gd — Spawnt Entities bei Lebensschwellen, engagt Spieler im Portal-Radius sofort
|
- RespawnTimer (Label, Countdown bei Tod)
|
||||||
|
- AbilityBar (HBoxContainer, RoleIcon + Abilities 1-4 + Passive)
|
||||||
|
- HudVitals (Node, hud_vitals.gd) — HP/Shield-Bars
|
||||||
|
- HudRespawn (Node, hud_respawn.gd) — Respawn-Timer
|
||||||
|
- HudAbilities (Node, hud_abilities.gd) — Ability-Bar + Cooldowns + Rollen-Icon
|
||||||
|
- HudEffects (Node, hud_effects.gd) — Effekt-Icons (nutzt EffectIconFactory)
|
||||||
|
|
||||||
## Stats (Resources)
|
# Abilities (Werte)
|
||||||
- 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:
|
- Schadens-Klasse:
|
||||||
- 1 Single: 30 Schaden, 20m Range, 1s CD, GCD
|
- AA: 10 Schaden, 10m
|
||||||
|
- 1 Single: 30 Schaden, 20m Range, 2s CD, GCD
|
||||||
- 2 AOE: 20 Schaden, 5m Range, 3s CD, GCD
|
- 2 AOE: 20 Schaden, 5m Range, 3s CD, GCD
|
||||||
- 3 Utility: Schild sofort auf 100%, 5s CD, kein 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
|
- 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)
|
- 5 Passive: 50% mehr Schaden Aura, 50m (permanent aktiv, kein CD)
|
||||||
- Jede Rolle hat ein eigenes AbilitySet
|
- Tank-Klasse:
|
||||||
- Beim Rollenwechsel wird das AbilitySet getauscht
|
- AA: 5 Schaden, 3m
|
||||||
- Elemente und Modifikatoren verändern Abilities nachträglich
|
- 1 Single: 15 Schaden, 3m Range, 2s CD, GCD
|
||||||
|
- 2 AOE: 10 Schaden, 10m Range, 3s CD, GCD
|
||||||
## Events
|
- 3 Utility: Schild sofort auf 100%, 5s CD, kein GCD
|
||||||
- attack_executed(attacker, position, direction, damage) — Angriff wurde ausgeführt
|
- 4 Ult: Schild 300%, 20s CD, GCD
|
||||||
- damage_dealt(attacker, target, damage) — Schaden wurde verteilt
|
- 5 Passive: 50% mehr Schild Aura, 50m (permanent aktiv, kein CD)
|
||||||
- damage_requested(attacker, target, amount) — Schaden zwischen Szenen anfordern
|
- Heiler-Klasse:
|
||||||
- entity_died(entity) — Entity ist gestorben
|
- AA: 1 Heilung, 20m
|
||||||
- shield_broken(entity) — Schild ist auf 0 gefallen
|
- 1 Single: 15 Heilung, 20m Range, 2s CD, GCD
|
||||||
- shield_regenerated(entity) — Schild ist wieder voll
|
- 2 AOE: 10 Heilung, 20m Range, 3s CD, GCD
|
||||||
- target_changed(player, target) — Spieler hat neues Ziel anvisiert
|
- 3 Utility: Schild sofort auf 100%, 5s CD, kein GCD
|
||||||
- player_respawned(player) — Spieler ist respawnt
|
- 4 Ult: 25 Heal Single + 10 AOE Heal 3m radius, 20m Range, 15s CD, GCD
|
||||||
- role_changed(player, role_type) — Spieler hat Rolle gewechselt
|
- 5 Passive: 50% mehr Heal Aura, 50m (permanent aktiv, kein CD)
|
||||||
- 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
|
|
||||||
|
|||||||
72
plan2.md
Normal file
72
plan2.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Kommunikation
|
||||||
|
|
||||||
|
## Datenfluss
|
||||||
|
```
|
||||||
|
Szene → Event → System → Autoload → Event → Szene/HUD
|
||||||
|
```
|
||||||
|
|
||||||
|
## Autoloads
|
||||||
|
- PlayerStats — Spieler-Daten (HP, Shield, Rolle, Target, Cooldowns, Position)
|
||||||
|
- EnemyStats — Enemy-Daten pro Node (HP, Shield, State, Target)
|
||||||
|
- BossStats — Boss-Daten pro Node (wie Enemy, andere Basiswerte)
|
||||||
|
- PortalStats — Portal-Daten pro Node (HP, Thresholds)
|
||||||
|
- EventBus — Signale
|
||||||
|
|
||||||
|
## Player
|
||||||
|
Scripts: init, movement, targeting, role, camera, ability
|
||||||
|
Events out:
|
||||||
|
- ability_use(player, ability_index)
|
||||||
|
- role_change_requested(player, role)
|
||||||
|
- target_requested(player, target)
|
||||||
|
|
||||||
|
## Enemy
|
||||||
|
Scripts: init, detection
|
||||||
|
Events out:
|
||||||
|
- enemy_detected(enemy, player)
|
||||||
|
- enemy_lost(enemy, player)
|
||||||
|
|
||||||
|
## Boss
|
||||||
|
Scripts: init, detection (erbt von Enemy)
|
||||||
|
Events out: wie Enemy
|
||||||
|
|
||||||
|
## Portal
|
||||||
|
Scripts: init
|
||||||
|
Events out:
|
||||||
|
- portal_entered(portal, player)
|
||||||
|
|
||||||
|
## Gate
|
||||||
|
Scripts: gate
|
||||||
|
Liest: PlayerStats (Szenenwechsel)
|
||||||
|
|
||||||
|
## Dungeon
|
||||||
|
Scripts: dungeon_manager
|
||||||
|
Hört: BossStats.died
|
||||||
|
|
||||||
|
## HUD
|
||||||
|
Scripts: hud_vitals, hud_respawn, hud_abilities, hud_effects
|
||||||
|
Hört: PlayerStats Events (health_changed, shield_changed, died, respawned, role_changed, cooldown_tick)
|
||||||
|
|
||||||
|
## Healthbar
|
||||||
|
Scripts: healthbar, healthbar_shield, healthbar_status, healthbar_effects
|
||||||
|
Hört: health_changed(entity), shield_changed(entity), target_changed, effect_applied/expired
|
||||||
|
|
||||||
|
## Systems → wer hört was, wer schreibt wohin
|
||||||
|
|
||||||
|
| System | Hört | Schreibt |
|
||||||
|
|--------|------|----------|
|
||||||
|
| role_system | role_change_requested | PlayerStats |
|
||||||
|
| ability_system | ability_use | → damage_system/heal_system |
|
||||||
|
| damage_system | (von ability, attack, ai, debuff) | PlayerStats/EnemyStats/BossStats/PortalStats |
|
||||||
|
| heal_system | (von ability, buff) | PlayerStats/EnemyStats/BossStats |
|
||||||
|
| attack_system | _process | → damage_system/heal_system |
|
||||||
|
| shield_system | _process | PlayerStats/EnemyStats/BossStats |
|
||||||
|
| cooldown_system | _process | PlayerStats |
|
||||||
|
| respawn_system | PlayerStats.died | PlayerStats |
|
||||||
|
| targeting_system | target_requested, entity_died | PlayerStats |
|
||||||
|
| aggro_system | damage_dealt, enemy_detected/lost, entity_died | EnemyStats/BossStats |
|
||||||
|
| aura_system | _process | → buff_system |
|
||||||
|
| buff_system | effect_requested | PlayerStats |
|
||||||
|
| debuff_system | effect_requested | → damage_system |
|
||||||
|
| spawn_system | health_changed (Portal) | EnemyStats |
|
||||||
|
| ai_system | _process | EnemyStats/BossStats, → damage_system |
|
||||||
|
| element_system | element_damage_dealt | → debuff_system |
|
||||||
@@ -11,13 +11,21 @@ config_version=5
|
|||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="mmo"
|
config/name="mmo"
|
||||||
run/main_scene="res://scenes/world.tscn"
|
run/main_scene="res://scenes/world/world.tscn"
|
||||||
config/features=PackedStringArray("4.6", "Forward Plus")
|
config/features=PackedStringArray("4.6", "Forward Plus")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
[autoload]
|
[autoload]
|
||||||
|
|
||||||
EventBus="*res://scripts/event_bus.gd"
|
EventBus="*res://autoloads/event_bus.gd"
|
||||||
|
PlayerData="*res://autoloads/player_stats.gd"
|
||||||
|
EnemyData="*res://autoloads/enemy_stats.gd"
|
||||||
|
BossData="*res://autoloads/boss_stats.gd"
|
||||||
|
PortalData="*res://autoloads/portal_stats.gd"
|
||||||
|
|
||||||
|
[dotnet]
|
||||||
|
|
||||||
|
project/assembly_name="mmo"
|
||||||
|
|
||||||
[input]
|
[input]
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
[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")])
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[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")])
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[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")])
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="EntityStats" load_steps=2 format=3]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/resources/entity_stats.gd" id="1"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1")
|
|
||||||
max_health = 100.0
|
|
||||||
health_regen = 0.0
|
|
||||||
max_shield = 50.0
|
|
||||||
shield_regen_delay = 3.0
|
|
||||||
shield_regen_time = 5.0
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="EntityStats" load_steps=2 format=3]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/resources/entity_stats.gd" id="1"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1")
|
|
||||||
max_health = 100.0
|
|
||||||
health_regen = 1.0
|
|
||||||
max_shield = 50.0
|
|
||||||
shield_regen_delay = 3.0
|
|
||||||
shield_regen_time = 5.0
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="EntityStats" load_steps=2 format=3]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/resources/entity_stats.gd" id="1"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1")
|
|
||||||
max_health = 500.0
|
|
||||||
health_regen = 0.0
|
|
||||||
max_shield = 0.0
|
|
||||||
254
scenes/dungeon/dungeon.tscn
Normal file
254
scenes/dungeon/dungeon.tscn
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
[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="Resource" path="res://scenes/enemy/boss_stats.tres" id="boss_stats"]
|
||||||
|
[ext_resource type="Script" path="res://systems/dungeon_system.gd" id="dungeon_system"]
|
||||||
|
[ext_resource type="PackedScene" path="res://scenes/portal/gate.tscn" id="gate"]
|
||||||
|
[ext_resource type="Script" path="res://systems/damage_system.gd" id="damage_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/health_system.gd" id="health_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/heal_system.gd" id="heal_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/shield_system.gd" id="shield_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/role_system.gd" id="role_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/ability_system.gd" id="ability_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/attack_system.gd" id="attack_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/cooldown_system.gd" id="cooldown_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/targeting_system.gd" id="targeting_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/aggro/aggro_system.gd" id="aggro_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/aggro/aggro_tracker.gd" id="aggro_tracker"]
|
||||||
|
[ext_resource type="Script" path="res://systems/aggro/aggro_decay.gd" id="aggro_decay"]
|
||||||
|
[ext_resource type="Script" path="res://systems/aggro/aggro_events.gd" id="aggro_events"]
|
||||||
|
[ext_resource type="Script" path="res://systems/ai_system.gd" id="ai_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/respawn_system.gd" id="respawn_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/spawn_system.gd" id="spawn_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/aura_system.gd" id="aura_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/buff_system.gd" id="buff_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/debuff_system.gd" id="debuff_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/element_system.gd" id="element_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/hud_system.gd" id="hud_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/nameplate_system.gd" id="nameplate_system"]
|
||||||
|
[ext_resource type="Resource" uid="uid://cgxtn7dfs40bh" path="res://scenes/player/role/tank/set.tres" id="tank_set"]
|
||||||
|
[ext_resource type="Resource" uid="uid://beodknb6i1pm4" path="res://scenes/player/role/damage/set.tres" id="damage_set"]
|
||||||
|
[ext_resource type="Resource" uid="uid://kcwuhnqy34mj" path="res://scenes/player/role/healer/set.tres" id="healer_set"]
|
||||||
|
|
||||||
|
[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="Systems" type="Node" parent="."]
|
||||||
|
|
||||||
|
[node name="HealthSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("health_system")
|
||||||
|
|
||||||
|
[node name="DamageSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("damage_system")
|
||||||
|
|
||||||
|
[node name="HealSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("heal_system")
|
||||||
|
|
||||||
|
[node name="ShieldSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("shield_system")
|
||||||
|
|
||||||
|
[node name="RoleSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("role_system")
|
||||||
|
tank_set = ExtResource("tank_set")
|
||||||
|
damage_set = ExtResource("damage_set")
|
||||||
|
healer_set = ExtResource("healer_set")
|
||||||
|
|
||||||
|
[node name="AbilitySystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("ability_system")
|
||||||
|
|
||||||
|
[node name="AttackSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("attack_system")
|
||||||
|
|
||||||
|
[node name="CooldownSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("cooldown_system")
|
||||||
|
|
||||||
|
[node name="TargetingSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("targeting_system")
|
||||||
|
|
||||||
|
[node name="AggroSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("aggro_system")
|
||||||
|
|
||||||
|
[node name="AggroTracker" type="Node" parent="Systems/AggroSystem"]
|
||||||
|
script = ExtResource("aggro_tracker")
|
||||||
|
|
||||||
|
[node name="AggroDecay" type="Node" parent="Systems/AggroSystem"]
|
||||||
|
script = ExtResource("aggro_decay")
|
||||||
|
|
||||||
|
[node name="AggroEvents" type="Node" parent="Systems/AggroSystem"]
|
||||||
|
script = ExtResource("aggro_events")
|
||||||
|
|
||||||
|
[node name="AISystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("ai_system")
|
||||||
|
|
||||||
|
[node name="RespawnSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("respawn_system")
|
||||||
|
|
||||||
|
[node name="SpawnSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("spawn_system")
|
||||||
|
|
||||||
|
[node name="AuraSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("aura_system")
|
||||||
|
|
||||||
|
[node name="BuffSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("buff_system")
|
||||||
|
|
||||||
|
[node name="DebuffSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("debuff_system")
|
||||||
|
|
||||||
|
[node name="ElementSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("element_system")
|
||||||
|
|
||||||
|
[node name="HudSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("hud_system")
|
||||||
|
|
||||||
|
[node name="NameplateSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("nameplate_system")
|
||||||
|
|
||||||
|
[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="." groups=["boss"] instance=ExtResource("enemy")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 75)
|
||||||
|
stats = ExtResource("boss_stats")
|
||||||
|
|
||||||
|
[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/world.tscn"
|
||||||
|
is_exit = true
|
||||||
|
|
||||||
|
[node name="DungeonSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("dungeon_system")
|
||||||
2
scenes/enemy/boss_stats.gd
Normal file
2
scenes/enemy/boss_stats.gd
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
extends EnemyStats
|
||||||
|
class_name BossStats
|
||||||
1
scenes/enemy/boss_stats.gd.uid
Normal file
1
scenes/enemy/boss_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dlawq281oesnf
|
||||||
20
scenes/enemy/boss_stats.tres
Normal file
20
scenes/enemy/boss_stats.tres
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[gd_resource type="Resource" script_class="BossStats" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/enemy/boss_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
|
||||||
|
speed = 3.0
|
||||||
|
attack_range = 2.0
|
||||||
|
attack_cooldown = 1.5
|
||||||
|
attack_damage = 5.0
|
||||||
|
regen_fast = 0.1
|
||||||
|
regen_slow = 0.01
|
||||||
|
aggro_decay = 1.0
|
||||||
|
portal_radius = 10.0
|
||||||
|
alert_radius = 10.0
|
||||||
11
scenes/enemy/detection.gd
Normal file
11
scenes/enemy/detection.gd
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
@onready var entity: CharacterBody3D = get_parent()
|
||||||
|
|
||||||
|
func _on_detection_area_body_entered(body: Node3D) -> void:
|
||||||
|
if body is CharacterBody3D and body.name == "Player":
|
||||||
|
EventBus.enemy_detected.emit(entity, body)
|
||||||
|
|
||||||
|
func _on_detection_area_body_exited(body: Node3D) -> void:
|
||||||
|
if body is CharacterBody3D and body.name == "Player":
|
||||||
|
EventBus.enemy_lost.emit(entity, body)
|
||||||
1
scenes/enemy/detection.gd.uid
Normal file
1
scenes/enemy/detection.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://b07aajhufqvb3
|
||||||
@@ -1,118 +1,90 @@
|
|||||||
[gd_scene load_steps=6 format=3]
|
[gd_scene format=3 uid="uid://db8pa55ev4l4a"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/enemy/enemy.gd" id="1"]
|
[ext_resource type="Script" uid="uid://vy6hyqok0p8b" path="res://scenes/enemy/init.gd" id="1"]
|
||||||
[ext_resource type="Script" path="res://scripts/components/health.gd" id="2"]
|
[ext_resource type="Script" uid="uid://b07aajhufqvb3" path="res://scenes/enemy/detection.gd" id="2"]
|
||||||
[ext_resource type="Script" path="res://scripts/components/shield.gd" id="3"]
|
[ext_resource type="Resource" uid="uid://cj1shmjwf0xeo" path="res://scenes/enemy/enemy_stats.tres" id="8"]
|
||||||
[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/enemy_stats.tres" id="8"]
|
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||||
radius = 0.4
|
radius = 0.4
|
||||||
height = 1.5
|
height = 1.5
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
|
||||||
|
albedo_color = Color(0.8, 0.1, 0.1, 1)
|
||||||
|
|
||||||
|
[sub_resource type="SphereMesh" id="SphereMesh_1"]
|
||||||
|
material = SubResource("StandardMaterial3D_1")
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_2"]
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_2"]
|
||||||
radius = 0.4
|
radius = 0.4
|
||||||
height = 1.5
|
height = 1.5
|
||||||
|
|
||||||
|
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
|
||||||
|
radius = 10.0
|
||||||
|
|
||||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_bg"]
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_bg"]
|
||||||
bg_color = Color(0.3, 0.1, 0.1, 1)
|
bg_color = Color(0.3, 0.1, 0.1, 1)
|
||||||
|
|
||||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_fill"]
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_fill"]
|
||||||
bg_color = Color(0.2, 0.8, 0.2, 1)
|
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"]
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_bg"]
|
||||||
bg_color = Color(0.1, 0.1, 0.3, 1)
|
bg_color = Color(0.1, 0.1, 0.3, 1)
|
||||||
|
|
||||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_fill"]
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_fill"]
|
||||||
bg_color = Color(0.2, 0.5, 0.9, 1)
|
bg_color = Color(0.2, 0.5, 0.9, 1)
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_1"]
|
[node name="Enemy" type="CharacterBody3D" unique_id=1724620529]
|
||||||
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")
|
script = ExtResource("1")
|
||||||
|
stats = ExtResource("8")
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1011138038]
|
||||||
shape = SubResource("CapsuleShape3D_1")
|
shape = SubResource("CapsuleShape3D_1")
|
||||||
|
|
||||||
[node name="Mesh" type="MeshInstance3D" parent="."]
|
[node name="Mesh" type="MeshInstance3D" parent="." unique_id=1598094615]
|
||||||
mesh = SubResource("SphereMesh_1")
|
mesh = SubResource("SphereMesh_1")
|
||||||
|
|
||||||
[node name="Health" type="Node" parent="."]
|
[node name="HitArea" type="Area3D" parent="." unique_id=893463784]
|
||||||
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_layer = 4
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="HitArea"]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="HitArea" unique_id=984781962]
|
||||||
shape = SubResource("CapsuleShape3D_2")
|
shape = SubResource("CapsuleShape3D_2")
|
||||||
|
|
||||||
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
|
[node name="NavigationAgent3D" type="NavigationAgent3D" parent="." unique_id=440641945]
|
||||||
|
|
||||||
[node name="EnemyMovement" type="Node" parent="."]
|
[node name="Detection" type="Node" parent="." unique_id=534240144]
|
||||||
script = ExtResource("5")
|
script = ExtResource("2")
|
||||||
|
|
||||||
[node name="EnemyCombat" type="Node" parent="."]
|
[node name="DetectionArea" type="Area3D" parent="." unique_id=1955178598]
|
||||||
script = ExtResource("6")
|
|
||||||
|
|
||||||
[node name="EnemyAggro" type="Node" parent="."]
|
|
||||||
script = ExtResource("7")
|
|
||||||
|
|
||||||
[node name="DetectionArea" type="Area3D" parent="."]
|
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 1
|
|
||||||
monitoring = true
|
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="DetectionArea"]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="DetectionArea" unique_id=557461347]
|
||||||
shape = SubResource("SphereShape3D_1")
|
shape = SubResource("SphereShape3D_1")
|
||||||
|
|
||||||
[node name="Healthbar" type="Sprite3D" parent="."]
|
[node name="Healthbar" type="Sprite3D" parent="." unique_id=1008728031]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
||||||
billboard = 1
|
billboard = 1
|
||||||
pixel_size = 0.01
|
|
||||||
script = ExtResource("4")
|
|
||||||
|
|
||||||
[node name="SubViewport" type="SubViewport" parent="Healthbar"]
|
[node name="SubViewport" type="SubViewport" parent="Healthbar" unique_id=1219060718]
|
||||||
transparent_bg = true
|
transparent_bg = true
|
||||||
size = Vector2i(104, 29)
|
size = Vector2i(104, 29)
|
||||||
|
|
||||||
[node name="Border" type="ColorRect" parent="Healthbar/SubViewport"]
|
[node name="Border" type="ColorRect" parent="Healthbar/SubViewport" unique_id=848146848]
|
||||||
offset_right = 104.0
|
offset_right = 104.0
|
||||||
offset_bottom = 29.0
|
offset_bottom = 29.0
|
||||||
color = Color(1, 0.9, 0.2, 1)
|
color = Color(1, 0.9, 0.2, 1)
|
||||||
|
|
||||||
[node name="HealthBar" type="ProgressBar" parent="Healthbar/SubViewport"]
|
[node name="HealthBar" type="ProgressBar" parent="Healthbar/SubViewport" unique_id=1206434403]
|
||||||
offset_left = 2.0
|
offset_left = 2.0
|
||||||
offset_top = 2.0
|
offset_top = 2.0
|
||||||
offset_right = 102.0
|
offset_right = 102.0
|
||||||
offset_bottom = 12.0
|
offset_bottom = 12.0
|
||||||
theme_override_styles/background = SubResource("StyleBoxFlat_health_bg")
|
theme_override_styles/background = SubResource("StyleBoxFlat_health_bg")
|
||||||
theme_override_styles/fill = SubResource("StyleBoxFlat_health_fill")
|
theme_override_styles/fill = SubResource("StyleBoxFlat_health_fill")
|
||||||
max_value = 100.0
|
|
||||||
value = 100.0
|
value = 100.0
|
||||||
show_percentage = false
|
show_percentage = false
|
||||||
|
|
||||||
[node name="ShieldBar" type="ProgressBar" parent="Healthbar/SubViewport"]
|
[node name="ShieldBar" type="ProgressBar" parent="Healthbar/SubViewport" unique_id=1891108036]
|
||||||
offset_left = 2.0
|
offset_left = 2.0
|
||||||
offset_top = 15.0
|
offset_top = 15.0
|
||||||
offset_right = 102.0
|
offset_right = 102.0
|
||||||
@@ -123,5 +95,5 @@ max_value = 50.0
|
|||||||
value = 50.0
|
value = 50.0
|
||||||
show_percentage = false
|
show_percentage = false
|
||||||
|
|
||||||
[connection signal="body_entered" from="DetectionArea" to="." method="_on_detection_area_body_entered"]
|
[connection signal="body_entered" from="DetectionArea" to="Detection" method="_on_detection_area_body_entered"]
|
||||||
[connection signal="body_exited" from="DetectionArea" to="." method="_on_detection_area_body_exited"]
|
[connection signal="body_exited" from="DetectionArea" to="Detection" method="_on_detection_area_body_exited"]
|
||||||
|
|||||||
12
scenes/enemy/enemy_stats.gd
Normal file
12
scenes/enemy/enemy_stats.gd
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
extends BaseStats
|
||||||
|
class_name EnemyStats
|
||||||
|
|
||||||
|
@export var speed := 3.0
|
||||||
|
@export var attack_range := 2.0
|
||||||
|
@export var attack_cooldown := 1.5
|
||||||
|
@export var attack_damage := 5.0
|
||||||
|
@export var regen_fast := 0.10
|
||||||
|
@export var regen_slow := 0.01
|
||||||
|
@export var aggro_decay := 1.0
|
||||||
|
@export var portal_radius := 10.0
|
||||||
|
@export var alert_radius := 10.0
|
||||||
1
scenes/enemy/enemy_stats.gd.uid
Normal file
1
scenes/enemy/enemy_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bh2uuuvl30y0x
|
||||||
7
scenes/enemy/enemy_stats.tres
Normal file
7
scenes/enemy/enemy_stats.tres
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[gd_resource type="Resource" script_class="EnemyStats" format=3 uid="uid://cj1shmjwf0xeo"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://bh2uuuvl30y0x" path="res://scenes/enemy/enemy_stats.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
max_shield = 50.0
|
||||||
30
scenes/enemy/init.gd
Normal file
30
scenes/enemy/init.gd
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
extends CharacterBody3D
|
||||||
|
|
||||||
|
@export var stats: EnemyStats
|
||||||
|
|
||||||
|
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
add_to_group("enemies")
|
||||||
|
if is_in_group("boss"):
|
||||||
|
BossData.register(self, stats)
|
||||||
|
BossData.set_stat(self, "spawn_position", global_position)
|
||||||
|
else:
|
||||||
|
EnemyData.register(self, stats)
|
||||||
|
EnemyData.set_stat(self, "spawn_position", global_position)
|
||||||
|
EventBus.entity_died.connect(_on_entity_died)
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
if is_in_group("boss"):
|
||||||
|
BossData.deregister(self)
|
||||||
|
else:
|
||||||
|
EnemyData.deregister(self)
|
||||||
|
|
||||||
|
func _on_entity_died(entity: Node) -> void:
|
||||||
|
if entity == self:
|
||||||
|
queue_free()
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
if not is_on_floor():
|
||||||
|
velocity.y -= gravity * delta
|
||||||
|
move_and_slide()
|
||||||
1
scenes/enemy/init.gd.uid
Normal file
1
scenes/enemy/init.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://vy6hyqok0p8b
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
[gd_scene format=3]
|
[gd_scene format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/player/hud.gd" id="1"]
|
|
||||||
|
|
||||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ability_active"]
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ability_active"]
|
||||||
bg_color = Color(0.2, 0.2, 0.2, 0.8)
|
bg_color = Color(0.2, 0.2, 0.2, 0.8)
|
||||||
border_width_bottom = 2
|
border_width_bottom = 2
|
||||||
@@ -34,8 +32,7 @@ bg_color = Color(0.1, 0.1, 0.3, 1)
|
|||||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_fill"]
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_fill"]
|
||||||
bg_color = Color(0.2, 0.5, 0.9, 1)
|
bg_color = Color(0.2, 0.5, 0.9, 1)
|
||||||
|
|
||||||
[node name="HUD" type="CanvasLayer"]
|
[node name="HUD" type="CanvasLayer" groups=["hud"]]
|
||||||
script = ExtResource("1")
|
|
||||||
|
|
||||||
[node name="HealthBar" type="ProgressBar" parent="."]
|
[node name="HealthBar" type="ProgressBar" parent="."]
|
||||||
offset_left = 10.0
|
offset_left = 10.0
|
||||||
@@ -48,6 +45,18 @@ max_value = 100.0
|
|||||||
value = 100.0
|
value = 100.0
|
||||||
show_percentage = false
|
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="."]
|
[node name="ShieldBar" type="ProgressBar" parent="."]
|
||||||
offset_left = 10.0
|
offset_left = 10.0
|
||||||
offset_top = 35.0
|
offset_top = 35.0
|
||||||
@@ -59,6 +68,18 @@ max_value = 50.0
|
|||||||
value = 50.0
|
value = 50.0
|
||||||
show_percentage = false
|
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="."]
|
[node name="RespawnTimer" type="Label" parent="."]
|
||||||
anchors_preset = 8
|
anchors_preset = 8
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
|
|||||||
9
scenes/player/ability.gd
Normal file
9
scenes/player/ability.gd
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
@onready var player: CharacterBody3D = get_parent()
|
||||||
|
|
||||||
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
for i in range(5):
|
||||||
|
if event.is_action_pressed("ability_%s" % (i + 1)):
|
||||||
|
EventBus.ability_use.emit(player, i)
|
||||||
|
return
|
||||||
1
scenes/player/ability.gd.uid
Normal file
1
scenes/player/ability.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://hh5yw7vcjdqr
|
||||||
12
scenes/player/init.gd
Normal file
12
scenes/player/init.gd
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
extends CharacterBody3D
|
||||||
|
|
||||||
|
@export var stats: PlayerStats
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
add_to_group("player")
|
||||||
|
PlayerData.init_from_resource(stats)
|
||||||
|
if PlayerData.returning_from_dungeon:
|
||||||
|
global_position = PlayerData.portal_position + Vector3(0, 1, -5)
|
||||||
|
PlayerData.returning_from_dungeon = false
|
||||||
|
elif PlayerData.dungeon_cleared:
|
||||||
|
PlayerData.clear_cache()
|
||||||
1
scenes/player/init.gd.uid
Normal file
1
scenes/player/init.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cx6k5473yxno
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
const SPEED := 5.0
|
|
||||||
const JUMP_VELOCITY := 4.5
|
|
||||||
|
|
||||||
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||||
|
|
||||||
@onready var player: CharacterBody3D = get_parent()
|
@onready var player: CharacterBody3D = get_parent()
|
||||||
@@ -12,7 +9,7 @@ func _physics_process(delta: float) -> void:
|
|||||||
player.velocity.y -= gravity * delta
|
player.velocity.y -= gravity * delta
|
||||||
|
|
||||||
if Input.is_action_just_pressed("ui_accept") and player.is_on_floor():
|
if Input.is_action_just_pressed("ui_accept") and player.is_on_floor():
|
||||||
player.velocity.y = JUMP_VELOCITY
|
player.velocity.y = PlayerData.jump_velocity
|
||||||
|
|
||||||
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
||||||
var camera_pivot := player.get_node("CameraPivot") as Node3D
|
var camera_pivot := player.get_node("CameraPivot") as Node3D
|
||||||
@@ -26,10 +23,10 @@ func _physics_process(delta: float) -> void:
|
|||||||
var direction := (forward * -input_dir.y + right * input_dir.x).normalized()
|
var direction := (forward * -input_dir.y + right * input_dir.x).normalized()
|
||||||
|
|
||||||
if direction:
|
if direction:
|
||||||
player.velocity.x = direction.x * SPEED
|
player.velocity.x = direction.x * PlayerData.speed
|
||||||
player.velocity.z = direction.z * SPEED
|
player.velocity.z = direction.z * PlayerData.speed
|
||||||
else:
|
else:
|
||||||
player.velocity.x = move_toward(player.velocity.x, 0, SPEED)
|
player.velocity.x = move_toward(player.velocity.x, 0, PlayerData.speed)
|
||||||
player.velocity.z = move_toward(player.velocity.z, 0, SPEED)
|
player.velocity.z = move_toward(player.velocity.z, 0, PlayerData.speed)
|
||||||
|
|
||||||
player.move_and_slide()
|
player.move_and_slide()
|
||||||
@@ -1,18 +1,12 @@
|
|||||||
[gd_scene format=3 uid="uid://cdnkbt1f0db7e"]
|
[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://cx6k5473yxno" path="res://scenes/player/init.gd" id="1"]
|
||||||
[ext_resource type="Script" uid="uid://cohjyjge1kqxb" path="res://scripts/player/camera.gd" id="2"]
|
[ext_resource type="Script" uid="uid://cohjyjge1kqxb" path="res://scenes/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://fg87dh8fbc8" path="res://scenes/player/movement.gd" id="3"]
|
||||||
[ext_resource type="Script" uid="uid://d15til6fsxw5b" path="res://scripts/player/combat.gd" id="4"]
|
[ext_resource type="Script" uid="uid://hh5yw7vcjdqr" path="res://scenes/player/ability.gd" id="4"]
|
||||||
[ext_resource type="Script" uid="uid://b053b4fkkeaod" path="res://scripts/components/health.gd" id="5"]
|
[ext_resource type="Script" uid="uid://b05nkuryipwny" path="res://scenes/player/targeting.gd" id="8"]
|
||||||
[ext_resource type="Script" uid="uid://bpfw71oprcvou" path="res://scripts/components/shield.gd" id="6"]
|
[ext_resource type="Script" uid="uid://dhomrampxola4" path="res://scenes/player/role/role.gd" id="10"]
|
||||||
[ext_resource type="Script" uid="uid://b05nkuryipwny" path="res://scripts/player/targeting.gd" id="8"]
|
[ext_resource type="Resource" uid="uid://btd0g0oiulssq" path="res://scenes/player/player_stats.tres" id="14"]
|
||||||
[ext_resource type="Script" uid="uid://dw3dtax5bx0of" path="res://scripts/player/respawn.gd" id="9"]
|
|
||||||
[ext_resource type="Script" path="res://scripts/player/role.gd" id="10"]
|
|
||||||
[ext_resource type="Resource" uid="uid://cgxtn7dfs40bh" path="res://resources/ability_sets/tank_set.tres" id="11"]
|
|
||||||
[ext_resource type="Resource" uid="uid://beodknb6i1pm4" path="res://resources/ability_sets/damage_set.tres" id="12"]
|
|
||||||
[ext_resource type="Resource" uid="uid://kcwuhnqy34mj" path="res://resources/ability_sets/healer_set.tres" id="13"]
|
|
||||||
[ext_resource type="Resource" path="res://resources/stats/player_stats.tres" id="14"]
|
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"]
|
||||||
radius = 0.3
|
radius = 0.3
|
||||||
@@ -22,44 +16,31 @@ height = 1.8
|
|||||||
radius = 0.3
|
radius = 0.3
|
||||||
height = 1.8
|
height = 1.8
|
||||||
|
|
||||||
[node name="Player" type="CharacterBody3D" unique_id=1350215040]
|
[node name="Player" type="CharacterBody3D" unique_id=197716516]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
|
stats = ExtResource("14")
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=33887999]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=311205642]
|
||||||
shape = SubResource("CapsuleShape3D_1")
|
shape = SubResource("CapsuleShape3D_1")
|
||||||
|
|
||||||
[node name="Mesh" type="MeshInstance3D" parent="." unique_id=1346931672]
|
[node name="Mesh" type="MeshInstance3D" parent="." unique_id=1514179122]
|
||||||
mesh = SubResource("CapsuleMesh_1")
|
mesh = SubResource("CapsuleMesh_1")
|
||||||
|
|
||||||
[node name="CameraPivot" type="Node3D" parent="." unique_id=1292689540]
|
[node name="CameraPivot" type="Node3D" parent="." unique_id=1881685457]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
|
|
||||||
[node name="Camera3D" type="Camera3D" parent="CameraPivot" unique_id=1225820651]
|
[node name="Camera3D" type="Camera3D" parent="CameraPivot" unique_id=2062990383]
|
||||||
transform = Transform3D(1, 0, 0, 0, 0.966, 0.259, 0, -0.259, 0.966, 0, 2, 5)
|
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]
|
[node name="Movement" type="Node" parent="." unique_id=811179177]
|
||||||
script = ExtResource("3")
|
script = ExtResource("3")
|
||||||
|
|
||||||
[node name="Combat" type="Node" parent="." unique_id=1754235583]
|
[node name="Ability" type="Node" parent="." unique_id=1184596245]
|
||||||
script = ExtResource("4")
|
script = ExtResource("4")
|
||||||
|
|
||||||
[node name="Targeting" type="Node" parent="." unique_id=592540710]
|
[node name="Targeting" type="Node" parent="." unique_id=1974574662]
|
||||||
script = ExtResource("8")
|
script = ExtResource("8")
|
||||||
|
|
||||||
[node name="Health" type="Node" parent="." unique_id=1872357630]
|
[node name="Role" type="Node" parent="." unique_id=1637643687]
|
||||||
script = ExtResource("5")
|
|
||||||
stats = ExtResource("14")
|
|
||||||
|
|
||||||
[node name="Shield" type="Node" parent="." unique_id=716948065]
|
|
||||||
script = ExtResource("6")
|
|
||||||
stats = ExtResource("14")
|
|
||||||
|
|
||||||
[node name="Respawn" type="Node" parent="." unique_id=1562314386]
|
|
||||||
script = ExtResource("9")
|
|
||||||
|
|
||||||
[node name="Role" type="Node" parent="." unique_id=134158295]
|
|
||||||
script = ExtResource("10")
|
script = ExtResource("10")
|
||||||
tank_set = ExtResource("11")
|
|
||||||
damage_set = ExtResource("12")
|
|
||||||
healer_set = ExtResource("13")
|
|
||||||
|
|||||||
10
scenes/player/player_stats.gd
Normal file
10
scenes/player/player_stats.gd
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
extends BaseStats
|
||||||
|
class_name PlayerStats
|
||||||
|
|
||||||
|
@export var speed := 5.0
|
||||||
|
@export var jump_velocity := 4.5
|
||||||
|
@export var target_range := 20.0
|
||||||
|
@export var combat_timeout := 3.0
|
||||||
|
@export var respawn_time := 3.0
|
||||||
|
@export var gcd_time := 0.5
|
||||||
|
@export var aa_cooldown := 0.5
|
||||||
1
scenes/player/player_stats.gd.uid
Normal file
1
scenes/player/player_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ypyntbavbsto
|
||||||
8
scenes/player/player_stats.tres
Normal file
8
scenes/player/player_stats.tres
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[gd_resource type="Resource" script_class="PlayerStats" format=3 uid="uid://btd0g0oiulssq"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://ypyntbavbsto" path="res://scenes/player/player_stats.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
health_regen = 1.0
|
||||||
|
max_shield = 50.0
|
||||||
16
scenes/player/role/ability.gd
Normal file
16
scenes/player/role/ability.gd
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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 cooldown: float = 0.0
|
||||||
|
@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"
|
||||||
|
@export var element: int = 0
|
||||||
7
scenes/player/role/ability_set.gd
Normal file
7
scenes/player/role/ability_set.gd
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://bpx3l13iuynfv"]
|
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://bpx3l13iuynfv"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
@@ -10,3 +10,4 @@ damage = 20.0
|
|||||||
ability_range = 5.0
|
ability_range = 5.0
|
||||||
cooldown = 3.0
|
cooldown = 3.0
|
||||||
icon = "2"
|
icon = "2"
|
||||||
|
element = 1
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://dadpl32yujwhe"]
|
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://dadpl32yujwhe"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://dwvc8b3cmce8l"]
|
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://dwvc8b3cmce8l"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
ability_name = "Single"
|
ability_name = "Single"
|
||||||
damage = 30.0
|
damage = 30.0
|
||||||
ability_range = 20.0
|
ability_range = 20.0
|
||||||
cooldown = 1.0
|
cooldown = 2.0
|
||||||
icon = "1"
|
icon = "1"
|
||||||
|
element = 1
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://s32wvlww2ls2"]
|
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://s32wvlww2ls2"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
@@ -11,3 +11,4 @@ ability_range = 20.0
|
|||||||
cooldown = 15.0
|
cooldown = 15.0
|
||||||
aoe_radius = 3.0
|
aoe_radius = 3.0
|
||||||
icon = "4"
|
icon = "4"
|
||||||
|
element = 1
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[gd_resource type="Resource" script_class="Ability" format=3 uid="uid://du0hyuuj26ea0"]
|
[gd_resource type="Resource" script_class="Ability" format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scripts/abilities/ability.gd" id="1"]
|
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
14
scenes/player/role/damage/set.tres
Normal file
14
scenes/player/role/damage/set.tres
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[gd_resource type="Resource" script_class="AbilitySet" format=3 uid="uid://beodknb6i1pm4"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://voedgs25cwrb" path="res://scenes/player/role/ability_set.gd" id="1"]
|
||||||
|
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1_ability"]
|
||||||
|
[ext_resource type="Resource" uid="uid://dwvc8b3cmce8l" path="res://scenes/player/role/damage/abilities/single.tres" id="2"]
|
||||||
|
[ext_resource type="Resource" uid="uid://bpx3l13iuynfv" path="res://scenes/player/role/damage/abilities/aoe.tres" id="3"]
|
||||||
|
[ext_resource type="Resource" path="res://scenes/player/role/damage/abilities/utility.tres" id="4"]
|
||||||
|
[ext_resource type="Resource" uid="uid://s32wvlww2ls2" path="res://scenes/player/role/damage/abilities/ult.tres" id="5"]
|
||||||
|
[ext_resource type="Resource" uid="uid://dadpl32yujwhe" path="res://scenes/player/role/damage/abilities/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 = 25.0
|
||||||
13
scenes/player/role/healer/abilities/aoe.tres
Normal file
13
scenes/player/role/healer/abilities/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://scenes/player/role/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
scenes/player/role/healer/abilities/passive.tres
Normal file
13
scenes/player/role/healer/abilities/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://scenes/player/role/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
scenes/player/role/healer/abilities/single.tres
Normal file
12
scenes/player/role/healer/abilities/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://scenes/player/role/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
scenes/player/role/healer/abilities/ult.tres
Normal file
14
scenes/player/role/healer/abilities/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://scenes/player/role/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
|
||||||
12
scenes/player/role/healer/abilities/utility.tres
Normal file
12
scenes/player/role/healer/abilities/utility.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://scenes/player/role/ability.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
ability_name = "Shield Reset"
|
||||||
|
type = 2
|
||||||
|
ability_range = 0.0
|
||||||
|
cooldown = 5.0
|
||||||
|
uses_gcd = false
|
||||||
|
icon = "3"
|
||||||
16
scenes/player/role/healer/set.tres
Normal file
16
scenes/player/role/healer/set.tres
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[gd_resource type="Resource" script_class="AbilitySet" format=3 uid="uid://kcwuhnqy34mj"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://voedgs25cwrb" path="res://scenes/player/role/ability_set.gd" id="1"]
|
||||||
|
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1_ability"]
|
||||||
|
[ext_resource type="Resource" path="res://scenes/player/role/healer/abilities/single.tres" id="2"]
|
||||||
|
[ext_resource type="Resource" path="res://scenes/player/role/healer/abilities/aoe.tres" id="3"]
|
||||||
|
[ext_resource type="Resource" path="res://scenes/player/role/healer/abilities/utility.tres" id="4"]
|
||||||
|
[ext_resource type="Resource" path="res://scenes/player/role/healer/abilities/ult.tres" id="5"]
|
||||||
|
[ext_resource type="Resource" path="res://scenes/player/role/healer/abilities/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
|
||||||
14
scenes/player/role/role.gd
Normal file
14
scenes/player/role/role.gd
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
@onready var player: CharacterBody3D = get_parent()
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
EventBus.role_change_requested.emit(player, PlayerData.current_role)
|
||||||
|
|
||||||
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
if event.is_action_pressed("class_tank"):
|
||||||
|
EventBus.role_change_requested.emit(player, PlayerData.Role.TANK)
|
||||||
|
elif event.is_action_pressed("class_damage"):
|
||||||
|
EventBus.role_change_requested.emit(player, PlayerData.Role.DAMAGE)
|
||||||
|
elif event.is_action_pressed("class_healer"):
|
||||||
|
EventBus.role_change_requested.emit(player, PlayerData.Role.HEALER)
|
||||||
12
scenes/player/role/tank/abilities/aoe.tres
Normal file
12
scenes/player/role/tank/abilities/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://scenes/player/role/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
scenes/player/role/tank/abilities/passive.tres
Normal file
13
scenes/player/role/tank/abilities/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://scenes/player/role/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
scenes/player/role/tank/abilities/single.tres
Normal file
11
scenes/player/role/tank/abilities/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://scenes/player/role/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
scenes/player/role/tank/abilities/ult.tres
Normal file
11
scenes/player/role/tank/abilities/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://scenes/player/role/ability.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
ability_name = "Fortress"
|
||||||
|
type = 2
|
||||||
|
damage = 300.0
|
||||||
|
cooldown = 20.0
|
||||||
|
icon = "4"
|
||||||
12
scenes/player/role/tank/abilities/utility.tres
Normal file
12
scenes/player/role/tank/abilities/utility.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://scenes/player/role/ability.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
ability_name = "Shield Reset"
|
||||||
|
type = 2
|
||||||
|
ability_range = 0.0
|
||||||
|
cooldown = 5.0
|
||||||
|
uses_gcd = false
|
||||||
|
icon = "3"
|
||||||
15
scenes/player/role/tank/set.tres
Normal file
15
scenes/player/role/tank/set.tres
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[gd_resource type="Resource" script_class="AbilitySet" format=3 uid="uid://cgxtn7dfs40bh"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://voedgs25cwrb" path="res://scenes/player/role/ability_set.gd" id="1"]
|
||||||
|
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://scenes/player/role/ability.gd" id="1_ability"]
|
||||||
|
[ext_resource type="Resource" path="res://scenes/player/role/tank/abilities/single.tres" id="2"]
|
||||||
|
[ext_resource type="Resource" path="res://scenes/player/role/tank/abilities/aoe.tres" id="3"]
|
||||||
|
[ext_resource type="Resource" path="res://scenes/player/role/tank/abilities/utility.tres" id="4"]
|
||||||
|
[ext_resource type="Resource" path="res://scenes/player/role/tank/abilities/ult.tres" id="5"]
|
||||||
|
[ext_resource type="Resource" path="res://scenes/player/role/tank/abilities/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
|
||||||
47
scenes/player/targeting.gd
Normal file
47
scenes/player/targeting.gd
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
const TARGET_RANGE := 20.0
|
||||||
|
|
||||||
|
var mouse_press_pos: Vector2 = Vector2.ZERO
|
||||||
|
|
||||||
|
@onready var player: CharacterBody3D = get_parent()
|
||||||
|
@onready var camera: Camera3D = get_parent().get_node("CameraPivot/Camera3D")
|
||||||
|
|
||||||
|
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
|
||||||
|
EventBus.target_requested.emit(player, hit_target)
|
||||||
|
else:
|
||||||
|
EventBus.target_requested.emit(player, null)
|
||||||
|
|
||||||
|
func _cycle_target() -> void:
|
||||||
|
var targets := get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("portals")
|
||||||
|
if targets.is_empty():
|
||||||
|
EventBus.target_requested.emit(player, null)
|
||||||
|
return
|
||||||
|
var current: Node3D = PlayerData.target
|
||||||
|
if current == null or current not in targets:
|
||||||
|
EventBus.target_requested.emit(player, targets[0])
|
||||||
|
return
|
||||||
|
var idx := targets.find(current)
|
||||||
|
var next_idx := (idx + 1) % targets.size()
|
||||||
|
EventBus.target_requested.emit(player, targets[next_idx])
|
||||||
34
scenes/portal/gate.gd
Normal file
34
scenes/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 PlayerData.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":
|
||||||
|
PlayerData.save_cache()
|
||||||
|
if is_exit:
|
||||||
|
PlayerData.returning_from_dungeon = true
|
||||||
|
else:
|
||||||
|
PlayerData.portal_position = global_position
|
||||||
|
call_deferred("_change_scene")
|
||||||
|
|
||||||
|
func _change_scene() -> void:
|
||||||
|
get_tree().change_scene_to_file(target_scene)
|
||||||
1
scenes/portal/gate.gd.uid
Normal file
1
scenes/portal/gate.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://ctci5mc3cd2ck
|
||||||
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://scenes/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"]
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
extends StaticBody3D
|
extends StaticBody3D
|
||||||
|
|
||||||
|
@export var stats: PortalStats
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
add_to_group("portals")
|
add_to_group("portals")
|
||||||
EventBus.entity_died.connect(_on_entity_died)
|
PortalData.register(self, stats)
|
||||||
|
|
||||||
func _on_entity_died(entity: Node) -> void:
|
func _exit_tree() -> void:
|
||||||
if entity == self:
|
PortalData.deregister(self)
|
||||||
queue_free()
|
|
||||||
|
|
||||||
func _on_detection_area_body_entered(body: Node3D) -> void:
|
func _on_detection_area_body_entered(body: Node3D) -> void:
|
||||||
if body is CharacterBody3D and body.name == "Player":
|
if body is CharacterBody3D and body.name == "Player":
|
||||||
EventBus.enemy_engaged.emit(self, body)
|
EventBus.portal_entered.emit(self, body)
|
||||||
1
scenes/portal/init.gd.uid
Normal file
1
scenes/portal/init.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dlexijybbqxop
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
[gd_scene format=3]
|
[gd_scene format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/portal/portal.gd" id="1"]
|
[ext_resource type="Script" path="res://scenes/portal/init.gd" id="1"]
|
||||||
[ext_resource type="Script" path="res://scripts/components/health.gd" id="2"]
|
[ext_resource type="Resource" path="res://scenes/portal/portal_stats.tres" id="6"]
|
||||||
[ext_resource type="Script" path="res://scripts/components/healthbar.gd" id="3"]
|
|
||||||
[ext_resource type="Script" path="res://scripts/components/spawner.gd" id="4"]
|
|
||||||
[ext_resource type="PackedScene" path="res://scenes/enemy/enemy.tscn" id="5"]
|
|
||||||
[ext_resource type="Resource" path="res://resources/stats/portal_stats.tres" id="6"]
|
|
||||||
|
|
||||||
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
|
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
|
||||||
radius = 10.0
|
radius = 10.0
|
||||||
@@ -35,6 +31,7 @@ bg_color = Color(0.2, 0.8, 0.2, 1)
|
|||||||
|
|
||||||
[node name="Portal" type="StaticBody3D"]
|
[node name="Portal" type="StaticBody3D"]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
|
stats = ExtResource("6")
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||||
shape = SubResource("CylinderShape3D_1")
|
shape = SubResource("CylinderShape3D_1")
|
||||||
@@ -42,10 +39,6 @@ shape = SubResource("CylinderShape3D_1")
|
|||||||
[node name="Mesh" type="MeshInstance3D" parent="."]
|
[node name="Mesh" type="MeshInstance3D" parent="."]
|
||||||
mesh = SubResource("CylinderMesh_1")
|
mesh = SubResource("CylinderMesh_1")
|
||||||
|
|
||||||
[node name="Health" type="Node" parent="."]
|
|
||||||
script = ExtResource("2")
|
|
||||||
stats = ExtResource("6")
|
|
||||||
|
|
||||||
[node name="HitArea" type="Area3D" parent="."]
|
[node name="HitArea" type="Area3D" parent="."]
|
||||||
collision_layer = 4
|
collision_layer = 4
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
@@ -61,15 +54,10 @@ monitoring = true
|
|||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="DetectionArea"]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="DetectionArea"]
|
||||||
shape = SubResource("SphereShape3D_1")
|
shape = SubResource("SphereShape3D_1")
|
||||||
|
|
||||||
[node name="Spawner" type="Node" parent="."]
|
|
||||||
script = ExtResource("4")
|
|
||||||
spawn_scene = ExtResource("5")
|
|
||||||
|
|
||||||
[node name="Healthbar" type="Sprite3D" parent="."]
|
[node name="Healthbar" type="Sprite3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 0)
|
||||||
billboard = 1
|
billboard = 1
|
||||||
pixel_size = 0.01
|
pixel_size = 0.01
|
||||||
script = ExtResource("3")
|
|
||||||
|
|
||||||
[node name="SubViewport" type="SubViewport" parent="Healthbar"]
|
[node name="SubViewport" type="SubViewport" parent="Healthbar"]
|
||||||
transparent_bg = true
|
transparent_bg = true
|
||||||
|
|||||||
5
scenes/portal/portal_stats.gd
Normal file
5
scenes/portal/portal_stats.gd
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
extends BaseStats
|
||||||
|
class_name PortalStats
|
||||||
|
|
||||||
|
@export var spawn_count := 3
|
||||||
|
@export var thresholds: Array[float] = [0.85, 0.70, 0.55, 0.40, 0.25, 0.10]
|
||||||
1
scenes/portal/portal_stats.gd.uid
Normal file
1
scenes/portal/portal_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bioid3s5oftxs
|
||||||
7
scenes/portal/portal_stats.tres
Normal file
7
scenes/portal/portal_stats.tres
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[gd_resource type="Resource" script_class="PortalStats" format=3 uid="uid://be2vv5u0jw0yw"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://bioid3s5oftxs" path="res://scenes/portal/portal_stats.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
max_health = 500.0
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
[gd_scene format=3 uid="uid://dy1icabu2ssbw"]
|
|
||||||
|
|
||||||
[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"]
|
|
||||||
|
|
||||||
[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="Portal" parent="." unique_id=2086501116 instance=ExtResource("portal")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 5)
|
|
||||||
48
scenes/world/portal_spawner.gd
Normal file
48
scenes/world/portal_spawner.gd
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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 PlayerData.portal_position != Vector3.ZERO and not PlayerData.dungeon_cleared:
|
||||||
|
call_deferred("_restore_gate")
|
||||||
|
else:
|
||||||
|
if PlayerData.dungeon_cleared:
|
||||||
|
PlayerData.clear_cache()
|
||||||
|
call_deferred("_spawn_portal")
|
||||||
|
|
||||||
|
func _restore_gate() -> void:
|
||||||
|
var gate: Node3D = GATE_SCENE.instantiate()
|
||||||
|
get_parent().add_child(gate)
|
||||||
|
gate.global_position = PlayerData.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)
|
||||||
|
|
||||||
|
func _cleanup_dead() -> void:
|
||||||
|
var valid: Array[Node] = []
|
||||||
|
for p in portals:
|
||||||
|
if is_instance_valid(p):
|
||||||
|
valid.append(p)
|
||||||
|
portals = valid
|
||||||
1
scenes/world/portal_spawner.gd.uid
Normal file
1
scenes/world/portal_spawner.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cskx6o07iukwh
|
||||||
175
scenes/world/world.tscn
Normal file
175
scenes/world/world.tscn
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
[gd_scene format=3 uid="uid://dy1icabu2ssbw"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://systems/ability_system.gd" id="ability_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/aggro/aggro_decay.gd" id="aggro_decay"]
|
||||||
|
[ext_resource type="Script" path="res://systems/aggro/aggro_events.gd" id="aggro_events"]
|
||||||
|
[ext_resource type="Script" path="res://systems/aggro/aggro_system.gd" id="aggro_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/aggro/aggro_tracker.gd" id="aggro_tracker"]
|
||||||
|
[ext_resource type="Script" path="res://systems/attack_system.gd" id="attack_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/cooldown_system.gd" id="cooldown_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/damage_system.gd" id="damage_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/health_system.gd" id="health_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/heal_system.gd" id="heal_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/role_system.gd" id="role_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/targeting_system.gd" id="targeting_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/portal_system.gd" id="portal_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/aura_system.gd" id="aura_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/buff_system.gd" id="buff_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/debuff_system.gd" id="debuff_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/element_system.gd" id="element_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/hud_system.gd" id="hud_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/nameplate_system.gd" id="nameplate_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/ai_system.gd" id="ai_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/respawn_system.gd" id="respawn_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/shield_system.gd" id="shield_system"]
|
||||||
|
[ext_resource type="Script" path="res://systems/spawn_system.gd" id="spawn_system"]
|
||||||
|
[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="Script" path="res://scenes/world/portal_spawner.gd" id="portal_spawner"]
|
||||||
|
[ext_resource type="Resource" uid="uid://cgxtn7dfs40bh" path="res://scenes/player/role/tank/set.tres" id="tank_set"]
|
||||||
|
[ext_resource type="Resource" uid="uid://beodknb6i1pm4" path="res://scenes/player/role/damage/set.tres" id="damage_set"]
|
||||||
|
[ext_resource type="Resource" uid="uid://kcwuhnqy34mj" path="res://scenes/player/role/healer/set.tres" id="healer_set"]
|
||||||
|
|
||||||
|
[sub_resource type="NavigationMesh" id="NavigationMesh_1"]
|
||||||
|
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"]
|
||||||
|
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_ground"]
|
||||||
|
albedo_texture = SubResource("NoiseTexture2D_1")
|
||||||
|
uv1_scale = Vector3(15, 15, 1)
|
||||||
|
|
||||||
|
[sub_resource type="PlaneMesh" id="PlaneMesh_1"]
|
||||||
|
material = SubResource("StandardMaterial3D_ground")
|
||||||
|
size = Vector2(100, 100)
|
||||||
|
|
||||||
|
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_1"]
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_tavern"]
|
||||||
|
albedo_color = Color(0.45, 0.3, 0.15, 1)
|
||||||
|
|
||||||
|
[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"]
|
||||||
|
|
||||||
|
[node name="Systems" type="Node" parent="."]
|
||||||
|
|
||||||
|
[node name="HealthSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("health_system")
|
||||||
|
|
||||||
|
[node name="DamageSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("damage_system")
|
||||||
|
|
||||||
|
[node name="HealSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("heal_system")
|
||||||
|
|
||||||
|
[node name="ShieldSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("shield_system")
|
||||||
|
|
||||||
|
[node name="RoleSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("role_system")
|
||||||
|
tank_set = ExtResource("tank_set")
|
||||||
|
damage_set = ExtResource("damage_set")
|
||||||
|
healer_set = ExtResource("healer_set")
|
||||||
|
|
||||||
|
[node name="AbilitySystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("ability_system")
|
||||||
|
|
||||||
|
[node name="AttackSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("attack_system")
|
||||||
|
|
||||||
|
[node name="CooldownSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("cooldown_system")
|
||||||
|
|
||||||
|
[node name="TargetingSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("targeting_system")
|
||||||
|
|
||||||
|
[node name="AggroSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("aggro_system")
|
||||||
|
|
||||||
|
[node name="AggroTracker" type="Node" parent="Systems/AggroSystem"]
|
||||||
|
script = ExtResource("aggro_tracker")
|
||||||
|
|
||||||
|
[node name="AggroDecay" type="Node" parent="Systems/AggroSystem"]
|
||||||
|
script = ExtResource("aggro_decay")
|
||||||
|
|
||||||
|
[node name="AggroEvents" type="Node" parent="Systems/AggroSystem"]
|
||||||
|
script = ExtResource("aggro_events")
|
||||||
|
|
||||||
|
[node name="AISystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("ai_system")
|
||||||
|
|
||||||
|
[node name="RespawnSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("respawn_system")
|
||||||
|
|
||||||
|
[node name="SpawnSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("spawn_system")
|
||||||
|
|
||||||
|
[node name="PortalSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("portal_system")
|
||||||
|
|
||||||
|
[node name="AuraSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("aura_system")
|
||||||
|
|
||||||
|
[node name="BuffSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("buff_system")
|
||||||
|
|
||||||
|
[node name="DebuffSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("debuff_system")
|
||||||
|
|
||||||
|
[node name="ElementSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("element_system")
|
||||||
|
|
||||||
|
[node name="HudSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("hud_system")
|
||||||
|
|
||||||
|
[node name="NameplateSystem" type="Node" parent="Systems"]
|
||||||
|
script = ExtResource("nameplate_system")
|
||||||
|
|
||||||
|
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0027503967, 0.014227867, 0.023231506)
|
||||||
|
navigation_mesh = SubResource("NavigationMesh_1")
|
||||||
|
|
||||||
|
[node name="Boden" type="MeshInstance3D" parent="NavigationRegion3D"]
|
||||||
|
mesh = SubResource("PlaneMesh_1")
|
||||||
|
|
||||||
|
[node name="BodenCollision" type="StaticBody3D" parent="."]
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="BodenCollision"]
|
||||||
|
shape = SubResource("WorldBoundaryShape3D_1")
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 0.707, 0.707, 0, -0.707, 0.707, 0, 10, 0)
|
||||||
|
shadow_enabled = true
|
||||||
|
|
||||||
|
[node name="Taverne" type="StaticBody3D" parent="."]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
|
||||||
|
|
||||||
|
[node name="Mesh" type="MeshInstance3D" parent="Taverne"]
|
||||||
|
mesh = SubResource("BoxMesh_tavern")
|
||||||
|
|
||||||
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="Taverne"]
|
||||||
|
shape = SubResource("BoxShape3D_tavern")
|
||||||
|
|
||||||
|
[node name="Player" parent="." instance=ExtResource("player")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, -5)
|
||||||
|
|
||||||
|
[node name="HUD" parent="." instance=ExtResource("hud")]
|
||||||
|
|
||||||
|
[node name="PortalSpawner" type="Node" parent="."]
|
||||||
|
script = ExtResource("portal_spawner")
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
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 cooldown: float = 0.0
|
|
||||||
@export var uses_gcd: bool = true
|
|
||||||
@export var aoe_radius: float = 0.0
|
|
||||||
@export var icon: String = ""
|
|
||||||
|
|
||||||
func execute(player: Node, targeting: Node) -> bool:
|
|
||||||
var dmg: float = _get_modified_damage(player, damage)
|
|
||||||
match type:
|
|
||||||
Type.SINGLE:
|
|
||||||
return _execute_single(player, targeting, dmg)
|
|
||||||
Type.AOE:
|
|
||||||
return _execute_aoe(player, dmg)
|
|
||||||
Type.UTILITY:
|
|
||||||
return _execute_utility(player)
|
|
||||||
Type.ULT:
|
|
||||||
return _execute_ult(player, targeting, dmg)
|
|
||||||
return false
|
|
||||||
|
|
||||||
func _get_modified_damage(player: Node, base: float) -> float:
|
|
||||||
var combat: Node = player.get_node("Combat")
|
|
||||||
return combat.apply_passive(base)
|
|
||||||
|
|
||||||
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):
|
|
||||||
return false
|
|
||||||
var dist: float = player.global_position.distance_to(target.global_position)
|
|
||||||
return dist <= ability_range
|
|
||||||
|
|
||||||
func _execute_single(player: Node, targeting: Node, dmg: float) -> bool:
|
|
||||||
if not _in_range(player, targeting):
|
|
||||||
return false
|
|
||||||
var target: Node3D = targeting.current_target
|
|
||||||
EventBus.damage_requested.emit(player, 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:
|
|
||||||
var hit := false
|
|
||||||
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)
|
|
||||||
hit = true
|
|
||||||
if hit:
|
|
||||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
|
|
||||||
return hit
|
|
||||||
|
|
||||||
func _execute_utility(player: Node) -> bool:
|
|
||||||
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)
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
|
|
||||||
func _execute_ult(player: Node, targeting: Node, dmg: float) -> bool:
|
|
||||||
if not _in_range(player, targeting):
|
|
||||||
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:
|
|
||||||
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)
|
|
||||||
EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg * 5.0)
|
|
||||||
return true
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
extends Resource
|
|
||||||
class_name AbilitySet
|
|
||||||
|
|
||||||
@export var abilities: Array[Ability] = []
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
extends Node
|
|
||||||
|
|
||||||
@export var stats: EntityStats
|
|
||||||
var max_health: float
|
|
||||||
var health_regen: float
|
|
||||||
var current_health: float
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
max_health = stats.max_health
|
|
||||||
health_regen = stats.health_regen
|
|
||||||
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 and health_regen > 0:
|
|
||||||
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())
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://b053b4fkkeaod
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
extends Sprite3D
|
|
||||||
|
|
||||||
@onready var viewport: SubViewport = $SubViewport
|
|
||||||
@onready var health_bar: ProgressBar = $SubViewport/HealthBar
|
|
||||||
@onready var border: ColorRect = $SubViewport/Border
|
|
||||||
@onready var health: Node = get_parent().get_node("Health")
|
|
||||||
@onready var parent_node: Node = get_parent()
|
|
||||||
|
|
||||||
var shield: Node = null
|
|
||||||
var shield_bar: ProgressBar = null
|
|
||||||
var style_normal: StyleBoxFlat
|
|
||||||
var style_aggro: StyleBoxFlat
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
texture = viewport.get_texture()
|
|
||||||
health_bar.max_value = health.max_health
|
|
||||||
shield = get_parent().get_node_or_null("Shield")
|
|
||||||
shield_bar = $SubViewport.get_node_or_null("ShieldBar")
|
|
||||||
if shield and shield_bar:
|
|
||||||
shield_bar.max_value = shield.max_shield
|
|
||||||
elif shield_bar:
|
|
||||||
shield_bar.visible = false
|
|
||||||
border.visible = false
|
|
||||||
style_normal = health_bar.get_theme_stylebox("fill").duplicate()
|
|
||||||
style_aggro = style_normal.duplicate()
|
|
||||||
style_aggro.bg_color = Color(0.2, 0.4, 0.9, 1)
|
|
||||||
EventBus.target_changed.connect(_on_target_changed)
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
|
||||||
health_bar.value = health.current_health
|
|
||||||
if shield and shield_bar:
|
|
||||||
shield_bar.value = shield.current_shield
|
|
||||||
var player: Node = get_tree().get_first_node_in_group("player")
|
|
||||||
if player and "target" in parent_node and parent_node.target == player:
|
|
||||||
health_bar.add_theme_stylebox_override("fill", style_aggro)
|
|
||||||
else:
|
|
||||||
health_bar.add_theme_stylebox_override("fill", style_normal)
|
|
||||||
|
|
||||||
func _on_target_changed(_player: Node, target: Node) -> void:
|
|
||||||
border.visible = (target == get_parent())
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://d1w7vm7t3k3ts
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
extends Node
|
|
||||||
|
|
||||||
@export var stats: EntityStats
|
|
||||||
var max_shield: float
|
|
||||||
var regen_delay: float
|
|
||||||
var regen_time: float
|
|
||||||
var current_shield: float
|
|
||||||
var regen_timer := 0.0
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
max_shield = stats.max_shield
|
|
||||||
regen_delay = stats.shield_regen_delay
|
|
||||||
regen_time = stats.shield_regen_time
|
|
||||||
current_shield = max_shield
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
|
||||||
if max_shield <= 0:
|
|
||||||
return
|
|
||||||
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
|
|
||||||
if current_shield <= 0:
|
|
||||||
EventBus.shield_broken.emit(get_parent())
|
|
||||||
EventBus.shield_changed.emit(get_parent(), current_shield, max_shield)
|
|
||||||
return amount - absorbed
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://bpfw71oprcvou
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
extends Node
|
|
||||||
|
|
||||||
@export var spawn_scene: PackedScene
|
|
||||||
@export var spawn_count := 3
|
|
||||||
@export var thresholds: Array[float] = [0.85, 0.70, 0.55, 0.40, 0.25, 0.10]
|
|
||||||
|
|
||||||
var triggered: Array[bool] = []
|
|
||||||
|
|
||||||
@onready var parent: Node3D = get_parent()
|
|
||||||
@onready var health: Node = get_parent().get_node("Health")
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
triggered.resize(thresholds.size())
|
|
||||||
triggered.fill(false)
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
|
||||||
if not spawn_scene or health.current_health <= 0:
|
|
||||||
return
|
|
||||||
var ratio: float = health.current_health / health.max_health
|
|
||||||
for i in range(thresholds.size()):
|
|
||||||
if not triggered[i] and ratio <= thresholds[i]:
|
|
||||||
triggered[i] = true
|
|
||||||
_spawn()
|
|
||||||
|
|
||||||
func _spawn() -> void:
|
|
||||||
var spawned: Array = []
|
|
||||||
for j in range(spawn_count):
|
|
||||||
var entity: Node = spawn_scene.instantiate()
|
|
||||||
var offset := Vector3(randf_range(-2, 2), 0, randf_range(-2, 2))
|
|
||||||
parent.get_parent().add_child(entity)
|
|
||||||
entity.global_position = parent.global_position + offset
|
|
||||||
if "spawn_position" in entity:
|
|
||||||
entity.spawn_position = parent.global_position
|
|
||||||
if "portal" in entity:
|
|
||||||
entity.portal = parent
|
|
||||||
spawned.append(entity)
|
|
||||||
var player: Node = get_tree().get_first_node_in_group("player")
|
|
||||||
if player:
|
|
||||||
var dist: float = parent.global_position.distance_to(player.global_position)
|
|
||||||
if dist <= 10.0:
|
|
||||||
for entity in spawned:
|
|
||||||
if entity.has_method("_engage"):
|
|
||||||
entity._engage(player)
|
|
||||||
EventBus.portal_spawn.emit(parent, spawned)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://cm2s3xkmuesey
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
extends CharacterBody3D
|
|
||||||
|
|
||||||
enum State { IDLE, CHASE, ATTACK, RETURN }
|
|
||||||
|
|
||||||
var state: int = State.IDLE
|
|
||||||
var target: Node3D = null
|
|
||||||
var spawn_position: Vector3
|
|
||||||
var portal: Node3D = null
|
|
||||||
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 _engage(new_target: Node3D) -> void:
|
|
||||||
if state == State.CHASE or state == State.ATTACK:
|
|
||||||
return
|
|
||||||
target = new_target
|
|
||||||
state = State.CHASE
|
|
||||||
var aggro: Node = get_node_or_null("EnemyAggro")
|
|
||||||
if aggro:
|
|
||||||
aggro.add_aggro(new_target, 1.0)
|
|
||||||
_alert_nearby()
|
|
||||||
|
|
||||||
func _alert_nearby() -> void:
|
|
||||||
var enemies := get_tree().get_nodes_in_group("enemies")
|
|
||||||
for enemy in enemies:
|
|
||||||
if enemy != self and is_instance_valid(enemy) and "state" in enemy:
|
|
||||||
if enemy.state == enemy.State.IDLE:
|
|
||||||
var dist: float = global_position.distance_to(enemy.global_position)
|
|
||||||
if dist <= 3.0:
|
|
||||||
enemy._engage(target)
|
|
||||||
|
|
||||||
func _on_detection_area_body_entered(body: Node3D) -> void:
|
|
||||||
if body is CharacterBody3D and body.name == "Player":
|
|
||||||
_engage(body)
|
|
||||||
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
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user