feat: merge Schritt 12 — lite Charakterbogen (manual conflict resolution)

This commit is contained in:
2026-04-16 00:56:20 +02:00
5 changed files with 183 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
# Ruf der Pilze — Projektstatus
Zuletzt aktualisiert: 2026-04-16 (Schritt 11 abgeschlossen — Sporennebel Slider; alle Mechaniken fertig)
Zuletzt aktualisiert: 2026-04-16 (Schritt 12 abgeschlossen — Lite Charakterbogen)
---
@@ -18,6 +18,8 @@ Zuletzt aktualisiert: 2026-04-16 (Schritt 11 abgeschlossen — Sporennebel Slide
game_started → Spieler in zugewiesenem Zimmer (SpawnPoint), DM in dm_view.tscn Stub
- **DM Regiepult Basics** — GameState Autoload, Overlay-RPC-Kette, EG+OG Top-Down nebeneinander,
Spieler-Positionsmarker (etagen-gefiltert), Live-Cam-Feeds pro Spieler, Overlay-Toggle-Panel
- **Lite Charakterbogen** — Rasse/Klasse OptionButtons, 6 Ability-Modifier SpinBoxen, 6 Archetype-Quick-Fill-Buttons;
Auto-Sync via `request_update_character` RPC, `character_updated` Signal in NetworkManager
### 🔄 In Arbeit
@@ -47,7 +49,7 @@ Zuletzt aktualisiert: 2026-04-16 (Schritt 11 abgeschlossen — Sporennebel Slide
### Charaktere + Assets
12. Lite Charakterbogen in Lobby — CharName, Rasse, Klasse, 6 Skill-Modifier; Archetypes für Dev
12. Lite Charakterbogen in Lobby — Rasse/Klasse OptionButtons, 6 Modifier-SpinBoxen, 6 Archetype-Buttons; RPC-Sync
13. ⏳ Charakter-Platzhalter — farbige Capsules pro Archetyp, austauschbar
14. ⏳ Tavern Art Pass — echte Props (Tische, Stühle, Tresen, Betten, Fässer, Kerzen) via Polyhaven/Sketchfab/Meshy

View File

@@ -251,7 +251,7 @@ Verzeichnis: `../Anna_Model/` (außerhalb von `ruf-der-pilze/`, im Repo-Root)
10. ✅ Spore Overlay — spore_overlay.gdshader, overlay_changed signal, CanvasLayer layer=10
11. ✅ Sporennebel Slider — SporeLevel Autoload, request_spore_level RPC, HSlider im DM-Panel
--- Charaktere + Assets ---
12. Lite Charakterbogen — CharName, Rasse, Klasse, 6 Skill-Modifier; Archetypes für Dev
12. Lite Charakterbogen — Rasse/Klasse OptionButtons, 6 Modifier-SpinBoxen, 6 Archetype-Buttons; RPC-Sync
13. ⏳ Charakter-Platzhalter — farbige Capsules pro Archetyp
14. ⏳ Tavern Art Pass — echte Props via Polyhaven/Sketchfab/Meshy AI
--- Klosterwelt ---
@@ -271,7 +271,6 @@ Verzeichnis: `../Anna_Model/` (außerhalb von `ruf-der-pilze/`, im Repo-Root)
26. ⏳ Aktions-Selektor — klassen-spezifische Zauber/Angriff-Effekte (nur visuell)
27. ⏳ Full Spore Effects — Nebel-Partikel, Bewegungsverzerrung
28. ⏳ Soundscape + Spieluhr-Melodie — direktionaler Audio, Annas Lied
---
## Arbeitsweise

View File

@@ -48,3 +48,103 @@ custom_minimum_size = Vector2(0, 150)
[node name="StartButton" type="Button" parent="CanvasLayer/WaitPanel"]
text = "Spiel starten"
visible = false
[node name="CharSheet" type="VBoxContainer" parent="CanvasLayer/WaitPanel"]
visible = false
[node name="LblChar" type="Label" parent="CanvasLayer/WaitPanel/CharSheet"]
text = "Charakter"
[node name="CharGrid" type="GridContainer" parent="CanvasLayer/WaitPanel/CharSheet"]
columns = 2
[node name="LblRace" type="Label" parent="CanvasLayer/WaitPanel/CharSheet/CharGrid"]
text = "Rasse"
[node name="RaceOption" type="OptionButton" parent="CanvasLayer/WaitPanel/CharSheet/CharGrid"]
[node name="LblClass" type="Label" parent="CanvasLayer/WaitPanel/CharSheet/CharGrid"]
text = "Klasse"
[node name="ClassOption" type="OptionButton" parent="CanvasLayer/WaitPanel/CharSheet/CharGrid"]
[node name="HSeparator" type="HSeparator" parent="CanvasLayer/WaitPanel/CharSheet"]
[node name="LblModifiers" type="Label" parent="CanvasLayer/WaitPanel/CharSheet"]
text = "Werte"
[node name="ModGrid" type="GridContainer" parent="CanvasLayer/WaitPanel/CharSheet"]
columns = 6
[node name="LblSTR" type="Label" parent="CanvasLayer/WaitPanel/CharSheet/ModGrid"]
text = "STR"
[node name="LblDEX" type="Label" parent="CanvasLayer/WaitPanel/CharSheet/ModGrid"]
text = "DEX"
[node name="LblCON" type="Label" parent="CanvasLayer/WaitPanel/CharSheet/ModGrid"]
text = "CON"
[node name="LblINT" type="Label" parent="CanvasLayer/WaitPanel/CharSheet/ModGrid"]
text = "INT"
[node name="LblWIS" type="Label" parent="CanvasLayer/WaitPanel/CharSheet/ModGrid"]
text = "WIS"
[node name="LblCHA" type="Label" parent="CanvasLayer/WaitPanel/CharSheet/ModGrid"]
text = "CHA"
[node name="SpinSTR" type="SpinBox" parent="CanvasLayer/WaitPanel/CharSheet/ModGrid"]
min_value = -5.0
max_value = 10.0
value = 0.0
[node name="SpinDEX" type="SpinBox" parent="CanvasLayer/WaitPanel/CharSheet/ModGrid"]
min_value = -5.0
max_value = 10.0
value = 0.0
[node name="SpinCON" type="SpinBox" parent="CanvasLayer/WaitPanel/CharSheet/ModGrid"]
min_value = -5.0
max_value = 10.0
value = 0.0
[node name="SpinINT" type="SpinBox" parent="CanvasLayer/WaitPanel/CharSheet/ModGrid"]
min_value = -5.0
max_value = 10.0
value = 0.0
[node name="SpinWIS" type="SpinBox" parent="CanvasLayer/WaitPanel/CharSheet/ModGrid"]
min_value = -5.0
max_value = 10.0
value = 0.0
[node name="SpinCHA" type="SpinBox" parent="CanvasLayer/WaitPanel/CharSheet/ModGrid"]
min_value = -5.0
max_value = 10.0
value = 0.0
[node name="HSeparator2" type="HSeparator" parent="CanvasLayer/WaitPanel/CharSheet"]
[node name="LblArchetype" type="Label" parent="CanvasLayer/WaitPanel/CharSheet"]
text = "Schnell-Auswahl (Dev)"
[node name="ArchetypeRow" type="HBoxContainer" parent="CanvasLayer/WaitPanel/CharSheet"]
[node name="BtnKrieger" type="Button" parent="CanvasLayer/WaitPanel/CharSheet/ArchetypeRow"]
text = "Krieger"
[node name="BtnMagier" type="Button" parent="CanvasLayer/WaitPanel/CharSheet/ArchetypeRow"]
text = "Magier"
[node name="BtnKleriker" type="Button" parent="CanvasLayer/WaitPanel/CharSheet/ArchetypeRow"]
text = "Kleriker"
[node name="BtnSchurke" type="Button" parent="CanvasLayer/WaitPanel/CharSheet/ArchetypeRow"]
text = "Schurke"
[node name="BtnDruide" type="Button" parent="CanvasLayer/WaitPanel/CharSheet/ArchetypeRow"]
text = "Druide"
[node name="BtnBarbar" type="Button" parent="CanvasLayer/WaitPanel/CharSheet/ArchetypeRow"]
text = "Barbar"

View File

@@ -16,6 +16,7 @@ signal player_list_synced()
signal game_started()
signal roll_received(roller_peer_id: int, player_name: String, d20_result: int, modifier: int, total: int)
signal overlay_changed(overlay_name: String)
signal character_updated(peer_id: int, data: Dictionary)
func start_server(port: int, max_clients: int) -> void:
@@ -197,3 +198,21 @@ func _relay_roll(roller_peer_id: int, d20_result: int, modifier: int) -> void:
var player_name: String = players.get(roller_peer_id, {}).get("name", "???")
var total := d20_result + modifier
roll_received.emit(roller_peer_id, player_name, d20_result, modifier, total)
# 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)

View File

@@ -3,6 +3,63 @@ extends Node3D
var _local_role: String = ""
var _pending_player_name: String = ""
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 := $CanvasLayer/WaitPanel/CharSheet/CharGrid/RaceOption as OptionButton
var class_opt := $CanvasLayer/WaitPanel/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($CanvasLayer/WaitPanel/CharSheet/ModGrid/SpinSTR.value),
"DEX": int($CanvasLayer/WaitPanel/CharSheet/ModGrid/SpinDEX.value),
"CON": int($CanvasLayer/WaitPanel/CharSheet/ModGrid/SpinCON.value),
"INT": int($CanvasLayer/WaitPanel/CharSheet/ModGrid/SpinINT.value),
"WIS": int($CanvasLayer/WaitPanel/CharSheet/ModGrid/SpinWIS.value),
"CHA": int($CanvasLayer/WaitPanel/CharSheet/ModGrid/SpinCHA.value),
}
func _send_char_data() -> void:
NetworkManager.request_update_character.rpc_id(1, _collect_char_data())
func _setup_char_sheet() -> void:
var race_opt := $CanvasLayer/WaitPanel/CharSheet/CharGrid/RaceOption as OptionButton
var class_opt := $CanvasLayer/WaitPanel/CharSheet/CharGrid/ClassOption as OptionButton
for race in ["Mensch", "Elf", "Zwerg", "Halbork", "Halbelf", "Tiefling"]:
race_opt.add_item(race)
for cls in ["Krieger", "Magier", "Kleriker", "Schurke", "Druide", "Barbar"]:
class_opt.add_item(cls)
for archetype_name: String in ARCHETYPES:
var btn := $CanvasLayer/WaitPanel/CharSheet/ArchetypeRow.get_node(
NodePath("Btn" + archetype_name)) as Button
var preset: Dictionary = ARCHETYPES[archetype_name]
btn.pressed.connect(func():
var r_opt := $CanvasLayer/WaitPanel/CharSheet/CharGrid/RaceOption as OptionButton
var c_opt := $CanvasLayer/WaitPanel/CharSheet/CharGrid/ClassOption as OptionButton
for i in r_opt.item_count:
if r_opt.get_item_text(i) == preset["race"]:
r_opt.selected = i
for i in c_opt.item_count:
if c_opt.get_item_text(i) == preset["class"]:
c_opt.selected = i
for stat: String in ["STR", "DEX", "CON", "INT", "WIS", "CHA"]:
($CanvasLayer/WaitPanel/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"]:
($CanvasLayer/WaitPanel/CharSheet/ModGrid.get_node(NodePath(spin_name)) as SpinBox
).value_changed.connect(func(_v: float): _send_char_data())
(race_opt as OptionButton).item_selected.connect(func(_i: int): _send_char_data())
(class_opt as OptionButton).item_selected.connect(func(_i: int): _send_char_data())
func _ready() -> void:
$CanvasLayer/JoinPanel/RoleOption.add_item("Spieler")
@@ -16,6 +73,7 @@ func _ready() -> void:
NetworkManager.player_joined.connect(_on_player_joined)
NetworkManager.player_left.connect(_on_player_left)
NetworkManager.player_list_synced.connect(_rebuild_player_list)
_setup_char_sheet()
func _on_join_pressed() -> void:
@@ -32,6 +90,7 @@ func _on_connected() -> void:
$CanvasLayer/JoinPanel.visible = false
$CanvasLayer/WaitPanel.visible = true
$CanvasLayer/WaitPanel/StartButton.visible = (_local_role == "dm")
$CanvasLayer/WaitPanel/CharSheet.visible = true
func _exit_tree() -> void: