From d36fa9739e54860b0ed164677d04749aeaccde8e Mon Sep 17 00:00:00 2001 From: Kroonk Date: Sat, 23 May 2026 01:03:36 +0200 Subject: [PATCH] Build first lobby browser UI --- README.md | 8 +- docs/setup.md | 2 + .../Brogether-Brogether/mod_main.gd | 540 ++++++++++++++++-- 3 files changed, 504 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 1c2e540..c30b7f5 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Der aktuelle Stand ist bewusst ein technischer Prototyp. Es ist noch keine ferti - Brotatos Run-Szene wird per Script-Extension beobachtet. - 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. ## Was noch nicht fertig ist @@ -54,6 +55,11 @@ Danach Brotato komplett neu starten. Brogether ist im Spiel standardmaessig unsichtbar. Im Hauptmenue gibt es einen neuen `BROGETHER`-Button; alternativ oeffnet `F5` das Brogether-Modal. Das Modal pausiert das Spiel wie Brotatos eigenes F8-Feedback-Fenster und kann mit `Escape`, `F5` oder dem Close-Button wieder geschlossen werden. +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. + ## Debug-Tasten ```text @@ -65,7 +71,7 @@ F10 Ping senden F11 Lokalen Loopback-Test starten ``` -Das Brogether-Modal enthaelt Buttons fuer Host, Join, Leave, Ping und Loopback. Es ist fuer die naechsten Zwei-Client-Tests gedacht und ersetzt die meisten Hotkey-Aktionen. +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. Brogether schreibt ein eigenes Log nach: diff --git a/docs/setup.md b/docs/setup.md index 636bf8b..df6f335 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -29,6 +29,8 @@ Danach Brotato ueber Steam starten. Die Mod sollte im Mod-Menue auftauchen. Brogether selbst bleibt danach unsichtbar, bis es geoeffnet wird. Im Hauptmenue fuegt die Mod einen `BROGETHER`-Button hinzu. Alternativ oeffnet `F5` dasselbe Modal. Das Modal verhaelt sich bewusst wie Brotatos F8-Feedback-Fenster: es pausiert, legt sich ueber das Spiel und kann mit `Escape`, `F5` oder `Close` geschlossen werden. +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. + ## 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 6e8af35..5ed439f 100644 --- a/src/mods-unpacked/Brogether-Brogether/mod_main.gd +++ b/src/mods-unpacked/Brogether-Brogether/mod_main.gd @@ -2,10 +2,16 @@ extends Node const MOD_ID := "Brogether-Brogether" const LOG_PATH := "user://brogether.log" +const BROGETHER_VERSION := "0.1.0" +const MAX_LOBBY_MEMBERS := 2 var steam = null var lobby_id := 0 var lobby_members := [] +var available_lobbies := [] +var selected_lobby_id := 0 +var pending_lobby_name := "" +var pending_lobby_password_hash := "" var steam_id := 0 var is_host := false var packet_sequence := 0 @@ -16,6 +22,18 @@ var panel: PanelContainer = null var panel_status_label: Label = null var panel_log_label: Label = null var panel_default_focus: Control = null +var browser_view: Control = null +var lobby_view: Control = null +var lobby_list_container: VBoxContainer = null +var lobby_empty_label: Label = null +var lobby_name_input: LineEdit = null +var lobby_password_input: LineEdit = null +var lobby_password_check: CheckBox = null +var join_password_input: LineEdit = null +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 status_time_left := 0.0 var restore_paused_value := false var restore_focus_control = null @@ -82,7 +100,7 @@ func _input(event: InputEvent) -> void: get_tree().set_input_as_handled() -func create_lobby() -> void: +func create_lobby(password: String = "", lobby_name: String = "") -> void: if steam == null: _log("cannot create lobby: Steam singleton missing") return @@ -90,20 +108,27 @@ func create_lobby() -> void: _log("already in lobby %s" % lobby_id) _update_debug_panel() return + pending_lobby_name = lobby_name.strip_edges() + if pending_lobby_name.empty(): + pending_lobby_name = "%s's Brogether Lobby" % _get_persona_name(steam_id) + pending_lobby_password_hash = _hash_password(password) var lobby_type := 2 - _log("creating public dev lobby") - steam.call("createLobby", lobby_type, 2) + _log("creating lobby '%s'" % pending_lobby_name) + steam.call("createLobby", lobby_type, MAX_LOBBY_MEMBERS) func find_lobbies() -> void: if steam == null: _log("cannot find lobbies: Steam singleton missing") return + available_lobbies.clear() + selected_lobby_id = 0 + _update_lobby_browser() if steam.has_method("addRequestLobbyListStringFilter"): steam.call("addRequestLobbyListStringFilter", "brogether", "1", 0) if steam.has_method("addRequestLobbyListDistanceFilter"): steam.call("addRequestLobbyListDistanceFilter", 2) - _log("requesting lobby list") + _log("refreshing lobby browser") steam.call("requestLobbyList") @@ -125,6 +150,7 @@ func leave_lobby() -> void: lobby_id = 0 lobby_members.clear() is_host = false + _show_browser_view() _update_debug_panel() @@ -253,10 +279,14 @@ func _on_lobby_created(connect: int, created_lobby_id: int) -> void: is_host = true _log("created lobby %s" % lobby_id) steam.call("setLobbyJoinable", lobby_id, true) - steam.call("setLobbyData", lobby_id, "name", "Brogether") + steam.call("setLobbyData", lobby_id, "name", pending_lobby_name) steam.call("setLobbyData", lobby_id, "brogether", "1") - steam.call("setLobbyData", lobby_id, "version", "0.1.0") + steam.call("setLobbyData", lobby_id, "version", BROGETHER_VERSION) + 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) _refresh_lobby_members() + _show_lobby_view() _update_debug_panel() @@ -270,17 +300,66 @@ func _on_lobby_joined(joined_lobby_id: int, _permissions: int, _locked: bool, re 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()]) _send_p2p_packet(0, {"type": "handshake", "from": steam_id}) + _show_lobby_view() _update_debug_panel() func _on_lobby_match_list(lobbies: Array) -> void: - _log("found %s lobby candidate(s)" % lobbies.size()) + available_lobbies.clear() for candidate in lobbies: var candidate_id := int(candidate) if steam.has_method("getLobbyData") and steam.call("getLobbyData", candidate_id, "brogether") == "1": - join_lobby(candidate_id) - return - _log("no Brogether lobby found") + available_lobbies.append(_get_lobby_summary(candidate_id)) + _log("found %s Brogether lobby candidate(s)" % available_lobbies.size()) + _update_lobby_browser() + + +func _get_lobby_summary(target_lobby_id: int) -> Dictionary: + var member_count := 0 + var max_members := MAX_LOBBY_MEMBERS + if steam != null and steam.has_method("getNumLobbyMembers"): + member_count = int(steam.call("getNumLobbyMembers", target_lobby_id)) + if steam != null and steam.has_method("getLobbyMemberLimit"): + max_members = int(steam.call("getLobbyMemberLimit", target_lobby_id)) + var owner_id := 0 + if steam != null and steam.has_method("getLobbyOwner"): + owner_id = int(steam.call("getLobbyOwner", target_lobby_id)) + return { + "id": target_lobby_id, + "name": _get_lobby_data(target_lobby_id, "name", "Brogether Lobby"), + "owner_id": owner_id, + "owner_name": _get_persona_name(owner_id), + "member_count": member_count, + "max_members": max_members, + "has_password": _get_lobby_data(target_lobby_id, "has_password", "0") == "1", + "password_hash": _get_lobby_data(target_lobby_id, "password_hash", ""), + "version": _get_lobby_data(target_lobby_id, "version", "") + } + + +func _get_lobby_data(target_lobby_id: int, key: String, fallback: String = "") -> String: + if steam == null or not steam.has_method("getLobbyData"): + return fallback + var value = steam.call("getLobbyData", target_lobby_id, key) + if value == null: + return fallback + var text := str(value) + return fallback if text.empty() else text + + +func _get_persona_name(target_steam_id: int) -> String: + if target_steam_id == 0: + return "Unknown" + if steam != null and steam.has_method("getFriendPersonaName"): + var persona := str(steam.call("getFriendPersonaName", target_steam_id)) + if not persona.empty(): + return persona + return str(target_steam_id) + + +func _hash_password(password: String) -> String: + var clean := password.strip_edges() + return "" if clean.empty() else str(clean.hash()) func _on_lobby_chat_update(this_lobby_id: int, _changed_id: int, _making_change_id: int, _chat_state: int) -> void: @@ -308,11 +387,9 @@ func _refresh_lobby_members() -> void: var member_count := int(steam.call("getNumLobbyMembers", lobby_id)) for index in range(member_count): var member_id := int(steam.call("getLobbyMemberByIndex", lobby_id, index)) - var persona := str(member_id) - if steam.has_method("getFriendPersonaName"): - persona = steam.call("getFriendPersonaName", member_id) - lobby_members.append({"steam_id": member_id, "name": persona}) + lobby_members.append({"steam_id": member_id, "name": _get_persona_name(member_id)}) _log("members: %s" % lobby_members) + _update_lobby_room() _update_debug_panel() @@ -452,72 +529,441 @@ func _create_debug_panel() -> void: panel.anchor_right = 0.5 panel.anchor_top = 0.5 panel.anchor_bottom = 0.5 - panel.margin_left = -260 - panel.margin_right = 260 - panel.margin_top = -170 - panel.margin_bottom = 170 + panel.margin_left = -430 + panel.margin_right = 430 + panel.margin_top = -300 + panel.margin_bottom = 300 panel.pause_mode = Node.PAUSE_MODE_PROCESS panel.visible = false - var style: StyleBoxFlat = StyleBoxFlat.new() - style.bg_color = Color(0.08, 0.09, 0.1, 0.96) - style.border_color = Color(0.55, 0.58, 0.62, 1.0) - style.set_border_width_all(1) - style.set_corner_radius_all(3) - style.content_margin_left = 18 - style.content_margin_right = 18 - style.content_margin_top = 16 - style.content_margin_bottom = 16 - panel.add_stylebox_override("panel", style) + panel.add_stylebox_override("panel", _make_panel_style(Color(0.075, 0.08, 0.09, 0.98), Color(0.55, 0.58, 0.62, 1.0), 18)) var box: VBoxContainer = VBoxContainer.new() box.name = "Content" - box.add_constant_override("separation", 10) + box.add_constant_override("separation", 12) panel.add_child(box) + var header: HBoxContainer = HBoxContainer.new() + header.add_constant_override("separation", 10) + box.add_child(header) + var title: Label = Label.new() title.text = "BROGETHER" - title.align = Label.ALIGN_CENTER + title.rect_min_size = Vector2(220, 28) title.add_color_override("font_color", Color(1, 1, 1, 1)) - box.add_child(title) + header.add_child(title) panel_status_label = Label.new() panel_status_label.text = "Steam: ..." + panel_status_label.align = Label.ALIGN_RIGHT panel_status_label.autowrap = true - box.add_child(panel_status_label) + panel_status_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + header.add_child(panel_status_label) - var grid: GridContainer = GridContainer.new() - grid.columns = 2 - grid.add_constant_override("hseparation", 6) - grid.add_constant_override("vseparation", 6) - box.add_child(grid) + var close_button: Button = Button.new() + close_button.text = "X" + close_button.rect_min_size = Vector2(42, 34) + close_button.connect("pressed", self, "close_debug_panel") + header.add_child(close_button) - panel_default_focus = _add_panel_button(grid, "Host", "create_lobby") - _add_panel_button(grid, "Join", "find_lobbies") - _add_panel_button(grid, "Leave", "leave_lobby") - _add_panel_button(grid, "Ping", "send_ping") - _add_panel_button(grid, "Loopback", "run_loopback_test") - _add_panel_button(grid, "Close", "close_debug_panel") + var separator: HSeparator = HSeparator.new() + box.add_child(separator) + + browser_view = HBoxContainer.new() + browser_view.name = "LobbyBrowser" + browser_view.size_flags_vertical = Control.SIZE_EXPAND_FILL + browser_view.add_constant_override("separation", 14) + box.add_child(browser_view) + + var browser_column: VBoxContainer = VBoxContainer.new() + browser_column.rect_min_size = Vector2(390, 410) + browser_column.size_flags_vertical = Control.SIZE_EXPAND_FILL + browser_column.add_constant_override("separation", 8) + browser_view.add_child(browser_column) + + var browser_title: Label = Label.new() + browser_title.text = "LOBBY BROWSER" + browser_title.add_color_override("font_color", Color(0.92, 0.96, 1.0, 1.0)) + browser_column.add_child(browser_title) + + var browser_hint: Label = Label.new() + browser_hint.text = "Offene Brogether-Lobbys werden hier angezeigt." + browser_hint.autowrap = true + browser_column.add_child(browser_hint) + + var scroll: ScrollContainer = ScrollContainer.new() + scroll.rect_min_size = Vector2(390, 300) + scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL + browser_column.add_child(scroll) + + lobby_list_container = VBoxContainer.new() + lobby_list_container.add_constant_override("separation", 6) + scroll.add_child(lobby_list_container) + + lobby_empty_label = Label.new() + lobby_empty_label.text = "Noch keine Lobbys geladen." + lobby_empty_label.autowrap = true + lobby_list_container.add_child(lobby_empty_label) + + var refresh_button: Button = Button.new() + refresh_button.text = "Refresh" + refresh_button.rect_min_size = Vector2(390, 36) + refresh_button.connect("pressed", self, "find_lobbies") + browser_column.add_child(refresh_button) + + var action_column: VBoxContainer = VBoxContainer.new() + action_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL + action_column.add_constant_override("separation", 10) + browser_view.add_child(action_column) + + var create_box: PanelContainer = PanelContainer.new() + create_box.add_stylebox_override("panel", _make_panel_style(Color(0.105, 0.11, 0.12, 0.95), Color(0.28, 0.31, 0.34, 1.0), 12)) + action_column.add_child(create_box) + + var create_content: VBoxContainer = VBoxContainer.new() + create_content.add_constant_override("separation", 8) + create_box.add_child(create_content) + + var create_title: Label = Label.new() + create_title.text = "LOBBY ERSTELLEN" + create_content.add_child(create_title) + + lobby_name_input = LineEdit.new() + lobby_name_input.placeholder_text = "Lobbyname" + lobby_name_input.rect_min_size = Vector2(360, 34) + create_content.add_child(lobby_name_input) + + lobby_password_check = CheckBox.new() + lobby_password_check.text = "Passwort verwenden" + create_content.add_child(lobby_password_check) + + lobby_password_input = LineEdit.new() + lobby_password_input.placeholder_text = "Passwort" + lobby_password_input.secret = true + lobby_password_input.rect_min_size = Vector2(360, 34) + create_content.add_child(lobby_password_input) + + var host_button: Button = Button.new() + host_button.text = "Host Lobby" + host_button.rect_min_size = Vector2(360, 38) + host_button.connect("pressed", self, "_create_lobby_from_ui") + create_content.add_child(host_button) + panel_default_focus = host_button + + var join_box: PanelContainer = PanelContainer.new() + join_box.add_stylebox_override("panel", _make_panel_style(Color(0.105, 0.11, 0.12, 0.95), Color(0.28, 0.31, 0.34, 1.0), 12)) + action_column.add_child(join_box) + + var join_content: VBoxContainer = VBoxContainer.new() + join_content.add_constant_override("separation", 8) + join_box.add_child(join_content) + + var join_title: Label = Label.new() + join_title.text = "AUSGEWAEHLTE LOBBY" + join_content.add_child(join_title) + + selected_lobby_label = Label.new() + selected_lobby_label.text = "Keine Lobby ausgewaehlt." + selected_lobby_label.autowrap = true + selected_lobby_label.rect_min_size = Vector2(360, 58) + join_content.add_child(selected_lobby_label) + + join_password_input = LineEdit.new() + join_password_input.placeholder_text = "Passwort, falls noetig" + join_password_input.secret = true + join_password_input.rect_min_size = Vector2(360, 34) + join_content.add_child(join_password_input) + + var join_button: Button = Button.new() + join_button.text = "Join Lobby" + join_button.rect_min_size = Vector2(360, 38) + join_button.connect("pressed", self, "_join_selected_lobby_from_ui") + join_content.add_child(join_button) + + lobby_view = VBoxContainer.new() + lobby_view.name = "LobbyRoom" + lobby_view.size_flags_vertical = Control.SIZE_EXPAND_FILL + lobby_view.add_constant_override("separation", 12) + lobby_view.visible = false + box.add_child(lobby_view) + + lobby_title_label = Label.new() + lobby_title_label.text = "WARTEBEREICH" + lobby_title_label.add_color_override("font_color", Color(0.92, 0.96, 1.0, 1.0)) + lobby_view.add_child(lobby_title_label) + + lobby_detail_label = Label.new() + lobby_detail_label.text = "Noch nicht in einer Lobby." + lobby_detail_label.autowrap = true + lobby_view.add_child(lobby_detail_label) + + lobby_members_container = GridContainer.new() + lobby_members_container.columns = 2 + lobby_members_container.add_constant_override("hseparation", 10) + lobby_members_container.add_constant_override("vseparation", 10) + lobby_members_container.size_flags_vertical = Control.SIZE_EXPAND_FILL + lobby_view.add_child(lobby_members_container) + + var lobby_actions: HBoxContainer = HBoxContainer.new() + lobby_actions.add_constant_override("separation", 8) + lobby_view.add_child(lobby_actions) + + _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") panel_log_label = Label.new() panel_log_label.text = "Ready" panel_log_label.autowrap = true - panel_log_label.rect_min_size = Vector2(480, 78) + panel_log_label.rect_min_size = Vector2(820, 44) box.add_child(panel_log_label) status_layer.add_child(panel) + _show_browser_view() _update_debug_panel() func _add_panel_button(parent: Control, label: String, method_name: String) -> Button: var button: Button = Button.new() button.text = label - button.rect_min_size = Vector2(236, 34) + button.rect_min_size = Vector2(130, 36) button.connect("pressed", self, method_name) parent.add_child(button) return button +func _make_panel_style(bg: Color, border: Color, margin: int) -> StyleBoxFlat: + var style: StyleBoxFlat = StyleBoxFlat.new() + style.bg_color = bg + style.border_color = border + style.set_border_width_all(1) + style.set_corner_radius_all(3) + style.content_margin_left = margin + style.content_margin_right = margin + style.content_margin_top = margin + style.content_margin_bottom = margin + return style + + +func _create_lobby_from_ui() -> void: + var password := "" + if lobby_password_check != null and lobby_password_check.pressed and lobby_password_input != null: + password = lobby_password_input.text + var display_name := "" + if lobby_name_input != null: + display_name = lobby_name_input.text + create_lobby(password, display_name) + + +func _join_selected_lobby_from_ui() -> void: + if selected_lobby_id == 0: + _log("select a lobby before joining") + return + var summary: Dictionary = _get_selected_lobby_summary() + if summary.has("has_password") and bool(summary["has_password"]): + var entered := "" + if join_password_input != null: + entered = join_password_input.text + if _hash_password(entered) != str(summary.get("password_hash", "")): + _log("wrong or missing lobby password") + return + join_lobby(selected_lobby_id) + + +func _show_browser_view() -> void: + if browser_view != null: + browser_view.visible = true + if lobby_view != null: + lobby_view.visible = false + _update_lobby_browser() + + +func _show_lobby_view() -> void: + if browser_view != null: + browser_view.visible = false + if lobby_view != null: + lobby_view.visible = true + _update_lobby_room() + + +func _update_lobby_browser() -> void: + if lobby_list_container == null: + return + _clear_children(lobby_list_container) + if available_lobbies.empty(): + lobby_empty_label = Label.new() + lobby_empty_label.text = "Keine Brogether-Lobbys gefunden.\nErstelle eine neue Lobby oder druecke Refresh." + lobby_empty_label.autowrap = true + lobby_empty_label.rect_min_size = Vector2(360, 80) + lobby_list_container.add_child(lobby_empty_label) + else: + for summary_data in available_lobbies: + var summary: Dictionary = summary_data + var button: Button = Button.new() + button.text = _format_lobby_summary(summary) + button.rect_min_size = Vector2(360, 74) + button.connect("pressed", self, "_on_lobby_row_pressed", [int(summary["id"])]) + lobby_list_container.add_child(button) + _update_selected_lobby_label() + + +func _format_lobby_summary(summary: Dictionary) -> String: + var lock_text := "PASSWORT" if bool(summary.get("has_password", false)) else "OFFEN" + return "%s\n%s/%s Spieler | %s | Host: %s" % [ + str(summary.get("name", "Brogether Lobby")), + str(summary.get("member_count", 0)), + str(summary.get("max_members", MAX_LOBBY_MEMBERS)), + lock_text, + str(summary.get("owner_name", "Unknown")) + ] + + +func _on_lobby_row_pressed(target_lobby_id: int) -> void: + selected_lobby_id = target_lobby_id + _update_selected_lobby_label() + + +func _update_selected_lobby_label() -> void: + if selected_lobby_label == null: + return + var summary: Dictionary = _get_selected_lobby_summary() + if summary.empty(): + selected_lobby_label.text = "Keine Lobby ausgewaehlt." + return + var password_text := "Passwort noetig" if bool(summary.get("has_password", false)) else "Kein Passwort" + selected_lobby_label.text = "%s\n%s/%s Spieler\n%s" % [ + str(summary.get("name", "Brogether Lobby")), + str(summary.get("member_count", 0)), + str(summary.get("max_members", MAX_LOBBY_MEMBERS)), + password_text + ] + + +func _get_selected_lobby_summary() -> Dictionary: + for summary_data in available_lobbies: + var summary: Dictionary = summary_data + if int(summary.get("id", 0)) == selected_lobby_id: + return summary + return {} + + +func _update_lobby_room() -> void: + if lobby_members_container == null: + return + _clear_children(lobby_members_container) + if lobby_id == 0: + if lobby_title_label != null: + lobby_title_label.text = "WARTEBEREICH" + if lobby_detail_label != null: + lobby_detail_label.text = "Noch nicht in einer Lobby." + return + var lobby_name: String = _get_lobby_data(lobby_id, "name", "Brogether Lobby") + var password_text := "mit Passwort" if _get_lobby_data(lobby_id, "has_password", "0") == "1" else "ohne Passwort" + 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] + 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) + + +func _add_member_card(member: Dictionary) -> void: + var card: PanelContainer = PanelContainer.new() + card.rect_min_size = Vector2(390, 128) + card.add_stylebox_override("panel", _make_panel_style(Color(0.12, 0.13, 0.14, 0.96), Color(0.32, 0.36, 0.39, 1.0), 10)) + lobby_members_container.add_child(card) + + var row: HBoxContainer = HBoxContainer.new() + row.add_constant_override("separation", 12) + card.add_child(row) + + var avatar: PanelContainer = PanelContainer.new() + avatar.rect_min_size = Vector2(74, 74) + avatar.add_stylebox_override("panel", _make_panel_style(_avatar_color(int(member.get("steam_id", 0))), Color(0.72, 0.76, 0.8, 1.0), 4)) + row.add_child(avatar) + + var initials: Label = Label.new() + initials.text = _initials(str(member.get("name", "?"))) + initials.align = Label.ALIGN_CENTER + initials.valign = Label.VALIGN_CENTER + initials.rect_min_size = Vector2(66, 66) + initials.add_color_override("font_color", Color(1, 1, 1, 1)) + avatar.add_child(initials) + + var text_box: VBoxContainer = VBoxContainer.new() + text_box.size_flags_horizontal = Control.SIZE_EXPAND_FILL + row.add_child(text_box) + + var name_label: Label = Label.new() + name_label.text = str(member.get("name", "Unknown")) + name_label.add_color_override("font_color", Color(0.95, 0.97, 1.0, 1.0)) + text_box.add_child(name_label) + + var id_label: Label = Label.new() + id_label.text = "Steam %s" % str(member.get("steam_id", 0)) + text_box.add_child(id_label) + + var role_label: Label = Label.new() + role_label.text = "Host" if int(member.get("steam_id", 0)) == _current_lobby_owner() else "Player" + text_box.add_child(role_label) + + +func _add_empty_member_slot(_index: int) -> void: + var card: PanelContainer = PanelContainer.new() + card.rect_min_size = Vector2(390, 128) + card.add_stylebox_override("panel", _make_panel_style(Color(0.09, 0.095, 0.105, 0.72), Color(0.2, 0.22, 0.24, 1.0), 10)) + lobby_members_container.add_child(card) + + var label: Label = Label.new() + label.text = "Freier Platz" + label.align = Label.ALIGN_CENTER + label.valign = Label.VALIGN_CENTER + label.rect_min_size = Vector2(360, 96) + card.add_child(label) + + +func _current_lobby_owner() -> int: + if steam != null and lobby_id != 0 and steam.has_method("getLobbyOwner"): + return int(steam.call("getLobbyOwner", lobby_id)) + return steam_id if is_host else 0 + + +func _initials(display_name: String) -> String: + var clean := display_name.strip_edges() + if clean.empty(): + return "?" + var result := "" + var parts := clean.split(" ", false) + for part in parts: + if result.length() >= 2: + break + if str(part).length() > 0: + result += str(part).substr(0, 1).to_upper() + if result.empty(): + result = clean.substr(0, 1).to_upper() + return result + + +func _avatar_color(value: int) -> Color: + var colors: Array = [ + Color(0.25, 0.45, 0.82, 1.0), + Color(0.68, 0.35, 0.72, 1.0), + Color(0.25, 0.58, 0.48, 1.0), + Color(0.74, 0.44, 0.28, 1.0) + ] + var color_index: int = int(abs(value)) % colors.size() + return colors[color_index] + + +func _clear_children(parent: Node) -> void: + for child in parent.get_children(): + parent.remove_child(child) + child.queue_free() + + func toggle_debug_panel() -> void: if panel != null and panel.visible: close_debug_panel() @@ -535,6 +981,10 @@ func open_debug_panel() -> void: dim_background.visible = true panel.visible = true set_process_input(true) + if lobby_id != 0: + _show_lobby_view() + else: + _show_browser_view() if panel_default_focus != null and is_instance_valid(panel_default_focus): panel_default_focus.call_deferred("grab_focus") _update_debug_panel("Panel opened")