From 01cb2e8fdf99bf29662038b23983d5fe2c85d8f3 Mon Sep 17 00:00:00 2001 From: Kroonk Date: Sat, 23 May 2026 09:30:47 +0200 Subject: [PATCH] Add lobby ready fake peer test mode --- README.md | 5 +- docs/setup.md | 2 + .../Brogether-Brogether/mod_main.gd | 174 +++++++++++++++++- 3 files changed, 178 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c30b7f5..0b8bd66 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Der aktuelle Stand ist bewusst ein technischer Prototyp. Es ist noch keine ferti - Run-Start, Spieler-Spawn und einfache Player-Snapshots werden geloggt bzw. fuer P2P-Versand vorbereitet. - Das Brogether-Modal ist standardmaessig versteckt und kann ueber das Hauptmenue oder `F5` geoeffnet werden. - Die UI enthaelt einen ersten Lobby-Browser, Lobby-Erstellung mit optionalem Passwort und einen Wartebereich mit Spielerkacheln. +- Der Wartebereich hat Ready-/Start-Status und einen Fake-Peer-Modus fuer lokale Tests ohne zweiten Client. ## Was noch nicht fertig ist @@ -58,7 +59,7 @@ Brogether ist im Spiel standardmaessig unsichtbar. Im Hauptmenue gibt es einen n Im Modal gibt es jetzt zwei Bereiche: - Lobby-Browser: zeigt gefundene Brogether-Lobbys mit Name, Host, Spielerzahl und Passwortstatus. -- Wartebereich: zeigt die Spieler in der aktuellen Lobby als Kacheln mit Steam-Namen und statischem Initialen-Avatar. +- Wartebereich: zeigt die Spieler in der aktuellen Lobby als Kacheln mit Steam-Namen, statischem Initialen-Avatar und Ready-Status. ## Debug-Tasten @@ -73,6 +74,8 @@ F11 Lokalen Loopback-Test starten Das Brogether-Modal enthaelt Buttons fuer Host, Join, Leave und Ping. Es ist fuer die naechsten Zwei-Client-Tests gedacht und ersetzt die meisten Hotkey-Aktionen. Passwort-Lobbys sind aktuell eine Mod-seitige Zugangskontrolle: die Lobby bleibt sichtbar, der Join-Button prueft aber das Passwort, bevor Brogether beitritt. +Fuer Tests ohne zweiten Steam-Client kann im Wartebereich `Add Fake` genutzt werden. Das fuegt einen lokalen Test-Peer hinzu, dessen Ready-Status mit `Fake Ready` umgeschaltet werden kann. `Start` wird nur fuer den Host aktiv, wenn alle sichtbaren Lobby-Mitglieder ready sind; aktuell schreibt Start nur ein Lobby-Protokollereignis ins Log und startet noch keinen echten Run. + Brogether schreibt ein eigenes Log nach: ```text diff --git a/docs/setup.md b/docs/setup.md index df6f335..9cabe2a 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -31,6 +31,8 @@ Brogether selbst bleibt danach unsichtbar, bis es geoeffnet wird. Im Hauptmenue Die UI hat jetzt einen ersten Lobby-Browser. Dort kann man Lobbys suchen, eine Lobby mit optionalem Passwort erstellen und einer ausgewaehlten Lobby beitreten. Nach Host/Join wechselt Brogether in einen Wartebereich mit Steam-Namen und statischen Initialen-Avataren fuer die Lobby-Spieler. +Ohne zweiten Client kann im Wartebereich `Add Fake` gedrueckt werden. Dadurch erscheint ein lokaler Test-Peer. Mit `Ready` und `Fake Ready` lassen sich beide Ready-Zustaende pruefen; `Start` wird beim Host erst aktiv, wenn alle sichtbaren Mitglieder ready sind. + ## Debug-Hotkeys im Spiel - `F5`: Brogether-Modal ein-/ausblenden diff --git a/src/mods-unpacked/Brogether-Brogether/mod_main.gd b/src/mods-unpacked/Brogether-Brogether/mod_main.gd index dd1ff61..7fd2b76 100644 --- a/src/mods-unpacked/Brogether-Brogether/mod_main.gd +++ b/src/mods-unpacked/Brogether-Brogether/mod_main.gd @@ -4,6 +4,7 @@ const MOD_ID := "Brogether-Brogether" const LOG_PATH := "user://brogether.log" const BROGETHER_VERSION := "0.1.0" const MAX_LOBBY_MEMBERS := 2 +const FAKE_PEER_ID := 48000000000000001 var steam = null var lobby_id := 0 @@ -12,6 +13,8 @@ var available_lobbies := [] var selected_lobby_id := 0 var pending_lobby_name := "" var pending_lobby_password_hash := "" +var lobby_ready_states := {} +var fake_peer_enabled := false var steam_id := 0 var is_host := false var packet_sequence := 0 @@ -34,6 +37,10 @@ var selected_lobby_label: Label = null var lobby_members_container: GridContainer = null var lobby_title_label: Label = null var lobby_detail_label: Label = null +var ready_button: Button = null +var start_button: Button = null +var fake_peer_button: Button = null +var fake_ready_button: Button = null var status_time_left := 0.0 var restore_paused_value := false var restore_focus_control = null @@ -69,6 +76,8 @@ func _exit_tree() -> void: is_host = false lobby_members.clear() available_lobbies.clear() + lobby_ready_states.clear() + fake_peer_enabled = false current_players.clear() _free_ui() steam = null @@ -164,6 +173,8 @@ func leave_lobby() -> void: steam.call("leaveLobby", lobby_id) lobby_id = 0 lobby_members.clear() + lobby_ready_states.clear() + fake_peer_enabled = false is_host = false _show_browser_view() _update_debug_panel() @@ -316,6 +327,8 @@ func _on_lobby_created(connect: int, created_lobby_id: int) -> void: steam.call("setLobbyData", lobby_id, "max_players", str(MAX_LOBBY_MEMBERS)) steam.call("setLobbyData", lobby_id, "has_password", "1" if not pending_lobby_password_hash.empty() else "0") steam.call("setLobbyData", lobby_id, "password_hash", pending_lobby_password_hash) + lobby_ready_states.clear() + _set_member_ready(steam_id, false) _refresh_lobby_members() _show_lobby_view() _update_debug_panel() @@ -327,6 +340,8 @@ func _on_lobby_joined(joined_lobby_id: int, _permissions: int, _locked: bool, re _log("join failed for lobby %s: %s" % [joined_lobby_id, response]) return lobby_id = joined_lobby_id + lobby_ready_states.clear() + _set_member_ready(steam_id, false) _refresh_lobby_members() is_host = steam.has_method("getLobbyOwner") and int(steam.call("getLobbyOwner", lobby_id)) == steam_id _log("joined lobby %s as %s with %s member(s)" % [lobby_id, "host" if is_host else "client", lobby_members.size()]) @@ -419,6 +434,9 @@ func _refresh_lobby_members() -> void: for index in range(member_count): var member_id := int(steam.call("getLobbyMemberByIndex", lobby_id, index)) lobby_members.append({"steam_id": member_id, "name": _get_persona_name(member_id)}) + if not lobby_ready_states.has(member_id): + _set_member_ready(member_id, false) + _append_fake_peer_if_needed() _log("members: %s" % lobby_members) _update_lobby_room() _update_debug_panel() @@ -471,6 +489,13 @@ func _handle_packet(sender: int, data, is_loopback: bool = false) -> void: }) "pong": _log("pong received from %s for sequence %s" % [sender, data.get("sequence", -1)]) + "ready_state": + var ready_sender := int(data.get("from", sender)) + _set_member_ready(ready_sender, bool(data.get("ready", false))) + _log("ready state from %s: %s" % [ready_sender, _ready_text(ready_sender)]) + _update_lobby_room() + "lobby_start": + _log("lobby start requested by %s: %s" % [sender, data]) "run_started": _log("remote run started from %s: %s" % [sender, data.get("state", {})]) "players_spawned": @@ -494,9 +519,12 @@ func _send_p2p_packet(target: int, packet_data: Dictionary) -> void: if target == 0: for member in lobby_members: var member_id := int(member["steam_id"]) - if member_id != steam_id: + if member_id != steam_id and not _is_fake_peer(member_id): steam.call("sendP2PPacket", member_id, payload, send_type, 0) else: + if _is_fake_peer(target): + _handle_packet(target, packet_data, true) + return steam.call("sendP2PPacket", target, payload, send_type, 0) @@ -739,6 +767,10 @@ func _create_debug_panel() -> void: lobby_actions.add_constant_override("separation", 8) lobby_view.add_child(lobby_actions) + ready_button = _add_panel_button(lobby_actions, "Ready", "toggle_local_ready") + start_button = _add_panel_button(lobby_actions, "Start", "request_lobby_start") + fake_peer_button = _add_panel_button(lobby_actions, "Fake Peer", "toggle_fake_peer") + fake_ready_button = _add_panel_button(lobby_actions, "Fake Ready", "toggle_fake_peer_ready") _add_panel_button(lobby_actions, "Ping", "send_ping") _add_panel_button(lobby_actions, "Leave", "leave_lobby") _add_panel_button(lobby_actions, "Browser", "_show_browser_view") @@ -801,6 +833,70 @@ func _join_selected_lobby_from_ui() -> void: join_lobby(selected_lobby_id) +func toggle_local_ready() -> void: + if lobby_id == 0: + _log("join or create a lobby before readying up") + return + var next_ready := not _is_member_ready(steam_id) + _set_member_ready(steam_id, next_ready) + _send_p2p_packet(0, { + "type": "ready_state", + "from": steam_id, + "ready": next_ready + }) + _log("local ready state: %s" % _ready_text(steam_id)) + _update_lobby_room() + + +func request_lobby_start() -> void: + if lobby_id == 0: + _log("create or join a lobby before starting") + return + if not is_host: + _log("only the lobby host can start") + return + if not _all_members_ready(): + _log("cannot start: not all lobby members are ready") + return + var start_packet := { + "type": "lobby_start", + "from": steam_id, + "lobby_id": str(lobby_id), + "member_count": lobby_members.size(), + "fake_peer_enabled": fake_peer_enabled + } + _log("lobby start ready: %s" % start_packet) + _send_p2p_packet(0, start_packet) + + +func toggle_fake_peer() -> void: + if lobby_id == 0: + _log("create a lobby before adding a fake peer") + return + fake_peer_enabled = not fake_peer_enabled + if fake_peer_enabled: + _set_member_ready(FAKE_PEER_ID, false) + _append_fake_peer_if_needed() + _handle_packet(FAKE_PEER_ID, {"type": "handshake", "from": FAKE_PEER_ID}, true) + _log("fake peer joined the lobby") + else: + _remove_fake_peer() + _log("fake peer left the lobby") + _update_lobby_room() + + +func toggle_fake_peer_ready() -> void: + if not fake_peer_enabled: + _log("add a fake peer before toggling fake ready") + return + var next_ready := not _is_member_ready(FAKE_PEER_ID) + _handle_packet(FAKE_PEER_ID, { + "type": "ready_state", + "from": FAKE_PEER_ID, + "ready": next_ready + }, true) + + func _show_browser_view() -> void: if browser_view != null: browser_view.visible = true @@ -893,12 +989,13 @@ func _update_lobby_room() -> void: if lobby_title_label != null: lobby_title_label.text = lobby_name.to_upper() if lobby_detail_label != null: - lobby_detail_label.text = "Wartebereich %s | %s/%s Spieler | %s" % [str(lobby_id), lobby_members.size(), MAX_LOBBY_MEMBERS, password_text] + lobby_detail_label.text = "Wartebereich %s | %s/%s Spieler | %s | %s" % [str(lobby_id), lobby_members.size(), MAX_LOBBY_MEMBERS, password_text, _lobby_ready_summary()] for member in lobby_members: _add_member_card(member) var empty_slot_count: int = max(0, MAX_LOBBY_MEMBERS - lobby_members.size()) for empty_slot in range(empty_slot_count): _add_empty_member_slot(empty_slot) + _update_lobby_controls() func _add_member_card(member: Dictionary) -> void: @@ -941,6 +1038,11 @@ func _add_member_card(member: Dictionary) -> void: role_label.text = "Host" if int(member.get("steam_id", 0)) == _current_lobby_owner() else "Player" text_box.add_child(role_label) + var ready_label: Label = Label.new() + ready_label.text = _ready_text(int(member.get("steam_id", 0))) + ready_label.add_color_override("font_color", Color(0.45, 0.9, 0.56, 1.0) if _is_member_ready(int(member.get("steam_id", 0))) else Color(0.9, 0.62, 0.4, 1.0)) + text_box.add_child(ready_label) + func _add_empty_member_slot(_index: int) -> void: var card: PanelContainer = PanelContainer.new() @@ -962,6 +1064,70 @@ func _current_lobby_owner() -> int: return steam_id if is_host else 0 +func _append_fake_peer_if_needed() -> void: + if not fake_peer_enabled: + return + for member in lobby_members: + if int(member.get("steam_id", 0)) == FAKE_PEER_ID: + return + lobby_members.append({"steam_id": FAKE_PEER_ID, "name": "Fake Test Peer", "fake": true}) + if not lobby_ready_states.has(FAKE_PEER_ID): + _set_member_ready(FAKE_PEER_ID, false) + + +func _remove_fake_peer() -> void: + for index in range(lobby_members.size() - 1, -1, -1): + var member: Dictionary = lobby_members[index] + if int(member.get("steam_id", 0)) == FAKE_PEER_ID: + lobby_members.remove(index) + lobby_ready_states.erase(FAKE_PEER_ID) + + +func _is_fake_peer(member_id: int) -> bool: + return member_id == FAKE_PEER_ID + + +func _set_member_ready(member_id: int, ready: bool) -> void: + lobby_ready_states[member_id] = ready + + +func _is_member_ready(member_id: int) -> bool: + return bool(lobby_ready_states.get(member_id, false)) + + +func _ready_text(member_id: int) -> String: + return "Ready" if _is_member_ready(member_id) else "Waiting" + + +func _all_members_ready() -> bool: + if lobby_members.empty(): + return false + for member in lobby_members: + if not _is_member_ready(int(member.get("steam_id", 0))): + return false + return true + + +func _lobby_ready_summary() -> String: + var ready_count := 0 + for member in lobby_members: + if _is_member_ready(int(member.get("steam_id", 0))): + ready_count += 1 + return "%s/%s ready" % [ready_count, lobby_members.size()] + + +func _update_lobby_controls() -> void: + if ready_button != null: + ready_button.text = "Unready" if _is_member_ready(steam_id) else "Ready" + if start_button != null: + start_button.disabled = not is_host or not _all_members_ready() + if fake_peer_button != null: + fake_peer_button.text = "Remove Fake" if fake_peer_enabled else "Add Fake" + if fake_ready_button != null: + fake_ready_button.disabled = not fake_peer_enabled + fake_ready_button.text = "Fake Unready" if fake_peer_enabled and _is_member_ready(FAKE_PEER_ID) else "Fake Ready" + + func _initials(display_name: String) -> String: var clean := display_name.strip_edges() if clean.empty(): @@ -1022,6 +1188,10 @@ func _free_ui() -> void: lobby_members_container = null lobby_title_label = null lobby_detail_label = null + ready_button = null + start_button = null + fake_peer_button = null + fake_ready_button = null func toggle_debug_panel() -> void: