Add synchronized Fishnet jobs and laptop UI
This commit is contained in:
parent
46df74361a
commit
26fbd41132
39 changed files with 3257 additions and 188 deletions
102
network/network_job_service.gd
Normal file
102
network/network_job_service.gd
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
class_name NetworkJobService
|
||||
extends Node
|
||||
|
||||
const MAX_SESSION_ID_LENGTH: int = 96
|
||||
|
||||
var _session: NetworkSession
|
||||
var _jobs: PlayerJobService
|
||||
var _sequence: int = 0
|
||||
var _last_received_sequence: int = -1
|
||||
|
||||
|
||||
func setup(session: NetworkSession, jobs: PlayerJobService) -> void:
|
||||
_session = session
|
||||
_jobs = jobs
|
||||
_session.state_changed.connect(_on_session_state_changed)
|
||||
_session.peer_authenticated.connect(_on_peer_authenticated)
|
||||
_jobs.board_changed.connect(_on_board_changed)
|
||||
|
||||
|
||||
func _on_session_state_changed(state: NetworkSession.State) -> void:
|
||||
_sequence = 0
|
||||
_last_received_sequence = -1
|
||||
if state == NetworkSession.State.JOINED_CLIENT:
|
||||
if not _session.supports_server_capability(NetworkProtocol.JOBS_CAPABILITY):
|
||||
_jobs.clear_remote_board()
|
||||
elif state in [
|
||||
NetworkSession.State.INACTIVE,
|
||||
NetworkSession.State.DISCONNECTING,
|
||||
NetworkSession.State.CONNECTION_FAILED,
|
||||
NetworkSession.State.SERVER_LOST,
|
||||
]:
|
||||
_jobs.clear_remote_board()
|
||||
|
||||
|
||||
func _on_peer_authenticated(peer_id: int, _display_name: String) -> void:
|
||||
if (
|
||||
_session == null
|
||||
or not _session.is_host()
|
||||
or not _session.peer_supports_capability(
|
||||
peer_id, NetworkProtocol.JOBS_CAPABILITY
|
||||
)
|
||||
):
|
||||
return
|
||||
_send_board(peer_id)
|
||||
|
||||
|
||||
func _on_board_changed() -> void:
|
||||
if _session == null or not _session.is_host():
|
||||
return
|
||||
for peer_id: int in _session.get_authenticated_peer_ids():
|
||||
if (
|
||||
peer_id != _session.get_local_peer_id()
|
||||
and _session.peer_supports_capability(
|
||||
peer_id, NetworkProtocol.JOBS_CAPABILITY
|
||||
)
|
||||
):
|
||||
_send_board(peer_id)
|
||||
|
||||
|
||||
func _send_board(peer_id: int) -> void:
|
||||
var board: Dictionary = _jobs.get_host_board_network_data()
|
||||
if board.is_empty() or not PlayerJobService.validate_board(board):
|
||||
return
|
||||
_sequence += 1
|
||||
receive_job_board.rpc_id(peer_id, {
|
||||
"session_id": _session.get_session_id(),
|
||||
"sequence": _sequence,
|
||||
"board": board,
|
||||
})
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", 0)
|
||||
func receive_job_board(data: Dictionary) -> void:
|
||||
if (
|
||||
_session == null
|
||||
or _jobs == null
|
||||
or not _session.is_joined_client()
|
||||
or not _session.supports_server_capability(NetworkProtocol.JOBS_CAPABILITY)
|
||||
or not validate_snapshot(data)
|
||||
or str(data.get("session_id", "")) != _session.get_session_id()
|
||||
):
|
||||
return
|
||||
var sequence: int = int(data.get("sequence", -1))
|
||||
if sequence <= _last_received_sequence:
|
||||
return
|
||||
var board: Dictionary = data.get("board", {})
|
||||
if _jobs.apply_remote_board(board):
|
||||
_last_received_sequence = sequence
|
||||
|
||||
|
||||
static func validate_snapshot(value: Variant) -> bool:
|
||||
if typeof(value) != TYPE_DICTIONARY:
|
||||
return false
|
||||
var data: Dictionary = value
|
||||
return (
|
||||
typeof(data.get("session_id")) == TYPE_STRING
|
||||
and not str(data.get("session_id", "")).is_empty()
|
||||
and str(data.get("session_id", "")).length() <= MAX_SESSION_ID_LENGTH
|
||||
and typeof(data.get("sequence")) == TYPE_INT
|
||||
and int(data.get("sequence", -1)) >= 0
|
||||
and PlayerJobService.validate_board(data.get("board"))
|
||||
)
|
||||
1
network/network_job_service.gd.uid
Normal file
1
network/network_job_service.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://drlyytr2o3uw8
|
||||
|
|
@ -23,6 +23,7 @@ const ART_SHOP_CAPABILITY: String = "art_shop_v1"
|
|||
const WORLD_TIME_CAPABILITY: String = "world_time_v1"
|
||||
const WORLD_WEATHER_CAPABILITY: String = "world_weather_v1"
|
||||
const FISH_QUALITY_CAPABILITY: String = "fish_quality_v1"
|
||||
const JOBS_CAPABILITY: String = "jobs_v1"
|
||||
|
||||
enum RejectionCode {
|
||||
NONE,
|
||||
|
|
@ -93,6 +94,7 @@ static func make_client_hello(
|
|||
SURFACE_DRAWING_CAPABILITY,
|
||||
WORLD_TIME_CAPABILITY,
|
||||
WORLD_WEATHER_CAPABILITY,
|
||||
JOBS_CAPABILITY,
|
||||
]),
|
||||
"cosmetic_snapshot": cosmetic_snapshot,
|
||||
"identity_fingerprint": identity_fingerprint,
|
||||
|
|
@ -213,6 +215,7 @@ static func make_server_hello(
|
|||
SURFACE_DRAWING_CAPABILITY,
|
||||
WORLD_TIME_CAPABILITY,
|
||||
WORLD_WEATHER_CAPABILITY,
|
||||
JOBS_CAPABILITY,
|
||||
"chat_v1",
|
||||
"mail_v1",
|
||||
"profile_v1",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ class_name NetworkSession
|
|||
extends Node
|
||||
|
||||
const DEFAULT_PORT: int = 7777
|
||||
const DEFAULT_PRIVATE_HOST_PORT_ATTEMPTS: int = 16
|
||||
const DEFAULT_SESSION_MAX_PLAYERS: int = 8
|
||||
const DEFAULT_TRANSPORT_MAX_CLIENTS: int = 31
|
||||
const CONNECTION_TIMEOUT_SECONDS: float = 10.0
|
||||
|
|
@ -97,6 +98,7 @@ var _local_appearance_snapshot: Dictionary = (
|
|||
CharacterCustomizationCatalog.default_snapshot()
|
||||
)
|
||||
var _moderation_disconnect_message := ""
|
||||
var _host_port: int = 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -131,7 +133,10 @@ func setup(
|
|||
_profile_ready = _profile_ready and _player_identity.load_or_create()
|
||||
|
||||
|
||||
func start_private_host(port: int = DEFAULT_PORT) -> bool:
|
||||
func start_private_host(
|
||||
port: int = DEFAULT_PORT,
|
||||
port_attempts: int = 1,
|
||||
) -> bool:
|
||||
if (
|
||||
state != State.INACTIVE
|
||||
or not _profile_ready
|
||||
|
|
@ -143,19 +148,31 @@ func start_private_host(port: int = DEFAULT_PORT) -> bool:
|
|||
if not _host_identity.load_or_create():
|
||||
_fail(_host_identity.error_message)
|
||||
return false
|
||||
if port < 1 or port > 65535:
|
||||
if port < 1 or port > 65535 or port_attempts < 1:
|
||||
_fail("The hosting port must be from 1 to 65535.")
|
||||
return false
|
||||
_operation_generation += 1
|
||||
_set_state(State.STARTING_PRIVATE_HOST, "Starting private game...")
|
||||
_replace_transport()
|
||||
var error: Error = _transport.start_host(
|
||||
port,
|
||||
transport_max_clients,
|
||||
)
|
||||
if error != OK:
|
||||
_fail("Could not start a private game on UDP port %d." % port)
|
||||
var selected_port: int = 0
|
||||
var final_port: int = mini(port + port_attempts - 1, 65535)
|
||||
for candidate_port: int in range(port, final_port + 1):
|
||||
if not _can_bind_udp_port(candidate_port):
|
||||
continue
|
||||
_replace_transport()
|
||||
var error: Error = _transport.start_host(
|
||||
candidate_port,
|
||||
transport_max_clients,
|
||||
)
|
||||
if error == OK:
|
||||
selected_port = candidate_port
|
||||
break
|
||||
if selected_port == 0:
|
||||
_fail(
|
||||
"Could not start a private game on UDP ports %d–%d."
|
||||
% [port, final_port]
|
||||
)
|
||||
return false
|
||||
_host_port = selected_port
|
||||
var peer: MultiplayerPeer = _transport.get_multiplayer_peer()
|
||||
peer.refuse_new_connections = true
|
||||
multiplayer.multiplayer_peer = peer
|
||||
|
|
@ -173,6 +190,7 @@ func start_private_host(port: int = DEFAULT_PORT) -> bool:
|
|||
NetworkProtocol.SURFACE_DRAWING_CAPABILITY,
|
||||
NetworkProtocol.WORLD_TIME_CAPABILITY,
|
||||
NetworkProtocol.WORLD_WEATHER_CAPABILITY,
|
||||
NetworkProtocol.JOBS_CAPABILITY,
|
||||
]),
|
||||
)
|
||||
_registry.update_appearance(1, _local_appearance_snapshot)
|
||||
|
|
@ -200,12 +218,23 @@ func start_private_host(port: int = DEFAULT_PORT) -> bool:
|
|||
var host_avatar := _spawn_service.get_avatar(1)
|
||||
if host_avatar != null:
|
||||
host_avatar.apply_appearance_snapshot(_local_appearance_snapshot)
|
||||
_set_state(State.PRIVATE_HOST, "Private game • UDP %d" % port)
|
||||
_set_state(State.PRIVATE_HOST, "Private game • UDP %d" % selected_port)
|
||||
host_openness_changed.emit(false)
|
||||
_emit_peer_count()
|
||||
return true
|
||||
|
||||
|
||||
func get_host_port() -> int:
|
||||
return _host_port if is_host() else 0
|
||||
|
||||
|
||||
static func _can_bind_udp_port(port: int) -> bool:
|
||||
var probe := PacketPeerUDP.new()
|
||||
var error: Error = probe.bind(port)
|
||||
probe.close()
|
||||
return error == OK
|
||||
|
||||
|
||||
func join_direct(endpoint_text: String) -> bool:
|
||||
if (
|
||||
state != State.INACTIVE
|
||||
|
|
@ -283,10 +312,10 @@ func set_host_open(is_open: bool) -> bool:
|
|||
_set_state(
|
||||
State.OPEN_HOST if is_open else State.PRIVATE_HOST,
|
||||
(
|
||||
"Open game • %d / %d players"
|
||||
"Open game • UDP %d • %d / %d players"
|
||||
if is_open
|
||||
else "Private game • %d / %d players"
|
||||
) % [_registry.size(), session_max_players]
|
||||
else "Private game • UDP %d • %d / %d players"
|
||||
) % [_host_port, _registry.size(), session_max_players]
|
||||
)
|
||||
host_openness_changed.emit(is_open)
|
||||
return true
|
||||
|
|
@ -1729,3 +1758,4 @@ func _teardown_peer() -> void:
|
|||
_server_identity_fingerprint = ""
|
||||
_server_identity_public_key = ""
|
||||
_session_identity_keys.clear()
|
||||
_host_port = 0
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ func _on_session_state_changed(state: NetworkSession.State) -> void:
|
|||
_sequence = 0
|
||||
_last_received_sequence = -1
|
||||
_sync_elapsed = 0.0
|
||||
_world_weather.set_persistence_tracking_enabled(true)
|
||||
_world_weather.begin_authoritative_session(
|
||||
_active_session_id.hash()
|
||||
)
|
||||
|
|
@ -52,6 +53,7 @@ func _on_session_state_changed(state: NetworkSession.State) -> void:
|
|||
_active_session_id = _session.get_session_id()
|
||||
_last_received_sequence = -1
|
||||
_sync_elapsed = 0.0
|
||||
_world_weather.set_persistence_tracking_enabled(false)
|
||||
if _session.supports_server_capability(
|
||||
NetworkProtocol.WORLD_WEATHER_CAPABILITY
|
||||
):
|
||||
|
|
@ -69,6 +71,7 @@ func _on_session_state_changed(state: NetworkSession.State) -> void:
|
|||
_sequence = 0
|
||||
_last_received_sequence = -1
|
||||
_sync_elapsed = 0.0
|
||||
_world_weather.set_persistence_tracking_enabled(false)
|
||||
_world_weather.end_session()
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue