Add synchronized time and weather systems
This commit is contained in:
parent
30ad9215ca
commit
58fb3d6605
37 changed files with 1900 additions and 17 deletions
|
|
@ -20,6 +20,8 @@ const MAIL_RELIABLE_CHANNEL: int = 9
|
|||
const ENET_CHANNEL_COUNT: int = 10
|
||||
const SURFACE_DRAWING_CAPABILITY: String = "surface_drawing_v2"
|
||||
const ART_SHOP_CAPABILITY: String = "art_shop_v1"
|
||||
const WORLD_TIME_CAPABILITY: String = "world_time_v1"
|
||||
const WORLD_WEATHER_CAPABILITY: String = "world_weather_v1"
|
||||
|
||||
enum RejectionCode {
|
||||
NONE,
|
||||
|
|
@ -87,6 +89,8 @@ static func make_client_hello(
|
|||
"client_nonce": client_nonce,
|
||||
"capability_flags": PackedStringArray([
|
||||
SURFACE_DRAWING_CAPABILITY,
|
||||
WORLD_TIME_CAPABILITY,
|
||||
WORLD_WEATHER_CAPABILITY,
|
||||
]),
|
||||
"cosmetic_snapshot": cosmetic_snapshot,
|
||||
"identity_fingerprint": identity_fingerprint,
|
||||
|
|
@ -204,6 +208,8 @@ static func make_server_hello(
|
|||
"equipment_v1",
|
||||
"fish_showcase_v1",
|
||||
SURFACE_DRAWING_CAPABILITY,
|
||||
WORLD_TIME_CAPABILITY,
|
||||
WORLD_WEATHER_CAPABILITY,
|
||||
"chat_v1",
|
||||
"mail_v1",
|
||||
"profile_v1",
|
||||
|
|
|
|||
|
|
@ -168,7 +168,11 @@ func start_private_host(port: int = DEFAULT_PORT) -> bool:
|
|||
NetworkProtocol.PROTOCOL_VERSION,
|
||||
_player_identity.fingerprint,
|
||||
_player_identity.public_pem,
|
||||
PackedStringArray([NetworkProtocol.SURFACE_DRAWING_CAPABILITY]),
|
||||
PackedStringArray([
|
||||
NetworkProtocol.SURFACE_DRAWING_CAPABILITY,
|
||||
NetworkProtocol.WORLD_TIME_CAPABILITY,
|
||||
NetworkProtocol.WORLD_WEATHER_CAPABILITY,
|
||||
]),
|
||||
)
|
||||
_registry.update_appearance(1, _local_appearance_snapshot)
|
||||
var host_profile_hello := NetworkProtocol.make_client_hello(
|
||||
|
|
@ -364,6 +368,8 @@ func supports_server_capability(capability: StringName) -> bool:
|
|||
NetworkProtocol.ART_SHOP_CAPABILITY,
|
||||
"item_use_v1", "equipment_v1", "fish_showcase_v1",
|
||||
NetworkProtocol.SURFACE_DRAWING_CAPABILITY,
|
||||
NetworkProtocol.WORLD_TIME_CAPABILITY,
|
||||
NetworkProtocol.WORLD_WEATHER_CAPABILITY,
|
||||
"chat_v1",
|
||||
"mail_v1",
|
||||
"profile_v1",
|
||||
|
|
@ -1074,7 +1080,11 @@ func receive_server_hello(data: Dictionary) -> void:
|
|||
NetworkProtocol.PROTOCOL_VERSION,
|
||||
_player_identity.fingerprint,
|
||||
_player_identity.public_pem,
|
||||
PackedStringArray([NetworkProtocol.SURFACE_DRAWING_CAPABILITY]),
|
||||
PackedStringArray([
|
||||
NetworkProtocol.SURFACE_DRAWING_CAPABILITY,
|
||||
NetworkProtocol.WORLD_TIME_CAPABILITY,
|
||||
NetworkProtocol.WORLD_WEATHER_CAPABILITY,
|
||||
]),
|
||||
)
|
||||
_registry.update_appearance(local_peer_id, _local_appearance_snapshot)
|
||||
var local_record := _registry.get_peer(local_peer_id)
|
||||
|
|
|
|||
135
network/network_world_time_service.gd
Normal file
135
network/network_world_time_service.gd
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
class_name NetworkWorldTimeService
|
||||
extends Node
|
||||
|
||||
const SYNC_INTERVAL_SECONDS: float = 5.0
|
||||
const MAX_SESSION_ID_LENGTH: int = 96
|
||||
|
||||
var _session: NetworkSession
|
||||
var _world_time: WorldTimeService
|
||||
var _sync_elapsed: float = 0.0
|
||||
var _sequence: int = 0
|
||||
var _last_received_sequence: int = -1
|
||||
var _active_session_id: String = ""
|
||||
|
||||
|
||||
func setup(session: NetworkSession, world_time: WorldTimeService) -> void:
|
||||
_session = session
|
||||
_world_time = world_time
|
||||
_session.state_changed.connect(_on_session_state_changed)
|
||||
_session.peer_authenticated.connect(_on_peer_authenticated)
|
||||
set_process(true)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if _session == null or _world_time == null or not _session.is_host():
|
||||
return
|
||||
_sync_elapsed += delta
|
||||
if _sync_elapsed < SYNC_INTERVAL_SECONDS:
|
||||
return
|
||||
_sync_elapsed = 0.0
|
||||
_broadcast_snapshot()
|
||||
|
||||
|
||||
func _on_session_state_changed(state: NetworkSession.State) -> void:
|
||||
if _world_time == null or _session == null:
|
||||
return
|
||||
if state in [NetworkSession.State.PRIVATE_HOST, NetworkSession.State.OPEN_HOST]:
|
||||
if _active_session_id != _session.get_session_id():
|
||||
_active_session_id = _session.get_session_id()
|
||||
_sequence = 0
|
||||
_last_received_sequence = -1
|
||||
_sync_elapsed = 0.0
|
||||
_world_time.begin_session()
|
||||
return
|
||||
if state == NetworkSession.State.JOINED_CLIENT:
|
||||
_active_session_id = _session.get_session_id()
|
||||
_last_received_sequence = -1
|
||||
_sync_elapsed = 0.0
|
||||
if _session.supports_server_capability(
|
||||
NetworkProtocol.WORLD_TIME_CAPABILITY
|
||||
):
|
||||
_world_time.begin_session()
|
||||
else:
|
||||
_world_time.end_session()
|
||||
return
|
||||
if state in [
|
||||
NetworkSession.State.INACTIVE,
|
||||
NetworkSession.State.DISCONNECTING,
|
||||
NetworkSession.State.CONNECTION_FAILED,
|
||||
NetworkSession.State.SERVER_LOST,
|
||||
]:
|
||||
_active_session_id = ""
|
||||
_sequence = 0
|
||||
_last_received_sequence = -1
|
||||
_sync_elapsed = 0.0
|
||||
_world_time.end_session()
|
||||
|
||||
|
||||
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.WORLD_TIME_CAPABILITY
|
||||
)
|
||||
):
|
||||
return
|
||||
_send_snapshot(peer_id)
|
||||
|
||||
|
||||
func _broadcast_snapshot() -> 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():
|
||||
continue
|
||||
if _session.peer_supports_capability(
|
||||
peer_id, NetworkProtocol.WORLD_TIME_CAPABILITY
|
||||
):
|
||||
_send_snapshot(peer_id)
|
||||
|
||||
|
||||
func _send_snapshot(peer_id: int) -> void:
|
||||
_sequence += 1
|
||||
receive_world_time_snapshot.rpc_id(peer_id, {
|
||||
"session_id": _session.get_session_id(),
|
||||
"time_hours": _world_time.get_time_hours(),
|
||||
"sequence": _sequence,
|
||||
})
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", 0)
|
||||
func receive_world_time_snapshot(data: Dictionary) -> void:
|
||||
if (
|
||||
_session == null
|
||||
or _world_time == null
|
||||
or not _session.is_joined_client()
|
||||
or not _session.supports_server_capability(
|
||||
NetworkProtocol.WORLD_TIME_CAPABILITY
|
||||
)
|
||||
or not validate_snapshot(data)
|
||||
or str(data["session_id"]) != _session.get_session_id()
|
||||
):
|
||||
return
|
||||
var sequence: int = int(data["sequence"])
|
||||
if sequence <= _last_received_sequence:
|
||||
return
|
||||
_last_received_sequence = sequence
|
||||
_world_time.synchronize_time(float(data["time_hours"]))
|
||||
|
||||
|
||||
static func validate_snapshot(data: Variant) -> bool:
|
||||
if typeof(data) != TYPE_DICTIONARY:
|
||||
return false
|
||||
var value: Dictionary = data
|
||||
return (
|
||||
typeof(value.get("session_id")) == TYPE_STRING
|
||||
and not str(value["session_id"]).is_empty()
|
||||
and str(value["session_id"]).length() <= MAX_SESSION_ID_LENGTH
|
||||
and typeof(value.get("time_hours")) in [TYPE_FLOAT, TYPE_INT]
|
||||
and is_finite(float(value["time_hours"]))
|
||||
and float(value["time_hours"]) >= 0.0
|
||||
and float(value["time_hours"]) < WorldTimeService.HOURS_PER_DAY
|
||||
and typeof(value.get("sequence")) == TYPE_INT
|
||||
and int(value["sequence"]) >= 0
|
||||
)
|
||||
1
network/network_world_time_service.gd.uid
Normal file
1
network/network_world_time_service.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://blig76aes25j
|
||||
156
network/network_world_weather_service.gd
Normal file
156
network/network_world_weather_service.gd
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
class_name NetworkWorldWeatherService
|
||||
extends Node
|
||||
|
||||
const SYNC_INTERVAL_SECONDS: float = 15.0
|
||||
const MAX_SESSION_ID_LENGTH: int = 96
|
||||
const MAX_REMAINING_SECONDS: float = 1800.0
|
||||
|
||||
var _session: NetworkSession
|
||||
var _world_weather: WorldWeatherService
|
||||
var _sync_elapsed: float = 0.0
|
||||
var _sequence: int = 0
|
||||
var _last_received_sequence: int = -1
|
||||
var _active_session_id: String = ""
|
||||
|
||||
|
||||
func setup(
|
||||
session: NetworkSession,
|
||||
world_weather: WorldWeatherService,
|
||||
) -> void:
|
||||
_session = session
|
||||
_world_weather = world_weather
|
||||
_session.state_changed.connect(_on_session_state_changed)
|
||||
_session.peer_authenticated.connect(_on_peer_authenticated)
|
||||
_world_weather.weather_changed.connect(_on_weather_changed)
|
||||
set_process(true)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if _session == null or _world_weather == null or not _session.is_host():
|
||||
return
|
||||
_sync_elapsed += delta
|
||||
if _sync_elapsed < SYNC_INTERVAL_SECONDS:
|
||||
return
|
||||
_sync_elapsed = 0.0
|
||||
_broadcast_snapshot()
|
||||
|
||||
|
||||
func _on_session_state_changed(state: NetworkSession.State) -> void:
|
||||
if _world_weather == null or _session == null:
|
||||
return
|
||||
if state in [NetworkSession.State.PRIVATE_HOST, NetworkSession.State.OPEN_HOST]:
|
||||
if _active_session_id != _session.get_session_id():
|
||||
_active_session_id = _session.get_session_id()
|
||||
_sequence = 0
|
||||
_last_received_sequence = -1
|
||||
_sync_elapsed = 0.0
|
||||
_world_weather.begin_authoritative_session(
|
||||
_active_session_id.hash()
|
||||
)
|
||||
return
|
||||
if state == NetworkSession.State.JOINED_CLIENT:
|
||||
_active_session_id = _session.get_session_id()
|
||||
_last_received_sequence = -1
|
||||
_sync_elapsed = 0.0
|
||||
if _session.supports_server_capability(
|
||||
NetworkProtocol.WORLD_WEATHER_CAPABILITY
|
||||
):
|
||||
_world_weather.begin_remote_session()
|
||||
else:
|
||||
_world_weather.end_session()
|
||||
return
|
||||
if state in [
|
||||
NetworkSession.State.INACTIVE,
|
||||
NetworkSession.State.DISCONNECTING,
|
||||
NetworkSession.State.CONNECTION_FAILED,
|
||||
NetworkSession.State.SERVER_LOST,
|
||||
]:
|
||||
_active_session_id = ""
|
||||
_sequence = 0
|
||||
_last_received_sequence = -1
|
||||
_sync_elapsed = 0.0
|
||||
_world_weather.end_session()
|
||||
|
||||
|
||||
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.WORLD_WEATHER_CAPABILITY
|
||||
)
|
||||
):
|
||||
return
|
||||
_send_snapshot(peer_id)
|
||||
|
||||
|
||||
func _on_weather_changed(
|
||||
_weather: WorldWeatherService.Weather,
|
||||
_seconds_remaining: float,
|
||||
) -> void:
|
||||
if _session != null and _session.is_host():
|
||||
_broadcast_snapshot()
|
||||
|
||||
|
||||
func _broadcast_snapshot() -> 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():
|
||||
continue
|
||||
if _session.peer_supports_capability(
|
||||
peer_id, NetworkProtocol.WORLD_WEATHER_CAPABILITY
|
||||
):
|
||||
_send_snapshot(peer_id)
|
||||
|
||||
|
||||
func _send_snapshot(peer_id: int) -> void:
|
||||
_sequence += 1
|
||||
receive_world_weather_snapshot.rpc_id(peer_id, {
|
||||
"session_id": _session.get_session_id(),
|
||||
"weather": int(_world_weather.get_weather()),
|
||||
"seconds_remaining": _world_weather.get_seconds_remaining(),
|
||||
"sequence": _sequence,
|
||||
})
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", 0)
|
||||
func receive_world_weather_snapshot(data: Dictionary) -> void:
|
||||
if (
|
||||
_session == null
|
||||
or _world_weather == null
|
||||
or not _session.is_joined_client()
|
||||
or not _session.supports_server_capability(
|
||||
NetworkProtocol.WORLD_WEATHER_CAPABILITY
|
||||
)
|
||||
or not validate_snapshot(data)
|
||||
or str(data["session_id"]) != _session.get_session_id()
|
||||
):
|
||||
return
|
||||
var sequence: int = int(data["sequence"])
|
||||
if sequence <= _last_received_sequence:
|
||||
return
|
||||
_last_received_sequence = sequence
|
||||
_world_weather.apply_authoritative_snapshot(
|
||||
int(data["weather"]) as WorldWeatherService.Weather,
|
||||
float(data["seconds_remaining"]),
|
||||
)
|
||||
|
||||
|
||||
static func validate_snapshot(data: Variant) -> bool:
|
||||
if typeof(data) != TYPE_DICTIONARY:
|
||||
return false
|
||||
var value: Dictionary = data
|
||||
return (
|
||||
typeof(value.get("session_id")) == TYPE_STRING
|
||||
and not str(value["session_id"]).is_empty()
|
||||
and str(value["session_id"]).length() <= MAX_SESSION_ID_LENGTH
|
||||
and typeof(value.get("weather")) == TYPE_INT
|
||||
and WorldWeatherService.is_valid_weather(int(value["weather"]))
|
||||
and typeof(value.get("seconds_remaining")) in [TYPE_FLOAT, TYPE_INT]
|
||||
and is_finite(float(value["seconds_remaining"]))
|
||||
and float(value["seconds_remaining"]) >= 0.0
|
||||
and float(value["seconds_remaining"]) <= MAX_REMAINING_SECONDS
|
||||
and typeof(value.get("sequence")) == TYPE_INT
|
||||
and int(value["sequence"]) >= 0
|
||||
)
|
||||
1
network/network_world_weather_service.gd.uid
Normal file
1
network/network_world_weather_service.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://llj46fx0oco7
|
||||
Loading…
Add table
Add a link
Reference in a new issue