Compare commits
6 Commits
cf5979803e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 52ad83a96d | |||
|
|
2d4002bd3f | ||
|
|
6d28b04c12 | ||
|
|
087a5ec8cc | ||
|
|
4b0f82c1de | ||
|
|
f21e30eb55 |
152
CLAUDE.md
152
CLAUDE.md
@@ -1,92 +1,98 @@
|
||||
# MMO Projekt
|
||||
|
||||
## Überblick
|
||||
Community-MMO in Godot 4.6 (Forward+, Jolt Physics). Ziel: Einsamkeit bekämpfen durch geographische Nähe der Spieler. Stilles Designprinzip — nicht als "Anti-Einsamkeits-Spiel" vermarktet.
|
||||
Community-MMO in Godot 4.6 (Forward+, Jolt Physics). Vision in `doc/_core.md`, Features in `doc/features.md`.
|
||||
|
||||
## Sprache
|
||||
Der User kommuniziert auf Deutsch. Code und Variablen auf Englisch. Kommentare nur wo nötig.
|
||||
|
||||
## Code-Style
|
||||
- GDScript mit 2 Spaces Einrückung (Godot Editor auf Spaces/2 eingestellt)
|
||||
- Explizite Typen bei Variablen die von Variant-Funktionen kommen (z.B. `var dist: float = ...` statt `var dist := ...` bei `distance_to()`, `min()`, `get_node_or_null()`)
|
||||
- Keine Debug-Prints im finalen Code (nur temporär zum Testen)
|
||||
- GDScript mit 2 Spaces Einrückung
|
||||
- Explizite Typen wo der Inferenzer scheitert (Ergebnisse von Variant-Funktionen, Helper-Returns die Node sein könnten)
|
||||
- Keine Debug-Prints im finalen Code
|
||||
- Keine emoji
|
||||
|
||||
## Architektur
|
||||
- **Stats (Model)**: Autoload, zentrale Datenhaltung aller Entity-Attribute. Basiswerte aus Resources.
|
||||
- **Systeme (Controller)**: Scene-Nodes in world.tscn/dungeon.tscn, lesen/schreiben über Stats.
|
||||
- **Szenen (Views)**: Rendern, Input senden, Events empfangen. Kein Gameplay-State.
|
||||
- **EventBus (Signals)**: Autoload, Kommunikation zwischen Szenen und Systemen.
|
||||
- **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
|
||||
Drei Grundordner nach Verantwortung. Resources liegen bei ihren Skripten.
|
||||
- `scenes/` — Darstellung + Input
|
||||
- `player/` — Spieler + player_stats + role/ (Rollen + Abilities)
|
||||
- `enemy/` — Gegner + enemy_stats + boss/ (Boss + boss_stats)
|
||||
- `portal/` — Portal + Gate + portal_stats
|
||||
- `dungeon/` — Dungeon + dungeon_manager
|
||||
- `hud/` — HUD
|
||||
- `world/` — Hauptszene + portal_spawner
|
||||
- `effect_icon_factory.gd` — Shared Utility (Effekt-Icons)
|
||||
- `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
|
||||
### Autoloads (`autoloads/`)
|
||||
- `EventBus` — alle Signals (Combat, Wave, Inventory, Dialog, Chat, …)
|
||||
- `Net` — ENet P2P Multiplayer-Manager (Singleplayer = OfflineMultiplayerPeer)
|
||||
- `Stats` — zentrale Registry: `register(entity, base_resource)` / `get_stat` / `set_stat`
|
||||
- `GameState` — Wave#, Scene-Pfade, Run-Seed, Pause, Dungeon-Seed
|
||||
- `SaveLoad` — JSON-basierte Persistenz (Stub für später)
|
||||
|
||||
## Planungsdokument
|
||||
`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.
|
||||
### Resources (`resources/`)
|
||||
- `stats/` — BaseStats + Player/Enemy/Boss/Portal/Gate/Village/Building Stats
|
||||
- `abilities/` — Ability + AbilitySet (3 Klassen × 5 Abilities, im RoleSystem in Code aufgebaut)
|
||||
- `items/` — Item, Recipe (Crafting in CraftingSystem in Code)
|
||||
- `buildings/` — Building Resource (Blueprints in BuildingSystem in Code)
|
||||
- `npcs/` — NpcProfile (Lore + Personality für Ollama)
|
||||
- `effects/` — Effect Resource + Element (NONE, FIRE)
|
||||
|
||||
## Core Loop
|
||||
1. Portale spawnen dynamisch auf der Karte (PortalSpawner, max 3, 20-40m vom Zentrum)
|
||||
2. Spieler greift Portal an → Gegner spawnen bei Lebensschwellen (85%/70%/55%/40%/25%/10%)
|
||||
3. Spieler bekämpft Gegner mit Abilities (Single, AOE, Utility, Ult) + Auto-Attack
|
||||
4. Portal bei 0 HP → Gate spawnt, Gegner werden entfernt
|
||||
5. Spieler betritt Gate → Dungeon (separate Szene, 4 Gegnergruppen + Boss)
|
||||
6. Spieler kann zwischen Welt und Dungeon hin und her (beide Gates aktiv solange Boss lebt)
|
||||
7. Boss stirbt → 2s Delay → Spieler wird zur Taverne teleportiert, Gates verschwinden
|
||||
8. Tod → 3s Respawn bei Taverne
|
||||
### Szenen (`scenes/`)
|
||||
- `menu/` — main_menu, lobby, options_menu
|
||||
- `world/` — world.tscn (Dorf-Welt mit Village + alle Systeme als Children)
|
||||
- `dungeon/` — dungeon.tscn (procedural generation via dungeon_generator.gd)
|
||||
- `entities/` — player, enemy, gate, portal, building, loot, npc, village
|
||||
- `hud/` — hud.tscn (alles-in-einem: vitals, abilities, chat, minimap, inventory, crafting, build, dialog, map, pause, game over)
|
||||
|
||||
## Kampfsystem
|
||||
- Auto-Attack: Rollenspezifisch (D: 10 Schaden/10m, T: 5 Schaden/3m, H: 1 Heilung/20m), 0.5s CD
|
||||
- 5 Abilities pro Rolle: Single, AOE, Utility, Ult, Passive — jede Rolle hat eigene Werte
|
||||
- GCD 0.5s (gilt für 1, 2, 4), Utility ignoriert GCD
|
||||
- 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)
|
||||
- Aggro-System: 1:1 Schaden, Tank 2x, Heilung 0.5x, verfällt -1/s, exponentiell außerhalb Portal-Radius
|
||||
### Systeme (`systems/`)
|
||||
Alle als Children unter `World/Systems` und `Dungeon/Systems` instanziert:
|
||||
- Combat: HealthSystem, ShieldSystem, RespawnSystem, CooldownSystem, AbilitySystem, AutoAttackSystem
|
||||
- Effects: EffectSystem, ElementSystem, AggroSystem, RoleSystem
|
||||
- World: SpawnSystem, WaveSystem, InvasionSystem, XpSystem, LootSystem
|
||||
- Player: InventorySystem, CraftingSystem, BuildingSystem
|
||||
- Social: NpcSystem, DialogSystem (Ollama HTTP), ChatSystem, MapSystem, AudioSystem
|
||||
|
||||
## Rollen
|
||||
- Tank (T), Schaden (D), Heiler (H) — wechselbar mit ALT+1/2/3
|
||||
- Jede Rolle hat eigenes AbilitySet mit unterschiedlichen Werten und Mechaniken
|
||||
- Tank: Weniger Schaden, Nahkampf, Schild-Ult (300%), Schild-Passive
|
||||
- Heiler: Heilt statt schadet (Single, AOE, Ult), Heal-Passive, AOE macht Schaden
|
||||
### Multiplayer-Pattern
|
||||
- ENet P2P, Host-Authoritative
|
||||
- Singleplayer = `Net.host_singleplayer()` (OfflineMultiplayerPeer)
|
||||
- Player-Entities tragen Authority des jeweiligen Peers (`_enter_tree() → set_multiplayer_authority(name.to_int())`)
|
||||
- Stats/Damage/Spawn/Wave/XP/Inventory laufen nur am Host (`if not multiplayer.is_server() and multiplayer.multiplayer_peer != null: return`)
|
||||
- Client-Aktionen via RPC zum Host: `_request_*.rpc_id(1, ...)` → Host validiert + sync via `MultiplayerSpawner` und `MultiplayerSynchronizer`
|
||||
- Sync-Felder pro Entity über `MultiplayerSynchronizer` mit `SceneReplicationConfig`
|
||||
|
||||
## 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
|
||||
### Input-Map
|
||||
- WASD + Space: Move + Jump
|
||||
- 1–4: Abilities (im Build-Mode: Bauteil-Auswahl)
|
||||
- ALT+1/2/3: Tank/Damage/Healer
|
||||
- Tab: Cycle target | LMB: Click target | RMB: Camera drag (capture mouse)
|
||||
- I: Inventory | C: Crafting | B: Build mode | M: Map | Y: Chat | E: Interact (NPC)
|
||||
- R: Rotate building preview
|
||||
- Escape: Pause / Cancel UI
|
||||
|
||||
## 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
|
||||
### Dialog (Ollama)
|
||||
- HTTP `localhost:11434/api/generate`, Modell `mistral-nemo`
|
||||
- Pro NPC: System-Prompt aus `lore` + `personality`
|
||||
- Fallback: `npc.fallback_text` wenn Ollama nicht erreichbar — Spiel bleibt voll spielbar
|
||||
|
||||
### Bauen
|
||||
- Grid-Snap 1m, 4 Bauteile (Floor, Wall, Door, Roof)
|
||||
- LMB im Build-Mode platziert, MMB entfernt
|
||||
- Material-Verbrauch aus Crafting-Items (alle aus `wood`, das aus `essence` gecrafted wird)
|
||||
- Persistenz via Host (Buildings sind echte Nodes unter `EntityRoot/Buildings`, MultiplayerSpawner repliziert)
|
||||
|
||||
### Wave-Loop
|
||||
1. WaveSystem startet 10-min Timer, spawnt 3 normale + 1 rotes Gate
|
||||
2. Gate stirbt → spawnt Portal an gleicher Position
|
||||
3. Spieler betritt Portal → Dungeon (procedural, 5–8 Räume + Boss)
|
||||
4. Boss-Tod → Welt-Rückkehr; rotes Portal → Wave++ + alle Stats skalieren
|
||||
5. Timer 0 ohne rotes Portal → Invasion (8+wave×2 Mobs zur Village)
|
||||
6. Village 0 HP → Game Over → Hauptmenü
|
||||
|
||||
## Workflow mit dem User
|
||||
- **plan.md ist zentral** — User will Änderungen zuerst in plan.md dokumentiert haben, dann implementieren
|
||||
- **Modularer Aufbau** — Jede Szene/Skript hat eine Aufgabe
|
||||
- **Schrittweise** — Erst planen, dann Code zeigen, dann implementieren
|
||||
- **Godot-Eigenheiten**: Nach Änderungen an Autoloads/Scripts Godot neu starten. Godot überschreibt .tscn beim Speichern — Szene im Editor schließen vor externen Edits.
|
||||
- Erst planen (`doc/` lesen), dann implementieren
|
||||
- Schrittweise, modulares Aufbauen — nicht alles auf einmal umbauen
|
||||
- Nach Änderungen: `godot-4 --headless --quit-after 60 res://scenes/world/world.tscn` um Parse-/Runtime-Fehler zu sehen
|
||||
- `doc/` nicht anfassen — das ist die User-Spec
|
||||
|
||||
## Smoke-Test
|
||||
- `godot-4 --headless --import` → Kein Error
|
||||
- `godot-4 --headless --quit-after 600 res://scenes/world/world.tscn` → Kein Error
|
||||
- `godot-4 --headless --quit-after 600 res://scenes/dungeon/dungeon.tscn` → Kein Error
|
||||
|
||||
## Bekannte Lücken / TODOs
|
||||
- Ollama muss installiert werden (Modell `mistral-nemo` erwartet) — Stub-Fallback funktioniert sonst
|
||||
- Save/Load Slots im Hauptmenü (Autoload existiert, UI fehlt)
|
||||
- Multiplayer Late-Join: Building-/Wave-State wird beim spätem Beitritt nicht synchronisiert
|
||||
- Damage Numbers / Hit FX / Particle Effects fehlen (rein Polish)
|
||||
|
||||
BIN
assets/audio/music/battle.wav
Normal file
BIN
assets/audio/music/battle.wav
Normal file
Binary file not shown.
24
assets/audio/music/battle.wav.import
Normal file
24
assets/audio/music/battle.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://d4a76sj0xk578"
|
||||
path="res://.godot/imported/battle.wav-74e134d2675fd72e2f3b1b22f3e2be00.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/music/battle.wav"
|
||||
dest_files=["res://.godot/imported/battle.wav-74e134d2675fd72e2f3b1b22f3e2be00.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
assets/audio/music/invasion.wav
Normal file
BIN
assets/audio/music/invasion.wav
Normal file
Binary file not shown.
24
assets/audio/music/invasion.wav.import
Normal file
24
assets/audio/music/invasion.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://clvph60pdl81v"
|
||||
path="res://.godot/imported/invasion.wav-6117678cf33bae18ebd1655477798610.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/music/invasion.wav"
|
||||
dest_files=["res://.godot/imported/invasion.wav-6117678cf33bae18ebd1655477798610.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
assets/audio/music/tavern.wav
Normal file
BIN
assets/audio/music/tavern.wav
Normal file
Binary file not shown.
24
assets/audio/music/tavern.wav.import
Normal file
24
assets/audio/music/tavern.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dnowuon22316o"
|
||||
path="res://.godot/imported/tavern.wav-8d46496f3ea930a74a1080b53ceb6a0e.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/music/tavern.wav"
|
||||
dest_files=["res://.godot/imported/tavern.wav-8d46496f3ea930a74a1080b53ceb6a0e.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
assets/audio/sfx/ability_cast.wav
Normal file
BIN
assets/audio/sfx/ability_cast.wav
Normal file
Binary file not shown.
24
assets/audio/sfx/ability_cast.wav.import
Normal file
24
assets/audio/sfx/ability_cast.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://crpv7tej1lwnn"
|
||||
path="res://.godot/imported/ability_cast.wav-05d75ac0fb52a99d863b460bd374fd73.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/sfx/ability_cast.wav"
|
||||
dest_files=["res://.godot/imported/ability_cast.wav-05d75ac0fb52a99d863b460bd374fd73.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
assets/audio/sfx/death.wav
Normal file
BIN
assets/audio/sfx/death.wav
Normal file
Binary file not shown.
24
assets/audio/sfx/death.wav.import
Normal file
24
assets/audio/sfx/death.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dmmeubtnbibaj"
|
||||
path="res://.godot/imported/death.wav-eb8bf206799fefce7cf26366434348b8.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/sfx/death.wav"
|
||||
dest_files=["res://.godot/imported/death.wav-eb8bf206799fefce7cf26366434348b8.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
assets/audio/sfx/hit.wav
Normal file
BIN
assets/audio/sfx/hit.wav
Normal file
Binary file not shown.
24
assets/audio/sfx/hit.wav.import
Normal file
24
assets/audio/sfx/hit.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://blv6fecsx5wax"
|
||||
path="res://.godot/imported/hit.wav-27e178036f6cee6545e9f025a3865a36.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/sfx/hit.wav"
|
||||
dest_files=["res://.godot/imported/hit.wav-27e178036f6cee6545e9f025a3865a36.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
assets/audio/sfx/invasion_alarm.wav
Normal file
BIN
assets/audio/sfx/invasion_alarm.wav
Normal file
Binary file not shown.
24
assets/audio/sfx/invasion_alarm.wav.import
Normal file
24
assets/audio/sfx/invasion_alarm.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://cys5bjcvl3d1q"
|
||||
path="res://.godot/imported/invasion_alarm.wav-f4375961fdd02f77d27cc495cdccde5c.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/sfx/invasion_alarm.wav"
|
||||
dest_files=["res://.godot/imported/invasion_alarm.wav-f4375961fdd02f77d27cc495cdccde5c.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
assets/audio/sfx/level_up.wav
Normal file
BIN
assets/audio/sfx/level_up.wav
Normal file
Binary file not shown.
24
assets/audio/sfx/level_up.wav.import
Normal file
24
assets/audio/sfx/level_up.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://2ogpkwinaq32"
|
||||
path="res://.godot/imported/level_up.wav-60e30bfe8ab247d27e7615e99e00b8f1.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/sfx/level_up.wav"
|
||||
dest_files=["res://.godot/imported/level_up.wav-60e30bfe8ab247d27e7615e99e00b8f1.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
assets/audio/sfx/portal_spawn.wav
Normal file
BIN
assets/audio/sfx/portal_spawn.wav
Normal file
Binary file not shown.
24
assets/audio/sfx/portal_spawn.wav.import
Normal file
24
assets/audio/sfx/portal_spawn.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://c6us2m0yer33r"
|
||||
path="res://.godot/imported/portal_spawn.wav-7a2c81bb1bec2cdea2b6aa74f4884f93.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/sfx/portal_spawn.wav"
|
||||
dest_files=["res://.godot/imported/portal_spawn.wav-7a2c81bb1bec2cdea2b6aa74f4884f93.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
assets/audio/sfx/tavern_damage.wav
Normal file
BIN
assets/audio/sfx/tavern_damage.wav
Normal file
Binary file not shown.
24
assets/audio/sfx/tavern_damage.wav.import
Normal file
24
assets/audio/sfx/tavern_damage.wav.import
Normal file
@@ -0,0 +1,24 @@
|
||||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dcfon26db14hk"
|
||||
path="res://.godot/imported/tavern_damage.wav-1e664835ae36452ac3c6f2da885f2ce2.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/audio/sfx/tavern_damage.wav"
|
||||
dest_files=["res://.godot/imported/tavern_damage.wav-1e664835ae36452ac3c6f2da885f2ce2.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
assets/models/buildings/building_home_A_blue.bin
Normal file
BIN
assets/models/buildings/building_home_A_blue.bin
Normal file
Binary file not shown.
136
assets/models/buildings/building_home_A_blue.gltf
Normal file
136
assets/models/buildings/building_home_A_blue.gltf
Normal file
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"asset" : {
|
||||
"generator" : "Khronos glTF Blender I/O v3.4.50",
|
||||
"version" : "2.0"
|
||||
},
|
||||
"scene" : 0,
|
||||
"scenes" : [
|
||||
{
|
||||
"name" : "Scene",
|
||||
"nodes" : [
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes" : [
|
||||
{
|
||||
"mesh" : 0,
|
||||
"name" : "building_home_A_blue"
|
||||
}
|
||||
],
|
||||
"materials" : [
|
||||
{
|
||||
"name" : "hexagons_medieval",
|
||||
"pbrMetallicRoughness" : {
|
||||
"baseColorTexture" : {
|
||||
"index" : 0
|
||||
},
|
||||
"metallicFactor" : 0,
|
||||
"roughnessFactor" : 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes" : [
|
||||
{
|
||||
"name" : "building_home_A_blue",
|
||||
"primitives" : [
|
||||
{
|
||||
"attributes" : {
|
||||
"POSITION" : 0,
|
||||
"TEXCOORD_0" : 1,
|
||||
"NORMAL" : 2
|
||||
},
|
||||
"indices" : 3,
|
||||
"material" : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures" : [
|
||||
{
|
||||
"sampler" : 0,
|
||||
"source" : 0
|
||||
}
|
||||
],
|
||||
"images" : [
|
||||
{
|
||||
"mimeType" : "image/png",
|
||||
"name" : "hexagons_medieval",
|
||||
"uri" : "hexagons_medieval.png"
|
||||
}
|
||||
],
|
||||
"accessors" : [
|
||||
{
|
||||
"bufferView" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 1540,
|
||||
"max" : [
|
||||
0.3959798812866211,
|
||||
0.9300001263618469,
|
||||
0.385000079870224
|
||||
],
|
||||
"min" : [
|
||||
-0.39597994089126587,
|
||||
-2.086162709247219e-08,
|
||||
-0.46865156292915344
|
||||
],
|
||||
"type" : "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView" : 1,
|
||||
"componentType" : 5126,
|
||||
"count" : 1540,
|
||||
"type" : "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView" : 2,
|
||||
"componentType" : 5126,
|
||||
"count" : 1540,
|
||||
"type" : "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView" : 3,
|
||||
"componentType" : 5123,
|
||||
"count" : 3033,
|
||||
"type" : "SCALAR"
|
||||
}
|
||||
],
|
||||
"bufferViews" : [
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 18480,
|
||||
"byteOffset" : 0,
|
||||
"target" : 34962
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 12320,
|
||||
"byteOffset" : 18480,
|
||||
"target" : 34962
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 18480,
|
||||
"byteOffset" : 30800,
|
||||
"target" : 34962
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 6066,
|
||||
"byteOffset" : 49280,
|
||||
"target" : 34963
|
||||
}
|
||||
],
|
||||
"samplers" : [
|
||||
{
|
||||
"magFilter" : 9729,
|
||||
"minFilter" : 9987
|
||||
}
|
||||
],
|
||||
"buffers" : [
|
||||
{
|
||||
"byteLength" : 55348,
|
||||
"uri" : "building_home_A_blue.bin"
|
||||
}
|
||||
]
|
||||
}
|
||||
42
assets/models/buildings/building_home_A_blue.gltf.import
Normal file
42
assets/models/buildings/building_home_A_blue.gltf.import
Normal file
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://digqjcku7u6jw"
|
||||
path="res://.godot/imported/building_home_A_blue.gltf-3d8bffb321af199d2bf47d5e9358bb70.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/buildings/building_home_A_blue.gltf"
|
||||
dest_files=["res://.godot/imported/building_home_A_blue.gltf-3d8bffb321af199d2bf47d5e9358bb70.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
BIN
assets/models/buildings/hexagons_medieval.png
Normal file
BIN
assets/models/buildings/hexagons_medieval.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
41
assets/models/buildings/hexagons_medieval.png.import
Normal file
41
assets/models/buildings/hexagons_medieval.png.import
Normal file
@@ -0,0 +1,41 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ikdyfxy2qnbi"
|
||||
path.s3tc="res://.godot/imported/hexagons_medieval.png-ea132e7b0754db3463363d12b2f4f568.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/buildings/hexagons_medieval.png"
|
||||
dest_files=["res://.godot/imported/hexagons_medieval.png-ea132e7b0754db3463363d12b2f4f568.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
BIN
assets/models/characters/Knight.glb
Normal file
BIN
assets/models/characters/Knight.glb
Normal file
Binary file not shown.
42
assets/models/characters/Knight.glb.import
Normal file
42
assets/models/characters/Knight.glb.import
Normal file
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://c7dnarple2ye6"
|
||||
path="res://.godot/imported/Knight.glb-f3b84697960dc30f97048f59f901a6e9.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/characters/Knight.glb"
|
||||
dest_files=["res://.godot/imported/Knight.glb-f3b84697960dc30f97048f59f901a6e9.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
BIN
assets/models/characters/Knight_knight_texture.png
Normal file
BIN
assets/models/characters/Knight_knight_texture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
44
assets/models/characters/Knight_knight_texture.png.import
Normal file
44
assets/models/characters/Knight_knight_texture.png.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://n1j7b5ws4ee0"
|
||||
path.s3tc="res://.godot/imported/Knight_knight_texture.png-fbfd6db275b893706e62960fa115ac3a.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "af4707185c3e9844f7ed166c1a2e24f8"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/characters/Knight_knight_texture.png"
|
||||
dest_files=["res://.godot/imported/Knight_knight_texture.png-fbfd6db275b893706e62960fa115ac3a.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
BIN
assets/models/characters/Skeleton_Mage.glb
Normal file
BIN
assets/models/characters/Skeleton_Mage.glb
Normal file
Binary file not shown.
42
assets/models/characters/Skeleton_Mage.glb.import
Normal file
42
assets/models/characters/Skeleton_Mage.glb.import
Normal file
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://7sbngaphdxht"
|
||||
path="res://.godot/imported/Skeleton_Mage.glb-dfda87ec6175e75ab440ca2eec40520e.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/characters/Skeleton_Mage.glb"
|
||||
dest_files=["res://.godot/imported/Skeleton_Mage.glb-dfda87ec6175e75ab440ca2eec40520e.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
BIN
assets/models/characters/Skeleton_Mage_skeleton_texture.png
Normal file
BIN
assets/models/characters/Skeleton_Mage_skeleton_texture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://wjwg0opxh8we"
|
||||
path.s3tc="res://.godot/imported/Skeleton_Mage_skeleton_texture.png-ddebdfcfc8bd13280cab68f2d8b07b2b.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "661544b1e8ba10b5a0c98b638eae6d0b"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/characters/Skeleton_Mage_skeleton_texture.png"
|
||||
dest_files=["res://.godot/imported/Skeleton_Mage_skeleton_texture.png-ddebdfcfc8bd13280cab68f2d8b07b2b.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
BIN
assets/models/characters/Skeleton_Minion.glb
Normal file
BIN
assets/models/characters/Skeleton_Minion.glb
Normal file
Binary file not shown.
42
assets/models/characters/Skeleton_Minion.glb.import
Normal file
42
assets/models/characters/Skeleton_Minion.glb.import
Normal file
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://dkn5fqfvkqog5"
|
||||
path="res://.godot/imported/Skeleton_Minion.glb-71f08c897190630c8e5ae9e5beda9052.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/characters/Skeleton_Minion.glb"
|
||||
dest_files=["res://.godot/imported/Skeleton_Minion.glb-71f08c897190630c8e5ae9e5beda9052.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
BIN
assets/models/characters/Skeleton_Minion_skeleton_texture.png
Normal file
BIN
assets/models/characters/Skeleton_Minion_skeleton_texture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://caa8qxgnpgv61"
|
||||
path.s3tc="res://.godot/imported/Skeleton_Minion_skeleton_texture.png-9dcb8889e4229fae505e24dc5a110852.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "661544b1e8ba10b5a0c98b638eae6d0b"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/characters/Skeleton_Minion_skeleton_texture.png"
|
||||
dest_files=["res://.godot/imported/Skeleton_Minion_skeleton_texture.png-9dcb8889e4229fae505e24dc5a110852.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
BIN
assets/models/characters/Skeleton_Warrior.glb
Normal file
BIN
assets/models/characters/Skeleton_Warrior.glb
Normal file
Binary file not shown.
42
assets/models/characters/Skeleton_Warrior.glb.import
Normal file
42
assets/models/characters/Skeleton_Warrior.glb.import
Normal file
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://cjgucsfp8qn1m"
|
||||
path="res://.godot/imported/Skeleton_Warrior.glb-87a31fa24e48c23f04684ddfdaed2d10.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/characters/Skeleton_Warrior.glb"
|
||||
dest_files=["res://.godot/imported/Skeleton_Warrior.glb-87a31fa24e48c23f04684ddfdaed2d10.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
BIN
assets/models/characters/Skeleton_Warrior_skeleton_texture.png
Normal file
BIN
assets/models/characters/Skeleton_Warrior_skeleton_texture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cvee751avas5j"
|
||||
path.s3tc="res://.godot/imported/Skeleton_Warrior_skeleton_texture.png-66c57b76e95467b6ce503b8825ef03ca.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
generator_parameters={
|
||||
"md5": "661544b1e8ba10b5a0c98b638eae6d0b"
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/characters/Skeleton_Warrior_skeleton_texture.png"
|
||||
dest_files=["res://.godot/imported/Skeleton_Warrior_skeleton_texture.png-66c57b76e95467b6ce503b8825ef03ca.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
@@ -1,64 +0,0 @@
|
||||
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 +0,0 @@
|
||||
uid://dbr02t7pt4vcn
|
||||
@@ -1,64 +0,0 @@
|
||||
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 +0,0 @@
|
||||
uid://bvxn6y15tvidu
|
||||
@@ -1,52 +1,60 @@
|
||||
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)
|
||||
signal entity_registered(entity: Node)
|
||||
signal entity_deregistered(entity: Node)
|
||||
|
||||
# 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)
|
||||
signal damage_requested(attacker: Node, target: Node, amount: float, element: int)
|
||||
signal heal_requested(healer: Node, target: Node, amount: float)
|
||||
signal damage_dealt(attacker: Node, target: Node, amount: float)
|
||||
signal entity_died(entity: Node)
|
||||
signal entity_respawned(entity: Node)
|
||||
signal health_changed(entity: Node, current: float, max: float)
|
||||
signal shield_changed(entity: Node, current: float, max: float)
|
||||
signal shield_broken(entity: Node)
|
||||
|
||||
# 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)
|
||||
signal ability_use_requested(player: Node, ability_index: int)
|
||||
signal ability_used(player: Node, ability_index: int, ability: Resource)
|
||||
signal cooldown_tick(entity: Node, cds: PackedFloat32Array, max_cds: PackedFloat32Array, gcd: float)
|
||||
signal role_changed(player: Node, role: int)
|
||||
signal target_changed(player: Node, target: Node)
|
||||
|
||||
# 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)
|
||||
signal effect_applied(target: Node, effect: Resource, source: Node)
|
||||
signal effect_expired(target: Node, effect: Resource)
|
||||
signal element_applied(target: Node, element: int)
|
||||
signal buff_changed(entity: Node, stat: StringName, value: float)
|
||||
|
||||
# Buff
|
||||
signal buff_changed(entity, stat, value)
|
||||
signal enemy_detected(enemy: Node, player: Node)
|
||||
signal enemy_lost(enemy: Node, player: Node)
|
||||
signal enemy_engaged(enemy: Node, target: Node)
|
||||
|
||||
# Gegner
|
||||
signal enemy_engaged(enemy, target)
|
||||
signal gate_destroyed(gate: Node)
|
||||
signal portal_spawned(portal: Node)
|
||||
signal portal_entered(portal: Node, player: Node)
|
||||
signal dungeon_cleared(seed: int)
|
||||
signal boss_defeated(boss: Node)
|
||||
|
||||
# Portal
|
||||
signal portal_spawn(portal, enemies)
|
||||
signal portal_defeated(portal)
|
||||
signal loot_dropped(items: Array, position: Vector3)
|
||||
signal item_picked_up(player: Node, item: Resource)
|
||||
signal inventory_changed(player: Node)
|
||||
signal item_crafted(player: Node, item: Resource)
|
||||
signal building_placed(building: Node)
|
||||
signal building_removed(building: Node)
|
||||
|
||||
# Dungeon
|
||||
signal dungeon_cleared()
|
||||
signal wave_started(wave_number: int)
|
||||
signal wave_timer_tick(seconds_remaining: float)
|
||||
signal wave_ended(wave_number: int, success: bool)
|
||||
signal invasion_started()
|
||||
signal invasion_ended(success: bool)
|
||||
signal village_damaged(current: float, max: float)
|
||||
signal village_destroyed()
|
||||
signal game_over()
|
||||
signal run_started(wave_number: int)
|
||||
|
||||
# Effects
|
||||
signal effect_requested(target, effect, source)
|
||||
signal effect_applied(target, effect)
|
||||
signal effect_expired(target, effect)
|
||||
signal xp_gained(player: Node, amount: float)
|
||||
signal level_up(player: Node, new_level: int)
|
||||
|
||||
# Elements
|
||||
signal element_damage_dealt(attacker, target, amount, element)
|
||||
signal element_applied(target, element)
|
||||
signal element_reaction(target, element_a, element_b, reaction_name)
|
||||
signal dialog_opened(player: Node, npc: Node)
|
||||
signal dialog_closed(player: Node)
|
||||
signal chat_message(peer_id: int, sender_name: String, text: String)
|
||||
|
||||
signal scene_change_requested(scene_path: String)
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://g7a7xkg1pgb4
|
||||
uid://361x7bdk2j6v
|
||||
|
||||
31
autoloads/game_lore.gd
Normal file
31
autoloads/game_lore.gd
Normal file
@@ -0,0 +1,31 @@
|
||||
extends Node
|
||||
|
||||
const WORLD_LORE: String = """Das Land heißt Aerwen. Vor siebzig Wintern öffnete sich der erste Schlund über Aerwen. Niemand weiß, woher sie kommen. Die Alten nennen es 'das Stille Beben' — der Himmel setzte für einen Atemzug aus, dann waren die ersten Risse da.
|
||||
|
||||
Die Schlünde sind Tore zu einer Anderseite — einem Land, das parallel zu unserem zu liegen scheint, aber gehässiger ist. Was dort lebt, atmet durch die Tore in unsere Welt: Knochenwächter, Schattenwölfe, manchmal Schlimmeres. Die meisten Dörfer und Städte sind in den letzten siebzig Jahren gefallen. Aerwen ist heute eine Karte aus leeren Höfen, verfallenen Stadtmauern und einigen wenigen sturen Siedlungen.
|
||||
|
||||
Schimmerthal ist eine dieser Siedlungen. Es liegt in einer flachen Senke, umgeben von brüchigen Wegen und verlassenen Gehöften. Die Bewohner nennen sich selbst 'die Sturen' — die, die nicht weiterziehen wollten oder konnten. Im Zentrum steht die Krumme-Wagen-Taverne, benannt nach einem verkrümmten Handwagen vor der Tür, der dort steht, seit niemand mehr lebt, der sich daran erinnern kann.
|
||||
|
||||
Unter der Taverne liegt ein Anker — ein alter Stein, von dem niemand mehr weiß, wer ihn gesetzt hat. Solange der Anker steht, schwächt er die Schlünde im Umkreis und lässt die Reisenden nicht vollständig sterben.
|
||||
|
||||
Die Schlünde (was Reisende 'Portale' nennen) atmen. Alle paar Stunden öffnet sich irgendwo in der Nähe ein neuer. Wer hindurchschreitet, kommt in einer Höhle der Anderseite heraus. Tötet man die Wächter im Schlund, kollabiert der Riss. Aber er wird ersetzt — irgendwo weiter draußen reißt ein neuer, stärkerer Schlund auf. Die Anderseite gibt nicht auf.
|
||||
|
||||
Etwa einmal pro Atemzug (so nennen die Bewohner die Wellen) reißt der Himmel rot auf — ein 'Tor des Herrn'. Dahinter sitzt eine große Bestie, ein Herr der Anderseite, der einen Schwarm anführt. Wird der Herr besiegt, beruhigt sich die Anderseite kurz. Danach kehrt sie wieder, lauter als vorher — der nächste Atemzug bringt stärkere Wächter. Verstreicht aber die Stunde, ohne dass der Herr fällt, dann erwacht er und führt seinen Schwarm durch den Riss, direkt auf die Taverne zu. Das nennen die Bewohner 'die Invasion'. Fällt die Taverne, fällt der Anker. Niemand weiß, was dann mit dem Land geschieht. Die wenigen, die einmal eine Invasion überlebt haben, sprechen nicht darüber.
|
||||
|
||||
Reisende werden die genannt, die nicht ganz sterben können. Wer einmal den Anker unter der Taverne berührt hat, den lässt der Tod los: ihre Wunden schließen sich, sie tauchen am Anker wieder auf, wenn sie fallen — solange der Anker steht. Es ist kein Segen. Manche im Dorf flüstern, ein Reisender trage ein Stück Anderseite in sich. Nur Reisende können tief in die Schlünde gehen, weil die Anderseite gewöhnliche Lebende zerreißt; einen Reisenden setzt sie nur unangenehm zurück. Reisende kommen selten und gehen oft. Niemand weiß, warum der Anker manche markiert und andere nicht.
|
||||
|
||||
Die wichtigsten Bewohner: Brena, Wirtin der Krumme-Wagen-Taverne, geboren in Schimmerthal, übernahm das Haus von ihrer Großmutter Mara, die als Kind den ersten Schlund aufreißen sah. Halvor, der Schmied, verlor sein rechtes Auge an einen Knochenwächter im fünften Sommer nach den Schlünden. Eyrie, die Murmlerin, ist alt genug, um sich an 'die Zeit davor' zu erinnern, und liest die Risse — sie weiß oft tagelang vorher, wann ein roter Schlund aufbricht. Rolf, der Bauer, bestellt die kleinen Felder östlich des Dorfes und hat zwei Söhne an die Schlünde verloren.
|
||||
|
||||
Geschichten im Dorf: Manche flüstern, die Anderseite seien wir selbst, vor langer Zeit — das gilt als Ammenmärchen. Andere sagen, der Anker sei kein Stein, sondern ein Versprechen, das jemand vor sehr langer Zeit gegeben hat. Eyrie sagt selten etwas dazu; wenn man sie fragt, lächelt sie nur: 'Frag den Anker, wenn du willst.' Es gibt Reisende, die behaupten, in der Anderseite manchmal Häuser zu sehen, die genau so aussehen wie die im Dorf. Halvor schnaubt, wenn er das hört."""
|
||||
|
||||
const DIALECT_HINTS: String = """- Sage 'Schlund' oder 'Riss' oder 'Tor' statt 'Portal'.
|
||||
- Sage 'Atemzug' statt 'Welle'.
|
||||
- Sage 'Reisender' (m) / 'Reisende' (f) statt 'Spieler'.
|
||||
- Sage 'Wächter' oder 'Herr' statt 'Boss'.
|
||||
- Sage 'Anderseite' für das, wohin die Schlünde führen.
|
||||
- Sage 'Anker' für den Stein unter der Taverne.
|
||||
- Im Zweifel ehrlich: 'Das weiß niemand.' statt erfinden."""
|
||||
|
||||
|
||||
func build_npc_context(_profile) -> String:
|
||||
return "WELT:\n%s\n\nSPRACHE:\n%s" % [WORLD_LORE, DIALECT_HINTS]
|
||||
1
autoloads/game_lore.gd.uid
Normal file
1
autoloads/game_lore.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://lyslauvf34ot
|
||||
39
autoloads/game_state.gd
Normal file
39
autoloads/game_state.gd
Normal file
@@ -0,0 +1,39 @@
|
||||
extends Node
|
||||
|
||||
const ROLE_TANK: int = 0
|
||||
const ROLE_DAMAGE: int = 1
|
||||
const ROLE_HEALER: int = 2
|
||||
|
||||
const SCENE_MAIN_MENU: String = "res://scenes/menu/main_menu.tscn"
|
||||
const SCENE_LOBBY: String = "res://scenes/menu/lobby.tscn"
|
||||
const SCENE_WORLD: String = "res://scenes/world/world.tscn"
|
||||
const SCENE_DUNGEON: String = "res://scenes/dungeon/dungeon.tscn"
|
||||
const SCENE_OPTIONS: String = "res://scenes/menu/options_menu.tscn"
|
||||
|
||||
var current_scene: String = SCENE_MAIN_MENU
|
||||
var paused: bool = false
|
||||
var run_seed: int = 0
|
||||
var dungeon_seed: int = 0
|
||||
var dungeon_red: bool = false
|
||||
var current_wave: int = 1
|
||||
var portal_return_position: Vector3 = Vector3.ZERO
|
||||
|
||||
func reset_run() -> void:
|
||||
run_seed = randi()
|
||||
current_wave = 1
|
||||
dungeon_seed = 0
|
||||
dungeon_red = false
|
||||
paused = false
|
||||
Stats.clear_all()
|
||||
|
||||
func change_scene(path: String) -> void:
|
||||
current_scene = path
|
||||
EventBus.scene_change_requested.emit(path)
|
||||
call_deferred("_do_change_scene", path)
|
||||
|
||||
func _do_change_scene(path: String) -> void:
|
||||
get_tree().change_scene_to_file(path)
|
||||
|
||||
func set_paused(value: bool) -> void:
|
||||
paused = value
|
||||
get_tree().paused = value
|
||||
1
autoloads/game_state.gd.uid
Normal file
1
autoloads/game_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dettmu50fjtvc
|
||||
148
autoloads/net.gd
Normal file
148
autoloads/net.gd
Normal file
@@ -0,0 +1,148 @@
|
||||
extends Node
|
||||
|
||||
const DEFAULT_PORT: int = 7777
|
||||
const MAX_PLAYERS: int = 4
|
||||
|
||||
signal peer_connected(id: int)
|
||||
signal peer_disconnected(id: int)
|
||||
signal connected_to_server()
|
||||
signal connection_failed()
|
||||
signal server_disconnected()
|
||||
signal world_ready()
|
||||
signal peer_world_loaded(peer_id: int)
|
||||
|
||||
var player_names: Dictionary = {}
|
||||
var local_name: String = "Player"
|
||||
var _expected_peers: Array = []
|
||||
var _ready_peers: Array = []
|
||||
|
||||
func _ready() -> void:
|
||||
multiplayer.peer_connected.connect(_on_peer_connected)
|
||||
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
|
||||
multiplayer.connected_to_server.connect(_on_connected_to_server)
|
||||
multiplayer.connection_failed.connect(_on_connection_failed)
|
||||
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
||||
|
||||
func host(port: int = DEFAULT_PORT, max_clients: int = MAX_PLAYERS - 1) -> bool:
|
||||
var peer := ENetMultiplayerPeer.new()
|
||||
var err := peer.create_server(port, max_clients)
|
||||
if err != OK:
|
||||
push_error("Net: failed to host on port %d (err=%d)" % [port, err])
|
||||
return false
|
||||
multiplayer.multiplayer_peer = peer
|
||||
player_names[1] = local_name
|
||||
return true
|
||||
|
||||
func join(address: String, port: int = DEFAULT_PORT) -> bool:
|
||||
var peer := ENetMultiplayerPeer.new()
|
||||
var err := peer.create_client(address, port)
|
||||
if err != OK:
|
||||
push_error("Net: failed to join %s:%d (err=%d)" % [address, port, err])
|
||||
return false
|
||||
multiplayer.multiplayer_peer = peer
|
||||
return true
|
||||
|
||||
func host_singleplayer() -> bool:
|
||||
if multiplayer.multiplayer_peer != null:
|
||||
multiplayer.multiplayer_peer.close()
|
||||
var peer := OfflineMultiplayerPeer.new()
|
||||
multiplayer.multiplayer_peer = peer
|
||||
player_names.clear()
|
||||
player_names[1] = local_name
|
||||
return true
|
||||
|
||||
func disconnect_net() -> void:
|
||||
if multiplayer.multiplayer_peer != null:
|
||||
multiplayer.multiplayer_peer.close()
|
||||
multiplayer.multiplayer_peer = null
|
||||
player_names.clear()
|
||||
|
||||
func is_host() -> bool:
|
||||
return multiplayer.multiplayer_peer == null or multiplayer.is_server()
|
||||
|
||||
func is_authority_for(node: Node) -> bool:
|
||||
return node.is_multiplayer_authority()
|
||||
|
||||
func local_id() -> int:
|
||||
if multiplayer.multiplayer_peer == null:
|
||||
return 1
|
||||
return multiplayer.get_unique_id()
|
||||
|
||||
func _on_peer_connected(id: int) -> void:
|
||||
peer_connected.emit(id)
|
||||
if is_host():
|
||||
_register_name.rpc_id(id, 1, local_name)
|
||||
|
||||
func _on_peer_disconnected(id: int) -> void:
|
||||
player_names.erase(id)
|
||||
peer_disconnected.emit(id)
|
||||
|
||||
func _on_connected_to_server() -> void:
|
||||
player_names[1] = "Host"
|
||||
player_names[multiplayer.get_unique_id()] = local_name
|
||||
_register_name.rpc(multiplayer.get_unique_id(), local_name)
|
||||
connected_to_server.emit()
|
||||
|
||||
func _on_connection_failed() -> void:
|
||||
multiplayer.multiplayer_peer = null
|
||||
connection_failed.emit()
|
||||
|
||||
func _on_server_disconnected() -> void:
|
||||
multiplayer.multiplayer_peer = null
|
||||
player_names.clear()
|
||||
server_disconnected.emit()
|
||||
|
||||
@rpc("any_peer", "reliable", "call_local")
|
||||
func _register_name(peer_id: int, name: String) -> void:
|
||||
player_names[peer_id] = name
|
||||
|
||||
func mark_world_loaded() -> void:
|
||||
if multiplayer.multiplayer_peer == null:
|
||||
_expected_peers = [1]
|
||||
_ready_peers = [1]
|
||||
world_ready.emit()
|
||||
return
|
||||
if is_host():
|
||||
_expected_peers = [1]
|
||||
for p in multiplayer.get_peers():
|
||||
_expected_peers.append(p)
|
||||
_ready_peers = [1]
|
||||
if _all_ready():
|
||||
_broadcast_ready.rpc()
|
||||
else:
|
||||
_client_world_loaded.rpc_id(1, multiplayer.get_unique_id())
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _client_world_loaded(peer_id: int) -> void:
|
||||
if not is_host():
|
||||
return
|
||||
if not peer_id in _expected_peers:
|
||||
_expected_peers.append(peer_id)
|
||||
if not peer_id in _ready_peers:
|
||||
_ready_peers.append(peer_id)
|
||||
peer_world_loaded.emit(peer_id)
|
||||
if _all_ready():
|
||||
_broadcast_ready.rpc()
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _load_scene_for_peer(path: String) -> void:
|
||||
GameState.change_scene(path)
|
||||
|
||||
func tell_peer_to_load_scene(peer_id: int, path: String) -> void:
|
||||
if not is_host():
|
||||
return
|
||||
_load_scene_for_peer.rpc_id(peer_id, path)
|
||||
|
||||
@rpc("authority", "reliable", "call_local")
|
||||
func _broadcast_ready() -> void:
|
||||
world_ready.emit()
|
||||
|
||||
func _all_ready() -> bool:
|
||||
for p in _expected_peers:
|
||||
if not p in _ready_peers:
|
||||
return false
|
||||
return true
|
||||
|
||||
func reset_world_ready() -> void:
|
||||
_expected_peers.clear()
|
||||
_ready_peers.clear()
|
||||
1
autoloads/net.gd.uid
Normal file
1
autoloads/net.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://1k1cufc2skfr
|
||||
@@ -1,145 +0,0 @@
|
||||
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 +0,0 @@
|
||||
uid://blmuqkl3aro5w
|
||||
@@ -1,45 +0,0 @@
|
||||
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 +0,0 @@
|
||||
uid://doullpjapcsk1
|
||||
46
autoloads/save_load.gd
Normal file
46
autoloads/save_load.gd
Normal file
@@ -0,0 +1,46 @@
|
||||
extends Node
|
||||
|
||||
const SAVE_DIR: String = "user://saves/"
|
||||
|
||||
func ensure_dir() -> void:
|
||||
DirAccess.make_dir_recursive_absolute(SAVE_DIR)
|
||||
|
||||
func save_path(slot: String) -> String:
|
||||
return SAVE_DIR + slot + ".save"
|
||||
|
||||
func save_run(slot: String, payload: Dictionary) -> bool:
|
||||
ensure_dir()
|
||||
var f := FileAccess.open(save_path(slot), FileAccess.WRITE)
|
||||
if f == null:
|
||||
push_error("SaveLoad: cannot open %s for write" % slot)
|
||||
return false
|
||||
f.store_string(JSON.stringify(payload, " "))
|
||||
f.close()
|
||||
return true
|
||||
|
||||
func load_run(slot: String) -> Dictionary:
|
||||
var path := save_path(slot)
|
||||
if not FileAccess.file_exists(path):
|
||||
return {}
|
||||
var f := FileAccess.open(path, FileAccess.READ)
|
||||
var text := f.get_as_text()
|
||||
f.close()
|
||||
var data: Variant = JSON.parse_string(text)
|
||||
if typeof(data) != TYPE_DICTIONARY:
|
||||
return {}
|
||||
return data
|
||||
|
||||
func list_slots() -> Array[String]:
|
||||
ensure_dir()
|
||||
var out: Array[String] = []
|
||||
var d := DirAccess.open(SAVE_DIR)
|
||||
if d == null:
|
||||
return out
|
||||
d.list_dir_begin()
|
||||
var fname := d.get_next()
|
||||
while fname != "":
|
||||
if not d.current_is_dir() and fname.ends_with(".save"):
|
||||
out.append(fname.trim_suffix(".save"))
|
||||
fname = d.get_next()
|
||||
d.list_dir_end()
|
||||
return out
|
||||
1
autoloads/save_load.gd.uid
Normal file
1
autoloads/save_load.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bstx6urqlutmq
|
||||
124
autoloads/stats.gd
Normal file
124
autoloads/stats.gd
Normal file
@@ -0,0 +1,124 @@
|
||||
extends Node
|
||||
|
||||
const SYNCED_STATS: Array = [
|
||||
"health", "shield", "max_health", "max_shield",
|
||||
"role", "level", "xp", "xp_to_next",
|
||||
"buff_damage", "buff_heal", "buff_shield",
|
||||
]
|
||||
const SYNC_INTERVAL: float = 0.10
|
||||
|
||||
var _entities: Dictionary = {}
|
||||
var _player_cache: Dictionary = {}
|
||||
var _dirty: Dictionary = {}
|
||||
var _accum: float = 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
set_process(true)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_accum += delta
|
||||
if _accum < SYNC_INTERVAL:
|
||||
return
|
||||
_accum = 0.0
|
||||
if multiplayer.multiplayer_peer == null or multiplayer.multiplayer_peer is OfflineMultiplayerPeer or not multiplayer.is_server():
|
||||
_dirty.clear()
|
||||
return
|
||||
if _dirty.is_empty():
|
||||
return
|
||||
for entity in _dirty.keys():
|
||||
if not is_instance_valid(entity) or not entity.is_inside_tree():
|
||||
continue
|
||||
_sync_stats_batch.rpc(String(entity.get_path()), _dirty[entity])
|
||||
_dirty.clear()
|
||||
|
||||
@rpc("authority", "unreliable_ordered")
|
||||
func _sync_stats_batch(path_str: String, changes: Dictionary) -> void:
|
||||
var entity := get_node_or_null(NodePath(path_str))
|
||||
if entity == null or not entity in _entities:
|
||||
return
|
||||
for stat in changes.keys():
|
||||
_entities[entity][stat] = changes[stat]
|
||||
match stat:
|
||||
"health":
|
||||
EventBus.health_changed.emit(entity, changes[stat], _entities[entity].get("max_health", 100.0))
|
||||
"shield":
|
||||
EventBus.shield_changed.emit(entity, changes[stat], _entities[entity].get("max_shield", 0.0))
|
||||
"role":
|
||||
EventBus.role_changed.emit(entity, changes[stat])
|
||||
"level":
|
||||
EventBus.level_up.emit(entity, changes[stat])
|
||||
"buff_damage", "buff_heal", "buff_shield":
|
||||
EventBus.buff_changed.emit(entity, StringName(stat), changes[stat])
|
||||
|
||||
func register(entity: Node, base_resource: Resource) -> void:
|
||||
if not is_instance_valid(entity):
|
||||
return
|
||||
var data: Dictionary = {}
|
||||
for prop in base_resource.get_property_list():
|
||||
var name: String = prop.name
|
||||
if not (prop.usage & PROPERTY_USAGE_STORAGE):
|
||||
continue
|
||||
if name in ["resource_local_to_scene", "resource_path", "resource_name", "resource_scene_unique_id", "script"]:
|
||||
continue
|
||||
data[name] = base_resource.get(name)
|
||||
data["health"] = data.get("max_health", 100.0)
|
||||
data["shield"] = data.get("max_shield", 0.0)
|
||||
data["shield_regen_timer"] = 0.0
|
||||
data["base_resource"] = base_resource
|
||||
_entities[entity] = data
|
||||
EventBus.entity_registered.emit(entity)
|
||||
|
||||
func deregister(entity: Node) -> void:
|
||||
if entity in _entities:
|
||||
_entities.erase(entity)
|
||||
EventBus.entity_deregistered.emit(entity)
|
||||
|
||||
func has(entity: Node) -> bool:
|
||||
return entity in _entities
|
||||
|
||||
func get_stat(entity: Node, stat: String, default: Variant = 0.0) -> Variant:
|
||||
if entity in _entities:
|
||||
return _entities[entity].get(stat, default)
|
||||
return default
|
||||
|
||||
func set_stat(entity: Node, stat: String, value: Variant) -> void:
|
||||
if not entity in _entities:
|
||||
return
|
||||
var prev: Variant = _entities[entity].get(stat)
|
||||
_entities[entity][stat] = value
|
||||
if stat in SYNCED_STATS and prev != value and multiplayer.multiplayer_peer != null and not (multiplayer.multiplayer_peer is OfflineMultiplayerPeer) and multiplayer.is_server() and is_instance_valid(entity) and entity.is_inside_tree():
|
||||
if not entity in _dirty:
|
||||
_dirty[entity] = {}
|
||||
_dirty[entity][stat] = value
|
||||
|
||||
|
||||
func get_all(entity: Node) -> Dictionary:
|
||||
return _entities.get(entity, {})
|
||||
|
||||
func entities() -> Array:
|
||||
return _entities.keys()
|
||||
|
||||
func entities_in_group(group: StringName) -> Array:
|
||||
var out: Array = []
|
||||
for e in _entities.keys():
|
||||
if is_instance_valid(e) and e.is_in_group(group):
|
||||
out.append(e)
|
||||
return out
|
||||
|
||||
func cache_player(peer_id: int, entity: Node) -> void:
|
||||
if entity in _entities:
|
||||
_player_cache[peer_id] = _entities[entity].duplicate(true)
|
||||
|
||||
func restore_player(peer_id: int, entity: Node) -> void:
|
||||
if peer_id in _player_cache:
|
||||
_entities[entity] = _player_cache[peer_id].duplicate(true)
|
||||
|
||||
func clear_player_cache(peer_id: int = -1) -> void:
|
||||
if peer_id == -1:
|
||||
_player_cache.clear()
|
||||
else:
|
||||
_player_cache.erase(peer_id)
|
||||
|
||||
func clear_all() -> void:
|
||||
_entities.clear()
|
||||
_player_cache.clear()
|
||||
1
autoloads/stats.gd.uid
Normal file
1
autoloads/stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cdrii2l4sefow
|
||||
@@ -1,8 +0,0 @@
|
||||
extends Resource
|
||||
class_name BaseStats
|
||||
|
||||
@export var max_health := 100.0
|
||||
@export var health_regen := 0.0
|
||||
@export var max_shield := 0.0
|
||||
@export var shield_regen_delay := 3.0
|
||||
@export var shield_regen_time := 5.0
|
||||
@@ -1 +0,0 @@
|
||||
uid://cet184f878lb8
|
||||
46
export_presets.cfg
Normal file
46
export_presets.cfg
Normal file
@@ -0,0 +1,46 @@
|
||||
[preset.0]
|
||||
|
||||
name="Linux"
|
||||
platform="Linux"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path=""
|
||||
patches=PackedStringArray()
|
||||
patch_delta_encoding=false
|
||||
patch_delta_compression_level_zstd=19
|
||||
patch_delta_min_reduction=0.1
|
||||
patch_delta_include_filters="*"
|
||||
patch_delta_exclude_filters=""
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
seed=0
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_export_mode=2
|
||||
|
||||
[preset.0.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
binary_format/embed_pck=false
|
||||
texture_format/s3tc_bptc=true
|
||||
texture_format/etc2_astc=false
|
||||
shader_baker/enabled=false
|
||||
binary_format/architecture="x86_64"
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script="#!/usr/bin/env bash
|
||||
export DISPLAY=:0
|
||||
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
|
||||
\"{temp_dir}/{exe_name}\" {cmd_args}"
|
||||
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
|
||||
pkill -x -f \"{temp_dir}/{exe_name} {cmd_args}\"
|
||||
rm -rf \"{temp_dir}\""
|
||||
@@ -11,17 +11,18 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="mmo"
|
||||
run/main_scene="res://scenes/world/world.tscn"
|
||||
run/main_scene="res://scenes/menu/main_menu.tscn"
|
||||
config/features=PackedStringArray("4.6", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
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"
|
||||
Net="*res://autoloads/net.gd"
|
||||
Stats="*res://autoloads/stats.gd"
|
||||
GameState="*res://autoloads/game_state.gd"
|
||||
SaveLoad="*res://autoloads/save_load.gd"
|
||||
GameLore="*res://autoloads/game_lore.gd"
|
||||
|
||||
[dotnet]
|
||||
|
||||
@@ -49,51 +50,109 @@ move_right={
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
jump={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
ability_1={
|
||||
"deadzone": 0.2,
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":49,"key_label":0,"unicode":49,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
ability_2={
|
||||
"deadzone": 0.2,
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":50,"key_label":0,"unicode":50,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
ability_3={
|
||||
"deadzone": 0.2,
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":51,"key_label":0,"unicode":51,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
ability_4={
|
||||
"deadzone": 0.2,
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":52,"key_label":0,"unicode":52,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
ability_5={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":53,"key_label":0,"unicode":53,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
target_next={
|
||||
"deadzone": 0.2,
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
class_tank={
|
||||
"deadzone": 0.2,
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":49,"key_label":0,"unicode":49,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
class_damage={
|
||||
"deadzone": 0.2,
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":50,"key_label":0,"unicode":50,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
class_healer={
|
||||
"deadzone": 0.2,
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":51,"key_label":0,"unicode":51,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
interact={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
inventory={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
crafting={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":67,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
build_mode={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":66,"key_label":0,"unicode":98,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
chat={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":89,"key_label":0,"unicode":121,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
map={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":77,"key_label":0,"unicode":109,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
pause={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
rotate_build={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
click_select={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
camera_drag={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":2,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[layer_names]
|
||||
|
||||
3d_physics/layer_1="World"
|
||||
3d_physics/layer_2="Player"
|
||||
3d_physics/layer_3="Enemy"
|
||||
3d_physics/layer_4="Hitbox"
|
||||
3d_physics/layer_5="Building"
|
||||
|
||||
[physics]
|
||||
|
||||
|
||||
20
resources/abilities/ability.gd
Normal file
20
resources/abilities/ability.gd
Normal file
@@ -0,0 +1,20 @@
|
||||
class_name Ability
|
||||
extends Resource
|
||||
|
||||
enum Type { SINGLE, AOE, UTILITY, ULT, PASSIVE }
|
||||
|
||||
@export var ability_name: StringName = &""
|
||||
@export var type: Type = Type.SINGLE
|
||||
@export var damage: float = 0.0
|
||||
@export var ability_range: float = 5.0
|
||||
@export var cooldown: float = 2.0
|
||||
@export var uses_gcd: bool = true
|
||||
@export var aoe_radius: float = 0.0
|
||||
@export var is_heal: bool = false
|
||||
@export var shield_value: float = 0.0
|
||||
@export var shield_multiplier: float = 0.0
|
||||
@export var passive_stat: StringName = &""
|
||||
@export var passive_value: float = 0.5
|
||||
@export var passive_radius: float = 50.0
|
||||
@export var element: int = Element.NONE
|
||||
@export var icon: Texture2D
|
||||
1
resources/abilities/ability.gd.uid
Normal file
1
resources/abilities/ability.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ma4hkxsfia27
|
||||
@@ -1,7 +1,9 @@
|
||||
extends Resource
|
||||
class_name AbilitySet
|
||||
extends Resource
|
||||
|
||||
@export var abilities: Array[Ability] = []
|
||||
@export var aa_damage: float = 10.0
|
||||
@export var role_name: StringName = &""
|
||||
@export var aa_damage: float = 5.0
|
||||
@export var aa_range: float = 10.0
|
||||
@export var aa_is_heal: bool = false
|
||||
@export var abilities: Array[Ability] = []
|
||||
@export var color: Color = Color.WHITE
|
||||
1
resources/abilities/ability_set.gd.uid
Normal file
1
resources/abilities/ability_set.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bn4hk5bimojsi
|
||||
13
resources/buildings/building.gd
Normal file
13
resources/buildings/building.gd
Normal file
@@ -0,0 +1,13 @@
|
||||
class_name Building
|
||||
extends Resource
|
||||
|
||||
enum Type { FLOOR, WALL, DOOR, ROOF }
|
||||
|
||||
@export var building_id: StringName = &""
|
||||
@export var display_name: String = ""
|
||||
@export var type: Type = Type.WALL
|
||||
@export var size: Vector3 = Vector3(1, 1, 1)
|
||||
@export var color: Color = Color.GRAY
|
||||
@export var icon: Texture2D
|
||||
@export var material_item: Item
|
||||
@export var material_cost: int = 1
|
||||
1
resources/buildings/building.gd.uid
Normal file
1
resources/buildings/building.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cb61cxd4li4ck
|
||||
15
resources/effects/effect.gd
Normal file
15
resources/effects/effect.gd
Normal file
@@ -0,0 +1,15 @@
|
||||
class_name Effect
|
||||
extends Resource
|
||||
|
||||
enum Type { BUFF, DEBUFF, AURA, DOT, HOT }
|
||||
|
||||
@export var effect_name: StringName = &""
|
||||
@export var type: Type = Type.BUFF
|
||||
@export var stat: StringName = &""
|
||||
@export var value: float = 0.0
|
||||
@export var is_multiplier: bool = false
|
||||
@export var duration: float = -1.0
|
||||
@export var aura_radius: float = 0.0
|
||||
@export var tick_interval: float = 0.0
|
||||
@export var element: int = Element.NONE
|
||||
@export var icon: Texture2D
|
||||
1
resources/effects/effect.gd.uid
Normal file
1
resources/effects/effect.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dvgpetv33u1a8
|
||||
19
resources/effects/element.gd
Normal file
19
resources/effects/element.gd
Normal file
@@ -0,0 +1,19 @@
|
||||
class_name Element
|
||||
extends RefCounted
|
||||
|
||||
const NONE: int = 0
|
||||
const FIRE: int = 1
|
||||
|
||||
static func name_of(e: int) -> String:
|
||||
match e:
|
||||
FIRE:
|
||||
return "Fire"
|
||||
_:
|
||||
return "None"
|
||||
|
||||
static func color_of(e: int) -> Color:
|
||||
match e:
|
||||
FIRE:
|
||||
return Color(1.0, 0.4, 0.1)
|
||||
_:
|
||||
return Color.WHITE
|
||||
1
resources/effects/element.gd.uid
Normal file
1
resources/effects/element.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://duxxvs1ild4we
|
||||
12
resources/items/item.gd
Normal file
12
resources/items/item.gd
Normal file
@@ -0,0 +1,12 @@
|
||||
class_name Item
|
||||
extends Resource
|
||||
|
||||
enum Category { ESSENCE, MATERIAL, EQUIPMENT, CONSUMABLE }
|
||||
|
||||
@export var item_id: StringName = &""
|
||||
@export var display_name: String = ""
|
||||
@export var category: Category = Category.MATERIAL
|
||||
@export var max_stack: int = 99
|
||||
@export var icon: Texture2D
|
||||
@export var equip_slot: StringName = &""
|
||||
@export var stat_bonus: Dictionary = {}
|
||||
1
resources/items/item.gd.uid
Normal file
1
resources/items/item.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cp3sxpqnv31o2
|
||||
9
resources/items/recipe.gd
Normal file
9
resources/items/recipe.gd
Normal file
@@ -0,0 +1,9 @@
|
||||
class_name Recipe
|
||||
extends Resource
|
||||
|
||||
@export var recipe_id: StringName = &""
|
||||
@export var display_name: String = ""
|
||||
@export var output_item: Item
|
||||
@export var output_count: int = 1
|
||||
@export var inputs: Array[Item] = []
|
||||
@export var input_counts: Array[int] = []
|
||||
1
resources/items/recipe.gd.uid
Normal file
1
resources/items/recipe.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ddntj0p320vkq
|
||||
10
resources/npcs/npc_profile.gd
Normal file
10
resources/npcs/npc_profile.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
class_name NpcProfile
|
||||
extends Resource
|
||||
|
||||
@export var npc_id: StringName = &""
|
||||
@export var display_name: String = ""
|
||||
@export_multiline var lore: String = ""
|
||||
@export_multiline var personality: String = ""
|
||||
@export_multiline var fallback_text: String = "..."
|
||||
@export var color: Color = Color(0.8, 0.7, 0.5)
|
||||
@export var greeting: String = "Hallo Reisender."
|
||||
1
resources/npcs/npc_profile.gd.uid
Normal file
1
resources/npcs/npc_profile.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bcqtqp3jfkfm1
|
||||
8
resources/stats/base_stats.gd
Normal file
8
resources/stats/base_stats.gd
Normal file
@@ -0,0 +1,8 @@
|
||||
class_name BaseStats
|
||||
extends Resource
|
||||
|
||||
@export var max_health: float = 100.0
|
||||
@export var health_regen: float = 0.0
|
||||
@export var max_shield: float = 0.0
|
||||
@export var shield_regen: float = 0.0
|
||||
@export var shield_regen_delay: float = 5.0
|
||||
1
resources/stats/base_stats.gd.uid
Normal file
1
resources/stats/base_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://rotp80banlee
|
||||
@@ -1,2 +1,4 @@
|
||||
extends EnemyStats
|
||||
class_name BossStats
|
||||
extends EnemyStats
|
||||
|
||||
@export var enrage_time: float = 60.0
|
||||
1
resources/stats/boss_stats.gd.uid
Normal file
1
resources/stats/boss_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cdyoffdfaor5m
|
||||
2
resources/stats/building_stats.gd
Normal file
2
resources/stats/building_stats.gd
Normal file
@@ -0,0 +1,2 @@
|
||||
class_name BuildingStats
|
||||
extends BaseStats
|
||||
1
resources/stats/building_stats.gd.uid
Normal file
1
resources/stats/building_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://le02tb117uh
|
||||
11
resources/stats/enemy_stats.gd
Normal file
11
resources/stats/enemy_stats.gd
Normal file
@@ -0,0 +1,11 @@
|
||||
class_name EnemyStats
|
||||
extends BaseStats
|
||||
|
||||
@export var speed: float = 3.0
|
||||
@export var attack_range: float = 2.0
|
||||
@export var attack_damage: float = 5.0
|
||||
@export var attack_cooldown: float = 1.5
|
||||
@export var aggro_radius: float = 12.0
|
||||
@export var leash_radius: float = 25.0
|
||||
@export var xp_value: float = 5.0
|
||||
@export var loot_chance: float = 1.0
|
||||
1
resources/stats/enemy_stats.gd.uid
Normal file
1
resources/stats/enemy_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bch6113rtsk05
|
||||
7
resources/stats/gate_stats.gd
Normal file
7
resources/stats/gate_stats.gd
Normal file
@@ -0,0 +1,7 @@
|
||||
class_name GateStats
|
||||
extends BaseStats
|
||||
|
||||
@export var spawn_count: int = 5
|
||||
@export var spawn_interval: float = 4.0
|
||||
@export var aggro_radius: float = 15.0
|
||||
@export var is_red: bool = false
|
||||
1
resources/stats/gate_stats.gd.uid
Normal file
1
resources/stats/gate_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cyroet7ce02ew
|
||||
18
resources/stats/player_stats.gd
Normal file
18
resources/stats/player_stats.gd
Normal file
@@ -0,0 +1,18 @@
|
||||
class_name PlayerStats
|
||||
extends BaseStats
|
||||
|
||||
@export var speed: float = 5.0
|
||||
@export var jump_velocity: float = 4.5
|
||||
@export var target_range: float = 20.0
|
||||
@export var combat_timeout: float = 5.0
|
||||
@export var respawn_time: float = 3.0
|
||||
@export var gcd_time: float = 0.5
|
||||
@export var aa_cooldown: float = 0.5
|
||||
|
||||
@export var level: int = 1
|
||||
@export var xp: float = 0.0
|
||||
@export var xp_to_next: float = 50.0
|
||||
|
||||
@export var buff_damage: float = 1.0
|
||||
@export var buff_heal: float = 1.0
|
||||
@export var buff_shield: float = 1.0
|
||||
1
resources/stats/player_stats.gd.uid
Normal file
1
resources/stats/player_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c42fif5nbdvur
|
||||
5
resources/stats/portal_stats.gd
Normal file
5
resources/stats/portal_stats.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
class_name PortalStats
|
||||
extends BaseStats
|
||||
|
||||
@export var lifetime: float = 0.0
|
||||
@export var is_red: bool = false
|
||||
1
resources/stats/portal_stats.gd.uid
Normal file
1
resources/stats/portal_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://beg4u508o3lcv
|
||||
2
resources/stats/village_stats.gd
Normal file
2
resources/stats/village_stats.gd
Normal file
@@ -0,0 +1,2 @@
|
||||
class_name VillageStats
|
||||
extends BaseStats
|
||||
1
resources/stats/village_stats.gd.uid
Normal file
1
resources/stats/village_stats.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dpyr5lcc45eub
|
||||
@@ -1,254 +1,133 @@
|
||||
[gd_scene format=3]
|
||||
[gd_scene load_steps=23 format=3 uid="uid://b0dungeon0001"]
|
||||
|
||||
[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"]
|
||||
[ext_resource type="Script" path="res://scenes/dungeon/dungeon_manager.gd" id="1"]
|
||||
[ext_resource type="PackedScene" uid="uid://b0player00001" path="res://scenes/entities/player/player.tscn" id="2"]
|
||||
[ext_resource type="Script" path="res://systems/health_system.gd" id="4"]
|
||||
[ext_resource type="Script" path="res://systems/shield_system.gd" id="5"]
|
||||
[ext_resource type="Script" path="res://systems/respawn_system.gd" id="6"]
|
||||
[ext_resource type="Script" path="res://systems/cooldown_system.gd" id="7"]
|
||||
[ext_resource type="Script" path="res://systems/role_system.gd" id="8"]
|
||||
[ext_resource type="Script" path="res://systems/effect_system.gd" id="9"]
|
||||
[ext_resource type="Script" path="res://systems/element_system.gd" id="10"]
|
||||
[ext_resource type="Script" path="res://systems/aggro_system.gd" id="11"]
|
||||
[ext_resource type="Script" path="res://systems/combat/ability_system.gd" id="12"]
|
||||
[ext_resource type="Script" path="res://systems/combat/auto_attack_system.gd" id="13"]
|
||||
[ext_resource type="Script" path="res://systems/spawn_system.gd" id="14"]
|
||||
[ext_resource type="Script" path="res://systems/xp_system.gd" id="17"]
|
||||
[ext_resource type="Script" path="res://systems/loot_system.gd" id="18"]
|
||||
[ext_resource type="Script" path="res://systems/inventory_system.gd" id="19"]
|
||||
[ext_resource type="Script" path="res://systems/chat_system.gd" id="24"]
|
||||
[ext_resource type="Script" path="res://systems/map_system.gd" id="25"]
|
||||
[ext_resource type="PackedScene" uid="uid://b0hud00001" path="res://scenes/hud/hud.tscn" id="26"]
|
||||
[ext_resource type="Script" path="res://systems/audio_system.gd" id="27"]
|
||||
|
||||
[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="ProceduralSkyMaterial" id="ProceduralSkyMaterial_1"]
|
||||
sky_top_color = Color(0.1, 0.05, 0.1, 1)
|
||||
sky_horizon_color = Color(0.15, 0.05, 0.05, 1)
|
||||
ground_horizon_color = Color(0.1, 0.05, 0.05, 1)
|
||||
ground_bottom_color = Color(0.0, 0.0, 0.0, 1)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_floor"]
|
||||
albedo_color = Color(0.2, 0.18, 0.15, 1)
|
||||
[sub_resource type="Sky" id="Sky_1"]
|
||||
sky_material = SubResource("ProceduralSkyMaterial_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)
|
||||
[sub_resource type="Environment" id="Environment_1"]
|
||||
background_mode = 2
|
||||
sky = SubResource("Sky_1")
|
||||
ambient_light_source = 3
|
||||
ambient_light_color = Color(0.2, 0.18, 0.2, 1)
|
||||
ambient_light_energy = 0.4
|
||||
|
||||
[node name="Dungeon" type="Node3D"]
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = SubResource("Environment_1")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(0.866, -0.354, 0.354, 0, 0.707, 0.707, -0.5, -0.612, 0.612, 0, 30, 0)
|
||||
light_energy = 0.7
|
||||
|
||||
[node name="DungeonGeometry" type="Node3D" parent="."]
|
||||
|
||||
[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")
|
||||
script = ExtResource("4")
|
||||
|
||||
[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")
|
||||
script = ExtResource("5")
|
||||
|
||||
[node name="RespawnSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("respawn_system")
|
||||
script = ExtResource("6")
|
||||
|
||||
[node name="SpawnSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("spawn_system")
|
||||
[node name="CooldownSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("7")
|
||||
|
||||
[node name="AuraSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("aura_system")
|
||||
[node name="RoleSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("8")
|
||||
|
||||
[node name="BuffSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("buff_system")
|
||||
|
||||
[node name="DebuffSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("debuff_system")
|
||||
[node name="EffectSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("9")
|
||||
|
||||
[node name="ElementSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("element_system")
|
||||
script = ExtResource("10")
|
||||
|
||||
[node name="HudSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("hud_system")
|
||||
[node name="AggroSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("11")
|
||||
|
||||
[node name="NameplateSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("nameplate_system")
|
||||
[node name="AbilitySystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("12")
|
||||
|
||||
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="."]
|
||||
navigation_mesh = SubResource("NavigationMesh_1")
|
||||
[node name="AutoAttackSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("13")
|
||||
|
||||
[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="SpawnSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("14")
|
||||
|
||||
[node name="BodenCollision" type="StaticBody3D" parent="."]
|
||||
[node name="LootSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("18")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="BodenCollision"]
|
||||
shape = SubResource("WorldBoundaryShape3D_1")
|
||||
[node name="XpSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("17")
|
||||
|
||||
[node name="WallSouth" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, -5.25)
|
||||
[node name="InventorySystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("19")
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="WallSouth"]
|
||||
mesh = SubResource("BoxMesh_north_south")
|
||||
[node name="ChatSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("24")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WallSouth"]
|
||||
shape = SubResource("BoxShape3D_north_south")
|
||||
[node name="MapSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("25")
|
||||
|
||||
[node name="WallNorth" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 85.25)
|
||||
[node name="AudioSystem" type="Node" parent="Systems"]
|
||||
script = ExtResource("27")
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="WallNorth"]
|
||||
mesh = SubResource("BoxMesh_north_south")
|
||||
[node name="EntityRoot" type="Node3D" parent="."]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WallNorth"]
|
||||
shape = SubResource("BoxShape3D_north_south")
|
||||
[node name="Players" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="WallEast" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.75, 1.5, 40)
|
||||
[node name="Enemies" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="WallEast"]
|
||||
mesh = SubResource("BoxMesh_east_west")
|
||||
[node name="Loot" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WallEast"]
|
||||
shape = SubResource("BoxShape3D_east_west")
|
||||
[node name="Portals" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="WallWest" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.75, 1.5, 40)
|
||||
[node name="Buildings" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="WallWest"]
|
||||
mesh = SubResource("BoxMesh_east_west")
|
||||
[node name="Gates" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="WallWest"]
|
||||
shape = SubResource("BoxShape3D_east_west")
|
||||
[node name="Npcs" type="Node3D" parent="EntityRoot"]
|
||||
|
||||
[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="PlayerSpawner" type="MultiplayerSpawner" parent="."]
|
||||
_spawnable_scenes = PackedStringArray("res://scenes/entities/player/player.tscn")
|
||||
spawn_path = NodePath("../EntityRoot/Players")
|
||||
|
||||
[node name="Player" parent="." instance=ExtResource("player")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, -3)
|
||||
[node name="EnemySpawner" type="MultiplayerSpawner" parent="."]
|
||||
_spawnable_scenes = PackedStringArray("res://scenes/entities/enemy/enemy.tscn")
|
||||
spawn_path = NodePath("../EntityRoot/Enemies")
|
||||
|
||||
[node name="HUD" parent="." instance=ExtResource("hud")]
|
||||
[node name="LootSpawner" type="MultiplayerSpawner" parent="."]
|
||||
_spawnable_scenes = PackedStringArray("res://scenes/entities/loot/loot_drop.tscn")
|
||||
spawn_path = NodePath("../EntityRoot/Loot")
|
||||
|
||||
[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")
|
||||
[node name="HUD" parent="." instance=ExtResource("26")]
|
||||
|
||||
132
scenes/dungeon/dungeon_generator.gd
Normal file
132
scenes/dungeon/dungeon_generator.gd
Normal file
@@ -0,0 +1,132 @@
|
||||
extends Node
|
||||
|
||||
const ROOM_HEIGHT: float = 4.0
|
||||
const WALL_THICKNESS: float = 0.4
|
||||
const CORRIDOR_WIDTH: float = 4.0
|
||||
const DOOR_WIDTH: float = 4.5
|
||||
|
||||
var rng: RandomNumberGenerator
|
||||
var rooms: Array = []
|
||||
|
||||
func generate(parent: Node3D, seed: int, scale_difficulty: float = 1.0) -> Dictionary:
|
||||
rng = RandomNumberGenerator.new()
|
||||
rng.seed = seed
|
||||
rooms.clear()
|
||||
var room_count: int = rng.randi_range(5, 8)
|
||||
var pos := Vector3(0, 0, 0)
|
||||
var dir := Vector3(0, 0, -1)
|
||||
for i in range(room_count):
|
||||
var w: float = rng.randf_range(8.0, 14.0)
|
||||
var d: float = rng.randf_range(8.0, 14.0)
|
||||
rooms.append({"pos": pos, "size": Vector3(w, ROOM_HEIGHT, d), "openings": []})
|
||||
if i == room_count - 1:
|
||||
break
|
||||
var corridor_len: float = rng.randf_range(4.0, 8.0)
|
||||
var step: Vector3 = dir * (max(w, d) * 0.5 + corridor_len + 4.0)
|
||||
pos += step
|
||||
if rng.randf() < 0.5:
|
||||
var rotate_left: bool = rng.randf() < 0.5
|
||||
dir = dir.rotated(Vector3.UP, PI * 0.5 * (1 if rotate_left else -1))
|
||||
_compute_openings()
|
||||
_build_geometry(parent)
|
||||
return {"rooms": rooms, "spawn": rooms[0].pos + Vector3(0, 1, 0), "boss": rooms[-1].pos}
|
||||
|
||||
func _compute_openings() -> void:
|
||||
for i in range(rooms.size() - 1):
|
||||
var rel: Vector3 = rooms[i + 1].pos - rooms[i].pos
|
||||
if abs(rel.x) > abs(rel.z):
|
||||
if rel.x > 0:
|
||||
rooms[i].openings.append("east")
|
||||
rooms[i + 1].openings.append("west")
|
||||
else:
|
||||
rooms[i].openings.append("west")
|
||||
rooms[i + 1].openings.append("east")
|
||||
else:
|
||||
if rel.z > 0:
|
||||
rooms[i].openings.append("south")
|
||||
rooms[i + 1].openings.append("north")
|
||||
else:
|
||||
rooms[i].openings.append("north")
|
||||
rooms[i + 1].openings.append("south")
|
||||
|
||||
func _build_geometry(parent: Node3D) -> void:
|
||||
for r in rooms:
|
||||
_build_room(parent, r.pos, r.size, r.openings)
|
||||
for i in range(rooms.size() - 1):
|
||||
_build_corridor(parent, rooms[i].pos, rooms[i + 1].pos)
|
||||
|
||||
func _build_room(parent: Node3D, center: Vector3, size: Vector3, openings: Array) -> void:
|
||||
_add_floor(parent, center, Vector2(size.x, size.z))
|
||||
var hw: float = size.x * 0.5
|
||||
var hd: float = size.z * 0.5
|
||||
_add_wall_with_opening(parent, center + Vector3(0, ROOM_HEIGHT * 0.5, -hd), Vector3(size.x, ROOM_HEIGHT, WALL_THICKNESS), "north", openings, true)
|
||||
_add_wall_with_opening(parent, center + Vector3(0, ROOM_HEIGHT * 0.5, hd), Vector3(size.x, ROOM_HEIGHT, WALL_THICKNESS), "south", openings, true)
|
||||
_add_wall_with_opening(parent, center + Vector3(-hw, ROOM_HEIGHT * 0.5, 0), Vector3(WALL_THICKNESS, ROOM_HEIGHT, size.z), "west", openings, false)
|
||||
_add_wall_with_opening(parent, center + Vector3(hw, ROOM_HEIGHT * 0.5, 0), Vector3(WALL_THICKNESS, ROOM_HEIGHT, size.z), "east", openings, false)
|
||||
|
||||
func _add_wall_with_opening(parent: Node3D, center: Vector3, size: Vector3, side: String, openings: Array, axis_x: bool) -> void:
|
||||
if not openings.has(side):
|
||||
_add_wall(parent, center, size)
|
||||
return
|
||||
if axis_x:
|
||||
var seg_len: float = (size.x - DOOR_WIDTH) * 0.5
|
||||
if seg_len <= 0.1:
|
||||
return
|
||||
var seg_offset: float = DOOR_WIDTH * 0.5 + seg_len * 0.5
|
||||
_add_wall(parent, center + Vector3(-seg_offset, 0, 0), Vector3(seg_len, size.y, size.z))
|
||||
_add_wall(parent, center + Vector3(seg_offset, 0, 0), Vector3(seg_len, size.y, size.z))
|
||||
else:
|
||||
var seg_len: float = (size.z - DOOR_WIDTH) * 0.5
|
||||
if seg_len <= 0.1:
|
||||
return
|
||||
var seg_offset: float = DOOR_WIDTH * 0.5 + seg_len * 0.5
|
||||
_add_wall(parent, center + Vector3(0, 0, -seg_offset), Vector3(size.x, size.y, seg_len))
|
||||
_add_wall(parent, center + Vector3(0, 0, seg_offset), Vector3(size.x, size.y, seg_len))
|
||||
|
||||
func _build_corridor(parent: Node3D, from: Vector3, to: Vector3) -> void:
|
||||
var mid: Vector3 = (from + to) * 0.5
|
||||
var d: float = from.distance_to(to)
|
||||
var dir: Vector3 = (to - from).normalized()
|
||||
_add_floor(parent, Vector3(mid.x, 0, mid.z), Vector2(d, CORRIDOR_WIDTH) if abs(dir.x) > abs(dir.z) else Vector2(CORRIDOR_WIDTH, d))
|
||||
|
||||
func _add_floor(parent: Node3D, center: Vector3, size: Vector2) -> void:
|
||||
var body := StaticBody3D.new()
|
||||
body.collision_layer = 1
|
||||
body.collision_mask = 0
|
||||
var mesh := MeshInstance3D.new()
|
||||
var box := BoxMesh.new()
|
||||
box.size = Vector3(size.x, 0.4, size.y)
|
||||
mesh.mesh = box
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(0.25, 0.22, 0.2)
|
||||
mesh.material_override = mat
|
||||
mesh.position = Vector3(0, -0.2, 0)
|
||||
var col := CollisionShape3D.new()
|
||||
var shape := BoxShape3D.new()
|
||||
shape.size = Vector3(size.x, 0.4, size.y)
|
||||
col.shape = shape
|
||||
col.position = Vector3(0, -0.2, 0)
|
||||
body.add_child(mesh)
|
||||
body.add_child(col)
|
||||
body.position = center
|
||||
parent.add_child(body)
|
||||
|
||||
func _add_wall(parent: Node3D, center: Vector3, size: Vector3) -> void:
|
||||
var body := StaticBody3D.new()
|
||||
body.collision_layer = 1
|
||||
body.collision_mask = 0
|
||||
var mesh := MeshInstance3D.new()
|
||||
var box := BoxMesh.new()
|
||||
box.size = size
|
||||
mesh.mesh = box
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(0.35, 0.32, 0.28)
|
||||
mesh.material_override = mat
|
||||
var col := CollisionShape3D.new()
|
||||
var shape := BoxShape3D.new()
|
||||
shape.size = size
|
||||
col.shape = shape
|
||||
body.add_child(mesh)
|
||||
body.add_child(col)
|
||||
body.position = center
|
||||
parent.add_child(body)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user