d3bed34c6c
Bundles several sessions' worth of previously uncommitted work: map categories with Domination/Conquest/King of the Hill mode stubs and a server-list mode filter; a procedurally-drawn character-creation screen replacing the old callsign-only PROFILE overlay; the on-foot groundwork (walkable station hub, ship interior, character controller) plus the RAM currency backend; and today's addition, an in-ship Helldivers-2-style navigation table that QUICK PLAY's queue/connect flow and the Belters/ Military hub travel now live behind, with hub-and-ship return paths and a context-aware pause menu usable both in-match and inside the ship. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
517 lines
17 KiB
GDScript
517 lines
17 KiB
GDScript
extends Control
|
|
|
|
# Replaces the old menu/server_browser.gd, which baked in its own race picker
|
|
# from before TeamSelect existed and was never wired to a button. This screen
|
|
# only picks a server (backed by the matchmaking API's GET /servers) and
|
|
# connects directly — race/ship selection happens in-world via TeamSelect,
|
|
# same as the matchmaking-queue join path.
|
|
|
|
const STATUS_COLOR := {
|
|
"available": Color(0.4, 0.9, 0.5),
|
|
"full": Color(0.95, 0.75, 0.3),
|
|
"offline": Color(0.55, 0.58, 0.65),
|
|
}
|
|
|
|
const PING_TIMEOUT_MSEC := 1500
|
|
const PING_COLOR_GOOD := Color(0.4, 0.9, 0.5)
|
|
const PING_COLOR_OK := Color(0.95, 0.75, 0.3)
|
|
const PING_COLOR_BAD := Color(0.9, 0.4, 0.4)
|
|
|
|
var _all_servers: Array = [] # every row from the last GET /servers, unfiltered
|
|
var _servers: Array = [] # _all_servers narrowed to _selected_category -- what's actually rendered/connectable
|
|
var _selected_category: String = "" # "" = ALL MODES; otherwise one of GameConfig.GAME_MODE_IDS
|
|
var _category_group: ButtonGroup
|
|
var _selected_index: int = -1
|
|
var _row_group: ButtonGroup
|
|
var _list_vbox: VBoxContainer
|
|
var _status_lbl: Label
|
|
var _connect_btn: Button
|
|
|
|
# Per-row PLAYERS/PING labels, index-aligned with _servers. Ping is measured
|
|
# by actively probing each server's UDP query port (game port + 1, see
|
|
# NetworkManager) rather than trusting the DB's static fields — the demo
|
|
# rows seeded by the matchmaking API aren't real reachable servers, so
|
|
# probing also doubles as a live "is this actually up" check for the ones
|
|
# that are.
|
|
var _players_labels: Array[Label] = []
|
|
var _ping_labels: Array[Label] = []
|
|
var _ping_probes: Array = [] # {udp: PacketPeerUDP, start_msec: int, done: bool} or null
|
|
|
|
|
|
func _ready() -> void:
|
|
_build_ui()
|
|
MusicManager.play_menu_music()
|
|
NetworkManager.connection_succeeded.connect(_on_connected)
|
|
NetworkManager.connection_failed.connect(_on_connect_failed)
|
|
_refresh_servers()
|
|
|
|
|
|
func _process(_delta: float) -> void:
|
|
if _ping_probes.is_empty():
|
|
return
|
|
var now := Time.get_ticks_msec()
|
|
for index in _ping_probes.size():
|
|
var probe = _ping_probes[index]
|
|
if probe == null or probe.done:
|
|
continue
|
|
var udp: PacketPeerUDP = probe.udp
|
|
if udp.get_available_packet_count() > 0:
|
|
var packet := udp.get_packet()
|
|
_apply_ping_result(index, packet.get_string_from_utf8(), now - probe.start_msec)
|
|
probe.done = true
|
|
elif now - probe.start_msec > PING_TIMEOUT_MSEC:
|
|
_apply_ping_result(index, "", -1)
|
|
probe.done = true
|
|
|
|
|
|
func _build_ui() -> void:
|
|
_add_bg()
|
|
_add_header()
|
|
_add_category_filters()
|
|
_add_list_panel()
|
|
_add_bottom_bar()
|
|
|
|
|
|
# Game-mode toggle bar (ALL MODES + one per GameConfig.GAME_MODE_IDS) -- a
|
|
# separate, filterable category from the map/address text each row already
|
|
# shows, so a player can narrow the list to e.g. just Team Deathmatch servers
|
|
# instead of reading mode off every row by eye. A ButtonGroup makes these
|
|
# mutually exclusive (radio-style), same pattern _row_group already uses for
|
|
# the server rows below.
|
|
func _add_category_filters() -> void:
|
|
var bar := HBoxContainer.new()
|
|
bar.anchor_left = 0.0
|
|
bar.anchor_top = 0.0
|
|
bar.anchor_right = 1.0
|
|
bar.anchor_bottom = 0.0
|
|
bar.offset_left = 60.0
|
|
bar.offset_top = 100.0
|
|
bar.offset_right = -60.0
|
|
bar.offset_bottom = 130.0
|
|
bar.add_theme_constant_override("separation", 8)
|
|
add_child(bar)
|
|
|
|
_category_group = ButtonGroup.new()
|
|
var categories: Array = [""]
|
|
categories.append_array(GameConfig.GAME_MODE_IDS)
|
|
for cat_id in categories:
|
|
bar.add_child(_make_category_button(cat_id))
|
|
|
|
|
|
func _make_category_button(cat_id: String) -> Button:
|
|
var btn := Button.new()
|
|
btn.text = "ALL MODES" if cat_id == "" else _format_mode_name(cat_id)
|
|
btn.toggle_mode = true
|
|
btn.button_pressed = cat_id == _selected_category
|
|
btn.button_group = _category_group
|
|
btn.custom_minimum_size = Vector2(0, 30)
|
|
btn.add_theme_font_size_override("font_size", 13)
|
|
|
|
var sn := StyleBoxFlat.new()
|
|
sn.bg_color = Color(1.0, 1.0, 1.0, 0.06)
|
|
sn.set_corner_radius_all(4)
|
|
sn.content_margin_left = 12.0
|
|
sn.content_margin_right = 12.0
|
|
var sh := StyleBoxFlat.new()
|
|
sh.bg_color = Color(1.0, 1.0, 1.0, 0.14)
|
|
sh.set_corner_radius_all(4)
|
|
sh.content_margin_left = 12.0
|
|
sh.content_margin_right = 12.0
|
|
var sp := StyleBoxFlat.new()
|
|
sp.bg_color = Color(1.0, 1.0, 1.0, 0.9)
|
|
sp.set_corner_radius_all(4)
|
|
sp.content_margin_left = 12.0
|
|
sp.content_margin_right = 12.0
|
|
btn.add_theme_stylebox_override("normal", sn)
|
|
btn.add_theme_stylebox_override("hover", sh)
|
|
btn.add_theme_stylebox_override("pressed", sp)
|
|
btn.add_theme_color_override("font_color", Color(0.85, 0.87, 0.92))
|
|
btn.add_theme_color_override("font_color_pressed", Color(0.05, 0.05, 0.08))
|
|
|
|
btn.pressed.connect(_on_category_selected.bind(cat_id))
|
|
return btn
|
|
|
|
|
|
func _on_category_selected(cat_id: String) -> void:
|
|
if _selected_category == cat_id:
|
|
return
|
|
_selected_category = cat_id
|
|
_apply_filter()
|
|
|
|
|
|
func _add_bg() -> void:
|
|
var bg := TextureRect.new()
|
|
bg.texture = load("res://assets/images/background/skybox/1.png")
|
|
bg.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
|
|
bg.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
|
|
bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
add_child(bg)
|
|
|
|
var ov := ColorRect.new()
|
|
ov.color = Color(0.0, 0.0, 0.0, 0.6)
|
|
ov.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
add_child(ov)
|
|
|
|
|
|
func _add_header() -> void:
|
|
var title := Label.new()
|
|
title.text = "SERVER SELECT"
|
|
title.anchor_left = 0.0
|
|
title.anchor_top = 0.0
|
|
title.anchor_right = 0.0
|
|
title.anchor_bottom = 0.0
|
|
title.offset_left = 60.0
|
|
title.offset_top = 48.0
|
|
title.offset_right = 700.0
|
|
title.offset_bottom = 92.0
|
|
title.add_theme_font_size_override("font_size", 32)
|
|
title.add_theme_color_override("font_color", Color(1.0, 1.0, 1.0))
|
|
add_child(title)
|
|
|
|
|
|
func _add_list_panel() -> void:
|
|
var col_hdr := HBoxContainer.new()
|
|
col_hdr.anchor_left = 0.0
|
|
col_hdr.anchor_top = 0.0
|
|
col_hdr.anchor_right = 1.0
|
|
col_hdr.anchor_bottom = 0.0
|
|
col_hdr.offset_left = 60.0
|
|
col_hdr.offset_top = 142.0
|
|
col_hdr.offset_right = -60.0
|
|
col_hdr.offset_bottom = 166.0
|
|
add_child(col_hdr)
|
|
|
|
var h_name := _field_label("MODE / ADDRESS")
|
|
h_name.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
col_hdr.add_child(h_name)
|
|
var h_players := _field_label("PLAYERS")
|
|
h_players.custom_minimum_size = Vector2(100, 0)
|
|
h_players.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
col_hdr.add_child(h_players)
|
|
var h_ping := _field_label("PING")
|
|
h_ping.custom_minimum_size = Vector2(80, 0)
|
|
h_ping.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
col_hdr.add_child(h_ping)
|
|
var h_status := _field_label("STATUS")
|
|
h_status.custom_minimum_size = Vector2(120, 0)
|
|
h_status.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
col_hdr.add_child(h_status)
|
|
|
|
var sep := HSeparator.new()
|
|
sep.anchor_left = 0.0
|
|
sep.anchor_top = 0.0
|
|
sep.anchor_right = 1.0
|
|
sep.anchor_bottom = 0.0
|
|
sep.offset_left = 60.0
|
|
sep.offset_top = 170.0
|
|
sep.offset_right = -60.0
|
|
sep.offset_bottom = 174.0
|
|
sep.add_theme_color_override("color", Color(1.0, 1.0, 1.0, 0.2))
|
|
add_child(sep)
|
|
|
|
var scroll := ScrollContainer.new()
|
|
scroll.anchor_left = 0.0
|
|
scroll.anchor_top = 0.0
|
|
scroll.anchor_right = 1.0
|
|
scroll.anchor_bottom = 1.0
|
|
scroll.offset_left = 60.0
|
|
scroll.offset_top = 182.0
|
|
scroll.offset_right = -60.0
|
|
scroll.offset_bottom = -84.0
|
|
add_child(scroll)
|
|
|
|
_list_vbox = VBoxContainer.new()
|
|
_list_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
_list_vbox.add_theme_constant_override("separation", 2)
|
|
scroll.add_child(_list_vbox)
|
|
|
|
|
|
func _field_label(txt: String) -> Label:
|
|
var lbl := Label.new()
|
|
lbl.text = txt
|
|
lbl.add_theme_font_size_override("font_size", 12)
|
|
lbl.add_theme_color_override("font_color", Color(0.6, 0.63, 0.7))
|
|
return lbl
|
|
|
|
|
|
func _add_bottom_bar() -> void:
|
|
var back_btn := Button.new()
|
|
back_btn.text = "BACK"
|
|
back_btn.anchor_left = 0.0
|
|
back_btn.anchor_top = 1.0
|
|
back_btn.anchor_right = 0.0
|
|
back_btn.anchor_bottom = 1.0
|
|
back_btn.offset_left = 60.0
|
|
back_btn.offset_top = -60.0
|
|
back_btn.offset_right = 200.0
|
|
back_btn.offset_bottom = -20.0
|
|
back_btn.add_theme_font_size_override("font_size", 15)
|
|
back_btn.pressed.connect(_on_back)
|
|
add_child(back_btn)
|
|
|
|
var refresh_btn := Button.new()
|
|
refresh_btn.text = "REFRESH"
|
|
refresh_btn.anchor_left = 0.0
|
|
refresh_btn.anchor_top = 1.0
|
|
refresh_btn.anchor_right = 0.0
|
|
refresh_btn.anchor_bottom = 1.0
|
|
refresh_btn.offset_left = 216.0
|
|
refresh_btn.offset_top = -60.0
|
|
refresh_btn.offset_right = 356.0
|
|
refresh_btn.offset_bottom = -20.0
|
|
refresh_btn.add_theme_font_size_override("font_size", 15)
|
|
refresh_btn.pressed.connect(_refresh_servers)
|
|
add_child(refresh_btn)
|
|
|
|
_status_lbl = Label.new()
|
|
_status_lbl.text = ""
|
|
_status_lbl.anchor_left = 0.0
|
|
_status_lbl.anchor_top = 1.0
|
|
_status_lbl.anchor_right = 1.0
|
|
_status_lbl.anchor_bottom = 1.0
|
|
_status_lbl.offset_left = 372.0
|
|
_status_lbl.offset_top = -60.0
|
|
_status_lbl.offset_right = -216.0
|
|
_status_lbl.offset_bottom = -20.0
|
|
_status_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
_status_lbl.add_theme_font_size_override("font_size", 13)
|
|
_status_lbl.add_theme_color_override("font_color", Color(0.6, 0.72, 0.86))
|
|
add_child(_status_lbl)
|
|
|
|
_connect_btn = Button.new()
|
|
_connect_btn.text = "CONNECT"
|
|
_connect_btn.disabled = true
|
|
_connect_btn.anchor_left = 1.0
|
|
_connect_btn.anchor_top = 1.0
|
|
_connect_btn.anchor_right = 1.0
|
|
_connect_btn.anchor_bottom = 1.0
|
|
_connect_btn.offset_left = -200.0
|
|
_connect_btn.offset_top = -60.0
|
|
_connect_btn.offset_right = -60.0
|
|
_connect_btn.offset_bottom = -20.0
|
|
_connect_btn.add_theme_font_size_override("font_size", 16)
|
|
_connect_btn.add_theme_color_override("font_color", Color(1.0, 1.0, 1.0))
|
|
var sn := StyleBoxFlat.new()
|
|
sn.bg_color = Color(1.0, 1.0, 1.0, 0.12)
|
|
sn.set_corner_radius_all(4)
|
|
var sh := StyleBoxFlat.new()
|
|
sh.bg_color = Color(1.0, 1.0, 1.0, 0.22)
|
|
sh.set_corner_radius_all(4)
|
|
var sd := StyleBoxFlat.new()
|
|
sd.bg_color = Color(1.0, 1.0, 1.0, 0.05)
|
|
sd.set_corner_radius_all(4)
|
|
_connect_btn.add_theme_stylebox_override("normal", sn)
|
|
_connect_btn.add_theme_stylebox_override("hover", sh)
|
|
_connect_btn.add_theme_stylebox_override("disabled", sd)
|
|
_connect_btn.pressed.connect(_on_connect)
|
|
add_child(_connect_btn)
|
|
|
|
|
|
# Fetches the full unfiltered server list from the API. Re-rendering for a
|
|
# category toggle (_on_category_selected()) doesn't need to hit the network
|
|
# again -- that's _apply_filter()'s job, working off _all_servers.
|
|
func _refresh_servers() -> void:
|
|
_status_lbl.add_theme_color_override("font_color", Color(0.6, 0.72, 0.86))
|
|
_status_lbl.text = "Loading servers..."
|
|
|
|
var result: Dictionary = await MatchmakingClient.list_servers()
|
|
if not result.ok:
|
|
_all_servers = []
|
|
_clear_rows()
|
|
_status_lbl.add_theme_color_override("font_color", Color(0.85, 0.4, 0.4))
|
|
_status_lbl.text = "Couldn't reach matchmaking service."
|
|
return
|
|
|
|
_all_servers = result.servers
|
|
_apply_filter()
|
|
|
|
|
|
func _clear_rows() -> void:
|
|
_selected_index = -1
|
|
_connect_btn.disabled = true
|
|
for child in _list_vbox.get_children():
|
|
child.queue_free()
|
|
for probe in _ping_probes:
|
|
if probe != null:
|
|
probe.udp.close()
|
|
_players_labels.clear()
|
|
_ping_labels.clear()
|
|
_ping_probes.clear()
|
|
|
|
|
|
# Narrows _all_servers down to _selected_category ("" = no filter, show
|
|
# everything) and rebuilds the row list from that -- the only place _servers
|
|
# (what rows/ping-probes/CONNECT actually index into) gets assigned.
|
|
func _apply_filter() -> void:
|
|
_clear_rows()
|
|
_status_lbl.add_theme_color_override("font_color", Color(0.6, 0.72, 0.86))
|
|
|
|
_servers = _all_servers.filter(
|
|
func(s): return _selected_category == "" or str(s.get("game_mode", "")) == _selected_category
|
|
)
|
|
if _servers.is_empty():
|
|
_status_lbl.text = (
|
|
"No servers online right now." if _selected_category == ""
|
|
else "No %s servers online right now." % _format_mode_name(_selected_category)
|
|
)
|
|
return
|
|
|
|
_status_lbl.text = ""
|
|
_row_group = ButtonGroup.new()
|
|
for i in _servers.size():
|
|
_list_vbox.add_child(_make_row(_servers[i], i))
|
|
_start_ping_probes()
|
|
|
|
|
|
func _start_ping_probes() -> void:
|
|
for i in _servers.size():
|
|
var server: Dictionary = _servers[i]
|
|
var udp := PacketPeerUDP.new()
|
|
var query_port := int(server.get("port", 0)) + NetworkManager.QUERY_PORT_OFFSET
|
|
if udp.connect_to_host(str(server.get("ip", "")), query_port) != OK:
|
|
_ping_probes.append(null)
|
|
continue
|
|
udp.put_packet(NetworkManager.PING_REQUEST.to_utf8_buffer())
|
|
_ping_probes.append({"udp": udp, "start_msec": Time.get_ticks_msec(), "done": false})
|
|
|
|
|
|
func _apply_ping_result(index: int, response: String, rtt_msec: int) -> void:
|
|
if index >= _ping_labels.size():
|
|
return
|
|
var ping_lbl := _ping_labels[index]
|
|
if response.begins_with("SPACEWAR_PONG:"):
|
|
var parts := response.split(":")
|
|
if parts.size() >= 3:
|
|
_players_labels[index].text = "%s / %s" % [parts[1], parts[2]]
|
|
ping_lbl.text = "%d ms" % rtt_msec
|
|
if rtt_msec <= 80:
|
|
ping_lbl.add_theme_color_override("font_color", PING_COLOR_GOOD)
|
|
elif rtt_msec <= 180:
|
|
ping_lbl.add_theme_color_override("font_color", PING_COLOR_OK)
|
|
else:
|
|
ping_lbl.add_theme_color_override("font_color", PING_COLOR_BAD)
|
|
else:
|
|
ping_lbl.text = "‒‒"
|
|
ping_lbl.add_theme_color_override("font_color", Color(0.5, 0.53, 0.6))
|
|
|
|
|
|
# "team_deathmatch" -> "TEAM DEATHMATCH" -- matches GameMode.get_mode_name()'s
|
|
# own formatting (world/game_modes/*.gd) so the server list and the in-match
|
|
# scoreboard/banner never show two different spellings of the same mode.
|
|
func _format_mode_name(mode_id: String) -> String:
|
|
return mode_id.replace("_", " ").to_upper()
|
|
|
|
|
|
func _make_row(server: Dictionary, index: int) -> Button:
|
|
var row := Button.new()
|
|
row.toggle_mode = true
|
|
row.button_group = _row_group
|
|
row.text = ""
|
|
row.custom_minimum_size = Vector2(0, 46)
|
|
row.add_theme_font_size_override("font_size", 16)
|
|
|
|
var sn := StyleBoxEmpty.new()
|
|
var sh := StyleBoxFlat.new()
|
|
sh.bg_color = Color(1.0, 1.0, 1.0, 0.08)
|
|
sh.border_width_left = 3
|
|
sh.border_color = Color(1.0, 1.0, 1.0, 0.9)
|
|
row.add_theme_stylebox_override("normal", sn)
|
|
row.add_theme_stylebox_override("hover", sh)
|
|
row.add_theme_stylebox_override("pressed", sh)
|
|
|
|
var hb := HBoxContainer.new()
|
|
hb.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
hb.offset_left = 16.0
|
|
hb.offset_right = -16.0
|
|
hb.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
row.add_child(hb)
|
|
|
|
var game_mode: String = _format_mode_name(str(server.get("game_mode", "team_deathmatch")))
|
|
var map_name: String = str(server.get("map_name", "?"))
|
|
var addr_lbl := Label.new()
|
|
addr_lbl.text = "%s · %s %s:%s" % [game_mode, map_name, server.get("ip", "?"), str(server.get("port", "?"))]
|
|
addr_lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
addr_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
addr_lbl.add_theme_color_override("font_color", Color(0.85, 0.87, 0.92))
|
|
hb.add_child(addr_lbl)
|
|
|
|
# Seeded from the DB row immediately; overwritten by the live UDP query
|
|
# response in _apply_ping_result if that server actually answers.
|
|
var players_lbl := Label.new()
|
|
players_lbl.text = "%s / %s" % [str(server.get("player_count", "?")), str(server.get("max_players", "?"))]
|
|
players_lbl.custom_minimum_size = Vector2(100, 0)
|
|
players_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
players_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
players_lbl.add_theme_font_size_override("font_size", 13)
|
|
players_lbl.add_theme_color_override("font_color", Color(0.75, 0.78, 0.85))
|
|
hb.add_child(players_lbl)
|
|
_players_labels.append(players_lbl)
|
|
|
|
var ping_lbl := Label.new()
|
|
ping_lbl.text = "…"
|
|
ping_lbl.custom_minimum_size = Vector2(80, 0)
|
|
ping_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
ping_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
ping_lbl.add_theme_font_size_override("font_size", 13)
|
|
ping_lbl.add_theme_color_override("font_color", Color(0.6, 0.63, 0.7))
|
|
hb.add_child(ping_lbl)
|
|
_ping_labels.append(ping_lbl)
|
|
|
|
var status: String = str(server.get("status", "offline"))
|
|
var col: Color = STATUS_COLOR.get(status, Color(0.6, 0.63, 0.7))
|
|
var status_lbl := Label.new()
|
|
status_lbl.text = status.to_upper()
|
|
status_lbl.custom_minimum_size = Vector2(120, 0)
|
|
status_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
status_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
status_lbl.add_theme_font_size_override("font_size", 13)
|
|
status_lbl.add_theme_color_override("font_color", col)
|
|
hb.add_child(status_lbl)
|
|
|
|
row.pressed.connect(_on_row_selected.bind(index))
|
|
return row
|
|
|
|
|
|
func _on_row_selected(index: int) -> void:
|
|
_selected_index = index
|
|
_connect_btn.disabled = false
|
|
|
|
|
|
func _on_connect() -> void:
|
|
if _selected_index < 0 or _selected_index >= _servers.size():
|
|
return
|
|
if GameConfig.player_name.strip_edges().is_empty():
|
|
_status_lbl.add_theme_color_override("font_color", Color(0.85, 0.4, 0.4))
|
|
_status_lbl.text = "Set your callsign from the main menu PROFILE first."
|
|
return
|
|
|
|
var server: Dictionary = _servers[_selected_index]
|
|
var status := str(server.get("status", "offline"))
|
|
if status == "offline":
|
|
_status_lbl.add_theme_color_override("font_color", Color(0.85, 0.4, 0.4))
|
|
_status_lbl.text = "That server is offline."
|
|
return
|
|
if status == "full":
|
|
_status_lbl.add_theme_color_override("font_color", Color(0.85, 0.4, 0.4))
|
|
_status_lbl.text = "That server is full."
|
|
return
|
|
|
|
_connect_btn.disabled = true
|
|
_status_lbl.add_theme_color_override("font_color", Color(0.6, 0.72, 0.86))
|
|
_status_lbl.text = "Connecting..."
|
|
NetworkManager.join_server(server.ip, int(server.port))
|
|
|
|
|
|
func _on_connected() -> void:
|
|
get_tree().change_scene_to_file("res://world/world.tscn")
|
|
|
|
|
|
func _on_connect_failed() -> void:
|
|
_connect_btn.disabled = false
|
|
_status_lbl.add_theme_color_override("font_color", Color(0.85, 0.4, 0.4))
|
|
_status_lbl.text = "Connection failed — is that server reachable?"
|
|
|
|
|
|
func _on_back() -> void:
|
|
get_tree().change_scene_to_file("res://menu/main_menu.tscn")
|