diff --git a/ruf-der-pilze/scenes/dm_view.tscn b/ruf-der-pilze/scenes/dm_view.tscn index 26f9816..36a314d 100644 --- a/ruf-der-pilze/scenes/dm_view.tscn +++ b/ruf-der-pilze/scenes/dm_view.tscn @@ -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"] diff --git a/ruf-der-pilze/scripts/dm_view.gd b/ruf-der-pilze/scripts/dm_view.gd index 9504fcf..ace9feb 100644 --- a/ruf-der-pilze/scripts/dm_view.gd +++ b/ruf-der-pilze/scripts/dm_view.gd @@ -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): diff --git a/ruf-der-pilze/scripts/network_manager.gd b/ruf-der-pilze/scripts/network_manager.gd index 69d24b3..61f0389 100644 --- a/ruf-der-pilze/scripts/network_manager.gd +++ b/ruf-der-pilze/scripts/network_manager.gd @@ -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) diff --git a/ruf-der-pilze/scripts/tavern.gd b/ruf-der-pilze/scripts/tavern.gd index 640e2f8..3f671bf 100644 --- a/ruf-der-pilze/scripts/tavern.gd +++ b/ruf-der-pilze/scripts/tavern.gd @@ -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")