# 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
