Add lobby ready fake peer test mode

This commit is contained in:
Kroonk
2026-05-23 09:30:47 +02:00
parent a706caef02
commit 01cb2e8fdf
3 changed files with 178 additions and 3 deletions

View File

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

View File

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

View File

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