From cf5979803e962644cedb1ab2a7fe73cfbb7d1f31 Mon Sep 17 00:00:00 2001 From: Marek Lenczewski Date: Thu, 16 Apr 2026 15:11:15 +0200 Subject: [PATCH] wahnsinn --- base.md | 42 ++++++++++++++++++++ module.md | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ plan.md | 86 +++++++++++++++++++++++++++++++++------- 3 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 base.md create mode 100644 module.md diff --git a/base.md b/base.md new file mode 100644 index 0000000..3994d15 --- /dev/null +++ b/base.md @@ -0,0 +1,42 @@ +# MMO — Vision & Kernspiel + +## Vision +Community-MMO (Godot 4.6). Ziel: Einsamkeit bekämpfen durch geographische Nähe der Spieler. Stilles Designprinzip — nicht als „Anti-Einsamkeits-Spiel" vermarktet. + +## Leitmotto +„Nie allein passiv konsumieren" + +## Phase 1 — Singleplayer-Kern + +### Welt +- Taverne im Zentrum — zu verteidigender Kern +- Regelmäßig spawnen Portale auf der Karte +- Jedes Portal führt in einen Dungeon +- Pro Welle spawnt zusätzlich **ein rotes Portal** (= deutlich stärkerer Dungeon) + +### Loop +1. Normale Portale clearen → XP → Level-Up +2. Rotes Portal angehen, sobald stark genug +3. Welle endet, wenn: + - **Rotes Portal besiegt** → nächste Welle, alle Portale stärker + - **Timer (1h) abläuft** → Monster des roten Portals brechen aus und greifen die Taverne an + - Invasion besiegt → nächste Welle + - Taverne zerstört → **Game Over (harter Reset)** + +### Ziel +So viele Wellen wie möglich überleben. + +### Progression +- Leveln durch Portal-Clears +- Jedes Level **verdoppelt alle Attribute** +- Kein Meta-Progress über Runs hinweg (Roguelike — harter Reset bei Game Over) + +## Spätere Phasen +- Multiplayer (mehrere Spieler verteidigen dieselbe Taverne gemeinsam) +- Crafting / Bau +- Weitere Elementar-Typen (aktuell nur Feuer) + +## Dokument-Rollen +- **base.md** (dieses Dokument) — Vision + WAS +- **plan.md** — Projektstruktur + Soll-Zustand (HOW) +- **CLAUDE.md** — aktueller Ist-Stand + Richtlinien diff --git a/module.md b/module.md new file mode 100644 index 0000000..3fdcc01 --- /dev/null +++ b/module.md @@ -0,0 +1,114 @@ +# Module — Implementierungs-Schritte zum Prototyp + +Jedes Modul ist ein self-contained Inkrement. Nach jedem Modul ist der Prototyp spielbarer als vorher. Pro Modul: planen → zeigen → implementieren → testen. + +## M1 — Ist-Stand-Audit ✓ +Existierender Code gegen `plan.md` abgeglichen. Ergebnis: Core-Loop zu ~60% da, aber große Abweichungen in Autoload- und System-Struktur. Siehe M1b. + +## M1b — Architektur-Refactor +Ist-Stand an `plan.md` angleichen, bevor neue Features gebaut werden. Jeder Sub-Schritt einzeln testen. + +### Sub-Schritte +1. **Zentraler `stats.gd` Autoload** + - Neue Datei `autoloads/stats.gd` mit API: `register(entity, resource)`, `get(entity, stat_name)`, `set(entity, stat_name, value)`, `deregister(entity)` + - Hält Dictionary[Node, Dictionary] für Laufzeit-Werte aller Entities + - Migration: player_stats.gd, enemy_stats.gd, boss_stats.gd, portal_stats.gd → alle Lese-/Schreib-Stellen auf zentrales `Stats` umstellen + - Entity-Registrierungs-Pattern: `_ready() → Stats.register(self, stats_resource)`, `_exit_tree() → Stats.deregister(self)` + - player_cache (für Szenenwechsel) in Stats + +2. **`game_state.gd` Autoload** + - Neue Datei `autoloads/game_state.gd` + - Speichert: current_role, player_position, current_wave (später) + - Wird von Szenen gelesen beim Laden + +3. **Systeme → Szenen-Skripte umziehen** + - `systems/role_system.gd` entfernen, Logik nach `scenes/player/role/role.gd` (prüfen: schon da?) + - `systems/targeting_system.gd` entfernen, Logik nach `scenes/player/targeting.gd` + - `systems/hud_system.gd` entfernen, Logik in `scenes/hud/hud_*.gd` (vitals, respawn, abilities, effects) + - `systems/nameplate_system.gd` entfernen, in Enemy-Szene integrieren + - `systems/portal_system.gd` entfernen, Logik in `scenes/portal/init.gd` / `portal.gd` + - `systems/dungeon_system.gd` entfernen, Logik in `scenes/dungeon/dungeon_manager.gd` + - Alle Signal-Verdrahtungen anpassen + +4. **Effect-Hierarchie aufräumen** + - `buff_system.gd` + `debuff_system.gd` → Children von `effect_system.gd` + - Neuer `buff_calc_system.gd` als Child von `effect_system.gd` (aggregiert Multiplier → Stats/Shield) + +5. **`attack_system.gd` vs `ability_system.gd`** + - Rollen klären: Ability-Trigger + Schaden-Applikation + - Wahrscheinlich: `attack_system.gd` = AutoAttack-Loop (_process-driven), `ability_system.gd` = Ability-Execution bei Input + - Falls Duplikat: mergen. Falls getrennt: umbenennen zu `auto_attack_system.gd` wie plan.md + +6. **Core-Loop-Test nach Refactor** + - Godot starten, Portal angreifen, Dungeon betreten, Boss besiegen, zur Taverne zurück + - Rollen-Wechsel testen, alle Abilities, Respawn, Effekte + - Keine Regressions + +7. **`plan.md` finalisieren** + - Ungenaue Referenzen (`combat.gd`, `combat_state.gd`, `enemy_movement.gd`) durch Ist-Skripte ersetzen + - System-Liste auf tatsächliche Anzahl anpassen + - Autoload-Sektion final + +## M2 — Startmenü +- `scenes/menu/main_menu.tscn` — Buttons: Singleplayer, Host, Join, Quit +- Singleplayer → `world.tscn`; Host/Join zunächst disabled (bis M11) +- `project.godot`: main_scene = main_menu + +## M3 — Taverne als Entity mit HP +- `TavernStats` Resource (nur `max_health`) +- Taverne-Node: `HitArea` (Area3D), `Healthbar`, registriert bei Stats +- HUD: Tavern-HP oben mittig +- Taverne HP = 10 × normales Portal HP + +## M4 — WaveSystem (Basis) +- `wave_system.gd`: Timer (1h), Wellen-Counter +- PortalSpawner refactoren: immer max 3 normale (sofort nachspawnen) +- HUD: Wellen-Nummer + Restzeit oben + +## M5 — Rotes Portal +- `PortalStats.variant` = NORMAL | RED +- Rotes Portal: 10× HP, rote Mesh, Gruppe `red_portal` +- WaveSystem spawnt 1 rotes pro Welle, nach Besieg → nächste Welle + neues rotes +- Dungeon-Gegner des roten Portals: 10× stärker + +## M6 — XP + Level-System +- `PlayerStats`: `level, xp, xp_to_next` +- `xp_system.gd`: XP bei Dungeon-Clear (1 normal, 10 rot) +- Fibonacci-Level-Kurve +- Level-Up: alle PlayerStats × 2, aktuelle HP/Schild auf neues Max +- HUD: XP-Balken + Level-Label + +## M7 — Gegner-Skalierung +- EnemyStats × 2^(player_level−1) bei Spawn +- Rotes Portal: zusätzlich × 10 + +## M8 — InvasionSystem + Game-Over +- Timer-Ablauf ohne rotes Portal besiegt: InvasionSystem aktiviert +- Alle noch lebenden Gegner des roten Portals brechen aus, spawnen am Rand, laufen zur Taverne +- Schaden an Taverne über HealthSystem +- Alle tot → `invasion_ended(true)` → nächste Welle + neues rotes +- Taverne HP 0 → `tavern_destroyed` → Game-Over-Overlay → 3s Delay → Reset: Level 1, Welle 1, Stats Base, zurück ins Startmenü oder neuer Run + +## M9 — Modelle + Animationen +- Einfache 3D-Modelle (Mixamo / Kenney / KayKit) für Spieler, Gegner, Boss, Portal, Taverne +- Animationen: Idle, Walk, Attack, Death +- Simple Shader (unlit oder lite-PBR mit Emissive für Portale) + +## M10 — Audio +- Musik: Taverne (ruhig), Kampf, Invasion (bedrohlich) +- SFX: Ability-Cast, Hit, Death, Portal-Spawn, Level-Up, Invasion-Alarm, Taverne-Schaden +- Quellen: frei zugängliche CC0-Packs (freesound, OpenGameArt) — wird pro Sound dokumentiert + +## M11 — Multiplayer P2P +- `ENetMultiplayerPeer` — Host öffnet Port, Client joint via IP +- `MultiplayerSpawner` für Spieler + Gegner +- `MultiplayerSynchronizer` für Stats-Felder +- Authoritative Host: Wave/Invasion/Taverne-State liegt beim Host +- Shared World (1 Taverne, alle Spieler verteidigen gemeinsam) +- Startmenü: Host/Join aktivieren + +## Reihenfolge-Begründung +- M1 klärt Ist-Stand (verhindert doppelte Arbeit) +- M2–M8 bringen den Run-Loop zum Laufen (Kernspiel komplett spielbar) +- M9–M10 machen es „erlebbar" (Look + Feel) +- M11 als Letztes — größter Brocken, baut auf stabilem Core auf diff --git a/plan.md b/plan.md index 95e727e..8157d15 100644 --- a/plan.md +++ b/plan.md @@ -1,3 +1,15 @@ +# Kernspiel +Siehe `base.md` für Vision und Ziel. Dieses Dokument beschreibt die Implementierung. + +## Run-Loop +- Welt mit Taverne im Zentrum, regelmäßig spawnen Portale (je ein Dungeon) +- Pro Welle zusätzlich **ein rotes Portal** (= deutlich stärkerer Dungeon) +- Portal-Clear (Dungeon-Boss besiegt) → XP → Level-Up **verdoppelt alle Attribute** +- Timer 1h pro Welle. Läuft er ab, ohne rotes Portal besiegt: Monster des roten Portals brechen aus → Invasion greift Taverne an +- Welle endet: + - Rotes Portal besiegt ODER Invasion abgewehrt → nächste Welle, alles stärker + - Taverne zerstört → **Game Over** (harter Reset, neuer Run) + # Projektstruktur ## Ordnerstruktur @@ -23,7 +35,7 @@ scenes/ — Darstellung + Input systems/ — Spiellogik aggro/ — AggroSystem (system, tracker, decay, events) + aggro_config effect.gd — Effect Resource (Buff/Debuff/Aura Daten) - 11× *_system.gd — health, shield, ability, auto_attack, cooldown, enemy_ai, respawn, spawn, effect, element, buff_calc + 14× *_system.gd — health, shield, ability, auto_attack, cooldown, enemy_ai, respawn, spawn, effect, element, buff_calc, wave, xp, invasion aura_system.gd — Aura-Propagierung (Child von EffectSystem) autoloads/ — Globaler Zustand event_bus.gd @@ -33,7 +45,7 @@ autoloads/ — Globaler Zustand ## Szenenbaum - Welt - - Systems (12 Systeme als Child-Nodes) + - Systems (15 Systeme als Child-Nodes) - Taverne - Player - Portale (dynamisch) @@ -90,6 +102,22 @@ autoloads/ — Globaler Zustand - element_damage_dealt(attacker, target, amount, element) - element_applied(target, element) - element_reaction(target, element_a, element_b, reaction_name) +- Wave: + - wave_started(wave_number) + - wave_timer_tick(seconds_remaining) + - wave_ended(wave_number, success) +- XP / Level: + - xp_gained(player, amount) + - level_up(player, new_level) +- Taverne: + - tavern_damaged(current, max) + - tavern_destroyed() +- Invasion: + - invasion_started(enemies) + - invasion_ended(success) +- Run: + - run_started(wave_number) + - game_over() ## Stats (autoload/stats.gd) - Speichert aktuelle Attribute aller Entities @@ -106,11 +134,15 @@ autoloads/ — Globaler Zustand - max_health, health_regen, max_shield, shield_regen_delay, shield_regen_time ### PlayerStats (player_stats.gd, extends BaseStats) - speed, jump_velocity, target_range, combat_timeout, respawn_time, gcd_time, aa_cooldown +- level, xp, xp_to_next — Level-Up verdoppelt alle Attribute ### EnemyStats (enemy_stats.gd, extends BaseStats) - speed, attack_range, attack_cooldown, attack_damage, regen_fast, regen_slow, aggro_decay, portal_radius, alert_radius ### BossStats (boss_stats.gd, extends EnemyStats) ### PortalStats (portal_stats.gd, extends BaseStats) - spawn_count, thresholds +- variant: NORMAL | RED (rotes Portal: höhere HP, stärkere Gegner + Boss) +### TavernStats (tavern_stats.gd, extends BaseStats) +- Nur max_health (kein Schild, kein Regen) — bei 0 HP → tavern_destroyed → Game Over ## resources/roles/ ### Ability (ability.gd) @@ -191,6 +223,26 @@ autoloads/ — Globaler Zustand - Portal-HP-Schwellen-Spawning - Listener: health_changed, entity_died - Event: portal_spawn +### WaveSystem (wave_system.gd) +- Wellen-Management: Timer (1h), Wellen-Counter, Portal-Skalierung pro Welle +- Startet neuen Run, spawnt 1× rotes Portal pro Welle, steuert normale Portal-Spawns +- Rotes Portal besiegt ODER Invasion abgewehrt → nächste Welle, Portal-Stats skalieren +- Timer-Ablauf ohne rotes Portal besiegt → löst InvasionSystem aus +- Bei tavern_destroyed: Game Over, nach Delay neuen Run starten (Reset: Level 1, Portale weg) +- Event: wave_started, wave_timer_tick, wave_ended, run_started, game_over +- Listener: portal_defeated, invasion_ended, tavern_destroyed +### XpSystem (xp_system.gd) +- XP bei Dungeon-Clear (Boss besiegt); rotes Portal gibt mehr XP +- Level-Up: alle PlayerStats-Attribute ×2, aktuelle HP/Schild auf neues Max heben +- Event: xp_gained, level_up +- Listener: entity_died (Boss), dungeon_cleared +### InvasionSystem (invasion_system.gd) +- Wird von WaveSystem ausgelöst, wenn Timer abläuft ohne rotes Portal besiegt +- Spawnt Monster des roten Portals an Karten-Rändern, Ziel = Taverne +- Schaden an Taverne über HealthSystem (TavernStats registriert wie Entity) +- Alle Invasions-Gegner tot → invasion_ended(true) +- Event: invasion_started, invasion_ended +- Listener: wave_ended (timeout), entity_died (Invasions-Gegner) # Szenen - Szenen sind Views — rendern, Input senden, Events empfangen @@ -200,12 +252,16 @@ autoloads/ — Globaler Zustand ## Welt (world/) - world.tscn — Hauptszene (100x100m) - - Systems (alle 11 Systeme als Child-Nodes) + - Systems (alle Systeme als Child-Nodes, siehe Systeme-Sektion) - NavigationRegion3D - Boden (MeshInstance3D, 100x100m PlaneMesh) - Kollision (StaticBody3D, WorldBoundaryShape3D) - Licht (DirectionalLight3D, 45°, Schatten) - Taverne (StaticBody3D, BoxMesh, Mitte der Karte) + - Gruppe (tavern) + - HitArea (Area3D) — nimmt Invasions-Schaden entgegen + - Healthbar (Sprite3D + SubViewport, groß, nur Health — kein Schild) + - Registriert bei Stats mit TavernStats Resource - Spieler (Instanz von player.tscn) - HUD (Instanz von hud.tscn) - PortalSpawner (Node, portal_spawner.gd) @@ -218,12 +274,12 @@ autoloads/ — Globaler Zustand - CameraPivot (Node3D, camera.gd) - Camera3D - Movement (Node, movement.gd) — WASD + Springen, liest Werte von Stats - - Combat (Node, combat.gd) — Input-Handler, emittiert ability_use_requested - - CombatState (Node, combat_state.gd) — in_combat-Tracking, Combat-Timer - - Role (Node, role.gd) — Rollenwechsel ALT+1/2/3, emittiert role_changed (auch bei _ready) + - Ability (Node, ability.gd) — Input-Handler 1/2/3/4, emittiert ability_use_requested + - Role (Node, role/role.gd) — Rollenwechsel ALT+1/2/3, emittiert role_changed (auch bei _ready) - Targeting (Node, targeting.gd) — Klick/TAB/Auto-Target, emittiert target_changed -- player.gd — Registriert bei Stats mit PlayerStats Resource, Sichtbarkeit bei Tod/Respawn +- init.gd — Registriert bei Stats mit PlayerStats Resource, Sichtbarkeit bei Tod/Respawn - camera.gd — LMB freies Umsehen, RMB Kamera + Laufrichtung +- Combat-State (in_combat, Combat-Timer) wird im AggroSystem (aggro_decay.gd) getrackt, nicht als Player-Node ## Gegner (enemy/) - enemy.tscn — CharacterBody3D @@ -231,14 +287,14 @@ autoloads/ — Globaler Zustand - Kollision (CapsuleShape3D) - Mesh (SphereMesh) - HitArea (Area3D) - - DetectionArea (Area3D, emittiert enemy_detected) + - DetectionArea (Area3D, Detection-Node, detection.gd, emittiert enemy_detected/lost) - NavigationAgent3D - - EnemyMovement (Node, enemy_movement.gd) — Empfängt Bewegungsbefehle + - Bewegung wird vom AI-System (ai_system.gd) gesteuert — Enemy hat keinen eigenen Movement-Node - Healthbar (Sprite3D + SubViewport, healthbar.gd) — Health-Anzeige - HealthbarShield (Node, healthbar_shield.gd) — Shield-Anzeige - HealthbarStatus (Node, healthbar_status.gd) — Target-Border + Aggro-Farbe - HealthbarEffects (Node, healthbar_effects.gd) — Effekt-Icons -- enemy.gd — Registriert bei Stats mit EnemyStats Resource, Detection-Area Signal +- init.gd — Registriert bei Stats mit EnemyStats Resource, Detection-Area Signal - Aggro-Regeln (Werte in AggroConfig Resource): - Aufbau: - Schaden = Aggro (1:1), Tank 2x Multiplikator @@ -258,20 +314,20 @@ autoloads/ — Globaler Zustand ## Boss (enemy/) - boss.tscn — wie enemy.tscn aber größer (Mesh lila, 1.5x) - Gruppe (enemies, boss) -- boss.gd — Erbt von enemy.gd, registriert bei Stats mit BossStats Resource +- Nutzt enemy/init.gd (gleiche Registrierung), aber mit BossStats Resource ## Portal (portal/) -- portal.tscn — StaticBody3D - - Gruppe (portals) +- portal.tscn — StaticBody3D (variant: NORMAL = blau, RED = rot, stärker) + - Gruppe (portals); rotes Portal zusätzlich in Gruppe (red_portal) - Kollision (CylinderShape3D) - - Mesh (CylinderMesh, blau) + - Mesh (CylinderMesh, blau normal / rot für rotes Portal) - HitArea (Area3D) - DetectionArea (Area3D, Auto-Targeting bei Betreten) - Healthbar (Sprite3D + SubViewport, healthbar.gd) - HealthbarShield (Node, healthbar_shield.gd) - HealthbarStatus (Node, healthbar_status.gd) - HealthbarEffects (Node, healthbar_effects.gd) -- portal.gd — Registriert bei Stats mit PortalStats Resource +- init.gd — Registriert bei Stats mit PortalStats Resource - Spawnt Gegner bei HP-Schwellen (→ SpawnSystem) ## Gate (portal/)