Fix multiplayer presentation and Fishnet synchronization
This commit is contained in:
parent
31d459b3a7
commit
2045d8e288
19 changed files with 748 additions and 41 deletions
|
|
@ -2,11 +2,13 @@ class_name NetworkJobService
|
|||
extends Node
|
||||
|
||||
const MAX_SESSION_ID_LENGTH: int = 96
|
||||
const BOARD_REQUEST_INTERVAL_SECONDS: float = 1.5
|
||||
|
||||
var _session: NetworkSession
|
||||
var _jobs: PlayerJobService
|
||||
var _sequence: int = 0
|
||||
var _last_received_sequence: int = -1
|
||||
var _board_request_accumulator: float = 0.0
|
||||
|
||||
|
||||
func setup(session: NetworkSession, jobs: PlayerJobService) -> void:
|
||||
|
|
@ -15,13 +17,36 @@ func setup(session: NetworkSession, jobs: PlayerJobService) -> void:
|
|||
_session.state_changed.connect(_on_session_state_changed)
|
||||
_session.peer_authenticated.connect(_on_peer_authenticated)
|
||||
_jobs.board_changed.connect(_on_board_changed)
|
||||
set_process(true)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if (
|
||||
_session == null
|
||||
or _jobs == null
|
||||
or not _session.is_joined_client()
|
||||
or not _session.supports_server_capability(
|
||||
NetworkProtocol.JOBS_CAPABILITY
|
||||
)
|
||||
or _jobs.has_active_board()
|
||||
):
|
||||
_board_request_accumulator = 0.0
|
||||
return
|
||||
_board_request_accumulator += delta
|
||||
if _board_request_accumulator < BOARD_REQUEST_INTERVAL_SECONDS:
|
||||
return
|
||||
_board_request_accumulator = 0.0
|
||||
_request_remote_board()
|
||||
|
||||
|
||||
func _on_session_state_changed(state: NetworkSession.State) -> void:
|
||||
_sequence = 0
|
||||
_last_received_sequence = -1
|
||||
_board_request_accumulator = 0.0
|
||||
if state == NetworkSession.State.JOINED_CLIENT:
|
||||
if not _session.supports_server_capability(NetworkProtocol.JOBS_CAPABILITY):
|
||||
if _session.supports_server_capability(NetworkProtocol.JOBS_CAPABILITY):
|
||||
_request_remote_board()
|
||||
else:
|
||||
_jobs.clear_remote_board()
|
||||
elif state in [
|
||||
NetworkSession.State.INACTIVE,
|
||||
|
|
@ -69,6 +94,33 @@ func _send_board(peer_id: int) -> void:
|
|||
})
|
||||
|
||||
|
||||
func _request_remote_board() -> void:
|
||||
if (
|
||||
_session != null
|
||||
and _session.is_joined_client()
|
||||
and _session.supports_server_capability(
|
||||
NetworkProtocol.JOBS_CAPABILITY
|
||||
)
|
||||
):
|
||||
request_job_board.rpc_id(1)
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
||||
func request_job_board() -> void:
|
||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
||||
if (
|
||||
_session == null
|
||||
or not _session.is_host()
|
||||
or sender_id <= 1
|
||||
or not _session.is_authenticated_peer(sender_id)
|
||||
or not _session.peer_supports_capability(
|
||||
sender_id, NetworkProtocol.JOBS_CAPABILITY
|
||||
)
|
||||
):
|
||||
return
|
||||
_send_board(sender_id)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", 0)
|
||||
func receive_job_board(data: Dictionary) -> void:
|
||||
if (
|
||||
|
|
@ -86,6 +138,7 @@ func receive_job_board(data: Dictionary) -> void:
|
|||
var board: Dictionary = data.get("board", {})
|
||||
if _jobs.apply_remote_board(board):
|
||||
_last_received_sequence = sequence
|
||||
_board_request_accumulator = 0.0
|
||||
|
||||
|
||||
static func validate_snapshot(value: Variant) -> bool:
|
||||
|
|
|
|||
94
network/network_player_animation_protocol.gd
Normal file
94
network/network_player_animation_protocol.gd
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
class_name NetworkPlayerAnimationProtocol
|
||||
extends RefCounted
|
||||
|
||||
const FORMAT_VERSION: int = 1
|
||||
const MAX_STATE_ID_LENGTH: int = 64
|
||||
const MAX_ACTION_SEQUENCE: int = 2147483647
|
||||
const MAX_ACTION_ELAPSED_SECONDS: float = 86400.0
|
||||
|
||||
const LOCOMOTION_IDLE: StringName = &"idle"
|
||||
const LOCOMOTION_WALKING: StringName = &"walking"
|
||||
const LOCOMOTION_RUNNING: StringName = &"running"
|
||||
|
||||
|
||||
static func make_state(
|
||||
locomotion_id: StringName,
|
||||
grounded: bool,
|
||||
action_id: StringName = &"",
|
||||
action_sequence: int = 0,
|
||||
action_elapsed: float = 0.0,
|
||||
) -> Dictionary:
|
||||
return {
|
||||
"format_version": FORMAT_VERSION,
|
||||
"locomotion_id": String(locomotion_id),
|
||||
"grounded": grounded,
|
||||
"action": make_action_state(
|
||||
action_id, action_sequence, action_elapsed
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
static func make_action_state(
|
||||
action_id: StringName = &"",
|
||||
action_sequence: int = 0,
|
||||
action_elapsed: float = 0.0,
|
||||
) -> Dictionary:
|
||||
return {
|
||||
"id": String(action_id),
|
||||
"sequence": action_sequence,
|
||||
"elapsed": action_elapsed,
|
||||
}
|
||||
|
||||
|
||||
static func validate_state(value: Variant) -> bool:
|
||||
if typeof(value) != TYPE_DICTIONARY:
|
||||
return false
|
||||
var state: Dictionary = value
|
||||
return (
|
||||
typeof(state.get("format_version")) == TYPE_INT
|
||||
and int(state["format_version"]) == FORMAT_VERSION
|
||||
and valid_state_id(state.get("locomotion_id"), false)
|
||||
and typeof(state.get("grounded")) == TYPE_BOOL
|
||||
and validate_action_state(state.get("action"))
|
||||
)
|
||||
|
||||
|
||||
static func validate_action_state(value: Variant) -> bool:
|
||||
if typeof(value) != TYPE_DICTIONARY:
|
||||
return false
|
||||
var action: Dictionary = value
|
||||
if (
|
||||
not valid_state_id(action.get("id"), true)
|
||||
or typeof(action.get("sequence")) != TYPE_INT
|
||||
or typeof(action.get("elapsed")) not in [TYPE_FLOAT, TYPE_INT]
|
||||
):
|
||||
return false
|
||||
var sequence: int = int(action["sequence"])
|
||||
var elapsed: float = float(action["elapsed"])
|
||||
return (
|
||||
sequence >= 0
|
||||
and sequence <= MAX_ACTION_SEQUENCE
|
||||
and is_finite(elapsed)
|
||||
and elapsed >= 0.0
|
||||
and elapsed <= MAX_ACTION_ELAPSED_SECONDS
|
||||
)
|
||||
|
||||
|
||||
static func valid_state_id(value: Variant, allow_empty: bool) -> bool:
|
||||
if typeof(value) not in [TYPE_STRING, TYPE_STRING_NAME]:
|
||||
return false
|
||||
var state_id: String = str(value)
|
||||
if state_id.is_empty():
|
||||
return allow_empty
|
||||
if state_id.length() > MAX_STATE_ID_LENGTH:
|
||||
return false
|
||||
for index: int in state_id.length():
|
||||
var codepoint: int = state_id.unicode_at(index)
|
||||
var valid_character: bool = (
|
||||
codepoint >= 97 and codepoint <= 122
|
||||
or codepoint >= 48 and codepoint <= 57
|
||||
or codepoint == 95
|
||||
)
|
||||
if not valid_character:
|
||||
return false
|
||||
return true
|
||||
1
network/network_player_animation_protocol.gd.uid
Normal file
1
network/network_player_animation_protocol.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cmaaep4n3p2tq
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
class_name NetworkProtocol
|
||||
extends RefCounted
|
||||
|
||||
const PROTOCOL_VERSION: int = 3
|
||||
const PROTOCOL_VERSION: int = 4
|
||||
const GAME_BUILD: String = "prealpha"
|
||||
const MAX_GAME_VERSION_LENGTH: int = 64
|
||||
const MAX_DISPLAY_NAME_LENGTH: int = 24
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const CONNECTION_TIMEOUT_SECONDS: float = 10.0
|
|||
const AUTHENTICATION_TIMEOUT_SECONDS: float = 60.0
|
||||
const INPUT_INTERVAL: float = 1.0 / 30.0
|
||||
const SNAPSHOT_INTERVAL: float = 1.0 / 30.0
|
||||
const MAX_MOVEMENT_INPUT_SEQUENCE: int = 2147483647
|
||||
|
||||
signal state_changed(state: State)
|
||||
signal status_message_changed(message: String)
|
||||
|
|
@ -1741,9 +1742,10 @@ func _is_valid_movement_input(data: Dictionary) -> bool:
|
|||
or typeof(data.get("sprint")) != TYPE_BOOL
|
||||
or typeof(data.get("sneak")) != TYPE_BOOL
|
||||
or typeof(data.get("slow_walk")) != TYPE_BOOL
|
||||
or (
|
||||
data.has("sitting")
|
||||
and typeof(data.get("sitting")) != TYPE_BOOL
|
||||
or typeof(data.get("sitting")) != TYPE_BOOL
|
||||
or typeof(data.get("casting")) != TYPE_BOOL
|
||||
or not NetworkPlayerAnimationProtocol.validate_action_state(
|
||||
data.get("animation_action")
|
||||
)
|
||||
or typeof(data.get("camera_yaw")) not in [TYPE_FLOAT, TYPE_INT]
|
||||
):
|
||||
|
|
@ -1751,11 +1753,18 @@ func _is_valid_movement_input(data: Dictionary) -> bool:
|
|||
var axis: Array = data["axis"]
|
||||
if axis.size() != 2:
|
||||
return false
|
||||
if (
|
||||
typeof(axis[0]) not in [TYPE_FLOAT, TYPE_INT]
|
||||
or typeof(axis[1]) not in [TYPE_FLOAT, TYPE_INT]
|
||||
):
|
||||
return false
|
||||
var x: float = float(axis[0])
|
||||
var y: float = float(axis[1])
|
||||
var camera_yaw: float = float(data["camera_yaw"])
|
||||
return (
|
||||
is_finite(x)
|
||||
int(data["sequence"]) > 0
|
||||
and int(data["sequence"]) <= MAX_MOVEMENT_INPUT_SEQUENCE
|
||||
and is_finite(x)
|
||||
and is_finite(y)
|
||||
and is_finite(camera_yaw)
|
||||
and absf(x) <= 1.01
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue