diff --git a/docs/plans/2026-04-16-schritt-12-charakterbogen.md b/docs/plans/2026-04-16-schritt-12-charakterbogen.md new file mode 100644 index 0000000..1f933d9 --- /dev/null +++ b/docs/plans/2026-04-16-schritt-12-charakterbogen.md @@ -0,0 +1,233 @@ +# Schritt 12 — Lite Charakterbogen in der Lobby + +> **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:** Spieler füllen in der Lobby ihren Charakter aus (Name, Rasse, Klasse, 6 Ability-Modifier). Die Daten werden an alle Peers gesynct. Quick-Fill-Buttons für Dev-Archetypen. + +**Architecture:** Neue Felder in `tavern_lobby.tscn` unter dem Join-Bereich. `tavern_lobby.gd` sendet bei Änderungen via neuem `request_update_character` RPC an Server; Server speichert unter `players[id]["char"]` und relayed an alle. NetworkManager bekommt Signal `character_updated(peer_id, data)`. + +**Tech Stack:** Godot 4.x GDScript, MultiplayerAPI RPC, OptionButton, SpinBox, GridContainer + +--- + +## Files + +| File | Aktion | +|------|--------| +| `ruf-der-pilze/scenes/tavern_lobby.tscn` | Modify — Charakterbogen-UI unter Join-Bereich | +| `ruf-der-pilze/scripts/tavern_lobby.gd` | Modify — Formular-Logik, Auto-Send bei Änderung | +| `ruf-der-pilze/scripts/network_manager.gd` | Modify — `request_update_character` + `_relay_character_update` RPC, `character_updated` Signal | +| `docs/STATUS.md` | Modify — Schritt 12 ✅ | + +--- + +## Task 1: Character-RPC in NetworkManager + +**Files:** Modify `ruf-der-pilze/scripts/network_manager.gd` + +- [ ] **Schritt 1: Signal + zwei RPCs am Ende von network_manager.gd hinzufügen** + + ```gdscript + signal character_updated(peer_id: int, data: Dictionary) + + # Client → Server + @rpc("any_peer", "call_remote", "reliable") + func request_update_character(data: Dictionary) -> void: + if not multiplayer.is_server(): return + var peer_id := multiplayer.get_remote_sender_id() + if not players.has(peer_id): return + players[peer_id]["char"] = data + _relay_character_update.rpc(peer_id, data) + + # Server → All + @rpc("authority", "call_local", "reliable") + func _relay_character_update(peer_id: int, data: Dictionary) -> void: + if players.has(peer_id): + players[peer_id]["char"] = data + character_updated.emit(peer_id, data) + ``` + +- [ ] **Schritt 2: Godot öffnen, Parse-Fehler prüfen** + + Godot-Editor öffnen und prüfen ob das Script fehlerfrei geladen wird (keine roten Fehler im Output-Panel). + +- [ ] **Schritt 3: Commit** + + ```bash + git add ruf-der-pilze/scripts/network_manager.gd + git commit -m "net: add request_update_character RPC + character_updated signal" + ``` + +--- + +## Task 2: Charakterbogen-UI in tavern_lobby.tscn + +**Files:** Modify `ruf-der-pilze/scenes/tavern_lobby.tscn` + +Node-Struktur unter dem bestehenden Join-Bereich (nach NameInput/JoinButton, aber vor der StartButton-Zeile). `CharSheet` ist zunächst `visible = false` — wird nach erfolgreichem Join eingeblendet. + +``` +VBoxContainer "CharSheet" + visible = false + Label "LblChar" — text="Charakter" + GridContainer "CharGrid" — columns=2 + Label — text="Rasse" + OptionButton "RaceOption" + items: Mensch, Elf, Zwerg, Halbork, Halbelf, Tiefling + Label — text="Klasse" + OptionButton "ClassOption" + items: Krieger, Magier, Kleriker, Schurke, Druide, Barbar + HSeparator + Label "LblModifiers" — text="Werte" + GridContainer "ModGrid" — columns=6 + Label — text="STR" + Label — text="DEX" + Label — text="CON" + Label — text="INT" + Label — text="WIS" + Label — text="CHA" + SpinBox "SpinSTR" — min=-5, max=10, value=0 + SpinBox "SpinDEX" — min=-5, max=10, value=0 + SpinBox "SpinCON" — min=-5, max=10, value=0 + SpinBox "SpinINT" — min=-5, max=10, value=0 + SpinBox "SpinWIS" — min=-5, max=10, value=0 + SpinBox "SpinCHA" — min=-5, max=10, value=0 + HSeparator + Label "LblArchetype" — text="Schnell-Auswahl (Dev)" + HBoxContainer "ArchetypeRow" + Button "BtnKrieger" — text="Krieger" + Button "BtnMagier" — text="Magier" + Button "BtnKleriker"— text="Kleriker" + Button "BtnSchurke" — text="Schurke" + Button "BtnDruide" — text="Druide" + Button "BtnBarbar" — text="Barbar" +``` + +- [ ] **Schritt 1: Nodes in tavern_lobby.tscn via Godot-Editor anlegen** + + `tavern_lobby.tscn` öffnen. Im Scene-Panel die bestehende UI-Hierarchie finden (CanvasLayer → Panel o.ä.). Den neuen `VBoxContainer "CharSheet"` nach den Join-Controls einfügen. + +- [ ] **Schritt 2: Layout in Godot-Editor prüfen** + + CharSheet sollte initial unsichtbar sein (`visible = false`). Kurz auf `visible = true` setzen, Layout im Viewport prüfen, dann wieder auf `false`. + +- [ ] **Schritt 3: Commit** + + ```bash + git add ruf-der-pilze/scenes/tavern_lobby.tscn + git commit -m "lobby: add character sheet UI nodes (CharSheet, ModGrid, ArchetypeRow)" + ``` + +--- + +## Task 3: Charakterbogen-Logik in tavern_lobby.gd + +**Files:** Modify `ruf-der-pilze/scripts/tavern_lobby.gd` + +Zuerst das Script lesen um die genauen Node-Pfade und bestehende Struktur zu verstehen. Die Pfade in den Code-Beispielen unten ggf. anpassen. + +- [ ] **Schritt 1: Konstante Archetyp-Presets + Helper `_collect_char_data()` hinzufügen** + + Am Anfang der Klasse einfügen (nach `extends` und bestehenden `var`-Deklarationen): + + ```gdscript + const ARCHETYPES := { + "Krieger": {"race": "Mensch", "class": "Krieger", "STR": 3, "DEX": 1, "CON": 2, "INT": 0, "WIS": 0, "CHA": 0}, + "Magier": {"race": "Elf", "class": "Magier", "STR":-1, "DEX": 2, "CON": 0, "INT": 4, "WIS": 2, "CHA": 1}, + "Kleriker": {"race": "Zwerg", "class": "Kleriker", "STR": 1, "DEX": 0, "CON": 2, "INT": 1, "WIS": 3, "CHA": 0}, + "Schurke": {"race": "Halbelf", "class": "Schurke", "STR": 0, "DEX": 4, "CON": 1, "INT": 1, "WIS": 1, "CHA": 2}, + "Druide": {"race": "Mensch", "class": "Druide", "STR": 0, "DEX": 1, "CON": 1, "INT": 1, "WIS": 3, "CHA": 0}, + "Barbar": {"race": "Halbork", "class": "Barbar", "STR": 4, "DEX": 2, "CON": 3, "INT":-1, "WIS": 0, "CHA":-1}, + } + + func _collect_char_data() -> Dictionary: + var race_opt := $/CharSheet/CharGrid/RaceOption as OptionButton + var class_opt := $/CharSheet/CharGrid/ClassOption as OptionButton + return { + "race": race_opt.get_item_text(race_opt.selected), + "class": class_opt.get_item_text(class_opt.selected), + "STR": int($/CharSheet/ModGrid/SpinSTR.value), + "DEX": int($/CharSheet/ModGrid/SpinDEX.value), + "CON": int($/CharSheet/ModGrid/SpinCON.value), + "INT": int($/CharSheet/ModGrid/SpinINT.value), + "WIS": int($/CharSheet/ModGrid/SpinWIS.value), + "CHA": int($/CharSheet/ModGrid/SpinCHA.value), + } + ``` + + `` durch den tatsächlichen Pfad zur UI-Ebene ersetzen (aus dem Script lesen). + +- [ ] **Schritt 2: `_setup_char_sheet()` hinzufügen und in `_ready()` aufrufen** + + ```gdscript + func _setup_char_sheet() -> void: + for archetype_name: String in ARCHETYPES: + var btn := $/CharSheet/ArchetypeRow.get_node( + NodePath("Btn" + archetype_name)) as Button + var preset: Dictionary = ARCHETYPES[archetype_name] + btn.pressed.connect(func(): + var race_opt := $/CharSheet/CharGrid/RaceOption as OptionButton + var class_opt := $/CharSheet/CharGrid/ClassOption as OptionButton + for i in race_opt.item_count: + if race_opt.get_item_text(i) == preset["race"]: + race_opt.selected = i + for i in class_opt.item_count: + if class_opt.get_item_text(i) == preset["class"]: + class_opt.selected = i + for stat: String in ["STR", "DEX", "CON", "INT", "WIS", "CHA"]: + ($/CharSheet/ModGrid.get_node( + NodePath("Spin" + stat)) as SpinBox).value = preset[stat] + _send_char_data() + ) + for spin_name: String in ["SpinSTR", "SpinDEX", "SpinCON", "SpinINT", "SpinWIS", "SpinCHA"]: + ($/CharSheet/ModGrid.get_node(NodePath(spin_name)) as SpinBox + ).value_changed.connect(func(_v: float): _send_char_data()) + ($/CharSheet/CharGrid/RaceOption as OptionButton + ).item_selected.connect(func(_i: int): _send_char_data()) + ($/CharSheet/CharGrid/ClassOption as OptionButton + ).item_selected.connect(func(_i: int): _send_char_data()) + + func _send_char_data() -> void: + NetworkManager.request_update_character.rpc_id(1, _collect_char_data()) + ``` + + `_setup_char_sheet()` am Ende von `_ready()` aufrufen. + +- [ ] **Schritt 3: CharSheet nach erfolgreichem Join einblenden** + + In der bestehenden Funktion die nach erfolgreichem Join aufgerufen wird (per NetworkManager-Signal oder direkt in `_on_join_button_pressed`), am Ende hinzufügen: + + ```gdscript + $/CharSheet.visible = true + ``` + +- [ ] **Schritt 4: Godot öffnen, Parse-Fehler prüfen** + +- [ ] **Schritt 5: End-to-End-Test** + + Server + DM-Client + Spieler-Client starten. + - Spieler joined → CharSheet erscheint + - Spieler klickt "Magier" → Race=Elf, Class=Magier, Modifier werden gesetzt + - Spieler ändert STR manuell → sendet sofort + - Im GDScript-Debugger: `NetworkManager.players[peer_id]["char"]` zeigt die Daten + - Zweiter Client: erhält `character_updated` Signal + +- [ ] **Schritt 6: Commit** + + ```bash + git add ruf-der-pilze/scripts/tavern_lobby.gd + git commit -m "lobby: wire character sheet form — archetypes, modifier spinboxes, auto-sync RPC" + ``` + +--- + +## Task 4: STATUS.md updaten + +- [ ] **Schritt 1: Schritt 12 als ✅ markieren in `docs/STATUS.md` und `ruf-der-pilze/CLAUDE.md`** + +- [ ] **Schritt 2: Commit** + + ```bash + git add docs/STATUS.md ruf-der-pilze/CLAUDE.md + git commit -m "docs: mark Schritt 12 complete — lite Charakterbogen" + ``` diff --git a/docs/plans/2026-04-16-schritt-13-capsules.md b/docs/plans/2026-04-16-schritt-13-capsules.md new file mode 100644 index 0000000..7d02dd4 --- /dev/null +++ b/docs/plans/2026-04-16-schritt-13-capsules.md @@ -0,0 +1,217 @@ +# Schritt 13 — Charakter-Platzhalter (Capsule-Avatare) + +> **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:** Jeder Spieler wird in der Lobby als farbige Kapsel mit Namens-Label dargestellt. Farbe kommt aus `players[id]["char"]["class"]` (Fallback: Grau wenn char-Daten noch nicht vorhanden). Capsules sind eine eigene Szene — einfach austauschbar gegen echte Modelle später. + +**Architecture:** Neue Szene `character_placeholder.tscn` (Node3D + CapsuleMesh + Label3D). `tavern_lobby.gd` instanziiert und entfernt Capsules wenn `player_joined`/`player_left` feuert. Capsules stehen an vordefinierten `Marker3D`-Lobby-Standpunkten (5 Spots in tavern_lobby.tscn). Farbe wird per `CharacterPlaceholder.set_color(class_name)` gesetzt; bei `character_updated`-Signal wird Farbe aktualisiert. + +**Tech Stack:** Godot 4.x GDScript, MeshInstance3D, CapsuleMesh, StandardMaterial3D, Label3D, Marker3D + +**Parallel-Hinweis:** Dieser Plan liest `players[id].get("char", {}).get("class", "")`. Falls Schritt 12 noch nicht gemergt ist, ist "class" leer → Grau-Fallback. Kein harter Blocker. + +--- + +## Files + +| File | Aktion | +|------|--------| +| `ruf-der-pilze/scenes/character_placeholder.tscn` | Create — Capsule + Label3D + Script | +| `ruf-der-pilze/scripts/character_placeholder.gd` | Create — `set_color(class_name)`, `set_label(name)` | +| `ruf-der-pilze/scenes/tavern_lobby.tscn` | Modify — 5 LobbySpot Marker3D-Nodes hinzufügen | +| `ruf-der-pilze/scripts/tavern_lobby.gd` | Modify — Capsule spawn/remove on join/leave | +| `docs/STATUS.md` | Modify — Schritt 13 ✅ | + +--- + +## Task 1: character_placeholder.gd + .tscn erstellen + +**Files:** +- Create: `ruf-der-pilze/scripts/character_placeholder.gd` +- Create: `ruf-der-pilze/scenes/character_placeholder.tscn` + +- [ ] **Schritt 1: character_placeholder.gd erstellen** + + Datei anlegen unter `ruf-der-pilze/scripts/character_placeholder.gd`: + + ```gdscript + extends Node3D + + const CLASS_COLORS: Dictionary = { + "Krieger": Color(0.80, 0.20, 0.20), # Rot + "Magier": Color(0.20, 0.20, 0.90), # Blau + "Kleriker": Color(0.90, 0.85, 0.20), # Gelb + "Schurke": Color(0.20, 0.65, 0.20), # Grün + "Druide": Color(0.40, 0.75, 0.30), # Hellgrün + "Barbar": Color(0.75, 0.40, 0.10), # Orange + } + const DEFAULT_COLOR := Color(0.65, 0.65, 0.65) # Grau + + func set_color(class_name: String) -> void: + var mat := StandardMaterial3D.new() + mat.albedo_color = CLASS_COLORS.get(class_name, DEFAULT_COLOR) + ($Capsule as MeshInstance3D).material_override = mat + + func set_label(player_name: String) -> void: + ($NameLabel as Label3D).text = player_name + ``` + +- [ ] **Schritt 2: character_placeholder.tscn via Godot-Editor erstellen** + + Neue Szene: `Datei → Neue Szene → Node3D als Root`. + Root umbenennen in `CharacterPlaceholder`, Script `character_placeholder.gd` zuweisen. + + Kinder-Nodes hinzufügen: + + **MeshInstance3D** mit Name `Capsule`: + - `mesh` = neues `CapsuleMesh` + - CapsuleMesh-Properties: `height = 1.8`, `radius = 0.3` + - `material_override` = neues `StandardMaterial3D`, `albedo_color = Color(0.65, 0.65, 0.65)` + + **Label3D** mit Name `NameLabel`: + - `text = ""` + - `position = Vector3(0.0, 1.2, 0.0)` + - `font_size = 48` + - `billboard = BILLBOARD_ENABLED` + - `no_depth_test = true` + + Szene als `ruf-der-pilze/scenes/character_placeholder.tscn` speichern. + +- [ ] **Schritt 3: Parse-Fehler prüfen** + + Godot-Editor: Ausgabe-Panel prüfen, keine Fehler in `character_placeholder.gd`. + +- [ ] **Schritt 4: Commit** + + ```bash + git add ruf-der-pilze/scenes/character_placeholder.tscn ruf-der-pilze/scripts/character_placeholder.gd + git commit -m "feat: add CharacterPlaceholder scene — colored capsule + name label" + ``` + +--- + +## Task 2: LobbySpot-Marker in tavern_lobby.tscn + +**Files:** Modify `ruf-der-pilze/scenes/tavern_lobby.tscn` + +`tavern_lobby.tscn` instanziiert `taproom.tscn` als Sub-Scene und hat einen CanvasLayer für UI. Die Marker3D-Nodes kommen in die 3D-Welt (unter der Root-Node, nicht unter CanvasLayer). + +- [ ] **Schritt 1: 5 Marker3D-Nodes in tavern_lobby.tscn hinzufügen** + + Im Godot-Editor `tavern_lobby.tscn` öffnen. Im Scene-Panel: Root-Node (oder die Node3D direkt unterhalb der Root) → `Marker3D` hinzufügen. Fünfmal wiederholen. + + Namen und Positionen: + ``` + Marker3D "LobbySpot0" — position = Vector3(-2.0, 0.0, 1.0) + Marker3D "LobbySpot1" — position = Vector3(-1.0, 0.0, 1.5) + Marker3D "LobbySpot2" — position = Vector3( 0.0, 0.0, 1.8) + Marker3D "LobbySpot3" — position = Vector3( 1.0, 0.0, 1.5) + Marker3D "LobbySpot4" — position = Vector3( 2.0, 0.0, 1.0) + ``` + + Koordinaten ggf. anpassen damit Kapseln im Gastraum stehen (nicht in Wänden). Orientierung der Marker: `rotation_degrees.y = 180` so dass Kapseln zur Kamera schauen. + +- [ ] **Schritt 2: Screenshot via godot-mcp prüfen** + + Godot-Editor: Szene kurz ausführen oder Viewport nutzen um zu prüfen ob Positionen im Gastraum liegen. Anpassen falls nötig. + +- [ ] **Schritt 3: Commit** + + ```bash + git add ruf-der-pilze/scenes/tavern_lobby.tscn + git commit -m "lobby: add 5 LobbySpot Marker3D for capsule placement" + ``` + +--- + +## Task 3: Capsule-Spawn-Logik in tavern_lobby.gd + +**Files:** Modify `ruf-der-pilze/scripts/tavern_lobby.gd` + +Zuerst das Script lesen um bestehende Struktur zu verstehen. Neue Felder und Methoden hinzufügen — bestehenden Code nicht anfassen. + +- [ ] **Schritt 1: Capsule-Tracking-Variablen + Preload hinzufügen** + + Am Anfang der Klasse (nach bestehenden `var`-Deklarationen): + + ```gdscript + const CharacterPlaceholder := preload("res://scenes/character_placeholder.tscn") + var _capsules: Dictionary = {} # peer_id: int → Node3D + var _lobby_spots: Array[Marker3D] = [] + ``` + +- [ ] **Schritt 2: Spot-Array + Signal-Verbindungen in `_ready()` ergänzen** + + Am Ende von `_ready()` anhängen: + + ```gdscript + _lobby_spots = [ + $LobbySpot0 as Marker3D, + $LobbySpot1 as Marker3D, + $LobbySpot2 as Marker3D, + $LobbySpot3 as Marker3D, + $LobbySpot4 as Marker3D, + ] + NetworkManager.player_joined.connect(_on_player_joined_capsule) + NetworkManager.player_left.connect(_on_player_left_capsule) + NetworkManager.character_updated.connect(_on_character_updated_capsule) + ``` + + Pfade `$LobbySpot0` etc. anpassen wenn Marker3D-Nodes unter einer anderen Parent-Node liegen. + +- [ ] **Schritt 3: Spawn/Remove/Update-Methoden hinzufügen** + + ```gdscript + func _on_player_joined_capsule(peer_id: int, _data: Dictionary) -> void: + if _capsules.has(peer_id): + return + var spot_index := _capsules.size() % _lobby_spots.size() + var capsule: Node3D = CharacterPlaceholder.instantiate() + add_child(capsule) + capsule.global_position = _lobby_spots[spot_index].global_position + var player_data: Dictionary = NetworkManager.players.get(peer_id, {}) + capsule.set_label(player_data.get("name", "???")) + capsule.set_color(player_data.get("char", {}).get("class", "")) + _capsules[peer_id] = capsule + + func _on_player_left_capsule(peer_id: int) -> void: + if not _capsules.has(peer_id): + return + (_capsules[peer_id] as Node3D).queue_free() + _capsules.erase(peer_id) + + func _on_character_updated_capsule(peer_id: int, data: Dictionary) -> void: + if not _capsules.has(peer_id): + return + (_capsules[peer_id] as Node3D).set_color(data.get("class", "")) + ``` + +- [ ] **Schritt 4: Godot öffnen, Parse-Fehler prüfen** + +- [ ] **Schritt 5: End-to-End-Test** + + Server + 2 Spieler-Clients starten. + - Spieler A joined → Kapsel A erscheint an LobbySpot0 (grau, kein char-Daten) + - Spieler B joined → Kapsel B erscheint an LobbySpot1 + - Wenn Schritt 12 gemergt: Spieler A klickt "Magier" → Kapsel A wird blau + - Spieler A verlässt → Kapsel A verschwindet + +- [ ] **Schritt 6: Commit** + + ```bash + git add ruf-der-pilze/scripts/tavern_lobby.gd + git commit -m "lobby: spawn colored capsule avatars for each player at LobbySpots" + ``` + +--- + +## Task 4: STATUS.md updaten + +- [ ] **Schritt 1: Schritt 13 als ✅ markieren in `docs/STATUS.md` und `ruf-der-pilze/CLAUDE.md`** + +- [ ] **Schritt 2: Commit** + + ```bash + git add docs/STATUS.md ruf-der-pilze/CLAUDE.md + git commit -m "docs: mark Schritt 13 complete — Charakter-Capsules" + ``` diff --git a/docs/plans/2026-04-16-schritt-14-tavern-art-pass.md b/docs/plans/2026-04-16-schritt-14-tavern-art-pass.md new file mode 100644 index 0000000..efc3677 --- /dev/null +++ b/docs/plans/2026-04-16-schritt-14-tavern-art-pass.md @@ -0,0 +1,313 @@ +# Schritt 14 — Tavern Art Pass + +> **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:** `taproom.tscn` (Gastraum EG, geteilt von Lobby + In-Game-Taverne) und die Obergeschoss-Zimmer in `tavern.tscn` bekommen echte 3D-Props statt Placeholder-Geometrie. Quellen: Polyhaven (CC0, kostenlos) via `blender-mcp`, optional Hyper3D/Meshy für fehlende Assets. + +**Architecture:** Assets via Blender-MCP downloaden/generieren → Skala normalisieren → als `.glb` nach `ruf-der-pilze/assets/props/` exportieren → via Godot-Editor in Szenen platzieren. `taproom.tscn` hat kein Script — Props als reine MeshInstance3D-Nodes einfügen. Keine Logic-Änderungen an Scripts. + +**Tech Stack:** Godot 4.x, Blender (blender-mcp MCP-Server), Polyhaven (CC0 Models), godot-mcp Screenshot-Loop + +**Asset-Liste:** + +| Asset | Anzahl | Szene | Polyhaven-Suche | +|-------|--------|-------|-----------------| +| Holztisch | 4–5 | taproom | `"wooden table"` / `"pub table"` | +| Holzstuhl/Hocker | 8–12 | taproom | `"wooden chair"` / `"stool"` | +| Bar-Counter/Tresen | 1 | taproom | `"bar counter"` (evtl. Hyper3D) | +| Fässer (Barrel) | 3–5 | taproom | `"barrel"` / `"wooden barrel"` | +| Kerzenhalter | 4–6 | taproom | `"candle"` / `"candlestick"` | +| Bett (einfach) | 1 Template | tavern OG | `"bed"` / `"medieval bed"` | +| Kleiner Tisch | 1 Template | tavern OG | (Wiederverwendung Tisch) | + +--- + +## Files + +| File | Aktion | +|------|--------| +| `ruf-der-pilze/assets/props/` | Verzeichnis anlegen, .glb Dateien ablegen | +| `ruf-der-pilze/scenes/taproom.tscn` | Modify — Props platzieren (Tische, Stühle, Tresen, Fässer, Kerzen) | +| `ruf-der-pilze/scenes/tavern.tscn` | Modify — Zimmer-Props (Bett, Tisch, Stuhl, Kerze) | +| `docs/STATUS.md` | Modify — Schritt 14 ✅ | + +--- + +## Task 1: Assets via Polyhaven-MCP suchen und downloaden + +**Vorbedingung:** Blender muss offen sein und der blender-mcp-Server muss laufen. + +- [ ] **Schritt 1: Polyhaven-Kategorien abrufen** + + ``` + Tool: mcp__blender__get_polyhaven_categories + ``` + + Kategorien für Models notieren (sollte "models" o.ä. enthalten). + +- [ ] **Schritt 2: Tisch-Assets suchen** + + ``` + Tool: mcp__blender__search_polyhaven_assets + Params: type="models", query="wooden table" + ``` + + Und: + ``` + Tool: mcp__blender__search_polyhaven_assets + Params: type="models", query="pub table" + ``` + + Passenden Asset-ID notieren (z.B. `pub_table`, `round_wooden_table`). + +- [ ] **Schritt 3: Stuhl/Hocker-Assets suchen** + + ``` + Tool: mcp__blender__search_polyhaven_assets + Params: type="models", query="wooden stool" + ``` + +- [ ] **Schritt 4: Fass-Assets suchen** + + ``` + Tool: mcp__blender__search_polyhaven_assets + Params: type="models", query="barrel" + ``` + +- [ ] **Schritt 5: Kerzen-Assets suchen** + + ``` + Tool: mcp__blender__search_polyhaven_assets + Params: type="models", query="candle" + ``` + +- [ ] **Schritt 6: Bett-Assets suchen** + + ``` + Tool: mcp__blender__search_polyhaven_assets + Params: type="models", query="bed" + ``` + +- [ ] **Schritt 7: Gefundene Assets herunterladen (nacheinander)** + + Für jeden Asset: + ``` + Tool: mcp__blender__download_polyhaven_asset + Params: asset_id="", asset_type="models", resolution="1k" + ``` + + Status pollen: + ``` + Tool: mcp__blender__get_polyhaven_status + ``` + + Warten bis `status = "completed"`. + +- [ ] **Schritt 8: Falls Bar-Counter nicht auf Polyhaven: Hyper3D nutzen** + + ``` + Tool: mcp__blender__generate_hyper3d_model_via_text + Params: prompt="medieval tavern bar counter, dark wood, worn surface, low poly game asset, simple geometry" + ``` + + Status pollen: + ``` + Tool: mcp__blender__get_hyper3d_status + ``` + + Asset importieren: + ``` + Tool: mcp__blender__import_generated_asset + ``` + +--- + +## Task 2: Assets in Blender normalisieren und als .glb exportieren + +- [ ] **Schritt 1: Blender-Szene prüfen** + + ``` + Tool: mcp__blender__get_scene_info + Tool: mcp__blender__get_viewport_screenshot + ``` + + Alle heruntergeladenen Assets sollten in der Szene sichtbar sein. + +- [ ] **Schritt 2: Assets-Verzeichnis anlegen** + + ```bash + mkdir -p /home/mpuchstein/Dev/Godot/DnD_Anna_OneShot/ruf-der-pilze/assets/props + ``` + +- [ ] **Schritt 3: Skala prüfen und normalisieren** + + Referenz-Maße für mittelalterliche Taverne: + - Tisch: ca. 0.75m hoch, 1.2m × 0.8m Fläche + - Stuhl/Hocker: ca. 0.45m hoch + - Barrel: ca. 0.8m hoch + - Kerzenhalter: ca. 0.3m hoch + - Bett: ca. 0.5m hoch, 2.0m × 1.0m + + Skala per Blender-Code anpassen (für jeden Asset einzeln ausführen): + + ```python + # Beispiel: Tisch auf 0.75m Höhe skalieren + import bpy + obj = bpy.data.objects[""] # Namen aus get_scene_info + dims = obj.dimensions + if dims.z > 0: + scale_factor = 0.75 / dims.z + obj.scale = (scale_factor, scale_factor, scale_factor) + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.transform_apply(scale=True) + ``` + + ``` + Tool: mcp__blender__execute_blender_code + Params: code="" + ``` + +- [ ] **Schritt 4: Assets einzeln als .glb exportieren** + + Für jeden Asset (Objekt selektieren, dann exportieren): + + ```python + import bpy + bpy.ops.object.select_all(action='DESELECT') + bpy.data.objects[""].select_set(True) + bpy.ops.export_scene.gltf( + filepath="/home/mpuchstein/Dev/Godot/DnD_Anna_OneShot/ruf-der-pilze/assets/props/table.glb", + export_format='GLB', + use_selection=True, + export_materials='EXPORT' + ) + ``` + + Zieldateien: + - `assets/props/table.glb` + - `assets/props/chair.glb` + - `assets/props/barrel.glb` + - `assets/props/candle.glb` + - `assets/props/bed.glb` + - `assets/props/bar_counter.glb` + +- [ ] **Schritt 5: Commit** + + ```bash + git add ruf-der-pilze/assets/props/ + git commit -m "assets: import tavern props — table, chair, barrel, candle, bed, bar_counter" + ``` + +--- + +## Task 3: taproom.tscn mit Props bestücken + +**Files:** Modify `ruf-der-pilze/scenes/taproom.tscn` + +`taproom.tscn` ist die Gastraum-Geometrie ohne Script. Hier werden Props als Kinder-Nodes eingefügt. Die Props werden als instanziierte .glb-Szenen oder MeshInstance3D-Nodes platziert. + +- [ ] **Schritt 1: Aktuellen Stand per Screenshot prüfen** + + In Godot: `taproom.tscn` öffnen. Godot-MCP Screenshot machen um die Raumgröße und Geometry zu sehen: + ``` + Tool: mcp__godot-mcp__scene + Params: action="get_node_properties", scene_path="res://scenes/taproom.tscn", node_path="." + ``` + +- [ ] **Schritt 2: Bar-Counter an Wand platzieren** + + Im Godot-Editor: `bar_counter.glb` (aus `assets/props/`) per Drag & Drop in `taproom.tscn` ziehen, oder Node3D hinzufügen und Mesh zuweisen. Position entlang einer Wand: + - Ungefähre Position: `Vector3(-4.0, 0.0, 0.0)` — an der linken Wand + - Rotation anpassen damit Counter zur Mitte zeigt + +- [ ] **Schritt 3: 4 Tische im Raum verteilen** + + Vier Tisch-Nodes platzieren, gleichmäßig verteilt: + ``` + Tisch 1: Vector3(-1.5, 0.0, -2.0) + Tisch 2: Vector3( 1.5, 0.0, -2.0) + Tisch 3: Vector3(-1.5, 0.0, 1.0) + Tisch 4: Vector3( 1.5, 0.0, 1.0) + ``` + +- [ ] **Schritt 4: Stühle/Hocker um jeden Tisch** + + Je 2–4 Stühle um jeden Tisch. Versatz vom Tisch-Zentrum: ca. 0.7m in jede Richtung. + +- [ ] **Schritt 5: 3–4 Fässer nahe Tresen** + + Fässer geclustert nahe dem Bar-Counter. Position: entlang der Tresen-Wand. + +- [ ] **Schritt 6: Kerzenhalter auf Tischen + Wandbefestigung** + + Je 1 Kerzenhalter auf jedem Tisch (Y-Position = Tischhöhe ~0.75). 2 Kerzenhalter an Wänden. + +- [ ] **Schritt 7: Screenshot via godot-mcp, Iterationen bis es gut aussieht** + + Mindestens 2–3 Korrekturrunden. Ziel: bewohnter, atmosphärischer Gastraum. + +- [ ] **Schritt 8: Commit** + + ```bash + git add ruf-der-pilze/scenes/taproom.tscn + git commit -m "art: taproom props — tables, chairs, bar counter, barrels, candles" + ``` + +--- + +## Task 4: Zimmer in tavern.tscn bestücken + +**Files:** Modify `ruf-der-pilze/scenes/tavern.tscn` + +Die Taverne hat 9 Zimmer im OG (SpawnPoint0–SpawnPoint8). Zuerst **Zimmer 1 (SpawnPoint0)** vollständig einrichten, dann das Muster für alle weiteren Zimmer wiederholen. + +- [ ] **Schritt 1: tavern.tscn öffnen und SpawnPoint0-Zimmer lokalisieren** + + Godot-Editor: `tavern.tscn` öffnen. Im Scene-Panel die SpawnPoint0-Node finden und deren Position notieren. + +- [ ] **Schritt 2: Zimmer 1 mit Props bestücken** + + Props relativ zu SpawnPoint0-Position (Offsets anpassen nach Zimmergeometrie): + - **Bett:** SpawnPoint.position + Vector3(1.2, 0.0, 0.5), an Wand + - **Kleiner Tisch:** SpawnPoint.position + Vector3(-1.0, 0.0, -0.8) + - **Stuhl:** neben Tisch, Offset ~0.5m + - **Kerzenhalter:** auf Tisch, Y = Tischhöhe + +- [ ] **Schritt 3: Screenshot prüfen** + + Godot-Editor Viewport oder godot-mcp Screenshot für Zimmer 1. + +- [ ] **Schritt 4: Zimmer 2–9 analog bestücken** + + Für SpawnPoint1–SpawnPoint8 dieselben Props einfügen. Positionen relativ zum jeweiligen SpawnPoint. + +- [ ] **Schritt 5: Commit** + + ```bash + git add ruf-der-pilze/scenes/tavern.tscn + git commit -m "art: tavern OG rooms — bed, table, chair, candle per room (9 rooms)" + ``` + +--- + +## Task 5: Gesamt-Test + STATUS.md + +- [ ] **Schritt 1: Spiel starten und Taverne visuell prüfen** + + Server + 2 Clients starten (oder Godot-Editor ausführen): + - `tavern_lobby.tscn`: Gastraum sieht bewohnt aus + - `tavern.tscn` nach game_started: EG-Gastraum mit Props, OG-Zimmer mit Bett/Tisch/Kerze + - Performance: FPS sollte bei >30 bleiben (Godot-Editor Statistiken) + +- [ ] **Schritt 2: Schritt 14 als ✅ markieren** + + In `docs/STATUS.md` und `ruf-der-pilze/CLAUDE.md`. + +- [ ] **Schritt 3: Commit** + + ```bash + git add docs/STATUS.md ruf-der-pilze/CLAUDE.md + git commit -m "docs: mark Schritt 14 complete — Tavern Art Pass" + ```