Build first lobby browser UI

This commit is contained in:
Kroonk
2026-05-23 01:03:36 +02:00
parent 7451be0f23
commit d36fa9739e
3 changed files with 504 additions and 46 deletions

View File

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

View File

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

View File

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