feat: merge Schritt 8 — dice roller

This commit is contained in:
2026-04-16 00:22:03 +02:00
4 changed files with 133 additions and 0 deletions

View File

@@ -56,6 +56,28 @@ size_flags_vertical = 3
[node name="PlayerList" type="VBoxContainer" parent="RootLayout/TopSection/SidePanel/OverlayScroll"]
[node name="SepDice" type="HSeparator" parent="RootLayout/TopSection/SidePanel"]
[node name="LblDice" type="Label" parent="RootLayout/TopSection/SidePanel"]
text = "Würfelwürfe"
[node name="DCRow" type="HBoxContainer" parent="RootLayout/TopSection/SidePanel"]
[node name="LblDC" type="Label" parent="RootLayout/TopSection/SidePanel/DCRow"]
text = "DC:"
[node name="DCSpinBox" type="SpinBox" parent="RootLayout/TopSection/SidePanel/DCRow"]
min_value = 1.0
max_value = 30.0
value = 12.0
[node name="RollLogScroll" type="ScrollContainer" parent="RootLayout/TopSection/SidePanel"]
size_flags_vertical = 3
[node name="RollLog" type="RichTextLabel" parent="RootLayout/TopSection/SidePanel/RollLogScroll"]
bbcode_enabled = true
fit_content = true
[node name="HSeparator" type="HSeparator" parent="RootLayout"]
[node name="PlayerCamsRow" type="HBoxContainer" parent="RootLayout"]

View File

@@ -4,6 +4,8 @@ const TAVERN_SCENE := "res://scenes/tavern.tscn"
const FLOOR_THRESHOLD := 3.5 # y < threshold → EG, y >= threshold → OG
const OVERLAY_CYCLE := ["default", "spore_active"]
var _dm_roll_log: RichTextLabel
var _eg_markers: Node3D
var _og_markers: Node3D
var _eg_markers_by_id: Dictionary = {} # peer_id → MeshInstance3D
@@ -16,6 +18,7 @@ func _ready() -> void:
_load_tavern_into_viewports()
_setup_player_cams()
_setup_overlay_panel()
_setup_dc_section()
func _load_tavern_into_viewports() -> void:
@@ -196,6 +199,24 @@ func _peer_color(peer_id: int) -> Color:
return colors[peer_id % colors.size()]
func _setup_dc_section() -> void:
_dm_roll_log = $RootLayout/TopSection/SidePanel/RollLogScroll/RollLog as RichTextLabel
NetworkManager.roll_received.connect(_on_roll_received_dm)
func _on_roll_received_dm(roller_peer_id: int, player_name: String, d20_result: int, modifier: int, total: int) -> void:
var dc: int = ($RootLayout/TopSection/SidePanel/DCRow/DCSpinBox as SpinBox).value as int
var mod_str := " %+d" % modifier if modifier != 0 else ""
var success := total >= dc
var result_str := "[color=green]Erfolg[/color]" if success else "[color=red]Fehlschlag[/color]"
var line := "[b]%s[/b]: %d%s = [b]%d[/b] vs DC%d%s" % [player_name, d20_result, mod_str, total, dc, result_str]
if d20_result == 20:
line += " [color=gold]★[/color]"
elif d20_result == 1:
line += " [color=red]☠[/color]"
_dm_roll_log.append_text(line + "\n")
func _update_player_cams() -> void:
for peer_id in _player_cam_cams.keys():
if not GameState.player_positions.has(peer_id):

View File

@@ -14,6 +14,7 @@ signal player_joined(peer_id: int, player_name: String, role: String)
signal player_left(peer_id: int)
signal player_list_synced()
signal game_started()
signal roll_received(roller_peer_id: int, player_name: String, d20_result: int, modifier: int, total: int)
func start_server(port: int, max_clients: int) -> void:
@@ -164,3 +165,18 @@ func set_overlay(overlay_name: String) -> void:
@rpc("any_peer", "call_remote", "unreliable")
func sync_player_position(player_id: int, position: Vector3, rotation: Vector3) -> void:
GameState.update_player_transform(player_id, position, rotation)
# Client calls this on server only (rpc_id(1, ...))
@rpc("any_peer", "call_remote", "reliable")
func broadcast_roll(roller_peer_id: int, d20_result: int, modifier: int) -> void:
if not multiplayer.is_server(): return
_relay_roll.rpc(roller_peer_id, d20_result, modifier)
# Server broadcasts to all (including itself via call_local)
@rpc("authority", "call_local", "reliable")
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)

View File

@@ -2,6 +2,9 @@ extends Node3D
const ROOM_COUNT := 8
var _modifier: int = 0
var _roll_log: RichTextLabel
func _ready() -> void:
var args := OS.get_cmdline_args() + OS.get_cmdline_user_args()
@@ -18,6 +21,7 @@ func _ready() -> void:
timer.autostart = true
timer.timeout.connect(_broadcast_camera_transform)
add_child(timer)
_setup_dice_ui()
func _get_dm_peer_id() -> int:
@@ -56,3 +60,73 @@ func _spawn_player(room_index: int) -> void:
add_child(controller)
controller.global_transform = spawn.global_transform
print("[Tavern] Spieler gespawnt in %s" % spawn_path)
func _setup_dice_ui() -> void:
var canvas := CanvasLayer.new()
canvas.name = "DiceUI"
add_child(canvas)
var panel := PanelContainer.new()
panel.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_LEFT)
panel.offset_top = -120
panel.offset_right = 400
canvas.add_child(panel)
var vbox := VBoxContainer.new()
panel.add_child(vbox)
_roll_log = RichTextLabel.new()
_roll_log.custom_minimum_size = Vector2(380, 60)
_roll_log.bbcode_enabled = true
vbox.add_child(_roll_log)
var hbox := HBoxContainer.new()
vbox.add_child(hbox)
var roll_btn := Button.new()
roll_btn.text = "d20 Würfeln"
roll_btn.pressed.connect(_on_roll_pressed)
hbox.add_child(roll_btn)
var mod_label := Label.new()
mod_label.text = " Mod:"
hbox.add_child(mod_label)
var mod_display := Label.new()
mod_display.text = "0"
mod_display.custom_minimum_size = Vector2(30, 0)
mod_display.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
var mod_minus := Button.new()
mod_minus.text = "-"
mod_minus.pressed.connect(func() -> void: _modifier -= 1; _update_mod_display(mod_display))
hbox.add_child(mod_minus)
hbox.add_child(mod_display)
var mod_plus := Button.new()
mod_plus.text = "+"
mod_plus.pressed.connect(func() -> void: _modifier += 1; _update_mod_display(mod_display))
hbox.add_child(mod_plus)
NetworkManager.roll_received.connect(_on_roll_received)
func _update_mod_display(label: Label) -> void:
label.text = "%+d" % _modifier if _modifier != 0 else "0"
func _on_roll_pressed() -> void:
var d20 := randi() % 20 + 1
NetworkManager.broadcast_roll.rpc_id(1, NetworkManager.my_id, d20, _modifier)
func _on_roll_received(roller_peer_id: int, player_name: String, d20_result: int, modifier: int, total: int) -> void:
var mod_str := " %+d" % modifier if modifier != 0 else ""
var line := "[b]%s[/b] würfelt %d%s = [b]%d[/b]" % [player_name, d20_result, mod_str, total]
if d20_result == 20:
line = "[color=gold]" + line + " ★ NAT 20![/color]"
elif d20_result == 1:
line = "[color=red]" + line + " ☠ NAT 1[/color]"
_roll_log.append_text(line + "\n")