Add public room discovery
This commit is contained in:
parent
59ef173ea7
commit
e204279b09
16 changed files with 857 additions and 22 deletions
|
|
@ -33,6 +33,14 @@ node names, connect persistent and networked state to that data.
|
||||||
network services validate and replicate bounded domains such as fishing,
|
network services validate and replicate bounded domains such as fishing,
|
||||||
sales, shops, item use, profiles, jobs, mail, chat, drawings, time, and weather.
|
sales, shops, item use, profiles, jobs, mail, chat, drawings, time, and weather.
|
||||||
|
|
||||||
|
`DiscoveryClient` is an optional directory layer beside `NetworkSession`. An
|
||||||
|
open host may publish a short-lived room lease, and the shared Join Game page
|
||||||
|
may browse compatible leases before handing the selected address back to the
|
||||||
|
existing direct ENet connection flow. The directory does not carry gameplay
|
||||||
|
traffic or become a gameplay authority. Its base URL comes from
|
||||||
|
`network/discovery/base_url`, with `NETFISHING_DISCOVERY_URL` available as a
|
||||||
|
development/deployment override.
|
||||||
|
|
||||||
The host is authoritative. Clients submit requests or evidence; the host
|
The host is authoritative. Clients submit requests or evidence; the host
|
||||||
derives trusted context from registered peers, authoritative regions, and
|
derives trusted context from registered peers, authoritative regions, and
|
||||||
server-owned state before mutating inventory, wallet, progression, or shared
|
server-owned state before mutating inventory, wallet, progression, or shared
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ const WorldPixelationPostprocessType = preload(
|
||||||
"res://main/world_pixelation_postprocess.gd"
|
"res://main/world_pixelation_postprocess.gd"
|
||||||
)
|
)
|
||||||
const NetworkSessionType = preload("res://network/network_session.gd")
|
const NetworkSessionType = preload("res://network/network_session.gd")
|
||||||
|
const DiscoveryClientType = preload("res://network/discovery_client.gd")
|
||||||
const NetworkProfilePreferencesType = preload(
|
const NetworkProfilePreferencesType = preload(
|
||||||
"res://network/network_profile_preferences.gd"
|
"res://network/network_profile_preferences.gd"
|
||||||
)
|
)
|
||||||
|
|
@ -131,6 +132,7 @@ const SHOP_PATTERN_SCALE: float = 1.75
|
||||||
%WorldPixelationPostprocess
|
%WorldPixelationPostprocess
|
||||||
)
|
)
|
||||||
@onready var _network_session: NetworkSessionType = %NetworkSession
|
@onready var _network_session: NetworkSessionType = %NetworkSession
|
||||||
|
@onready var _discovery: DiscoveryClientType = %DiscoveryClient
|
||||||
@onready var _world_time: WorldTimeServiceType = %WorldTimeService
|
@onready var _world_time: WorldTimeServiceType = %WorldTimeService
|
||||||
@onready var _world_time_visuals: WorldTimeVisualControllerType = (
|
@onready var _world_time_visuals: WorldTimeVisualControllerType = (
|
||||||
%WorldTimeVisualController
|
%WorldTimeVisualController
|
||||||
|
|
@ -313,6 +315,7 @@ func _initialize_after_data_root() -> void:
|
||||||
_server_trust,
|
_server_trust,
|
||||||
_host_bans,
|
_host_bans,
|
||||||
)
|
)
|
||||||
|
_discovery.setup(_network_session)
|
||||||
_world_time_visuals.setup(
|
_world_time_visuals.setup(
|
||||||
_world_time,
|
_world_time,
|
||||||
_test_world.get_world_environment(),
|
_test_world.get_world_environment(),
|
||||||
|
|
@ -546,6 +549,7 @@ func _initialize_after_data_root() -> void:
|
||||||
_asset_reservations,
|
_asset_reservations,
|
||||||
_network_profile_service,
|
_network_profile_service,
|
||||||
_network_player_list,
|
_network_player_list,
|
||||||
|
_discovery,
|
||||||
_settings_manager,
|
_settings_manager,
|
||||||
_network_surface_drawing,
|
_network_surface_drawing,
|
||||||
_player.art_unlocks,
|
_player.art_unlocks,
|
||||||
|
|
@ -623,6 +627,7 @@ func _initialize_after_data_root() -> void:
|
||||||
_network_session,
|
_network_session,
|
||||||
_saved_servers,
|
_saved_servers,
|
||||||
_server_trust,
|
_server_trust,
|
||||||
|
_discovery,
|
||||||
)
|
)
|
||||||
var pause_menu: PauseMenuType = _game_ui.get_pause_menu()
|
var pause_menu: PauseMenuType = _game_ui.get_pause_menu()
|
||||||
pause_menu.setup(
|
pause_menu.setup(
|
||||||
|
|
@ -633,6 +638,7 @@ func _initialize_after_data_root() -> void:
|
||||||
_network_session,
|
_network_session,
|
||||||
_saved_servers,
|
_saved_servers,
|
||||||
_server_trust,
|
_server_trust,
|
||||||
|
_discovery,
|
||||||
)
|
)
|
||||||
title_screen.join_game_requested.connect(_on_title_join_game_requested)
|
title_screen.join_game_requested.connect(_on_title_join_game_requested)
|
||||||
pause_menu.join_game_requested.connect(_on_pause_join_game_requested)
|
pause_menu.join_game_requested.connect(_on_pause_join_game_requested)
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@
|
||||||
[ext_resource type="Script" uid="uid://b0c3o2vy76fg3" path="res://world/shoreline_ambience.gd" id="54_shoreline_ambience"]
|
[ext_resource type="Script" uid="uid://b0c3o2vy76fg3" path="res://world/shoreline_ambience.gd" id="54_shoreline_ambience"]
|
||||||
[ext_resource type="AudioStream" uid="uid://dck3tf8qkadea" path="res://audio/ambience/waves.wav" id="55_waves"]
|
[ext_resource type="AudioStream" uid="uid://dck3tf8qkadea" path="res://audio/ambience/waves.wav" id="55_waves"]
|
||||||
[ext_resource type="Script" uid="uid://c80x8jkdnx0xy" path="res://settings/controller_mapping_manager.gd" id="56_controller_mapping"]
|
[ext_resource type="Script" uid="uid://c80x8jkdnx0xy" path="res://settings/controller_mapping_manager.gd" id="56_controller_mapping"]
|
||||||
|
[ext_resource type="Script" uid="uid://d0jv8n2nfqia0" path="res://network/discovery_client.gd" id="57_discovery"]
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water_native"]
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water_native"]
|
||||||
shader = ExtResource("40_title_water")
|
shader = ExtResource("40_title_water")
|
||||||
|
|
@ -214,6 +215,10 @@ script = ExtResource("38_identity_backup")
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
script = ExtResource("17_network_session")
|
script = ExtResource("17_network_session")
|
||||||
|
|
||||||
|
[node name="DiscoveryClient" type="Node" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
script = ExtResource("57_discovery")
|
||||||
|
|
||||||
[node name="NetworkProfilePreferences" type="Node" parent="." unique_id=2119445545]
|
[node name="NetworkProfilePreferences" type="Node" parent="." unique_id=2119445545]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
script = ExtResource("18_network_profile")
|
script = ExtResource("18_network_profile")
|
||||||
|
|
|
||||||
478
network/discovery_client.gd
Normal file
478
network/discovery_client.gd
Normal file
|
|
@ -0,0 +1,478 @@
|
||||||
|
class_name DiscoveryClient
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
signal rooms_updated(rooms: Array[Dictionary])
|
||||||
|
signal browse_status_changed(message: String, is_error: bool)
|
||||||
|
signal host_settings_changed(room_name: String, discoverable: bool)
|
||||||
|
signal host_status_changed(message: String, is_error: bool)
|
||||||
|
|
||||||
|
const BASE_URL_SETTING: String = "network/discovery/base_url"
|
||||||
|
const BASE_URL_ENVIRONMENT: String = "NETFISHING_DISCOVERY_URL"
|
||||||
|
const SETTINGS_PATH: String = "user://network_discovery.cfg"
|
||||||
|
const DEFAULT_ROOM_NAME: String = "NETfishing Room"
|
||||||
|
const MAX_ROOM_NAME_LENGTH: int = 48
|
||||||
|
const HEARTBEAT_INTERVAL_SECONDS: float = 15.0
|
||||||
|
const REQUEST_TIMEOUT_SECONDS: float = 8.0
|
||||||
|
|
||||||
|
enum HostRequestKind {
|
||||||
|
NONE,
|
||||||
|
CREATE,
|
||||||
|
UPDATE,
|
||||||
|
DELETE,
|
||||||
|
}
|
||||||
|
|
||||||
|
var _session: NetworkSession
|
||||||
|
var _base_url: String = ""
|
||||||
|
var _room_name: String = DEFAULT_ROOM_NAME
|
||||||
|
var _discoverable: bool = false
|
||||||
|
var _host_status_message: String = ""
|
||||||
|
var _host_status_is_error: bool = false
|
||||||
|
var _lease_room_id: String = ""
|
||||||
|
var _lease_token: String = ""
|
||||||
|
var _host_request_kind: HostRequestKind = HostRequestKind.NONE
|
||||||
|
var _host_request_in_flight: bool = false
|
||||||
|
var _host_sync_queued: bool = false
|
||||||
|
var _browse_request_in_flight: bool = false
|
||||||
|
var _host_request: HTTPRequest
|
||||||
|
var _browse_request: HTTPRequest
|
||||||
|
var _heartbeat: Timer
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_host_request = HTTPRequest.new()
|
||||||
|
_host_request.name = "HostLeaseRequest"
|
||||||
|
_host_request.timeout = REQUEST_TIMEOUT_SECONDS
|
||||||
|
add_child(_host_request)
|
||||||
|
_host_request.request_completed.connect(_on_host_request_completed)
|
||||||
|
|
||||||
|
_browse_request = HTTPRequest.new()
|
||||||
|
_browse_request.name = "RoomBrowseRequest"
|
||||||
|
_browse_request.timeout = REQUEST_TIMEOUT_SECONDS
|
||||||
|
add_child(_browse_request)
|
||||||
|
_browse_request.request_completed.connect(_on_browse_request_completed)
|
||||||
|
|
||||||
|
_heartbeat = Timer.new()
|
||||||
|
_heartbeat.name = "HostLeaseHeartbeat"
|
||||||
|
_heartbeat.wait_time = HEARTBEAT_INTERVAL_SECONDS
|
||||||
|
_heartbeat.one_shot = false
|
||||||
|
add_child(_heartbeat)
|
||||||
|
_heartbeat.timeout.connect(_synchronize_host_lease)
|
||||||
|
_load_settings()
|
||||||
|
_base_url = _configured_base_url()
|
||||||
|
|
||||||
|
|
||||||
|
func setup(session: NetworkSession) -> void:
|
||||||
|
_session = session
|
||||||
|
_session.set_session_display_name(_room_name)
|
||||||
|
if not _session.state_changed.is_connected(_on_session_state_changed):
|
||||||
|
_session.state_changed.connect(_on_session_state_changed)
|
||||||
|
if not _session.peer_count_changed.is_connected(_on_peer_count_changed):
|
||||||
|
_session.peer_count_changed.connect(_on_peer_count_changed)
|
||||||
|
if not _session.host_openness_changed.is_connected(_on_host_openness_changed):
|
||||||
|
_session.host_openness_changed.connect(_on_host_openness_changed)
|
||||||
|
host_settings_changed.emit(_room_name, _discoverable)
|
||||||
|
if not is_configured():
|
||||||
|
_set_host_status("Room discovery is not configured in this build.", true)
|
||||||
|
elif _session.is_open_host():
|
||||||
|
_set_host_status("Room is open but unlisted.", false)
|
||||||
|
else:
|
||||||
|
_set_host_status("Open the game before listing it publicly.", false)
|
||||||
|
|
||||||
|
|
||||||
|
func is_configured() -> bool:
|
||||||
|
return not _base_url.is_empty()
|
||||||
|
|
||||||
|
|
||||||
|
func get_base_url() -> String:
|
||||||
|
return _base_url
|
||||||
|
|
||||||
|
|
||||||
|
func get_room_name() -> String:
|
||||||
|
return _room_name
|
||||||
|
|
||||||
|
|
||||||
|
func is_discoverable() -> bool:
|
||||||
|
return _discoverable
|
||||||
|
|
||||||
|
|
||||||
|
func get_host_status_message() -> String:
|
||||||
|
return _host_status_message
|
||||||
|
|
||||||
|
|
||||||
|
func host_status_is_error() -> bool:
|
||||||
|
return _host_status_is_error
|
||||||
|
|
||||||
|
|
||||||
|
func set_room_name(value: String) -> bool:
|
||||||
|
var cleaned: String = _sanitize_room_name(value)
|
||||||
|
if cleaned.is_empty():
|
||||||
|
_set_host_status("Room name cannot be empty.", true)
|
||||||
|
return false
|
||||||
|
if cleaned == _room_name:
|
||||||
|
return true
|
||||||
|
_room_name = cleaned
|
||||||
|
_save_settings()
|
||||||
|
if _session != null:
|
||||||
|
_session.set_session_display_name(_room_name)
|
||||||
|
host_settings_changed.emit(_room_name, _discoverable)
|
||||||
|
if _discoverable:
|
||||||
|
_synchronize_host_lease()
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func set_discoverable(enabled: bool) -> bool:
|
||||||
|
if enabled and (
|
||||||
|
_session == null
|
||||||
|
or not _session.is_open_host()
|
||||||
|
or not is_configured()
|
||||||
|
):
|
||||||
|
_set_host_status(
|
||||||
|
"Open the game before enabling discovery."
|
||||||
|
if is_configured()
|
||||||
|
else "Room discovery is not configured in this build.",
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
if _discoverable == enabled:
|
||||||
|
return true
|
||||||
|
_discoverable = enabled
|
||||||
|
host_settings_changed.emit(_room_name, _discoverable)
|
||||||
|
if enabled:
|
||||||
|
_heartbeat.start()
|
||||||
|
_set_host_status("Publishing room…", false)
|
||||||
|
_synchronize_host_lease()
|
||||||
|
else:
|
||||||
|
_heartbeat.stop()
|
||||||
|
_remove_host_lease()
|
||||||
|
_set_host_status("Room is open but unlisted.", false)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func request_rooms() -> bool:
|
||||||
|
if not is_configured():
|
||||||
|
rooms_updated.emit([])
|
||||||
|
browse_status_changed.emit(
|
||||||
|
"Public room discovery is not configured in this build.", true
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
if _browse_request_in_flight:
|
||||||
|
return true
|
||||||
|
var game_version: String = str(
|
||||||
|
ProjectSettings.get_setting("application/config/version", "unknown")
|
||||||
|
)
|
||||||
|
var url: String = "%s/v1/rooms?game_version=%s&protocol_version=%d" % [
|
||||||
|
_base_url,
|
||||||
|
game_version.uri_encode(),
|
||||||
|
NetworkProtocol.PROTOCOL_VERSION,
|
||||||
|
]
|
||||||
|
var error: Error = _browse_request.request(url)
|
||||||
|
if error != OK:
|
||||||
|
rooms_updated.emit([])
|
||||||
|
browse_status_changed.emit("Could not request public rooms.", true)
|
||||||
|
return false
|
||||||
|
_browse_request_in_flight = true
|
||||||
|
browse_status_changed.emit("Looking for public rooms…", false)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func room_endpoint(room: Dictionary) -> String:
|
||||||
|
var address: String = str(room.get("address", "")).strip_edges()
|
||||||
|
var port: int = int(room.get("port", 0))
|
||||||
|
if address.is_empty() or port < 1 or port > 65535:
|
||||||
|
return ""
|
||||||
|
if ":" in address and not address.begins_with("["):
|
||||||
|
address = "[%s]" % address
|
||||||
|
return "%s:%d" % [address, port]
|
||||||
|
|
||||||
|
|
||||||
|
func _configured_base_url() -> String:
|
||||||
|
var value: String = OS.get_environment(BASE_URL_ENVIRONMENT).strip_edges()
|
||||||
|
if value.is_empty():
|
||||||
|
value = str(ProjectSettings.get_setting(BASE_URL_SETTING, "")).strip_edges()
|
||||||
|
while value.ends_with("/"):
|
||||||
|
value = value.left(value.length() - 1)
|
||||||
|
if not value.is_empty() and not (
|
||||||
|
value.begins_with("https://") or value.begins_with("http://")
|
||||||
|
):
|
||||||
|
push_warning("Ignored invalid NETfishing discovery URL.")
|
||||||
|
return ""
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
func _synchronize_host_lease() -> void:
|
||||||
|
if not _should_advertise():
|
||||||
|
if not _lease_room_id.is_empty():
|
||||||
|
_remove_host_lease()
|
||||||
|
return
|
||||||
|
if _host_request_in_flight:
|
||||||
|
_host_sync_queued = true
|
||||||
|
return
|
||||||
|
var method: HTTPClient.Method = HTTPClient.METHOD_POST
|
||||||
|
var url: String = "%s/v1/rooms" % _base_url
|
||||||
|
var headers := PackedStringArray(["Content-Type: application/json"])
|
||||||
|
_host_request_kind = HostRequestKind.CREATE
|
||||||
|
if not _lease_room_id.is_empty():
|
||||||
|
method = HTTPClient.METHOD_PUT
|
||||||
|
url = "%s/v1/rooms/%s" % [_base_url, _lease_room_id.uri_encode()]
|
||||||
|
headers.append("Authorization: Bearer %s" % _lease_token)
|
||||||
|
_host_request_kind = HostRequestKind.UPDATE
|
||||||
|
_start_host_request(url, headers, method, JSON.stringify(_host_payload()))
|
||||||
|
|
||||||
|
|
||||||
|
func _remove_host_lease() -> void:
|
||||||
|
if _lease_room_id.is_empty():
|
||||||
|
return
|
||||||
|
if _host_request_in_flight:
|
||||||
|
_host_sync_queued = true
|
||||||
|
return
|
||||||
|
var url: String = "%s/v1/rooms/%s" % [
|
||||||
|
_base_url, _lease_room_id.uri_encode()
|
||||||
|
]
|
||||||
|
var headers := PackedStringArray([
|
||||||
|
"Authorization: Bearer %s" % _lease_token,
|
||||||
|
])
|
||||||
|
_host_request_kind = HostRequestKind.DELETE
|
||||||
|
_start_host_request(url, headers, HTTPClient.METHOD_DELETE, "")
|
||||||
|
|
||||||
|
|
||||||
|
func _start_host_request(
|
||||||
|
url: String,
|
||||||
|
headers: PackedStringArray,
|
||||||
|
method: HTTPClient.Method,
|
||||||
|
body: String,
|
||||||
|
) -> void:
|
||||||
|
var error: Error = _host_request.request(url, headers, method, body)
|
||||||
|
if error != OK:
|
||||||
|
_host_request_kind = HostRequestKind.NONE
|
||||||
|
_set_host_status("Could not contact room discovery.", true)
|
||||||
|
return
|
||||||
|
_host_request_in_flight = true
|
||||||
|
|
||||||
|
|
||||||
|
func _host_payload() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"room_name": _room_name,
|
||||||
|
"port": _session.get_host_port(),
|
||||||
|
"current_players": _session.get_player_count(),
|
||||||
|
"max_players": _session.get_session_max_players(),
|
||||||
|
"game_version": str(
|
||||||
|
ProjectSettings.get_setting("application/config/version", "unknown")
|
||||||
|
),
|
||||||
|
"protocol_version": NetworkProtocol.PROTOCOL_VERSION,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func _should_advertise() -> bool:
|
||||||
|
return (
|
||||||
|
_discoverable
|
||||||
|
and is_configured()
|
||||||
|
and _session != null
|
||||||
|
and _session.is_open_host()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_host_request_completed(
|
||||||
|
result: int,
|
||||||
|
response_code: int,
|
||||||
|
_headers: PackedStringArray,
|
||||||
|
body: PackedByteArray,
|
||||||
|
) -> void:
|
||||||
|
var completed_kind: HostRequestKind = _host_request_kind
|
||||||
|
_host_request_kind = HostRequestKind.NONE
|
||||||
|
_host_request_in_flight = false
|
||||||
|
var response: Dictionary = _parse_response_dictionary(body)
|
||||||
|
var transport_ok: bool = result == HTTPRequest.RESULT_SUCCESS
|
||||||
|
match completed_kind:
|
||||||
|
HostRequestKind.CREATE:
|
||||||
|
if transport_ok and response_code == HTTPClient.RESPONSE_CREATED:
|
||||||
|
var room: Dictionary = response.get("room", {})
|
||||||
|
_lease_room_id = str(room.get("room_id", ""))
|
||||||
|
_lease_token = str(response.get("lease_token", ""))
|
||||||
|
if _lease_room_id.is_empty() or _lease_token.is_empty():
|
||||||
|
_clear_lease()
|
||||||
|
_set_host_status("Discovery returned an invalid lease.", true)
|
||||||
|
else:
|
||||||
|
_set_host_status("Room is listed publicly.", false)
|
||||||
|
else:
|
||||||
|
_set_host_status(_request_failure(response), true)
|
||||||
|
HostRequestKind.UPDATE:
|
||||||
|
if transport_ok and response_code == HTTPClient.RESPONSE_OK:
|
||||||
|
_set_host_status("Room is listed publicly.", false)
|
||||||
|
elif response_code in [
|
||||||
|
HTTPClient.RESPONSE_UNAUTHORIZED,
|
||||||
|
HTTPClient.RESPONSE_NOT_FOUND,
|
||||||
|
]:
|
||||||
|
_clear_lease()
|
||||||
|
_host_sync_queued = true
|
||||||
|
else:
|
||||||
|
_set_host_status(_request_failure(response), true)
|
||||||
|
HostRequestKind.DELETE:
|
||||||
|
_clear_lease()
|
||||||
|
if response_code not in [
|
||||||
|
HTTPClient.RESPONSE_NO_CONTENT,
|
||||||
|
HTTPClient.RESPONSE_NOT_FOUND,
|
||||||
|
] and transport_ok:
|
||||||
|
_set_host_status(_request_failure(response), true)
|
||||||
|
_:
|
||||||
|
pass
|
||||||
|
if not _should_advertise() and not _lease_room_id.is_empty():
|
||||||
|
_host_sync_queued = true
|
||||||
|
if _host_sync_queued:
|
||||||
|
_host_sync_queued = false
|
||||||
|
call_deferred("_synchronize_host_lease")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_browse_request_completed(
|
||||||
|
result: int,
|
||||||
|
response_code: int,
|
||||||
|
_headers: PackedStringArray,
|
||||||
|
body: PackedByteArray,
|
||||||
|
) -> void:
|
||||||
|
_browse_request_in_flight = false
|
||||||
|
var response: Dictionary = _parse_response_dictionary(body)
|
||||||
|
if result != HTTPRequest.RESULT_SUCCESS or response_code != HTTPClient.RESPONSE_OK:
|
||||||
|
rooms_updated.emit([])
|
||||||
|
browse_status_changed.emit(_request_failure(response), true)
|
||||||
|
return
|
||||||
|
var raw_rooms: Variant = response.get("rooms", [])
|
||||||
|
if typeof(raw_rooms) != TYPE_ARRAY:
|
||||||
|
rooms_updated.emit([])
|
||||||
|
browse_status_changed.emit("Discovery returned an invalid room list.", true)
|
||||||
|
return
|
||||||
|
var rooms: Array[Dictionary] = []
|
||||||
|
for value: Variant in raw_rooms:
|
||||||
|
if typeof(value) != TYPE_DICTIONARY:
|
||||||
|
continue
|
||||||
|
var room: Dictionary = value
|
||||||
|
if _valid_public_room(room):
|
||||||
|
rooms.append(room.duplicate(true))
|
||||||
|
rooms_updated.emit(rooms)
|
||||||
|
browse_status_changed.emit(
|
||||||
|
"No public rooms are available."
|
||||||
|
if rooms.is_empty()
|
||||||
|
else "%d public room%s found." % [rooms.size(), "" if rooms.size() == 1 else "s"],
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _valid_public_room(room: Dictionary) -> bool:
|
||||||
|
var expected_version: String = str(
|
||||||
|
ProjectSettings.get_setting("application/config/version", "unknown")
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
typeof(room.get("room_id")) == TYPE_STRING
|
||||||
|
and typeof(room.get("room_name")) == TYPE_STRING
|
||||||
|
and typeof(room.get("address")) == TYPE_STRING
|
||||||
|
and typeof(room.get("game_version")) == TYPE_STRING
|
||||||
|
and str(room.get("game_version")) == expected_version
|
||||||
|
and _valid_json_integer(room.get("port"), 1, 65535)
|
||||||
|
and _valid_json_integer(room.get("current_players"), 1, 128)
|
||||||
|
and _valid_json_integer(room.get("max_players"), 1, 128)
|
||||||
|
and int(room["current_players"]) <= int(room["max_players"])
|
||||||
|
and _valid_json_integer(
|
||||||
|
room.get("protocol_version"),
|
||||||
|
NetworkProtocol.PROTOCOL_VERSION,
|
||||||
|
NetworkProtocol.PROTOCOL_VERSION,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _valid_json_integer(value: Variant, minimum: int, maximum: int) -> bool:
|
||||||
|
if typeof(value) == TYPE_INT:
|
||||||
|
return int(value) >= minimum and int(value) <= maximum
|
||||||
|
if typeof(value) != TYPE_FLOAT:
|
||||||
|
return false
|
||||||
|
var number: float = float(value)
|
||||||
|
return (
|
||||||
|
is_finite(number)
|
||||||
|
and number == floor(number)
|
||||||
|
and number >= float(minimum)
|
||||||
|
and number <= float(maximum)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _parse_response_dictionary(body: PackedByteArray) -> Dictionary:
|
||||||
|
if body.is_empty():
|
||||||
|
return {}
|
||||||
|
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
|
||||||
|
return parsed if typeof(parsed) == TYPE_DICTIONARY else {}
|
||||||
|
|
||||||
|
|
||||||
|
func _request_failure(response: Dictionary) -> String:
|
||||||
|
var error: Variant = response.get("error", {})
|
||||||
|
if typeof(error) == TYPE_DICTIONARY:
|
||||||
|
var message: String = str((error as Dictionary).get("message", "")).strip_edges()
|
||||||
|
if not message.is_empty():
|
||||||
|
return message
|
||||||
|
return "Room discovery is temporarily unavailable."
|
||||||
|
|
||||||
|
|
||||||
|
func _on_session_state_changed(state: NetworkSession.State) -> void:
|
||||||
|
if state == NetworkSession.State.OPEN_HOST:
|
||||||
|
if _discoverable:
|
||||||
|
_heartbeat.start()
|
||||||
|
_synchronize_host_lease()
|
||||||
|
else:
|
||||||
|
_set_host_status("Room is open but unlisted.", false)
|
||||||
|
return
|
||||||
|
if state in [
|
||||||
|
NetworkSession.State.PRIVATE_HOST,
|
||||||
|
NetworkSession.State.INACTIVE,
|
||||||
|
NetworkSession.State.DISCONNECTING,
|
||||||
|
NetworkSession.State.CONNECTION_FAILED,
|
||||||
|
NetworkSession.State.SERVER_LOST,
|
||||||
|
]:
|
||||||
|
if _discoverable:
|
||||||
|
_discoverable = false
|
||||||
|
host_settings_changed.emit(_room_name, false)
|
||||||
|
_heartbeat.stop()
|
||||||
|
_remove_host_lease()
|
||||||
|
if state == NetworkSession.State.PRIVATE_HOST:
|
||||||
|
_set_host_status("Open the game before listing it publicly.", false)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_host_openness_changed(is_open: bool) -> void:
|
||||||
|
if not is_open and _discoverable:
|
||||||
|
set_discoverable(false)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_peer_count_changed(_player_count: int, _max_players: int) -> void:
|
||||||
|
if _discoverable:
|
||||||
|
_synchronize_host_lease()
|
||||||
|
|
||||||
|
|
||||||
|
func _clear_lease() -> void:
|
||||||
|
_lease_room_id = ""
|
||||||
|
_lease_token = ""
|
||||||
|
|
||||||
|
|
||||||
|
func _set_host_status(message: String, is_error: bool) -> void:
|
||||||
|
_host_status_message = message
|
||||||
|
_host_status_is_error = is_error
|
||||||
|
host_status_changed.emit(message, is_error)
|
||||||
|
|
||||||
|
|
||||||
|
func _sanitize_room_name(value: String) -> String:
|
||||||
|
var cleaned: String = value.strip_edges().replace("\n", " ").replace("\r", " ")
|
||||||
|
cleaned = cleaned.replace("\t", " ")
|
||||||
|
while " " in cleaned:
|
||||||
|
cleaned = cleaned.replace(" ", " ")
|
||||||
|
return cleaned.left(MAX_ROOM_NAME_LENGTH)
|
||||||
|
|
||||||
|
|
||||||
|
func _load_settings() -> void:
|
||||||
|
var config := ConfigFile.new()
|
||||||
|
if config.load(SETTINGS_PATH) == OK:
|
||||||
|
var saved_name: String = _sanitize_room_name(
|
||||||
|
str(config.get_value("host", "room_name", DEFAULT_ROOM_NAME))
|
||||||
|
)
|
||||||
|
if not saved_name.is_empty():
|
||||||
|
_room_name = saved_name
|
||||||
|
|
||||||
|
|
||||||
|
func _save_settings() -> void:
|
||||||
|
var config := ConfigFile.new()
|
||||||
|
config.set_value("host", "room_name", _room_name)
|
||||||
|
var error: Error = config.save(SETTINGS_PATH)
|
||||||
|
if error != OK:
|
||||||
|
push_warning("Could not save the local NETfishing room name.")
|
||||||
1
network/discovery_client.gd.uid
Normal file
1
network/discovery_client.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://d0jv8n2nfqia0
|
||||||
|
|
@ -89,6 +89,10 @@ func is_local_host() -> bool:
|
||||||
return _session.is_host()
|
return _session.is_host()
|
||||||
|
|
||||||
|
|
||||||
|
func is_open_host() -> bool:
|
||||||
|
return _session.is_open_host()
|
||||||
|
|
||||||
|
|
||||||
func get_relationships() -> Array[Dictionary]:
|
func get_relationships() -> Array[Dictionary]:
|
||||||
return _relationships.get_records()
|
return _relationships.get_records()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,7 @@ static func make_server_hello(
|
||||||
assigned_peer_id: int,
|
assigned_peer_id: int,
|
||||||
player_count: int,
|
player_count: int,
|
||||||
max_players: int,
|
max_players: int,
|
||||||
|
server_display_name: String = "NETfishing",
|
||||||
) -> Dictionary:
|
) -> Dictionary:
|
||||||
return {
|
return {
|
||||||
"accepted": accepted,
|
"accepted": accepted,
|
||||||
|
|
@ -205,7 +206,7 @@ static func make_server_hello(
|
||||||
"protocol_version": PROTOCOL_VERSION,
|
"protocol_version": PROTOCOL_VERSION,
|
||||||
"session_id": session_id,
|
"session_id": session_id,
|
||||||
"assigned_peer_id": assigned_peer_id,
|
"assigned_peer_id": assigned_peer_id,
|
||||||
"server_display_name": "NETfishing",
|
"server_display_name": server_display_name,
|
||||||
"player_count": player_count,
|
"player_count": player_count,
|
||||||
"max_players": max_players,
|
"max_players": max_players,
|
||||||
"capability_flags": PackedStringArray([
|
"capability_flags": PackedStringArray([
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ signal connection_error(message: String)
|
||||||
signal peer_authenticated(peer_id: int, display_name: String)
|
signal peer_authenticated(peer_id: int, display_name: String)
|
||||||
signal peer_removed(peer_id: int)
|
signal peer_removed(peer_id: int)
|
||||||
signal host_openness_changed(is_open: bool)
|
signal host_openness_changed(is_open: bool)
|
||||||
|
signal session_display_name_changed(display_name: String)
|
||||||
signal peer_count_changed(player_count: int, max_players: int)
|
signal peer_count_changed(player_count: int, max_players: int)
|
||||||
signal peer_display_name_changed(peer_id: int, display_name: String)
|
signal peer_display_name_changed(peer_id: int, display_name: String)
|
||||||
signal peer_profile_changed(
|
signal peer_profile_changed(
|
||||||
|
|
@ -99,6 +100,7 @@ var _local_appearance_snapshot: Dictionary = (
|
||||||
)
|
)
|
||||||
var _moderation_disconnect_message := ""
|
var _moderation_disconnect_message := ""
|
||||||
var _host_port: int = 0
|
var _host_port: int = 0
|
||||||
|
var _session_display_name: String = "NETfishing Room"
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -228,6 +230,20 @@ func get_host_port() -> int:
|
||||||
return _host_port if is_host() else 0
|
return _host_port if is_host() else 0
|
||||||
|
|
||||||
|
|
||||||
|
func set_session_display_name(value: String) -> void:
|
||||||
|
var cleaned: String = value.strip_edges().left(48)
|
||||||
|
if cleaned.is_empty():
|
||||||
|
cleaned = "NETfishing Room"
|
||||||
|
if cleaned == _session_display_name:
|
||||||
|
return
|
||||||
|
_session_display_name = cleaned
|
||||||
|
session_display_name_changed.emit(_session_display_name)
|
||||||
|
|
||||||
|
|
||||||
|
func get_session_display_name() -> String:
|
||||||
|
return _session_display_name
|
||||||
|
|
||||||
|
|
||||||
static func _can_bind_udp_port(port: int) -> bool:
|
static func _can_bind_udp_port(port: int) -> bool:
|
||||||
var probe := PacketPeerUDP.new()
|
var probe := PacketPeerUDP.new()
|
||||||
var error: Error = probe.bind(port)
|
var error: Error = probe.bind(port)
|
||||||
|
|
@ -1038,7 +1054,8 @@ func submit_client_hello(data: Dictionary) -> void:
|
||||||
_session_id,
|
_session_id,
|
||||||
sender_id,
|
sender_id,
|
||||||
_registry.size(),
|
_registry.size(),
|
||||||
session_max_players
|
session_max_players,
|
||||||
|
_session_display_name,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
receive_spawn_list.rpc_id(sender_id, _build_spawn_list())
|
receive_spawn_list.rpc_id(sender_id, _build_spawn_list())
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,10 @@ alternate_fishing={
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[network]
|
||||||
|
|
||||||
|
discovery/base_url="https://discovery.netfishing.org"
|
||||||
|
|
||||||
[physics]
|
[physics]
|
||||||
|
|
||||||
3d/physics_engine="Jolt Physics"
|
3d/physics_engine="Jolt Physics"
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,7 @@ func setup(
|
||||||
reservations: PlayerAssetReservationService,
|
reservations: PlayerAssetReservationService,
|
||||||
network_profile_service: NetworkProfileService,
|
network_profile_service: NetworkProfileService,
|
||||||
network_player_list: NetworkPlayerListService,
|
network_player_list: NetworkPlayerListService,
|
||||||
|
discovery: DiscoveryClient,
|
||||||
settings_manager: PlayerSettingsManagerType,
|
settings_manager: PlayerSettingsManagerType,
|
||||||
surface_drawing: NetworkSurfaceDrawingService,
|
surface_drawing: NetworkSurfaceDrawingService,
|
||||||
art_unlocks: PlayerArtUnlocks,
|
art_unlocks: PlayerArtUnlocks,
|
||||||
|
|
@ -372,6 +373,7 @@ func setup(
|
||||||
reservations,
|
reservations,
|
||||||
network_profile_service,
|
network_profile_service,
|
||||||
network_player_list,
|
network_player_list,
|
||||||
|
discovery,
|
||||||
player_jobs,
|
player_jobs,
|
||||||
world_time,
|
world_time,
|
||||||
world_environment,
|
world_environment,
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,14 @@ signal join_requested(endpoint: String)
|
||||||
signal back_requested
|
signal back_requested
|
||||||
|
|
||||||
enum Mode {
|
enum Mode {
|
||||||
|
DISCOVER,
|
||||||
DIRECT,
|
DIRECT,
|
||||||
SAVED,
|
SAVED,
|
||||||
RECENT,
|
RECENT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DISCOVERY_REFRESH_SECONDS: float = 8.0
|
||||||
|
|
||||||
const ADDRESS_FORMAT_HELP: String = (
|
const ADDRESS_FORMAT_HELP: String = (
|
||||||
"Hostname, IPv4, or [IPv6]. Port 7777 is used when omitted; "
|
"Hostname, IPv4, or [IPv6]. Port 7777 is used when omitted; "
|
||||||
+ "include the port shown by a host when it differs."
|
+ "include the port shown by a host when it differs."
|
||||||
|
|
@ -27,10 +30,12 @@ const DIRECT_WORKFLOW_HELP: String = (
|
||||||
@onready var _name_helper: Label = %NameHelper
|
@onready var _name_helper: Label = %NameHelper
|
||||||
@onready var _server_list: ItemList = %ServerList
|
@onready var _server_list: ItemList = %ServerList
|
||||||
@onready var _details: Label = %Details
|
@onready var _details: Label = %Details
|
||||||
|
@onready var _discover_button: Button = %DiscoverButton
|
||||||
@onready var _direct_button: Button = %DirectButton
|
@onready var _direct_button: Button = %DirectButton
|
||||||
@onready var _saved_button: Button = %SavedButton
|
@onready var _saved_button: Button = %SavedButton
|
||||||
@onready var _recent_button: Button = %RecentButton
|
@onready var _recent_button: Button = %RecentButton
|
||||||
@onready var _join_button: Button = %JoinButton
|
@onready var _join_button: Button = %JoinButton
|
||||||
|
@onready var _refresh_button: Button = %RefreshButton
|
||||||
@onready var _save_button: Button = %SaveButton
|
@onready var _save_button: Button = %SaveButton
|
||||||
@onready var _edit_button: Button = %EditButton
|
@onready var _edit_button: Button = %EditButton
|
||||||
@onready var _favorite_button: Button = %FavoriteButton
|
@onready var _favorite_button: Button = %FavoriteButton
|
||||||
|
|
@ -47,10 +52,14 @@ const DIRECT_WORKFLOW_HELP: String = (
|
||||||
var _network_session: NetworkSession
|
var _network_session: NetworkSession
|
||||||
var _saved_servers: SavedServerStore
|
var _saved_servers: SavedServerStore
|
||||||
var _server_trust: ServerTrustStore
|
var _server_trust: ServerTrustStore
|
||||||
|
var _discovery: DiscoveryClient
|
||||||
var _gameplay_context: bool = false
|
var _gameplay_context: bool = false
|
||||||
var _mode: Mode = Mode.DIRECT
|
var _mode: Mode = Mode.DISCOVER
|
||||||
var _visible_entries: Array[SavedServerEntry] = []
|
var _visible_entries: Array[SavedServerEntry] = []
|
||||||
var _selected_entry: SavedServerEntry
|
var _selected_entry: SavedServerEntry
|
||||||
|
var _discovery_rooms: Array[Dictionary] = []
|
||||||
|
var _selected_discovery_index: int = -1
|
||||||
|
var _discovery_refresh_timer: Timer
|
||||||
var _editing_entry_id: String = ""
|
var _editing_entry_id: String = ""
|
||||||
var _name_entry_active: bool = false
|
var _name_entry_active: bool = false
|
||||||
var _delete_armed: bool = false
|
var _delete_armed: bool = false
|
||||||
|
|
@ -63,16 +72,19 @@ func _ready() -> void:
|
||||||
"panel", UtilityPageStyle.panel_style()
|
"panel", UtilityPageStyle.panel_style()
|
||||||
)
|
)
|
||||||
for button: BaseButton in [
|
for button: BaseButton in [
|
||||||
_direct_button, _saved_button, _recent_button, _join_button,
|
_discover_button, _direct_button, _saved_button, _recent_button,
|
||||||
|
_refresh_button, _join_button,
|
||||||
_save_button, _edit_button, _favorite_button, _delete_button,
|
_save_button, _edit_button, _favorite_button, _delete_button,
|
||||||
_cancel_button, _back_button, _open_close_button,
|
_cancel_button, _back_button, _open_close_button,
|
||||||
]:
|
]:
|
||||||
UtilityPageStyle.apply_ocean_button(button)
|
UtilityPageStyle.apply_ocean_button(button)
|
||||||
UtilityPageStyle.apply_ocean_line_edit(_address)
|
UtilityPageStyle.apply_ocean_line_edit(_address)
|
||||||
UtilityPageStyle.apply_ocean_line_edit(_name_edit)
|
UtilityPageStyle.apply_ocean_line_edit(_name_edit)
|
||||||
|
_discover_button.pressed.connect(_set_mode.bind(Mode.DISCOVER))
|
||||||
_direct_button.pressed.connect(_set_mode.bind(Mode.DIRECT))
|
_direct_button.pressed.connect(_set_mode.bind(Mode.DIRECT))
|
||||||
_saved_button.pressed.connect(_set_mode.bind(Mode.SAVED))
|
_saved_button.pressed.connect(_set_mode.bind(Mode.SAVED))
|
||||||
_recent_button.pressed.connect(_set_mode.bind(Mode.RECENT))
|
_recent_button.pressed.connect(_set_mode.bind(Mode.RECENT))
|
||||||
|
_refresh_button.pressed.connect(_request_discovery_refresh)
|
||||||
_join_button.pressed.connect(_request_join)
|
_join_button.pressed.connect(_request_join)
|
||||||
_save_button.pressed.connect(_on_save_pressed)
|
_save_button.pressed.connect(_on_save_pressed)
|
||||||
_edit_button.pressed.connect(_on_edit_pressed)
|
_edit_button.pressed.connect(_on_edit_pressed)
|
||||||
|
|
@ -97,6 +109,11 @@ func _ready() -> void:
|
||||||
)
|
)
|
||||||
_delete_confirmation.confirmed.connect(_confirm_delete)
|
_delete_confirmation.confirmed.connect(_confirm_delete)
|
||||||
_delete_confirmation.cancelled.connect(_cancel_delete)
|
_delete_confirmation.cancelled.connect(_cancel_delete)
|
||||||
|
_discovery_refresh_timer = Timer.new()
|
||||||
|
_discovery_refresh_timer.wait_time = DISCOVERY_REFRESH_SECONDS
|
||||||
|
_discovery_refresh_timer.one_shot = false
|
||||||
|
_discovery_refresh_timer.timeout.connect(_request_discovery_refresh)
|
||||||
|
add_child(_discovery_refresh_timer)
|
||||||
hide()
|
hide()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -105,11 +122,13 @@ func setup(
|
||||||
saved_servers: SavedServerStore,
|
saved_servers: SavedServerStore,
|
||||||
gameplay_context: bool,
|
gameplay_context: bool,
|
||||||
server_trust: ServerTrustStore = null,
|
server_trust: ServerTrustStore = null,
|
||||||
|
discovery: DiscoveryClient = null,
|
||||||
) -> void:
|
) -> void:
|
||||||
_network_session = network_session
|
_network_session = network_session
|
||||||
_saved_servers = saved_servers
|
_saved_servers = saved_servers
|
||||||
_gameplay_context = gameplay_context
|
_gameplay_context = gameplay_context
|
||||||
_server_trust = server_trust
|
_server_trust = server_trust
|
||||||
|
_discovery = discovery
|
||||||
if not _network_session.state_changed.is_connected(_on_state_changed):
|
if not _network_session.state_changed.is_connected(_on_state_changed):
|
||||||
_network_session.state_changed.connect(_on_state_changed)
|
_network_session.state_changed.connect(_on_state_changed)
|
||||||
if not _network_session.status_message_changed.is_connected(
|
if not _network_session.status_message_changed.is_connected(
|
||||||
|
|
@ -131,6 +150,17 @@ func setup(
|
||||||
and not _saved_servers.data_changed.is_connected(_on_store_changed)
|
and not _saved_servers.data_changed.is_connected(_on_store_changed)
|
||||||
):
|
):
|
||||||
_saved_servers.data_changed.connect(_on_store_changed)
|
_saved_servers.data_changed.connect(_on_store_changed)
|
||||||
|
if _discovery != null:
|
||||||
|
if not _discovery.rooms_updated.is_connected(
|
||||||
|
_on_discovery_rooms_updated
|
||||||
|
):
|
||||||
|
_discovery.rooms_updated.connect(_on_discovery_rooms_updated)
|
||||||
|
if not _discovery.browse_status_changed.is_connected(
|
||||||
|
_on_discovery_status_changed
|
||||||
|
):
|
||||||
|
_discovery.browse_status_changed.connect(
|
||||||
|
_on_discovery_status_changed
|
||||||
|
)
|
||||||
_refresh()
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -149,6 +179,7 @@ func open_page(preserved_endpoint: String = "") -> void:
|
||||||
|
|
||||||
func close_page() -> void:
|
func close_page() -> void:
|
||||||
_clear_edit_state()
|
_clear_edit_state()
|
||||||
|
_discovery_refresh_timer.stop()
|
||||||
hide()
|
hide()
|
||||||
var current_viewport: Viewport = get_viewport()
|
var current_viewport: Viewport = get_viewport()
|
||||||
if current_viewport != null:
|
if current_viewport != null:
|
||||||
|
|
@ -166,9 +197,15 @@ func set_status(message: String) -> void:
|
||||||
func _set_mode(mode: Mode) -> void:
|
func _set_mode(mode: Mode) -> void:
|
||||||
_mode = mode
|
_mode = mode
|
||||||
_selected_entry = null
|
_selected_entry = null
|
||||||
|
_selected_discovery_index = -1
|
||||||
_clear_edit_state()
|
_clear_edit_state()
|
||||||
_refresh_entries()
|
_refresh_entries()
|
||||||
_refresh()
|
_refresh()
|
||||||
|
if mode == Mode.DISCOVER:
|
||||||
|
_discovery_refresh_timer.start()
|
||||||
|
_request_discovery_refresh()
|
||||||
|
else:
|
||||||
|
_discovery_refresh_timer.stop()
|
||||||
if not is_visible_in_tree():
|
if not is_visible_in_tree():
|
||||||
return
|
return
|
||||||
if mode == Mode.DIRECT:
|
if mode == Mode.DIRECT:
|
||||||
|
|
@ -183,11 +220,15 @@ func _request_join() -> void:
|
||||||
NetworkSession.State.SERVER_LOST,
|
NetworkSession.State.SERVER_LOST,
|
||||||
]:
|
]:
|
||||||
_network_session.reset_failure()
|
_network_session.reset_failure()
|
||||||
var endpoint_text: String = (
|
var endpoint_text: String = _address.text
|
||||||
_selected_entry.normalized_endpoint
|
if _mode == Mode.DISCOVER:
|
||||||
if _mode != Mode.DIRECT and _selected_entry != null
|
var room: Dictionary = _selected_discovery_room()
|
||||||
else _address.text
|
if _discovery_room_is_full(room):
|
||||||
)
|
_set_status("That room is full.", true)
|
||||||
|
return
|
||||||
|
endpoint_text = _discovery.room_endpoint(room) if _discovery != null else ""
|
||||||
|
elif _mode != Mode.DIRECT and _selected_entry != null:
|
||||||
|
endpoint_text = _selected_entry.normalized_endpoint
|
||||||
var endpoint: ConnectionEndpoint = EndpointParser.parse(endpoint_text)
|
var endpoint: ConnectionEndpoint = EndpointParser.parse(endpoint_text)
|
||||||
if not endpoint.is_valid():
|
if not endpoint.is_valid():
|
||||||
_set_status(endpoint.error_message, true)
|
_set_status(endpoint.error_message, true)
|
||||||
|
|
@ -339,6 +380,13 @@ func _cancel_delete() -> void:
|
||||||
|
|
||||||
|
|
||||||
func _on_list_item_selected(index: int) -> void:
|
func _on_list_item_selected(index: int) -> void:
|
||||||
|
if _mode == Mode.DISCOVER:
|
||||||
|
if index < 0 or index >= _discovery_rooms.size():
|
||||||
|
return
|
||||||
|
_selected_discovery_index = index
|
||||||
|
_selected_entry = null
|
||||||
|
_refresh()
|
||||||
|
return
|
||||||
if index < 0 or index >= _visible_entries.size():
|
if index < 0 or index >= _visible_entries.size():
|
||||||
return
|
return
|
||||||
_selected_entry = _visible_entries[index]
|
_selected_entry = _visible_entries[index]
|
||||||
|
|
@ -357,6 +405,19 @@ func _select_entry_id(entry_id: String) -> void:
|
||||||
func _refresh_entries() -> void:
|
func _refresh_entries() -> void:
|
||||||
_visible_entries.clear()
|
_visible_entries.clear()
|
||||||
_server_list.clear()
|
_server_list.clear()
|
||||||
|
if _mode == Mode.DISCOVER:
|
||||||
|
for room: Dictionary in _discovery_rooms:
|
||||||
|
var player_count: int = int(room.get("current_players", 0))
|
||||||
|
var maximum: int = int(room.get("max_players", 0))
|
||||||
|
_server_list.add_item(
|
||||||
|
"%s — %d / %d players%s" % [
|
||||||
|
str(room.get("room_name", "Public room")),
|
||||||
|
player_count,
|
||||||
|
maximum,
|
||||||
|
" — full" if player_count >= maximum else "",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return
|
||||||
if _saved_servers == null or _mode == Mode.DIRECT:
|
if _saved_servers == null or _mode == Mode.DIRECT:
|
||||||
return
|
return
|
||||||
_visible_entries = (
|
_visible_entries = (
|
||||||
|
|
@ -394,7 +455,16 @@ func _refresh() -> void:
|
||||||
NetworkSession.State.AUTHENTICATING,
|
NetworkSession.State.AUTHENTICATING,
|
||||||
]
|
]
|
||||||
var direct: bool = _mode == Mode.DIRECT
|
var direct: bool = _mode == Mode.DIRECT
|
||||||
var selected: bool = _selected_entry != null
|
var discovery_mode: bool = _mode == Mode.DISCOVER
|
||||||
|
var selected: bool = (
|
||||||
|
_selected_discovery_index >= 0
|
||||||
|
if discovery_mode
|
||||||
|
else _selected_entry != null
|
||||||
|
)
|
||||||
|
_discover_button.button_pressed = discovery_mode
|
||||||
|
_direct_button.button_pressed = direct
|
||||||
|
_saved_button.button_pressed = _mode == Mode.SAVED
|
||||||
|
_recent_button.button_pressed = _mode == Mode.RECENT
|
||||||
_address.visible = direct or _name_entry_active
|
_address.visible = direct or _name_entry_active
|
||||||
_address_label.visible = _address.visible
|
_address_label.visible = _address.visible
|
||||||
_address_helper.visible = _address.visible
|
_address_helper.visible = _address.visible
|
||||||
|
|
@ -407,9 +477,20 @@ func _refresh() -> void:
|
||||||
_name_helper.visible = _name_entry_active
|
_name_helper.visible = _name_entry_active
|
||||||
_server_list.visible = not direct and not _name_entry_active
|
_server_list.visible = not direct and not _name_entry_active
|
||||||
_details.visible = not direct and not _name_entry_active
|
_details.visible = not direct and not _name_entry_active
|
||||||
_join_button.disabled = connecting or (not direct and not selected)
|
_join_button.disabled = (
|
||||||
|
connecting
|
||||||
|
or (not direct and not selected)
|
||||||
|
or (
|
||||||
|
discovery_mode
|
||||||
|
and selected
|
||||||
|
and _discovery_room_is_full(_selected_discovery_room())
|
||||||
|
)
|
||||||
|
)
|
||||||
_join_button.text = "join\nnow" if direct else "join"
|
_join_button.text = "join\nnow" if direct else "join"
|
||||||
_save_button.visible = direct or _mode == Mode.RECENT or _name_entry_active
|
_refresh_button.visible = discovery_mode and not _name_entry_active
|
||||||
|
_save_button.visible = (
|
||||||
|
direct or _mode == Mode.RECENT or _name_entry_active
|
||||||
|
)
|
||||||
_save_button.disabled = connecting or (
|
_save_button.disabled = connecting or (
|
||||||
_mode == Mode.RECENT and not selected and not _name_entry_active
|
_mode == Mode.RECENT and not selected and not _name_entry_active
|
||||||
)
|
)
|
||||||
|
|
@ -418,10 +499,14 @@ func _refresh() -> void:
|
||||||
_favorite_button.visible = _mode == Mode.SAVED and selected
|
_favorite_button.visible = _mode == Mode.SAVED and selected
|
||||||
_favorite_button.text = (
|
_favorite_button.text = (
|
||||||
"unfavorite"
|
"unfavorite"
|
||||||
if selected and _selected_entry.favorite
|
if _mode == Mode.SAVED
|
||||||
|
and _selected_entry != null
|
||||||
|
and _selected_entry.favorite
|
||||||
else "favorite"
|
else "favorite"
|
||||||
)
|
)
|
||||||
_delete_button.visible = not direct and selected
|
_delete_button.visible = (
|
||||||
|
_mode in [Mode.SAVED, Mode.RECENT] and selected
|
||||||
|
)
|
||||||
_delete_button.text = "remove" if _mode == Mode.RECENT else "delete"
|
_delete_button.text = "remove" if _mode == Mode.RECENT else "delete"
|
||||||
_cancel_button.visible = connecting
|
_cancel_button.visible = connecting
|
||||||
_open_close_button.visible = (
|
_open_close_button.visible = (
|
||||||
|
|
@ -453,10 +538,20 @@ func _refresh() -> void:
|
||||||
)
|
)
|
||||||
if not direct:
|
if not direct:
|
||||||
if selected:
|
if selected:
|
||||||
_details.text = _format_entry_details(_selected_entry)
|
|
||||||
elif _visible_entries.is_empty():
|
|
||||||
_details.text = (
|
_details.text = (
|
||||||
"No saved servers yet."
|
_format_discovery_details(_selected_discovery_room())
|
||||||
|
if discovery_mode
|
||||||
|
else _format_entry_details(_selected_entry)
|
||||||
|
)
|
||||||
|
elif (
|
||||||
|
_discovery_rooms.is_empty()
|
||||||
|
if discovery_mode
|
||||||
|
else _visible_entries.is_empty()
|
||||||
|
):
|
||||||
|
_details.text = (
|
||||||
|
"No public rooms are available."
|
||||||
|
if discovery_mode
|
||||||
|
else "No saved servers yet."
|
||||||
if _mode == Mode.SAVED
|
if _mode == Mode.SAVED
|
||||||
else "No recent connections yet."
|
else "No recent connections yet."
|
||||||
)
|
)
|
||||||
|
|
@ -464,7 +559,7 @@ func _refresh() -> void:
|
||||||
_details.text = "Select a server."
|
_details.text = "Select a server."
|
||||||
var warning: String = (
|
var warning: String = (
|
||||||
_saved_servers.get_recovery_warning()
|
_saved_servers.get_recovery_warning()
|
||||||
if _saved_servers != null
|
if _saved_servers != null and not discovery_mode
|
||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
if not warning.is_empty() and _status.text.is_empty():
|
if not warning.is_empty() and _status.text.is_empty():
|
||||||
|
|
@ -517,6 +612,69 @@ func _format_entry_details(entry: SavedServerEntry) -> String:
|
||||||
return "\n".join(parts)
|
return "\n".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
func _format_discovery_details(room: Dictionary) -> String:
|
||||||
|
if room.is_empty():
|
||||||
|
return "Select a public room."
|
||||||
|
return "\n".join([
|
||||||
|
"Room: %s" % str(room.get("room_name", "Public room")),
|
||||||
|
"Players: %d / %d" % [
|
||||||
|
int(room.get("current_players", 0)),
|
||||||
|
int(room.get("max_players", 0)),
|
||||||
|
],
|
||||||
|
"Connection: %s" % (
|
||||||
|
_discovery.room_endpoint(room) if _discovery != null else "—"
|
||||||
|
),
|
||||||
|
"Direct UDP connection • ping is measured after joining",
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
func _selected_discovery_room() -> Dictionary:
|
||||||
|
if (
|
||||||
|
_selected_discovery_index < 0
|
||||||
|
or _selected_discovery_index >= _discovery_rooms.size()
|
||||||
|
):
|
||||||
|
return {}
|
||||||
|
return _discovery_rooms[_selected_discovery_index]
|
||||||
|
|
||||||
|
|
||||||
|
func _discovery_room_is_full(room: Dictionary) -> bool:
|
||||||
|
if room.is_empty():
|
||||||
|
return false
|
||||||
|
return int(room.get("current_players", 0)) >= int(room.get("max_players", 0))
|
||||||
|
|
||||||
|
|
||||||
|
func _request_discovery_refresh() -> void:
|
||||||
|
if (
|
||||||
|
_discovery == null
|
||||||
|
or _mode != Mode.DISCOVER
|
||||||
|
or not is_visible_in_tree()
|
||||||
|
):
|
||||||
|
return
|
||||||
|
_discovery.request_rooms()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_discovery_rooms_updated(rooms: Array[Dictionary]) -> void:
|
||||||
|
var selected_id: String = str(
|
||||||
|
_selected_discovery_room().get("room_id", "")
|
||||||
|
)
|
||||||
|
_discovery_rooms = rooms.duplicate(true)
|
||||||
|
_selected_discovery_index = -1
|
||||||
|
if _mode == Mode.DISCOVER:
|
||||||
|
_refresh_entries()
|
||||||
|
if not selected_id.is_empty():
|
||||||
|
for index: int in _discovery_rooms.size():
|
||||||
|
if str(_discovery_rooms[index].get("room_id", "")) == selected_id:
|
||||||
|
_selected_discovery_index = index
|
||||||
|
_server_list.select(index)
|
||||||
|
break
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_discovery_status_changed(message: String, is_error: bool) -> void:
|
||||||
|
if _mode == Mode.DISCOVER and is_visible_in_tree():
|
||||||
|
_set_status(message, is_error)
|
||||||
|
|
||||||
|
|
||||||
func _format_result_code(result_code: String) -> String:
|
func _format_result_code(result_code: String) -> String:
|
||||||
match result_code.strip_edges().to_upper():
|
match result_code.strip_edges().to_upper():
|
||||||
"SUCCESS":
|
"SUCCESS":
|
||||||
|
|
|
||||||
|
|
@ -114,22 +114,32 @@ layout_mode = 2
|
||||||
theme_override_constants/separation = 14
|
theme_override_constants/separation = 14
|
||||||
alignment = 1
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="DiscoverButton" type="Button" parent="Paper/Margin/Layout/Modes"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(100, 72)
|
||||||
|
layout_mode = 2
|
||||||
|
toggle_mode = true
|
||||||
|
text = "discover"
|
||||||
|
|
||||||
[node name="DirectButton" type="Button" parent="Paper/Margin/Layout/Modes"]
|
[node name="DirectButton" type="Button" parent="Paper/Margin/Layout/Modes"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(100, 72)
|
custom_minimum_size = Vector2(100, 72)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
toggle_mode = true
|
||||||
text = "direct"
|
text = "direct"
|
||||||
|
|
||||||
[node name="SavedButton" type="Button" parent="Paper/Margin/Layout/Modes"]
|
[node name="SavedButton" type="Button" parent="Paper/Margin/Layout/Modes"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(100, 72)
|
custom_minimum_size = Vector2(100, 72)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
toggle_mode = true
|
||||||
text = "saved"
|
text = "saved"
|
||||||
|
|
||||||
[node name="RecentButton" type="Button" parent="Paper/Margin/Layout/Modes"]
|
[node name="RecentButton" type="Button" parent="Paper/Margin/Layout/Modes"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(100, 72)
|
custom_minimum_size = Vector2(100, 72)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
toggle_mode = true
|
||||||
text = "recent"
|
text = "recent"
|
||||||
|
|
||||||
[node name="AddressLabel" type="Label" parent="Paper/Margin/Layout"]
|
[node name="AddressLabel" type="Label" parent="Paper/Margin/Layout"]
|
||||||
|
|
@ -258,6 +268,13 @@ layout_mode = 2
|
||||||
theme_override_constants/separation = 7
|
theme_override_constants/separation = 7
|
||||||
alignment = 1
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="RefreshButton" type="Button" parent="Paper/Margin/Layout/Actions"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
custom_minimum_size = Vector2(82, 72)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "refresh"
|
||||||
|
|
||||||
[node name="JoinButton" type="Button" parent="Paper/Margin/Layout/Actions"]
|
[node name="JoinButton" type="Button" parent="Paper/Margin/Layout/Actions"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(82, 72)
|
custom_minimum_size = Vector2(82, 72)
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ func setup(
|
||||||
network_session: NetworkSessionType,
|
network_session: NetworkSessionType,
|
||||||
saved_servers: SavedServerStoreType,
|
saved_servers: SavedServerStoreType,
|
||||||
server_trust: ServerTrustStore,
|
server_trust: ServerTrustStore,
|
||||||
|
discovery: DiscoveryClient,
|
||||||
) -> void:
|
) -> void:
|
||||||
_player = player
|
_player = player
|
||||||
_save_manager = save_manager
|
_save_manager = save_manager
|
||||||
|
|
@ -114,7 +115,9 @@ func setup(
|
||||||
_fishing_spot = fishing_spot
|
_fishing_spot = fishing_spot
|
||||||
_network_session = network_session
|
_network_session = network_session
|
||||||
_saved_servers = saved_servers
|
_saved_servers = saved_servers
|
||||||
_join_game_page.setup(network_session, saved_servers, true, server_trust)
|
_join_game_page.setup(
|
||||||
|
network_session, saved_servers, true, server_trust, discovery
|
||||||
|
)
|
||||||
if not _fishing_spot.bite_activated.is_connected(_on_bite_activated):
|
if not _fishing_spot.bite_activated.is_connected(_on_bite_activated):
|
||||||
_fishing_spot.bite_activated.connect(_on_bite_activated)
|
_fishing_spot.bite_activated.connect(_on_bite_activated)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -526,6 +526,7 @@ func setup(
|
||||||
reservations: PlayerAssetReservationService,
|
reservations: PlayerAssetReservationService,
|
||||||
network_profile_service: NetworkProfileService,
|
network_profile_service: NetworkProfileService,
|
||||||
network_player_list: NetworkPlayerListService,
|
network_player_list: NetworkPlayerListService,
|
||||||
|
discovery: DiscoveryClient,
|
||||||
player_jobs: PlayerJobService,
|
player_jobs: PlayerJobService,
|
||||||
world_time: WorldTimeService,
|
world_time: WorldTimeService,
|
||||||
world_environment: WorldEnvironment,
|
world_environment: WorldEnvironment,
|
||||||
|
|
@ -558,7 +559,7 @@ func setup(
|
||||||
world_environment,
|
world_environment,
|
||||||
world_sun,
|
world_sun,
|
||||||
)
|
)
|
||||||
_players_page.setup(network_player_list)
|
_players_page.setup(network_player_list, discovery)
|
||||||
_catalog_logbook.setup(collection_log, inventory, catalog)
|
_catalog_logbook.setup(collection_log, inventory, catalog)
|
||||||
_the_net_page.setup(player_jobs, world_time)
|
_the_net_page.setup(player_jobs, world_time)
|
||||||
_network_mail_service.unread_count_changed.connect(
|
_network_mail_service.unread_count_changed.connect(
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,15 @@ class_name PlayersPage
|
||||||
extends Control
|
extends Control
|
||||||
|
|
||||||
var _service: NetworkPlayerListService
|
var _service: NetworkPlayerListService
|
||||||
|
var _discovery: DiscoveryClient
|
||||||
var _count_label: Label
|
var _count_label: Label
|
||||||
var _tabs: HBoxContainer
|
var _tabs: HBoxContainer
|
||||||
var _list: VBoxContainer
|
var _list: VBoxContainer
|
||||||
var _status: Label
|
var _status: Label
|
||||||
|
var _host_settings_panel: PanelContainer
|
||||||
|
var _room_name_edit: LineEdit
|
||||||
|
var _discoverable_toggle: CheckButton
|
||||||
|
var _host_discovery_status: Label
|
||||||
var _current_tab := 0
|
var _current_tab := 0
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -15,13 +20,22 @@ func _ready() -> void:
|
||||||
_build()
|
_build()
|
||||||
|
|
||||||
|
|
||||||
func setup(service: NetworkPlayerListService) -> void:
|
func setup(
|
||||||
|
service: NetworkPlayerListService,
|
||||||
|
discovery: DiscoveryClient,
|
||||||
|
) -> void:
|
||||||
_service = service
|
_service = service
|
||||||
|
_discovery = discovery
|
||||||
_service.entries_changed.connect(_refresh)
|
_service.entries_changed.connect(_refresh)
|
||||||
_service.moderation_finished.connect(func(_ok: bool, message: String) -> void:
|
_service.moderation_finished.connect(func(_ok: bool, message: String) -> void:
|
||||||
_status.text = message
|
_status.text = message
|
||||||
_refresh()
|
_refresh()
|
||||||
)
|
)
|
||||||
|
if _discovery != null:
|
||||||
|
_discovery.host_settings_changed.connect(
|
||||||
|
_on_host_settings_changed
|
||||||
|
)
|
||||||
|
_discovery.host_status_changed.connect(_on_host_status_changed)
|
||||||
_refresh()
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -61,6 +75,7 @@ func _build() -> void:
|
||||||
)
|
)
|
||||||
_count_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
_count_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
tab_row.add_child(_count_label)
|
tab_row.add_child(_count_label)
|
||||||
|
_build_host_settings(root)
|
||||||
var scroll := ScrollContainer.new()
|
var scroll := ScrollContainer.new()
|
||||||
scroll.custom_minimum_size = Vector2(0, 310)
|
scroll.custom_minimum_size = Vector2(0, 310)
|
||||||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
|
|
@ -77,6 +92,47 @@ func _build() -> void:
|
||||||
root.add_child(_status)
|
root.add_child(_status)
|
||||||
|
|
||||||
|
|
||||||
|
func _build_host_settings(root: VBoxContainer) -> void:
|
||||||
|
_host_settings_panel = PanelContainer.new()
|
||||||
|
_host_settings_panel.add_theme_stylebox_override(
|
||||||
|
"panel", UtilityPageStyle.row_style(false)
|
||||||
|
)
|
||||||
|
root.add_child(_host_settings_panel)
|
||||||
|
var row := HBoxContainer.new()
|
||||||
|
row.custom_minimum_size.y = 58.0
|
||||||
|
row.add_theme_constant_override("separation", 10)
|
||||||
|
_host_settings_panel.add_child(row)
|
||||||
|
var label := Label.new()
|
||||||
|
label.text = "room name"
|
||||||
|
label.add_theme_color_override(
|
||||||
|
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||||
|
)
|
||||||
|
row.add_child(label)
|
||||||
|
_room_name_edit = LineEdit.new()
|
||||||
|
_room_name_edit.custom_minimum_size = Vector2(300.0, 42.0)
|
||||||
|
_room_name_edit.max_length = DiscoveryClient.MAX_ROOM_NAME_LENGTH
|
||||||
|
_room_name_edit.placeholder_text = DiscoveryClient.DEFAULT_ROOM_NAME
|
||||||
|
UtilityPageStyle.apply_ocean_line_edit(_room_name_edit)
|
||||||
|
_room_name_edit.text_submitted.connect(
|
||||||
|
func(_value: String) -> void: _commit_room_name()
|
||||||
|
)
|
||||||
|
_room_name_edit.focus_exited.connect(_commit_room_name)
|
||||||
|
row.add_child(_room_name_edit)
|
||||||
|
_discoverable_toggle = CheckButton.new()
|
||||||
|
_discoverable_toggle.text = "list publicly"
|
||||||
|
_discoverable_toggle.toggled.connect(_on_discoverable_toggled)
|
||||||
|
UtilityPageStyle.apply_ocean_button(_discoverable_toggle)
|
||||||
|
row.add_child(_discoverable_toggle)
|
||||||
|
_host_discovery_status = Label.new()
|
||||||
|
_host_discovery_status.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
_host_discovery_status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
|
_host_discovery_status.add_theme_font_size_override("font_size", 14)
|
||||||
|
_host_discovery_status.add_theme_color_override(
|
||||||
|
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||||
|
)
|
||||||
|
row.add_child(_host_discovery_status)
|
||||||
|
|
||||||
|
|
||||||
func _select_tab(index: int) -> void:
|
func _select_tab(index: int) -> void:
|
||||||
if index == 2 and (_service == null or not _service.is_local_host()):
|
if index == 2 and (_service == null or not _service.is_local_host()):
|
||||||
return
|
return
|
||||||
|
|
@ -95,6 +151,7 @@ func _refresh() -> void:
|
||||||
var button := _tabs.get_child(index) as Button
|
var button := _tabs.get_child(index) as Button
|
||||||
button.button_pressed = index == _current_tab
|
button.button_pressed = index == _current_tab
|
||||||
button.visible = index != 2 or _service.is_local_host()
|
button.visible = index != 2 or _service.is_local_host()
|
||||||
|
_refresh_host_settings()
|
||||||
for child: Node in _list.get_children():
|
for child: Node in _list.get_children():
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
match _current_tab:
|
match _current_tab:
|
||||||
|
|
@ -106,6 +163,76 @@ func _refresh() -> void:
|
||||||
_build_ban_rows()
|
_build_ban_rows()
|
||||||
|
|
||||||
|
|
||||||
|
func _refresh_host_settings() -> void:
|
||||||
|
if _host_settings_panel == null:
|
||||||
|
return
|
||||||
|
var host_visible: bool = _service.is_local_host() and _current_tab == 0
|
||||||
|
_host_settings_panel.visible = host_visible
|
||||||
|
if not host_visible or _discovery == null:
|
||||||
|
return
|
||||||
|
if not _room_name_edit.has_focus():
|
||||||
|
_room_name_edit.text = _discovery.get_room_name()
|
||||||
|
_discoverable_toggle.set_pressed_no_signal(_discovery.is_discoverable())
|
||||||
|
var can_publish: bool = (
|
||||||
|
_service.is_local_host()
|
||||||
|
and _discovery.is_configured()
|
||||||
|
and _service.is_open_host()
|
||||||
|
)
|
||||||
|
_discoverable_toggle.disabled = not can_publish
|
||||||
|
_discoverable_toggle.tooltip_text = (
|
||||||
|
"Advertise this open game in the public room browser."
|
||||||
|
if can_publish
|
||||||
|
else "Open the game before listing it publicly."
|
||||||
|
if _discovery.is_configured()
|
||||||
|
else "Room discovery is not configured in this build."
|
||||||
|
)
|
||||||
|
_on_host_status_changed(
|
||||||
|
_discovery.get_host_status_message(),
|
||||||
|
_discovery.host_status_is_error(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _commit_room_name() -> void:
|
||||||
|
if _discovery == null:
|
||||||
|
return
|
||||||
|
if not _discovery.set_room_name(_room_name_edit.text):
|
||||||
|
_room_name_edit.text = _discovery.get_room_name()
|
||||||
|
else:
|
||||||
|
_room_name_edit.text = _discovery.get_room_name()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_discoverable_toggled(enabled: bool) -> void:
|
||||||
|
if _discovery == null:
|
||||||
|
return
|
||||||
|
if not _discovery.set_discoverable(enabled):
|
||||||
|
_discoverable_toggle.set_pressed_no_signal(
|
||||||
|
_discovery.is_discoverable()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_host_settings_changed(
|
||||||
|
room_name: String,
|
||||||
|
discoverable: bool,
|
||||||
|
) -> void:
|
||||||
|
if _room_name_edit != null and not _room_name_edit.has_focus():
|
||||||
|
_room_name_edit.text = room_name
|
||||||
|
if _discoverable_toggle != null:
|
||||||
|
_discoverable_toggle.set_pressed_no_signal(discoverable)
|
||||||
|
_refresh_host_settings()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_host_status_changed(message: String, is_error: bool) -> void:
|
||||||
|
if _host_discovery_status == null:
|
||||||
|
return
|
||||||
|
_host_discovery_status.text = message
|
||||||
|
_host_discovery_status.add_theme_color_override(
|
||||||
|
"font_color",
|
||||||
|
UtilityPageStyle.OCEAN_DANGER
|
||||||
|
if is_error
|
||||||
|
else UtilityPageStyle.OCEAN_TEXT_SECONDARY,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _build_active_rows() -> void:
|
func _build_active_rows() -> void:
|
||||||
if _service.is_local_host():
|
if _service.is_local_host():
|
||||||
_build_session_artwork_controls()
|
_build_session_artwork_controls()
|
||||||
|
|
|
||||||
|
|
@ -236,10 +236,13 @@ func setup(
|
||||||
network_session: NetworkSessionType,
|
network_session: NetworkSessionType,
|
||||||
saved_servers: SavedServerStoreType,
|
saved_servers: SavedServerStoreType,
|
||||||
server_trust: ServerTrustStore,
|
server_trust: ServerTrustStore,
|
||||||
|
discovery: DiscoveryClient,
|
||||||
) -> void:
|
) -> void:
|
||||||
_save_manager = save_manager
|
_save_manager = save_manager
|
||||||
_settings_manager = settings_manager
|
_settings_manager = settings_manager
|
||||||
_join_game_page.setup(network_session, saved_servers, false, server_trust)
|
_join_game_page.setup(
|
||||||
|
network_session, saved_servers, false, server_trust, discovery
|
||||||
|
)
|
||||||
_join_game_page.join_requested.connect(join_game_requested.emit)
|
_join_game_page.join_requested.connect(join_game_requested.emit)
|
||||||
_join_game_page.back_requested.connect(_close_join_game)
|
_join_game_page.back_requested.connect(_close_join_game)
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue