diff --git a/docs/STATUS.md b/docs/STATUS.md index 8b8685f..4d19dea 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,7 +1,6 @@ # Ruf der Pilze — Projektstatus -Zuletzt aktualisiert: 2026-04-16 (Schritt 12 abgeschlossen — Lite Charakterbogen) - +Zuletzt aktualisiert: 2026-04-16 (Schritt 13 abgeschlossen — Charakter-Capsules) --- ## Aktueller Stand @@ -20,7 +19,6 @@ Zuletzt aktualisiert: 2026-04-16 (Schritt 12 abgeschlossen — Lite Charakterbog 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 — (nichts aktiv) @@ -50,7 +48,7 @@ Zuletzt aktualisiert: 2026-04-16 (Schritt 12 abgeschlossen — Lite Charakterbog ### Charaktere + Assets 12. ✅ Lite Charakterbogen in Lobby — Rasse/Klasse OptionButtons, 6 Modifier-SpinBoxen, 6 Archetype-Buttons; RPC-Sync -13. ⏳ Charakter-Platzhalter — farbige Capsules pro Archetyp, austauschbar +13. ✅ Charakter-Platzhalter — farbige Capsule-Avatare in der Lobby, 5 LobbySpots, Farbe aus Klasse 14. ⏳ Tavern Art Pass — echte Props (Tische, Stühle, Tresen, Betten, Fässer, Kerzen) via Polyhaven/Sketchfab/Meshy ### Klosterwelt @@ -75,7 +73,6 @@ Zuletzt aktualisiert: 2026-04-16 (Schritt 12 abgeschlossen — Lite Charakterbog 26. ⏳ Aktions-Selektor — klassen-spezifische Zauber/Angriff-Effekte (rein visuell, Atmosphäre) 27. ⏳ Full Spore Effects — Nebel-Partikel vom Boden/Decke, Bewegungsverzerrung 28. ⏳ Soundscape + Spieluhr-Melodie — direktionaler Audio, Annas Lied im Sanctum - --- ## Design-Entscheidungen (festgelegt) diff --git a/ruf-der-pilze/scenes/character_placeholder.tscn b/ruf-der-pilze/scenes/character_placeholder.tscn new file mode 100644 index 0000000..24197d7 --- /dev/null +++ b/ruf-der-pilze/scenes/character_placeholder.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=5 format=3] + +[ext_resource type="Script" path="res://scripts/character_placeholder.gd" id="1"] + +[sub_resource type="CapsuleMesh" id="2"] +height = 1.8 +radius = 0.3 + +[sub_resource type="StandardMaterial3D" id="3"] +albedo_color = Color(0.65, 0.65, 0.65, 1) + +[sub_resource type="LabelSettings" id="4"] +font_size = 48 + +[node name="CharacterPlaceholder" type="Node3D"] +script = ExtResource("1") + +[node name="Capsule" type="MeshInstance3D" parent="."] +mesh = SubResource("2") +material_override = SubResource("3") + +[node name="NameLabel" type="Label3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.2, 0) +text = "" +font_size = 48 +billboard = 1 +no_depth_test = true +label_settings = SubResource("4") diff --git a/ruf-der-pilze/scenes/tavern_lobby.tscn b/ruf-der-pilze/scenes/tavern_lobby.tscn index be4675b..e1b10ec 100644 --- a/ruf-der-pilze/scenes/tavern_lobby.tscn +++ b/ruf-der-pilze/scenes/tavern_lobby.tscn @@ -148,3 +148,18 @@ text = "Druide" [node name="BtnBarbar" type="Button" parent="CanvasLayer/WaitPanel/CharSheet/ArchetypeRow"] text = "Barbar" + +[node name="LobbySpot0" type="Marker3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.0, 0.0, 1.0) + +[node name="LobbySpot1" type="Marker3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.0, 0.0, 1.5) + +[node name="LobbySpot2" type="Marker3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0, 0.0, 1.8) + +[node name="LobbySpot3" type="Marker3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.0, 0.0, 1.5) + +[node name="LobbySpot4" type="Marker3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.0, 0.0, 1.0) \ No newline at end of file diff --git a/ruf-der-pilze/scripts/character_placeholder.gd b/ruf-der-pilze/scripts/character_placeholder.gd new file mode 100644 index 0000000..5f5a4ae --- /dev/null +++ b/ruf-der-pilze/scripts/character_placeholder.gd @@ -0,0 +1,19 @@ +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 diff --git a/ruf-der-pilze/scripts/tavern_lobby.gd b/ruf-der-pilze/scripts/tavern_lobby.gd index 198afb6..d294e70 100644 --- a/ruf-der-pilze/scripts/tavern_lobby.gd +++ b/ruf-der-pilze/scripts/tavern_lobby.gd @@ -1,7 +1,11 @@ extends Node3D +const CharacterPlaceholder := preload("res://scenes/character_placeholder.tscn") + var _local_role: String = "" var _pending_player_name: String = "" +var _capsules: Dictionary = {} # peer_id: int → Node3D +var _lobby_spots: Array[Marker3D] = [] const ARCHETYPES := { "Krieger": {"race": "Mensch", "class": "Krieger", "STR": 3, "DEX": 1, "CON": 2, "INT": 0, "WIS": 0, "CHA": 0}, @@ -75,6 +79,18 @@ func _ready() -> void: NetworkManager.player_list_synced.connect(_rebuild_player_list) _setup_char_sheet() + _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) + if NetworkManager.has_signal("character_updated"): + NetworkManager.character_updated.connect(_on_character_updated_capsule) + func _on_join_pressed() -> void: var player_name: String = ($CanvasLayer/JoinPanel/NameInput as LineEdit).text.strip_edges() @@ -99,6 +115,10 @@ func _exit_tree() -> void: NetworkManager.player_joined.disconnect(_on_player_joined) NetworkManager.player_left.disconnect(_on_player_left) NetworkManager.player_list_synced.disconnect(_rebuild_player_list) + NetworkManager.player_joined.disconnect(_on_player_joined_capsule) + NetworkManager.player_left.disconnect(_on_player_left_capsule) + if NetworkManager.has_signal("character_updated") and NetworkManager.character_updated.is_connected(_on_character_updated_capsule): + NetworkManager.character_updated.disconnect(_on_character_updated_capsule) func _on_connection_failed() -> void: @@ -125,3 +145,29 @@ func _rebuild_player_list() -> void: func _on_start_pressed() -> void: NetworkManager.request_start_game.rpc_id(1) + + +func _on_player_joined_capsule(peer_id: int, _player_name: String, _role: String) -> 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", ""))