feat: merge Schritt 13 — Charakter-Capsules (manual conflict resolution)

This commit is contained in:
2026-04-16 01:01:45 +02:00
5 changed files with 110 additions and 5 deletions

View File

@@ -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)

View File

@@ -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")

View File

@@ -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)

View File

@@ -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

View File

@@ -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", ""))