chore: initial project state (godot-mcp addon, docs)

This commit is contained in:
2026-04-13 04:34:39 +02:00
commit c4d29486c4
67 changed files with 6185 additions and 0 deletions

39
docs/STATUS.md Normal file
View File

@@ -0,0 +1,39 @@
# Ruf der Pilze — Projektstatus
Zuletzt aktualisiert: 2026-04-13
---
## Aktueller Stand
### ✅ Abgeschlossen
- MCP-Addon eingerichtet (`godot-mcp`, Claude Code verbunden)
- Projektstruktur angelegt (`ruf-der-pilze/`)
- CLAUDE.md mit vollständiger Spielkonzept-Dokumentation
### 🔄 In Arbeit
— (nichts aktiv)
### ⏳ Als nächstes
- **Multiplayer Grundgerüst** → Plan: `docs/plans/multiplayer-grundgeruest.md`
- Server-Szene (headless-fähig)
- Clients verbinden
- Basis-RPC testen
---
## Entwicklungs-Reihenfolge (gesamt)
1. ✅ MCP eingerichtet
2. ⏳ Multiplayer Grundgerüst (Server, Clients verbinden, rpc testen)
3. ⏳ Refektorium — asymmetrische Wahrnehmung (erster Raum)
4. ⏳ DM Regiepult Basics — Overlay-Toggle
5. ⏳ Alle Räume aufbauen
6. ⏳ Polish — Audio, Nebel, Licht, Würfel-UI
---
## Offene Entscheidungen
- Transport: ENet oder WebSocket? (WebSocket = Browser-kompatibel, ENet = performanter)
- VPS bereits vorhanden oder noch einzurichten?

0
docs/plans/.gitkeep Normal file
View File

View File

@@ -0,0 +1,184 @@
# Multiplayer Grundgerüst — Design Spec
**Datum:** 2026-04-13
**Status:** Abgenommen
**Projekt:** Ruf der Pilze (`ruf-der-pilze/`)
---
## Kontext
Das Spiel benötigt eine Multiplayer-Basis bevor irgendeine Spiellogik gebaut werden kann. Dieses Grundgerüst schafft die Verbindungsschicht: Server startet, Clients verbinden, RPCs funktionieren. Alles Weitere (Räume, Overlays, DM-Regiepult) baut darauf auf.
---
## Entscheidungen
| Entscheidung | Wahl | Begründung |
|---|---|---|
| Transport | ENet | Native Godot UDP, geringste Latenz, kein Browser-Support benötigt |
| Dev-Server | Lokal headless | Kein VPS-Overhead während Entwicklung |
| Max. Verbindungen | 8 | 5 Spieler + 1 DM + 2 Reserve |
| Architektur | Eine Hauptszene + Autoload | Godot-idiomatisch |
| Port | 4242 | Projektstandard |
| Server-Erkennung | `--server` CLI-Argument | `OS.has_feature("dedicated_server")` gilt nur für Server-Exports, nicht für headless Dev-Runs |
---
## Dateistruktur
```
ruf-der-pilze/
├── scenes/
│ └── main.tscn ← Root-Szene (Node)
├── scripts/
│ ├── main.gd ← Modus-Erkennung + Bootstrap
│ └── network_manager.gd ← Autoload: Peer-Verwaltung, Signale
└── project.godot ← NetworkManager als Autoload registriert
```
---
## Architektur
### NetworkManager (Autoload)
Singleton, der den gesamten Multiplayer-Peer hält. Szenen greifen über `NetworkManager` auf Verbindungsstatus und peer_ids zu.
**Verantwortlichkeiten:**
- ENetMultiplayerPeer erstellen (Server oder Client)
- Signale weiterleiten: `peer_connected(id)`, `peer_disconnected(id)`, `connected_to_server()`, `connection_failed()`
- Eigene peer_id bereitstellen: `NetworkManager.my_id` (wird bei Verbindung gesetzt)
- Liste aller verbundenen peers: `NetworkManager.peers` (Dictionary `id → {}`)
**Signale:**
```gdscript
signal peer_connected(id: int)
signal peer_disconnected(id: int)
signal connection_failed()
signal connected_to_server()
```
### main.gd — Modus-Erkennung
```gdscript
func _ready() -> void:
# OS.has_feature("dedicated_server") gilt nur bei Server-Export-Template.
# Für lokale Dev-Runs: --server als CLI-Argument übergeben.
var is_server := OS.has_feature("dedicated_server") \
or "--server" in OS.get_cmdline_user_args()
if is_server:
NetworkManager.start_server(4242, 8)
else:
NetworkManager.join_server("127.0.0.1", 4242)
```
### Server-Initialisierung
```gdscript
func start_server(port: int, max_clients: int) -> void:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_server(port, max_clients)
if err != OK:
push_error("[Server] Port %d konnte nicht gebunden werden: %s" % [port, error_string(err)])
return
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
print("[Server] Gestartet auf Port %d" % port)
func _on_peer_connected(id: int) -> void:
peers[id] = {}
peer_connected.emit(id)
print("[Server] Peer verbunden: %d" % id)
welcome.rpc_id(id, id) # nur an diesen Client
func _on_peer_disconnected(id: int) -> void:
peers.erase(id)
peer_disconnected.emit(id)
print("[Server] Peer getrennt: %d" % id)
```
### Client-Verbindung
```gdscript
func join_server(ip: String, port: int) -> void:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_client(ip, port)
if err != OK:
push_error("[Client] Verbindung zu %s:%d fehlgeschlagen: %s" % [ip, port, error_string(err)])
return
multiplayer.multiplayer_peer = peer
multiplayer.connected_to_server.connect(_on_connected_to_server)
multiplayer.connection_failed.connect(_on_connection_failed)
func _on_connected_to_server() -> void:
my_id = multiplayer.get_unique_id()
connected_to_server.emit()
print("[Client] Verbunden. Meine peer_id: %d" % my_id)
func _on_connection_failed() -> void:
connection_failed.emit()
push_error("[Client] Verbindung fehlgeschlagen")
```
### RPC-Smoke-Test
Server schickt nach jeder neuen Verbindung ein `welcome`-RPC **nur an diesen Client**:
```gdscript
# Hinweis: call_remote bedeutet der Server führt diese Funktion NICHT lokal aus.
# Sie wird ausschließlich auf dem Ziel-Client ausgeführt.
@rpc("authority", "call_remote", "reliable")
func welcome(peer_id: int) -> void:
print("[Client] Willkommen, meine peer_id ist: %d" % peer_id)
```
Erwartetes Verhalten:
- **Server-Terminal:** zeigt nur `[Server] Peer verbunden: 123456`
- **Client-Output:** zeigt `[Client] Verbunden. Meine peer_id: 123456` und `[Client] Willkommen, meine peer_id ist: 123456`
---
## Verifikation
### Schritt 1 — Server starten (headless)
```bash
cd ruf-der-pilze/
godot --headless -- --server
# Erwartete Ausgabe: [Server] Gestartet auf Port 4242
```
> **Hinweis:** `--` trennt Godot-Argumente von User-Argumenten. `OS.get_cmdline_user_args()` gibt `["--server"]` zurück.
### Schritt 2 — Client verbinden (Godot Editor, F5)
```
# Server-Terminal zeigt:
[Server] Peer verbunden: 123456
# Client-Ausgabe (Godot Output-Panel):
[Client] Verbunden. Meine peer_id: 123456
[Client] Willkommen, meine peer_id ist: 123456
```
### Schritt 3 — Zweiten Client testen
Zweiten Godot-Prozess starten → Server zeigt beide peer_ids, beide Clients erhalten ihre individuelle Welcome-Nachricht mit korrekter peer_id.
### Schritt 4 — Disconnect testen
Client schließen → Server-Terminal zeigt `[Server] Peer getrennt: 123456`, `peers` Dictionary hat den Eintrag entfernt.
---
## Was dieses Grundgerüst NICHT enthält
- Spieler-Rollen (Spieler vs. DM) — kommt im nächsten Schritt
- Authentifizierung / Lobby-Logik
- Reconnect-Handling
- Irgendwelche Spiellogik
---
## Nächster Schritt
Aufbauend auf diesem Grundgerüst: **Lobby + Rollen** (Spieler registrieren sich mit Name und Rolle, DM bekommt Sonderrechte).

View File

@@ -0,0 +1,338 @@
# Multiplayer Grundgerüst — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Server und Client verbinden sich via ENet, der Server schickt bei Verbindung ein welcome-RPC an den Client — verifiziert durch Terminal-Output.
**Architecture:** Eine einzige `main.tscn` erkennt per `OS.has_feature("dedicated_server") or "--server" in OS.get_cmdline_user_args()` ob sie Server oder Client ist und delegiert an den `NetworkManager`-Autoload. Der `NetworkManager` hält den ENet-Peer und leitet alle Verbindungs-Signale weiter.
**Tech Stack:** Godot 4.6.2, GDScript, ENetMultiplayerPeer, Godot MultiplayerAPI
**Spec:** `docs/plans/2026-04-13-multiplayer-grundgeruest-design.md`
**Arbeitsverzeichnis:** Alle Befehle werden aus `ruf-der-pilze/` ausgeführt, sofern nicht anders angegeben.
---
## Dateiübersicht
| Datei | Aktion | Verantwortlichkeit |
|---|---|---|
| `scripts/network_manager.gd` | Erstellen | Autoload: ENet-Peer, Signale, peer_id-Verwaltung |
| `scripts/main.gd` | Erstellen | Modus-Erkennung (Server/Client), Bootstrap |
| `scenes/main.tscn` | Erstellen | Root-Szene (Node + main.gd) |
| `project.godot` | Modifizieren | NetworkManager als Autoload, main.tscn als Hauptszene |
---
### Task 0: Voraussetzungen
**Files:** keine
- [ ] **Step 1: Godot-Binary prüfen**
```bash
which godot || which godot4
```
Merke dir den Namen (wahrscheinlich `godot4` auf Arch Linux). Ersetze `godot` in allen folgenden Schritten durch den tatsächlichen Binary-Namen.
- [ ] **Step 2: Verzeichnisse anlegen**
```bash
# aus ruf-der-pilze/
mkdir -p scripts scenes
```
- [ ] **Step 3: Git initialisieren (falls noch nicht vorhanden)**
Das Git-Repository soll den gesamten `DnD_Anna_OneShot/`-Ordner abdecken, damit `docs/` und `ruf-der-pilze/` gemeinsam versioniert werden.
```bash
# aus DnD_Anna_OneShot/ (eine Ebene höher als ruf-der-pilze/)
cd /home/mpuchstein/Dev/Godot/DnD_Anna_OneShot
git status 2>/dev/null || git init
```
Falls `git init` ausgeführt wurde: einmalig committen was schon da ist:
```bash
git add .
git commit -m "chore: initial project state (godot-mcp addon, docs)"
```
Danach für alle weiteren Schritte wieder in `ruf-der-pilze/` wechseln:
```bash
cd ruf-der-pilze/
```
---
### Task 1: NetworkManager-Autoload schreiben
**Files:**
- Create: `scripts/network_manager.gd`
- [ ] **Step 1: Datei erstellen**
Erstelle `ruf-der-pilze/scripts/network_manager.gd`:
```gdscript
extends Node
signal peer_connected(id: int)
signal peer_disconnected(id: int)
signal connected_to_server()
signal connection_failed()
var peers: Dictionary = {}
var my_id: int = 0
func start_server(port: int, max_clients: int) -> void:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_server(port, max_clients)
if err != OK:
push_error("[Server] Port %d konnte nicht gebunden werden: %s" % [port, error_string(err)])
return
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
print("[Server] Gestartet auf Port %d" % port)
func join_server(ip: String, port: int) -> void:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_client(ip, port)
if err != OK:
push_error("[Client] Verbindung zu %s:%d fehlgeschlagen: %s" % [ip, port, error_string(err)])
return
multiplayer.multiplayer_peer = peer
multiplayer.connected_to_server.connect(_on_connected_to_server)
multiplayer.connection_failed.connect(_on_connection_failed)
func _on_peer_connected(id: int) -> void:
peers[id] = {}
peer_connected.emit(id)
print("[Server] Peer verbunden: %d" % id)
welcome.rpc_id(id, id)
func _on_peer_disconnected(id: int) -> void:
peers.erase(id)
peer_disconnected.emit(id)
print("[Server] Peer getrennt: %d" % id)
func _on_connected_to_server() -> void:
my_id = multiplayer.get_unique_id()
connected_to_server.emit()
print("[Client] Verbunden. Meine peer_id: %d" % my_id)
func _on_connection_failed() -> void:
connection_failed.emit()
push_error("[Client] Verbindung fehlgeschlagen")
@rpc("authority", "call_remote", "reliable")
func welcome(peer_id: int) -> void:
# call_remote: wird NUR auf dem Ziel-Client ausgeführt, nicht auf dem Server.
print("[Client] Willkommen, meine peer_id ist: %d" % peer_id)
```
- [ ] **Step 2: Syntax im Editor prüfen**
`scripts/network_manager.gd` in Godot öffnen → Script-Editor zeigt keine roten Fehler-Markierungen. Kein Parse-Error im Output-Panel.
- [ ] **Step 3: Commit**
```bash
# aus ruf-der-pilze/
git add scripts/network_manager.gd
git commit -m "net: add NetworkManager autoload with ENet server/client and welcome RPC"
```
---
### Task 2: Autoload in project.godot registrieren
**Files:**
- Modify: `project.godot`
- [ ] **Step 1: Autoload über Editor registrieren**
In Godot: **Project → Project Settings → Autoload**
- Pfad: `res://scripts/network_manager.gd`
- Name: `NetworkManager`
- Global Variable: ✅ aktiviert
- Auf "Add" klicken → "NetworkManager" erscheint in der Liste
`project.godot` enthält danach:
```ini
[autoload]
NetworkManager="*res://scripts/network_manager.gd"
```
- [ ] **Step 2: Verifizieren**
Godot-Editor schließen und neu starten (Projekt erneut öffnen). Prüfen ob im Output-Panel beim Start Fehler erscheinen. Kein "Could not load script" oder "NetworkManager not found" → Autoload korrekt registriert.
- [ ] **Step 3: Commit**
```bash
# aus ruf-der-pilze/
git add project.godot
git commit -m "net: register NetworkManager as autoload"
```
---
### Task 3: main.gd schreiben
**Files:**
- Create: `scripts/main.gd`
- [ ] **Step 1: Datei erstellen**
Erstelle `ruf-der-pilze/scripts/main.gd`:
```gdscript
extends Node
func _ready() -> void:
# OS.has_feature("dedicated_server") gilt nur bei Server-Export-Template.
# Für lokale Dev-Runs: --server als User-Argument übergeben (nach -- in CLI).
var is_server := OS.has_feature("dedicated_server") \
or "--server" in OS.get_cmdline_user_args()
if is_server:
NetworkManager.start_server(4242, 8)
else:
NetworkManager.join_server("127.0.0.1", 4242)
```
- [ ] **Step 2: Syntax prüfen**
Script im Godot-Editor öffnen → keine Fehler.
- [ ] **Step 3: Commit**
```bash
# aus ruf-der-pilze/
git add scripts/main.gd
git commit -m "net: add main.gd with server/client mode detection"
```
---
### Task 4: main.tscn erstellen und als Hauptszene setzen
**Files:**
- Create: `scenes/main.tscn`
- Modify: `project.godot`
- [ ] **Step 1: Szene erstellen**
In Godot: **Scene → New Scene**
- Root-Node: `Node` (nicht Node2D oder Node3D)
- Node umbenennen zu: `Main`
- Script anhängen: über den Inspector → Script-Slot → `res://scripts/main.gd` wählen
- Speichern als: `res://scenes/main.tscn` (Ctrl+S)
- [ ] **Step 2: Als Hauptszene setzen**
**Project → Project Settings → Application → Run → Main Scene**`res://scenes/main.tscn` auswählen
- [ ] **Step 3: Commit**
```bash
# aus ruf-der-pilze/
git add scenes/main.tscn project.godot
git commit -m "net: add main.tscn as root scene"
```
---
### Task 5: Integration testen — Server + Client
**Files:** keine Änderungen
- [ ] **Step 1: Server headless starten**
```bash
# aus ruf-der-pilze/ — Binary-Name ggf. anpassen (siehe Task 0)
godot --headless -- --server
```
Erwartete Ausgabe:
```
[Server] Gestartet auf Port 4242
```
Falls kein Output: `OS.get_cmdline_user_args()` prüfen — das `--` ist zwingend (trennt Godot-Args von User-Args). Ohne `--` landet `--server` in `get_cmdline_args()`, nicht in `get_cmdline_user_args()`.
- [ ] **Step 2: Client verbinden (Godot Editor)**
Godot-Editor → F5 (Projekt ausführen, kein `--server` Argument)
*Server-Terminal:*
```
[Server] Peer verbunden: 123456
```
*Godot Output-Panel (Client):*
```
[Client] Verbunden. Meine peer_id: 123456
[Client] Willkommen, meine peer_id ist: 123456
```
Hinweis: Der Server printet `welcome` **nicht**`call_remote` bedeutet die Funktion läuft nur auf dem Ziel-Client.
- [ ] **Step 3: Disconnect testen**
Client (Editor) schließen / F8 → Server-Terminal zeigt:
```
[Server] Peer getrennt: 123456
```
- [ ] **Step 4: STATUS.md updaten**
`/home/mpuchstein/Dev/Godot/DnD_Anna_OneShot/docs/STATUS.md` öffnen und aktualisieren:
```markdown
### ✅ Abgeschlossen
- MCP-Addon eingerichtet
- Projektstruktur angelegt
- CLAUDE.md mit vollständiger Spielkonzept-Dokumentation
- **Multiplayer Grundgerüst** — ENet Server/Client, NetworkManager Autoload, welcome RPC verifiziert
### ⏳ Als nächstes
- Lobby + Rollen (Spieler registrieren sich mit Name + Rolle, DM kriegt Sonderrechte)
Plan: noch zu erstellen
```
- [ ] **Step 5: Finaler Commit**
```bash
# aus DnD_Anna_OneShot/ (Repo-Root, weil docs/ dort liegt)
cd /home/mpuchstein/Dev/Godot/DnD_Anna_OneShot
git add docs/STATUS.md
git commit -m "docs: mark Multiplayer Grundgerüst as complete in STATUS.md"
```
---
## Verifikations-Checkliste
Vor "fertig" erklären:
- [ ] `[Server] Gestartet auf Port 4242` erscheint beim headless Start
- [ ] `[Server] Peer verbunden: <id>` erscheint wenn Client verbindet
- [ ] `[Client] Verbunden. Meine peer_id: <id>` erscheint im Client
- [ ] `[Client] Willkommen, meine peer_id ist: <id>` erscheint im Client (RPC angekommen)
- [ ] `[Server] Peer getrennt: <id>` erscheint wenn Client schließt
- [ ] Kein `push_error` oder Parser-Fehler in keiner Ansicht