docs: add tavern scene implementation plan
This commit is contained in:
606
docs/plans/2026-04-14-tavern-scene-plan.md
Normal file
606
docs/plans/2026-04-14-tavern-scene-plan.md
Normal file
@@ -0,0 +1,606 @@
|
||||
# Taverne In-Game-Szene 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:** 2-stöckige In-Game-Taverne bauen (Blockout), Gastraum als Sub-Scene extrahieren, und nach `game_started` Spieler in ihre Zimmer und DM in eine Stub-Szene routen.
|
||||
|
||||
**Architecture:** `taproom.tscn` (Geometrie-Only) wird von `tavern_lobby.tscn` und `tavern.tscn` instanziert. `SceneManager` routet bei `game_started` Spieler zu `tavern.tscn` und DM zu `dm_view.tscn`. Room-Zuweisung wird als Parameter in `start_game` RPC mitgeschickt (verhindert Timing-Probleme zwischen RPC-Empfang und Scene-`_ready()`).
|
||||
|
||||
**Tech Stack:** Godot 4.x, GDScript, ENet Multiplayer, BoxMesh Placeholder-Geometrie, godot-mcp MCP Tools
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
| Aktion | Pfad | Verantwortung |
|
||||
|--------|------|---------------|
|
||||
| Umbenennen | `scenes/tavern.tscn` → `scenes/tavern_lobby.tscn` | Lobby/Warteraum |
|
||||
| Umbenennen | `scripts/tavern.gd` → `scripts/tavern_lobby.gd` | Lobby-Script |
|
||||
| Erstellen | `scenes/taproom.tscn` | Gastraum-Geometrie (kein Script, kein UI) |
|
||||
| Erstellen | `scenes/tavern.tscn` | In-Game-Taverne (Erd- + Obergeschoss) |
|
||||
| Erstellen | `scenes/dm_view.tscn` | DM-Szene Stub |
|
||||
| Erstellen | `scripts/tavern.gd` | Spawn-Logik: Spieler im richtigen Zimmer |
|
||||
| Erstellen | `scripts/dm_view.gd` | DM-Stub-Script |
|
||||
| Ändern | `scripts/scene_manager.gd` | SCENES dict, `_on_root_ready`, `_on_game_started`, `pending_room_index` |
|
||||
| Ändern | `scripts/network_manager.gd` | `start_game(room_assignments)`, `_build_room_assignments()` |
|
||||
|
||||
Alle Pfade relativ zu `ruf-der-pilze/`.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Bestehende Tavern-Dateien umbenennen
|
||||
|
||||
**Files:**
|
||||
- Rename: `scenes/tavern.tscn` → `scenes/tavern_lobby.tscn`
|
||||
- Rename: `scripts/tavern.gd` → `scripts/tavern_lobby.gd`
|
||||
|
||||
- [ ] **Step 1: Dateien per git mv umbenennen**
|
||||
|
||||
```bash
|
||||
cd ruf-der-pilze
|
||||
git mv scenes/tavern.tscn scenes/tavern_lobby.tscn
|
||||
git mv scripts/tavern.gd scripts/tavern_lobby.gd
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Script-Referenz in `tavern_lobby.tscn` aktualisieren**
|
||||
|
||||
`tavern_lobby.tscn` referenziert das Script noch als `res://scripts/tavern.gd`. Ändere die erste Zeile:
|
||||
|
||||
```
|
||||
# ALT:
|
||||
[ext_resource type="Script" path="res://scripts/tavern.gd" id="1"]
|
||||
# NEU:
|
||||
[ext_resource type="Script" path="res://scripts/tavern_lobby.gd" id="1"]
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Klassennamen in `tavern_lobby.gd` anpassen (optional, aber sauber)**
|
||||
|
||||
Datei `scripts/tavern_lobby.gd` — keine Änderung am Code nötig, `extends Node3D` bleibt.
|
||||
|
||||
- [ ] **Step 4: Verifizieren — Godot öffnen, Scene-Fehler prüfen**
|
||||
|
||||
Öffne Godot. Prüfe im Editor dass `tavern_lobby.tscn` keine Fehler im Script-Attachment zeigt.
|
||||
Erwartung: Scene öffnet, JoinPanel sichtbar, keine Fehlermeldungen im Output.
|
||||
|
||||
- [ ] **Step 5: Committen**
|
||||
|
||||
```bash
|
||||
git add scenes/tavern_lobby.tscn scripts/tavern_lobby.gd
|
||||
git commit -m "refactor: rename tavern → tavern_lobby (scene + script)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: SceneManager aktualisieren
|
||||
|
||||
**Files:**
|
||||
- Modify: `scripts/scene_manager.gd`
|
||||
|
||||
- [ ] **Step 1: SCENES dict, pending_room_index und `_on_root_ready` aktualisieren**
|
||||
|
||||
Ersetze den gesamten Inhalt von `scripts/scene_manager.gd`:
|
||||
|
||||
```gdscript
|
||||
extends Node
|
||||
|
||||
const SCENES := {
|
||||
"lobby": "res://scenes/tavern_lobby.tscn",
|
||||
"tavern": "res://scenes/tavern.tscn",
|
||||
"dm_view": "res://scenes/dm_view.tscn",
|
||||
"entrance_hall": "res://scenes/entrance_hall.tscn",
|
||||
"refectory": "res://scenes/refectory.tscn",
|
||||
"library": "res://scenes/library.tscn",
|
||||
"chapel": "res://scenes/chapel.tscn",
|
||||
"cloister": "res://scenes/cloister.tscn",
|
||||
"sanctum": "res://scenes/sanctum.tscn",
|
||||
}
|
||||
|
||||
var pending_room_index: int = 0
|
||||
var _current_scene_node: Node = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
NetworkManager.game_started.connect(_on_game_started)
|
||||
get_tree().root.ready.connect(_on_root_ready, CONNECT_ONE_SHOT)
|
||||
|
||||
|
||||
func _on_root_ready() -> void:
|
||||
var args := OS.get_cmdline_args() + OS.get_cmdline_user_args()
|
||||
var is_server := OS.has_feature("dedicated_server") or "--server" in args
|
||||
if not is_server:
|
||||
transition_to("lobby")
|
||||
|
||||
|
||||
func transition_to(scene_name: String) -> void:
|
||||
var path: String = SCENES.get(scene_name, "")
|
||||
var packed := ResourceLoader.load(path, "PackedScene", ResourceLoader.CACHE_MODE_REUSE) as PackedScene
|
||||
if packed == null:
|
||||
push_error("[SceneManager] Scene not found: %s" % scene_name)
|
||||
return
|
||||
var container := get_node_or_null("/root/Main/CurrentScene")
|
||||
if container == null:
|
||||
push_error("[SceneManager] CurrentScene node not found in main.tscn")
|
||||
return
|
||||
if _current_scene_node != null:
|
||||
_current_scene_node.get_parent().remove_child(_current_scene_node)
|
||||
_current_scene_node.queue_free()
|
||||
_current_scene_node = null
|
||||
_current_scene_node = packed.instantiate()
|
||||
container.add_child(_current_scene_node)
|
||||
|
||||
|
||||
func _on_game_started() -> void:
|
||||
var args := OS.get_cmdline_args() + OS.get_cmdline_user_args()
|
||||
if OS.has_feature("dedicated_server") or "--server" in args:
|
||||
return
|
||||
var role: String = NetworkManager.players.get(NetworkManager.my_id, {}).get("role", "player")
|
||||
if role == "dm":
|
||||
transition_to("dm_view")
|
||||
else:
|
||||
transition_to("tavern")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verifizieren — Lobby lädt noch**
|
||||
|
||||
Starte das Spiel (kein Server). Erwartung: Lobby erscheint mit JoinPanel.
|
||||
Console: `[SceneManager]` kein Fehler-Output.
|
||||
|
||||
- [ ] **Step 3: Committen**
|
||||
|
||||
```bash
|
||||
git add scripts/scene_manager.gd
|
||||
git commit -m "feat: update SceneManager — lobby/tavern/dm_view keys, game_started routing"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: taproom.tscn erstellen und Lobby darauf umstellen
|
||||
|
||||
**Files:**
|
||||
- Create: `scenes/taproom.tscn`
|
||||
- Modify: `scenes/tavern_lobby.tscn`
|
||||
|
||||
Die bestehende Geometrie aus `tavern_lobby.tscn` (Floor, Ceiling, Wände) wird in `taproom.tscn` extrahiert. Der Gastraum bleibt atmosphärisch identisch.
|
||||
|
||||
- [ ] **Step 1: `taproom.tscn` erstellen**
|
||||
|
||||
Erstelle `scenes/taproom.tscn` mit folgender Node-Struktur (MCP-Tool oder manuell):
|
||||
|
||||
```
|
||||
Node3D "Taproom" ← Root, kein Script
|
||||
StaticBody3D "Floor"
|
||||
MeshInstance3D mesh: BoxMesh(12, 0.1, 8)
|
||||
CollisionShape3D shape: BoxShape3D(12, 0.1, 8)
|
||||
StaticBody3D "Ceiling"
|
||||
MeshInstance3D mesh: BoxMesh(12, 0.1, 8), transform.origin.y = 4.0
|
||||
CollisionShape3D shape: BoxShape3D(12, 0.1, 8)
|
||||
StaticBody3D "WallBack"
|
||||
MeshInstance3D mesh: BoxMesh(12, 4, 0.1), transform.origin = (0, 2, -4)
|
||||
CollisionShape3D
|
||||
StaticBody3D "WallLeft"
|
||||
MeshInstance3D mesh: BoxMesh(0.1, 4, 8), transform.origin = (-6, 2, 0)
|
||||
CollisionShape3D
|
||||
StaticBody3D "WallRight"
|
||||
MeshInstance3D mesh: BoxMesh(0.1, 4, 8), transform.origin = (6, 2, 0)
|
||||
CollisionShape3D
|
||||
StaticBody3D "Bar" ← Tresen: Küchen-Grenze
|
||||
MeshInstance3D mesh: BoxMesh(4, 1.1, 0.3), transform.origin = (4, 0.55, -3.5)
|
||||
CollisionShape3D
|
||||
OmniLight3D "Light"
|
||||
transform.origin = (0, 2.5, 0)
|
||||
light_color = Color(0.91, 0.643, 0.29)
|
||||
light_energy = 1.2
|
||||
omni_range = 10.0
|
||||
WorldEnvironment "WorldEnvironment"
|
||||
environment: (fog, warm ambient — gleich wie in bisheriger tavern.tscn)
|
||||
```
|
||||
|
||||
Materialien: Braun-Töne wie in bisheriger `tavern.tscn` (StandardMaterial3D albedo_color ≈ Color(0.169, 0.102, 0.051)).
|
||||
|
||||
- [ ] **Step 2: `tavern_lobby.tscn` aktualisieren**
|
||||
|
||||
Ersetze die bestehende Geometrie in `tavern_lobby.tscn` durch eine Instanz von `taproom.tscn`. Die Szene behält Camera3D und CanvasLayer.
|
||||
|
||||
Neue Node-Struktur von `tavern_lobby.tscn`:
|
||||
```
|
||||
Node3D "TavernLobby" ← script = tavern_lobby.gd
|
||||
[taproom.tscn instanziert als Child] ← Name: "Taproom"
|
||||
Camera3D "Camera3D" ← bestehende Lobby-Kamera (transform bleibt)
|
||||
CanvasLayer "CanvasLayer" ← JoinPanel + WaitPanel (unverändert)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verifizieren**
|
||||
|
||||
Starte das Spiel. Erwartung: Lobby sieht identisch aus wie vorher (gleicher Gastraum, gleiche UI).
|
||||
Falls Geometrie fehlt oder falsch positioniert: Transform-Werte in taproom.tscn korrigieren.
|
||||
|
||||
- [ ] **Step 4: Committen**
|
||||
|
||||
```bash
|
||||
git add scenes/taproom.tscn scenes/tavern_lobby.tscn
|
||||
git commit -m "refactor: extract taproom as reusable sub-scene, update lobby"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: `tavern.tscn` Erdgeschoss bauen
|
||||
|
||||
**Files:**
|
||||
- Create: `scenes/tavern.tscn`
|
||||
- Create: `scripts/tavern.gd` (Stub — Spawn-Logik kommt in Task 7)
|
||||
|
||||
Erdgeschoss: Taproom + sichtbare Küche (nicht betretbar) + Private Gemächer (Tür) + Ausgang + Myzelspur.
|
||||
|
||||
- [ ] **Step 1: `scripts/tavern.gd` Stub erstellen**
|
||||
|
||||
```gdscript
|
||||
extends Node3D
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
print("[Tavern] Scene loaded. Room index: %d" % SceneManager.pending_room_index)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: `scenes/tavern.tscn` Erdgeschoss bauen**
|
||||
|
||||
Node-Struktur:
|
||||
```
|
||||
Node3D "Tavern" ← script = tavern.gd
|
||||
[taproom.tscn instanziert] ← Name: "Taproom", transform.origin = (0,0,0)
|
||||
Node3D "GroundFloor"
|
||||
StaticBody3D "KitchenBack" ← Hinterwand Küche (sichtbar hinter Tresen)
|
||||
MeshInstance3D mesh: BoxMesh(4, 3, 0.1), transform.origin = (4, 1.5, -5.9)
|
||||
StaticBody3D "KitchenLeft"
|
||||
MeshInstance3D mesh: BoxMesh(0.1, 3, 2.3), transform.origin = (2.05, 1.5, -4.85)
|
||||
StaticBody3D "KitchenRight"
|
||||
MeshInstance3D mesh: BoxMesh(0.1, 3, 2.3), transform.origin = (5.95, 1.5, -4.85)
|
||||
StaticBody3D "PrivateRoomDoor" ← Tür zu Privatgemächern (geschlossen)
|
||||
MeshInstance3D mesh: BoxMesh(0.1, 4, 2), transform.origin = (-6, 2, 2)
|
||||
StaticBody3D "ExitDoor" ← Ausgang zur Myzelspur (später: interaktiv)
|
||||
MeshInstance3D mesh: BoxMesh(0.1, 4, 2), transform.origin = (6, 2, 4)
|
||||
Node3D "MyzelTrailGround" ← Myzelspur: Treppe → Ausgang
|
||||
MeshInstance3D "TrailSegment1" mesh: BoxMesh(0.5, 0.01, 3), grünlich, transform.origin = (0, 0.06, 1.5)
|
||||
MeshInstance3D "TrailSegment2" mesh: BoxMesh(0.5, 0.01, 4), transform.origin = (2, 0.06, 0)
|
||||
# Weitere Segmente dem Weg entlang anpassen
|
||||
```
|
||||
|
||||
Material Myzelspur: StandardMaterial3D, albedo_color = Color(0.2, 0.8, 0.3), emission_enabled = true, emission = Color(0.1, 0.4, 0.1).
|
||||
|
||||
- [ ] **Step 3: Verifizieren — Scene lädt ohne Fehler**
|
||||
|
||||
Öffne `tavern.tscn` im Godot Editor. Erwartung: Kein Parser-Fehler, Erdgeschoss sichtbar in 3D-Vorschau.
|
||||
|
||||
- [ ] **Step 4: Committen**
|
||||
|
||||
```bash
|
||||
git add scenes/tavern.tscn scripts/tavern.gd
|
||||
git commit -m "feat: tavern scene — ground floor blockout (taproom, kitchen, exit, myzel trail)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: `tavern.tscn` Obergeschoss bauen
|
||||
|
||||
**Files:**
|
||||
- Modify: `scenes/tavern.tscn`
|
||||
|
||||
8 Gästezimmer + Annas Zimmer + Korridor + Treppe. Jedes Gästezimmer hat einen `Marker3D` SpawnPoint.
|
||||
|
||||
Maße: Gebäude ~16m breit, 8m tief. Korridor: 1.5m breit, Zimmer: 3m x 3.5m.
|
||||
|
||||
- [ ] **Step 1: Treppe einbauen**
|
||||
|
||||
In `tavern.tscn` unter `GroundFloor` hinzufügen:
|
||||
```
|
||||
StaticBody3D "Stairs"
|
||||
MeshInstance3D mesh: BoxMesh(1.5, 0.2, 4) als Rampe — transform mit Rotation X ca. -25°
|
||||
transform.origin = (0, 1, 3) ← verbindet EG mit OG
|
||||
CollisionShape3D
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Obergeschoss-Struktur und Korridor bauen**
|
||||
|
||||
Füge zu `tavern.tscn` hinzu:
|
||||
```
|
||||
Node3D "UpperFloor"
|
||||
StaticBody3D "Floor"
|
||||
MeshInstance3D mesh: BoxMesh(16, 0.1, 8), transform.origin = (2, 4, 0)
|
||||
CollisionShape3D
|
||||
StaticBody3D "Ceiling"
|
||||
MeshInstance3D mesh: BoxMesh(16, 0.1, 8), transform.origin = (2, 7, 0)
|
||||
CollisionShape3D
|
||||
StaticBody3D "WallBack"
|
||||
MeshInstance3D mesh: BoxMesh(16, 3, 0.1), transform.origin = (2, 5.5, -4)
|
||||
CollisionShape3D
|
||||
StaticBody3D "WallFront"
|
||||
MeshInstance3D mesh: BoxMesh(16, 3, 0.1), transform.origin = (2, 5.5, 4)
|
||||
CollisionShape3D
|
||||
StaticBody3D "WallLeft"
|
||||
MeshInstance3D mesh: BoxMesh(0.1, 3, 8), transform.origin = (-6, 5.5, 0)
|
||||
CollisionShape3D
|
||||
StaticBody3D "WallRight"
|
||||
MeshInstance3D mesh: BoxMesh(0.1, 3, 8), transform.origin = (10, 5.5, 0)
|
||||
CollisionShape3D
|
||||
StaticBody3D "CorridorWall" ← Korridor-Innenwand (trennt Zimmer von Korridor)
|
||||
MeshInstance3D mesh: BoxMesh(16, 3, 0.1), transform.origin = (2, 5.5, 2)
|
||||
CollisionShape3D
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 8 Gästezimmer mit SpawnPoints**
|
||||
|
||||
Pro Zimmer (i = 1..8, je 2m breit, nebeneinander):
|
||||
```
|
||||
Node3D "Room{i}" ← parent: UpperFloor
|
||||
StaticBody3D "Bed"
|
||||
MeshInstance3D mesh: BoxMesh(1.8, 0.5, 0.9), transform.origin lokal (0, 0.25, -2.5)
|
||||
CollisionShape3D
|
||||
Marker3D "SpawnPoint" ← Name MUSS "SpawnPoint" sein
|
||||
transform.origin lokal (0, 1.0, -1) ← steht neben dem Bett
|
||||
```
|
||||
|
||||
Zimmer-Positionen (global, je 2m Abstand in X):
|
||||
- Room1: x = -5, z = 2.8, y = 4.1
|
||||
- Room2: x = -3, z = 2.8, y = 4.1
|
||||
- Room3: x = -1, z = 2.8, y = 4.1
|
||||
- ... bis Room8: x = 9, z = 2.8, y = 4.1
|
||||
|
||||
- [ ] **Step 4: Annas Zimmer (Room9) mit Myzel-Dekoration**
|
||||
|
||||
```
|
||||
Node3D "Room9" ← Annas Zimmer, ganz rechts (x = 9, oder eigener Bereich)
|
||||
StaticBody3D "Bed"
|
||||
MeshInstance3D mesh: BoxMesh(1.8, 0.5, 0.9) — Material: myzel-überzogen (grünlich)
|
||||
StaticBody3D "OpenDoor" ← Tür steht offen (BoxMesh als Tür-Panel, seitlich gedreht)
|
||||
MeshInstance3D mesh: BoxMesh(0.05, 2.5, 1.2), transform rotiert 90° in Y
|
||||
Node3D "MyzelDecor"
|
||||
MeshInstance3D "MyzelPatch1" ← Fleck auf dem Boden, grünlich
|
||||
MeshInstance3D "MyzelPatch2" ← Fleck an der Wand
|
||||
Node3D "MyzelTrailAnna" ← Spur aus Annas Zimmer in den Korridor
|
||||
MeshInstance3D "TrailOut" mesh: BoxMesh(0.4, 0.01, 1.5), grünlich, transform.origin lokal (0, 0.06, 1.2)
|
||||
```
|
||||
|
||||
Alle Myzel-Materialien: gleich wie in Task 4 (grünlich, leicht emissiv).
|
||||
|
||||
- [ ] **Step 5: Verifizieren — Obergeschoss in Editor prüfen**
|
||||
|
||||
Öffne `tavern.tscn`. Vergewissere dich im 3D-Viewport:
|
||||
- Obergeschoss sichtbar über dem Taproom
|
||||
- 9 Zimmer mit Betten erkennbar
|
||||
- Room9 (Annas Zimmer) mit Myzel-Dekoration
|
||||
- SpawnPoints als gelbe Marker sichtbar (Marker3D Icons im Editor)
|
||||
|
||||
- [ ] **Step 6: Committen**
|
||||
|
||||
```bash
|
||||
git add scenes/tavern.tscn
|
||||
git commit -m "feat: tavern scene — upper floor blockout (8 rooms + Anna's room, SpawnPoints)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: NetworkManager — Room-Zuweisung in start_game
|
||||
|
||||
**Files:**
|
||||
- Modify: `scripts/network_manager.gd`
|
||||
|
||||
Room-Index wird als Dictionary-Parameter in `start_game` RPC mitgeschickt. Das vermeidet Timing-Probleme (SceneManager.pending_room_index ist gesetzt bevor `game_started` emittiert wird).
|
||||
|
||||
- [ ] **Step 1: `_build_room_assignments()` hinzufügen**
|
||||
|
||||
Füge diese Methode am Ende von `network_manager.gd` hinzu:
|
||||
|
||||
```gdscript
|
||||
func _build_room_assignments() -> Dictionary:
|
||||
var result := {}
|
||||
var player_peers: Array = players.keys().filter(
|
||||
func(id: int) -> bool: return players[id].role == "player"
|
||||
)
|
||||
for i in player_peers.size():
|
||||
result[player_peers[i]] = i % 8 # 8 Gästezimmer (Index 0–7)
|
||||
return result
|
||||
```
|
||||
|
||||
- [ ] **Step 2: `start_game` RPC um room_assignments erweitern**
|
||||
|
||||
Ändere die bestehende `start_game` Funktion:
|
||||
|
||||
```gdscript
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func start_game(room_assignments: Dictionary) -> void:
|
||||
if my_id in room_assignments:
|
||||
SceneManager.pending_room_index = room_assignments[my_id]
|
||||
game_started.emit()
|
||||
```
|
||||
|
||||
- [ ] **Step 3: `request_start_game` anpassen — Assignments bauen und mitschicken**
|
||||
|
||||
Ändere die bestehende `request_start_game` Funktion:
|
||||
|
||||
```gdscript
|
||||
@rpc("any_peer", "call_remote", "reliable")
|
||||
func request_start_game() -> void:
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
var requester_id := multiplayer.get_remote_sender_id()
|
||||
if players.get(requester_id, {}).get("role", "") != "dm":
|
||||
return
|
||||
var assignments := _build_room_assignments()
|
||||
start_game.rpc(assignments)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verifizieren — Script-Syntax prüfen**
|
||||
|
||||
Öffne Godot. Erwartung: Kein GDScript-Fehler in `network_manager.gd`.
|
||||
Alternativ: `godot --check-only` im `ruf-der-pilze/` Verzeichnis ausführen.
|
||||
|
||||
- [ ] **Step 5: Committen**
|
||||
|
||||
```bash
|
||||
git add scripts/network_manager.gd
|
||||
git commit -m "feat: include room assignments in start_game RPC"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: `tavern.gd` — Spawn-Logik implementieren
|
||||
|
||||
**Files:**
|
||||
- Modify: `scripts/tavern.gd`
|
||||
|
||||
Liest `SceneManager.pending_room_index` und platziert einen minimalen PlayerController (Node3D + Camera3D) am zugehörigen SpawnPoint.
|
||||
|
||||
- [ ] **Step 1: `tavern.gd` implementieren**
|
||||
|
||||
Ersetze den Stub-Inhalt von `scripts/tavern.gd`:
|
||||
|
||||
```gdscript
|
||||
extends Node3D
|
||||
|
||||
const ROOM_COUNT := 8
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var args := OS.get_cmdline_args() + OS.get_cmdline_user_args()
|
||||
if OS.has_feature("dedicated_server") or "--server" in args:
|
||||
return
|
||||
var room_index := SceneManager.pending_room_index
|
||||
_spawn_player(room_index)
|
||||
|
||||
|
||||
func _spawn_player(room_index: int) -> void:
|
||||
var room_num := (room_index % ROOM_COUNT) + 1 # Zimmer heißen Room1..Room8
|
||||
var spawn_path := "UpperFloor/Room%d/SpawnPoint" % room_num
|
||||
var spawn := get_node_or_null(spawn_path) as Marker3D
|
||||
if spawn == null:
|
||||
push_error("[Tavern] SpawnPoint nicht gefunden: %s" % spawn_path)
|
||||
return
|
||||
var controller := Node3D.new()
|
||||
controller.name = "PlayerController"
|
||||
var camera := Camera3D.new()
|
||||
camera.name = "Camera3D"
|
||||
camera.current = true
|
||||
controller.add_child(camera)
|
||||
add_child(controller)
|
||||
controller.global_transform = spawn.global_transform
|
||||
print("[Tavern] Spieler gespawnt in %s" % spawn_path)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verifizieren — Solo-Test**
|
||||
|
||||
Starte das Spiel ohne Multiplayer. Verbinde mit Server und starte das Spiel als DM.
|
||||
Als Spieler-Client: Erwartung: Szene wechselt zu `tavern.tscn`, Kamera steht im zugewiesenen Zimmer.
|
||||
Console: `[Tavern] Spieler gespawnt in UpperFloor/Room1/SpawnPoint`
|
||||
|
||||
- [ ] **Step 3: Committen**
|
||||
|
||||
```bash
|
||||
git add scripts/tavern.gd
|
||||
git commit -m "feat: tavern.gd — spawn player at assigned room SpawnPoint"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: `dm_view.tscn` Stub erstellen
|
||||
|
||||
**Files:**
|
||||
- Create: `scenes/dm_view.tscn`
|
||||
- Create: `scripts/dm_view.gd`
|
||||
|
||||
Minimale Platzhalter-Szene für den DM. Wird in Schritt 7 (DM Regiepult) vollständig ausgebaut.
|
||||
|
||||
- [ ] **Step 1: `scripts/dm_view.gd` erstellen**
|
||||
|
||||
```gdscript
|
||||
extends Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
print("[DmView] DM-Szene geladen (Stub)")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: `scenes/dm_view.tscn` erstellen**
|
||||
|
||||
Node-Struktur:
|
||||
```
|
||||
Node "DmView" ← script = dm_view.gd
|
||||
SubViewportContainer "ViewportContainer"
|
||||
SubViewport
|
||||
Camera3D "Camera3D" ← top-down, transform.origin = (0, 20, 0), rotation_degrees.x = -90
|
||||
current = true
|
||||
CanvasLayer "UI"
|
||||
Label "StubLabel"
|
||||
text = "DM View — Stub\n(wird in Schritt 7 ausgebaut)"
|
||||
horizontal_alignment = CENTER
|
||||
anchors_preset = CENTER
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verifizieren**
|
||||
|
||||
Starte als DM-Client, starte das Spiel. Erwartung: DM sieht Label "DM View — Stub".
|
||||
|
||||
- [ ] **Step 4: Committen**
|
||||
|
||||
```bash
|
||||
git add scenes/dm_view.tscn scripts/dm_view.gd
|
||||
git commit -m "feat: dm_view stub scene"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: End-to-End Test
|
||||
|
||||
**Files:** keine Änderungen
|
||||
|
||||
Vollständiger Durchlauf: Server + 2 Spieler-Clients + 1 DM-Client.
|
||||
|
||||
- [ ] **Step 1: Server starten**
|
||||
|
||||
```bash
|
||||
cd ruf-der-pilze
|
||||
godot --headless -- --server
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Spieler-Client 1 starten und verbinden**
|
||||
|
||||
Starte Godot-Client 1. Lobby erscheint, verbinden als "Spieler1" (Rolle: Spieler).
|
||||
Erwartung: JoinPanel verschwindet, WaitPanel zeigt Spielerliste.
|
||||
|
||||
- [ ] **Step 3: Spieler-Client 2 starten und verbinden**
|
||||
|
||||
Starte Godot-Client 2. Verbinden als "Spieler2" (Rolle: Spieler).
|
||||
Erwartung: Beide Clients sehen beide Spieler in der WaitPanel-Liste.
|
||||
|
||||
- [ ] **Step 4: DM-Client starten, verbinden und Spiel starten**
|
||||
|
||||
Starte Godot-Client 3. Verbinden als "DM1" (Rolle: DM). StartButton wird sichtbar.
|
||||
Klicke StartButton.
|
||||
|
||||
- [ ] **Step 5: Ergebnis prüfen**
|
||||
|
||||
Erwartungen:
|
||||
- Spieler-Client 1: Szene wechselt zu `tavern.tscn`, Kamera steht in Room1
|
||||
- Spieler-Client 2: Szene wechselt zu `tavern.tscn`, Kamera steht in Room2
|
||||
- DM-Client: Szene wechselt zu `dm_view.tscn`, Label "DM View — Stub" sichtbar
|
||||
- Console (Server): Keine Fehler, `[Server] game_started` verarbeitet
|
||||
- Console (Clients): `[Tavern] Spieler gespawnt in UpperFloor/Room1/SpawnPoint` etc.
|
||||
|
||||
- [ ] **Step 6: MCP Screenshot für visuelle Verifikation**
|
||||
|
||||
Nutze `mcp__godot-mcp__scene` Screenshot-Tool um die Spieler-Perspektive und DM-Ansicht zu dokumentieren.
|
||||
|
||||
- [ ] **Step 7: docs/STATUS.md und CLAUDE.md updaten**
|
||||
|
||||
In `docs/STATUS.md`:
|
||||
- Schritt 5 (Szenen-Wechsel nach Spielstart) auf ✅ setzen
|
||||
- "In Arbeit" leeren
|
||||
- Schritt 6 (Refectorium) als "Als nächstes" eintragen
|
||||
|
||||
In `ruf-der-pilze/CLAUDE.md`:
|
||||
- Entwicklungs-Reihenfolge: Schritt 5 auf ✅
|
||||
|
||||
- [ ] **Step 8: Finaler Commit**
|
||||
|
||||
```bash
|
||||
git add docs/STATUS.md ruf-der-pilze/CLAUDE.md
|
||||
git commit -m "docs: mark scene switching complete, update STATUS"
|
||||
```
|
||||
Reference in New Issue
Block a user