Add identity-backed server moderation
This commit is contained in:
parent
d944367301
commit
41192e4ef5
15 changed files with 896 additions and 32 deletions
|
|
@ -58,6 +58,7 @@ var _session: NetworkSession
|
|||
var _base_url: String = ""
|
||||
var _room_name: String = DEFAULT_ROOM_NAME
|
||||
var _room_name_uses_default: bool = true
|
||||
var _host_settings_persistence_enabled: bool = true
|
||||
var _discoverable: bool = false
|
||||
var _host_status_message: String = ""
|
||||
var _host_status_is_error: bool = false
|
||||
|
|
@ -218,6 +219,11 @@ func get_public_join_state() -> PublicJoinState:
|
|||
return _public_join_state
|
||||
|
||||
|
||||
func configure_dedicated_runtime(room_name: String) -> bool:
|
||||
_host_settings_persistence_enabled = false
|
||||
return set_room_name(room_name)
|
||||
|
||||
|
||||
func set_room_name(value: String) -> bool:
|
||||
var cleaned: String = _sanitize_room_name(value)
|
||||
if cleaned.is_empty():
|
||||
|
|
@ -1047,6 +1053,8 @@ func _load_settings() -> void:
|
|||
|
||||
|
||||
func _save_settings() -> void:
|
||||
if not _host_settings_persistence_enabled:
|
||||
return
|
||||
var config := ConfigFile.new()
|
||||
config.set_value("host", "room_name", _room_name)
|
||||
config.set_value(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ var _surface_drawing: NetworkSurfaceDrawingService
|
|||
var _revision := 0
|
||||
var _host_block_pairs: Dictionary[String, bool] = {}
|
||||
var _peer_fingerprints: Dictionary[int, String] = {}
|
||||
var _remote_bans: Array[Dictionary] = []
|
||||
var _ban_snapshot_requested: bool = false
|
||||
var _ban_snapshot_loaded: bool = false
|
||||
|
||||
|
||||
func setup(
|
||||
|
|
@ -40,6 +43,7 @@ func setup(
|
|||
func(_count: int, _maximum: int) -> void: _sync_authenticated_peers()
|
||||
)
|
||||
_session.state_changed.connect(_on_session_state_changed)
|
||||
_session.operator_status_changed.connect(_on_operator_status_changed)
|
||||
_relationships.relationship_changed.connect(_on_relationship_changed)
|
||||
_bans.bans_changed.connect(_changed)
|
||||
_chat.set_relationship_store(_relationships)
|
||||
|
|
@ -64,13 +68,24 @@ func get_entries() -> Array[PlayerListEntry]:
|
|||
entry.compact_fingerprint = NetworkIdentityCrypto.compact_suffix(record.identity_fingerprint)
|
||||
entry.display_name = record.display_name
|
||||
entry.is_host = peer_id == 1
|
||||
entry.is_operator = _session.is_peer_operator(peer_id)
|
||||
entry.is_local_player = peer_id == local_id
|
||||
entry.continuity_state = _known.identity_status(record.identity_fingerprint, record.display_name)
|
||||
entry.ping_to_host_ms = _session.get_peer_rtt_ms(peer_id)
|
||||
entry.muted = _relationships.is_muted(record.identity_fingerprint)
|
||||
entry.blocked = blocked
|
||||
entry.can_kick = _session.is_host() and peer_id != local_id and peer_id != 1
|
||||
entry.can_kick = (
|
||||
_session.can_local_moderate()
|
||||
and peer_id != local_id
|
||||
and peer_id != 1
|
||||
and (_session.is_host() or not entry.is_operator)
|
||||
)
|
||||
entry.can_ban = entry.can_kick
|
||||
entry.can_manage_operator = (
|
||||
_session.can_manage_operators()
|
||||
and peer_id != local_id
|
||||
and peer_id != 1
|
||||
)
|
||||
entry.revision = _revision
|
||||
result.append(entry)
|
||||
result.sort_custom(_entry_before)
|
||||
|
|
@ -89,6 +104,14 @@ func is_local_host() -> bool:
|
|||
return _session.is_host()
|
||||
|
||||
|
||||
func is_local_moderator() -> bool:
|
||||
return _session.can_local_moderate()
|
||||
|
||||
|
||||
func can_manage_operators() -> bool:
|
||||
return _session.can_manage_operators()
|
||||
|
||||
|
||||
func is_open_host() -> bool:
|
||||
return _session.is_open_host()
|
||||
|
||||
|
|
@ -102,7 +125,12 @@ func get_relationships() -> Array[Dictionary]:
|
|||
|
||||
|
||||
func get_bans() -> Array[Dictionary]:
|
||||
return _bans.get_bans(_session.get_host_identity_fingerprint()) if _session.is_host() else []
|
||||
if _session.is_host():
|
||||
return _bans.get_bans(_session.get_host_identity_fingerprint())
|
||||
if not _session.is_local_operator():
|
||||
return []
|
||||
_request_ban_snapshot()
|
||||
return _remote_bans.duplicate(true)
|
||||
|
||||
|
||||
func set_surface_drawing_service(
|
||||
|
|
@ -131,14 +159,12 @@ func get_session_artwork_counts() -> Vector2i:
|
|||
|
||||
|
||||
func reset_session_artwork() -> bool:
|
||||
if not _session.is_host() or _surface_drawing == null:
|
||||
if _session.is_host():
|
||||
return _reset_session_artwork_on_host()
|
||||
if not _session.is_local_operator():
|
||||
return false
|
||||
var ok: bool = _surface_drawing.clear_session_artwork()
|
||||
moderation_finished.emit(
|
||||
ok,
|
||||
"Session artwork cleared." if ok else "Session artwork could not be cleared.",
|
||||
)
|
||||
return ok
|
||||
request_reset_session_artwork.rpc_id(1)
|
||||
return true
|
||||
|
||||
|
||||
func set_muted(fingerprint: String, display_name: String, value: bool) -> bool:
|
||||
|
|
@ -154,12 +180,13 @@ func set_blocked(fingerprint: String, display_name: String, value: bool) -> bool
|
|||
|
||||
|
||||
func kick(peer_id: int, fingerprint: String, revision: int) -> bool:
|
||||
if not _valid_moderation_target(peer_id, fingerprint, revision):
|
||||
moderation_finished.emit(false, "That player is no longer connected.")
|
||||
if _session.is_host():
|
||||
return _kick_on_host(peer_id, fingerprint, revision, true)
|
||||
if not _session.is_local_operator():
|
||||
moderation_finished.emit(false, "Only the host or an operator can remove players.")
|
||||
return false
|
||||
var ok := _session.kick_authenticated_peer(peer_id, fingerprint)
|
||||
moderation_finished.emit(ok, "Player removed." if ok else "Player could not be removed.")
|
||||
return ok
|
||||
request_kick.rpc_id(1, peer_id, fingerprint)
|
||||
return true
|
||||
|
||||
|
||||
func ban(
|
||||
|
|
@ -168,22 +195,158 @@ func ban(
|
|||
display_name: String,
|
||||
revision: int,
|
||||
) -> bool:
|
||||
if not _valid_moderation_target(peer_id, fingerprint, revision):
|
||||
moderation_finished.emit(false, "That player is no longer connected.")
|
||||
if _session.is_host():
|
||||
return _ban_on_host(
|
||||
peer_id, fingerprint, display_name, revision, true
|
||||
)
|
||||
if not _session.is_local_operator():
|
||||
moderation_finished.emit(false, "Only the host or an operator can ban players.")
|
||||
return false
|
||||
var host_fingerprint := _session.get_host_identity_fingerprint()
|
||||
if not _bans.ban(host_fingerprint, fingerprint, display_name):
|
||||
moderation_finished.emit(false, "Ban could not be saved.")
|
||||
return false
|
||||
var ok := _session.kick_authenticated_peer(peer_id, fingerprint, true)
|
||||
moderation_finished.emit(ok, "Player banned." if ok else "Ban saved.")
|
||||
request_ban.rpc_id(1, peer_id, fingerprint)
|
||||
return true
|
||||
|
||||
|
||||
func unban(fingerprint: String) -> bool:
|
||||
if not _session.is_host():
|
||||
if _session.is_host():
|
||||
var ok: bool = _bans.unban(
|
||||
_session.get_host_identity_fingerprint(), fingerprint
|
||||
)
|
||||
moderation_finished.emit(
|
||||
ok, "Player unbanned." if ok else "Ban could not be removed."
|
||||
)
|
||||
return ok
|
||||
if not _session.is_local_operator():
|
||||
moderation_finished.emit(false, "Only the host or an operator can remove bans.")
|
||||
return false
|
||||
return _bans.unban(_session.get_host_identity_fingerprint(), fingerprint)
|
||||
request_unban.rpc_id(1, fingerprint)
|
||||
return true
|
||||
|
||||
|
||||
func set_operator(
|
||||
peer_id: int,
|
||||
fingerprint: String,
|
||||
enabled: bool,
|
||||
revision: int,
|
||||
) -> bool:
|
||||
if (
|
||||
not _session.can_manage_operators()
|
||||
or not _valid_moderation_target(
|
||||
peer_id, fingerprint, revision, true, true
|
||||
)
|
||||
):
|
||||
moderation_finished.emit(false, "That player is no longer connected.")
|
||||
return false
|
||||
var ok: bool = _session.set_peer_operator(peer_id, fingerprint, enabled)
|
||||
moderation_finished.emit(
|
||||
ok,
|
||||
("Player is now an operator." if enabled else "Operator access removed.")
|
||||
if ok
|
||||
else "Operator access could not be changed.",
|
||||
)
|
||||
return ok
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
||||
func request_kick(peer_id: int, fingerprint: String) -> void:
|
||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
||||
if not _valid_operator_sender(sender_id):
|
||||
return
|
||||
var ok: bool = _kick_on_host(peer_id, fingerprint, -1, false)
|
||||
_send_moderation_result(
|
||||
sender_id, ok, "Player removed." if ok else "Player could not be removed."
|
||||
)
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
||||
func request_ban(peer_id: int, fingerprint: String) -> void:
|
||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
||||
if not _valid_operator_sender(sender_id):
|
||||
return
|
||||
var record: PeerRegistry.PeerRecord = _session.get_peer_record(peer_id)
|
||||
var display_name: String = record.display_name if record != null else "Player"
|
||||
var ok: bool = _ban_on_host(
|
||||
peer_id, fingerprint, display_name, -1, false
|
||||
)
|
||||
_send_moderation_result(
|
||||
sender_id, ok, "Player banned." if ok else "Player could not be banned."
|
||||
)
|
||||
_send_ban_snapshot(sender_id)
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
||||
func request_unban(fingerprint: String) -> void:
|
||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
||||
if (
|
||||
not _valid_operator_sender(sender_id)
|
||||
or not NetworkIdentityCrypto.valid_fingerprint(fingerprint)
|
||||
):
|
||||
return
|
||||
var ok: bool = _bans.unban(
|
||||
_session.get_host_identity_fingerprint(), fingerprint
|
||||
)
|
||||
_send_moderation_result(
|
||||
sender_id, ok, "Player unbanned." if ok else "Ban could not be removed."
|
||||
)
|
||||
_send_ban_snapshot(sender_id)
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
||||
func request_reset_session_artwork() -> void:
|
||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
||||
if not _valid_operator_sender(sender_id):
|
||||
return
|
||||
var ok: bool = _reset_session_artwork_on_host(false)
|
||||
_send_moderation_result(
|
||||
sender_id,
|
||||
ok,
|
||||
"Session artwork cleared."
|
||||
if ok
|
||||
else "Session artwork could not be cleared.",
|
||||
)
|
||||
|
||||
|
||||
@rpc("any_peer", "call_remote", "reliable", 0)
|
||||
func request_ban_snapshot() -> void:
|
||||
var sender_id: int = multiplayer.get_remote_sender_id()
|
||||
if _valid_operator_sender(sender_id):
|
||||
_send_ban_snapshot(sender_id)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", 0)
|
||||
func receive_moderation_result(success: bool, message: String) -> void:
|
||||
if not _session.is_joined_client():
|
||||
return
|
||||
moderation_finished.emit(success, message.left(120))
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", 0)
|
||||
func receive_ban_snapshot(records: Array) -> void:
|
||||
if not _session.is_local_operator() or records.size() > HostBanStore.MAX_BANS:
|
||||
return
|
||||
var sanitized: Array[Dictionary] = []
|
||||
for value: Variant in records:
|
||||
if typeof(value) != TYPE_DICTIONARY:
|
||||
return
|
||||
var record: Dictionary = value
|
||||
var fingerprint: String = str(record.get("target_fingerprint", ""))
|
||||
var display_name: String = str(
|
||||
record.get("last_known_display_name", "Player")
|
||||
).strip_edges().left(NetworkProtocol.MAX_DISPLAY_NAME_LENGTH)
|
||||
if (
|
||||
not NetworkIdentityCrypto.valid_fingerprint(fingerprint)
|
||||
or display_name.is_empty()
|
||||
or typeof(record.get("banned_unix")) != TYPE_INT
|
||||
):
|
||||
return
|
||||
sanitized.append({
|
||||
"target_fingerprint": fingerprint,
|
||||
"last_known_display_name": display_name,
|
||||
"banned_unix": int(record["banned_unix"]),
|
||||
})
|
||||
_remote_bans = sanitized
|
||||
_ban_snapshot_requested = false
|
||||
_ban_snapshot_loaded = true
|
||||
_changed()
|
||||
|
||||
|
||||
func is_locally_blocked(fingerprint: String) -> bool:
|
||||
|
|
@ -271,6 +434,22 @@ func _on_session_state_changed(state: NetworkSession.State) -> void:
|
|||
]:
|
||||
_host_block_pairs.clear()
|
||||
_peer_fingerprints.clear()
|
||||
_remote_bans.clear()
|
||||
_ban_snapshot_requested = false
|
||||
_ban_snapshot_loaded = false
|
||||
elif state == NetworkSession.State.JOINED_CLIENT:
|
||||
_request_ban_snapshot()
|
||||
_changed()
|
||||
|
||||
|
||||
func _on_operator_status_changed(peer_id: int, enabled: bool) -> void:
|
||||
if peer_id == _session.get_local_peer_id():
|
||||
if enabled:
|
||||
_request_ban_snapshot()
|
||||
else:
|
||||
_remote_bans.clear()
|
||||
_ban_snapshot_requested = false
|
||||
_ban_snapshot_loaded = false
|
||||
_changed()
|
||||
|
||||
|
||||
|
|
@ -295,16 +474,137 @@ func _peer_for_fingerprint(fingerprint: String) -> int:
|
|||
return 0
|
||||
|
||||
|
||||
func _valid_moderation_target(peer_id: int, fingerprint: String, revision: int) -> bool:
|
||||
if not _session.is_host() or revision != _revision or peer_id == 1:
|
||||
func _valid_moderation_target(
|
||||
peer_id: int,
|
||||
fingerprint: String,
|
||||
revision: int,
|
||||
require_revision: bool,
|
||||
can_target_operator: bool,
|
||||
) -> bool:
|
||||
if (
|
||||
not _session.is_host()
|
||||
or (require_revision and revision != _revision)
|
||||
or peer_id == 1
|
||||
or (not can_target_operator and _session.is_peer_operator(peer_id))
|
||||
):
|
||||
return false
|
||||
var record := _session.get_peer_record(peer_id)
|
||||
return record != null and record.identity_fingerprint == fingerprint
|
||||
|
||||
|
||||
func _valid_operator_sender(peer_id: int) -> bool:
|
||||
return (
|
||||
_session.is_host()
|
||||
and peer_id > 1
|
||||
and _session.is_authenticated_peer(peer_id)
|
||||
and _session.is_peer_operator(peer_id)
|
||||
)
|
||||
|
||||
|
||||
func _kick_on_host(
|
||||
peer_id: int,
|
||||
fingerprint: String,
|
||||
revision: int,
|
||||
local_host_request: bool,
|
||||
) -> bool:
|
||||
if not _valid_moderation_target(
|
||||
peer_id,
|
||||
fingerprint,
|
||||
revision,
|
||||
local_host_request,
|
||||
local_host_request,
|
||||
):
|
||||
if local_host_request:
|
||||
moderation_finished.emit(false, "That player is no longer connected.")
|
||||
return false
|
||||
var ok: bool = _session.kick_authenticated_peer(peer_id, fingerprint)
|
||||
if local_host_request:
|
||||
moderation_finished.emit(
|
||||
ok, "Player removed." if ok else "Player could not be removed."
|
||||
)
|
||||
return ok
|
||||
|
||||
|
||||
func _ban_on_host(
|
||||
peer_id: int,
|
||||
fingerprint: String,
|
||||
display_name: String,
|
||||
revision: int,
|
||||
local_host_request: bool,
|
||||
) -> bool:
|
||||
if not _valid_moderation_target(
|
||||
peer_id,
|
||||
fingerprint,
|
||||
revision,
|
||||
local_host_request,
|
||||
local_host_request,
|
||||
):
|
||||
if local_host_request:
|
||||
moderation_finished.emit(false, "That player is no longer connected.")
|
||||
return false
|
||||
var record: PeerRegistry.PeerRecord = _session.get_peer_record(peer_id)
|
||||
var trusted_display_name: String = (
|
||||
record.display_name if record != null else display_name
|
||||
)
|
||||
var host_fingerprint: String = _session.get_host_identity_fingerprint()
|
||||
if not _bans.ban(host_fingerprint, fingerprint, trusted_display_name):
|
||||
if local_host_request:
|
||||
moderation_finished.emit(false, "Ban could not be saved.")
|
||||
return false
|
||||
var ok: bool = _session.kick_authenticated_peer(peer_id, fingerprint, true)
|
||||
if local_host_request:
|
||||
moderation_finished.emit(ok, "Player banned." if ok else "Ban saved.")
|
||||
return true
|
||||
|
||||
|
||||
func _reset_session_artwork_on_host(
|
||||
emit_local_result: bool = true,
|
||||
) -> bool:
|
||||
if not _session.is_host() or _surface_drawing == null:
|
||||
return false
|
||||
var ok: bool = _surface_drawing.clear_session_artwork()
|
||||
if emit_local_result:
|
||||
moderation_finished.emit(
|
||||
ok,
|
||||
"Session artwork cleared."
|
||||
if ok
|
||||
else "Session artwork could not be cleared.",
|
||||
)
|
||||
return ok
|
||||
|
||||
|
||||
func _send_moderation_result(
|
||||
peer_id: int,
|
||||
success: bool,
|
||||
message: String,
|
||||
) -> void:
|
||||
receive_moderation_result.rpc_id(peer_id, success, message.left(120))
|
||||
|
||||
|
||||
func _send_ban_snapshot(peer_id: int) -> void:
|
||||
if not _valid_operator_sender(peer_id):
|
||||
return
|
||||
receive_ban_snapshot.rpc_id(
|
||||
peer_id,
|
||||
_bans.get_bans(_session.get_host_identity_fingerprint()),
|
||||
)
|
||||
|
||||
|
||||
func _request_ban_snapshot() -> void:
|
||||
if (
|
||||
_session.is_local_operator()
|
||||
and not _ban_snapshot_requested
|
||||
and not _ban_snapshot_loaded
|
||||
):
|
||||
_ban_snapshot_requested = true
|
||||
request_ban_snapshot.rpc_id(1)
|
||||
|
||||
|
||||
func _entry_before(a: PlayerListEntry, b: PlayerListEntry) -> bool:
|
||||
if a.is_host != b.is_host:
|
||||
return a.is_host
|
||||
if a.is_operator != b.is_operator:
|
||||
return a.is_operator
|
||||
if a.is_local_player != b.is_local_player:
|
||||
return a.is_local_player
|
||||
var compared := a.display_name.naturalnocasecmp_to(b.display_name)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ signal server_trust_required(
|
|||
is_changed: bool,
|
||||
)
|
||||
signal peer_identity_observed(peer_id: int, status: String)
|
||||
signal operator_status_changed(peer_id: int, is_operator: bool)
|
||||
signal server_lost
|
||||
signal remote_recovery_requested(peer_id: int, entry_position: Vector3)
|
||||
signal remote_recovery_presentation_changed(
|
||||
|
|
@ -102,6 +103,10 @@ var _moderation_disconnect_message := ""
|
|||
var _host_port: int = 0
|
||||
var _session_display_name: String = "NETfishing Room"
|
||||
var _dedicated_host: bool = false
|
||||
var _configured_operator_fingerprints: Dictionary[String, bool] = {}
|
||||
var _session_operator_fingerprints: Dictionary[String, bool] = {}
|
||||
var _operator_peer_ids: Dictionary[int, bool] = {}
|
||||
var _local_operator: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -137,6 +142,20 @@ func setup(
|
|||
_profile_ready = _profile_ready and _player_identity.load_or_create()
|
||||
|
||||
|
||||
func configure_dedicated_operators(
|
||||
fingerprints: PackedStringArray,
|
||||
) -> bool:
|
||||
if state != State.INACTIVE:
|
||||
return false
|
||||
var configured: Dictionary[String, bool] = {}
|
||||
for fingerprint: String in fingerprints:
|
||||
if not NetworkIdentityCrypto.valid_fingerprint(fingerprint):
|
||||
return false
|
||||
configured[fingerprint] = true
|
||||
_configured_operator_fingerprints = configured
|
||||
return true
|
||||
|
||||
|
||||
func start_private_host(
|
||||
port: int = DEFAULT_PORT,
|
||||
port_attempts: int = 1,
|
||||
|
|
@ -215,6 +234,9 @@ func _start_host(
|
|||
multiplayer.multiplayer_peer = peer
|
||||
_session_id = Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
_registry.clear()
|
||||
_session_operator_fingerprints.clear()
|
||||
_operator_peer_ids.clear()
|
||||
_local_operator = false
|
||||
_spawn_service.clear_remote_players()
|
||||
if not dedicated:
|
||||
_register_player_host()
|
||||
|
|
@ -301,6 +323,44 @@ func is_dedicated_host() -> bool:
|
|||
return is_host() and _dedicated_host
|
||||
|
||||
|
||||
func is_local_operator() -> bool:
|
||||
return state == State.JOINED_CLIENT and _local_operator
|
||||
|
||||
|
||||
func can_local_moderate() -> bool:
|
||||
return is_host() or is_local_operator()
|
||||
|
||||
|
||||
func can_manage_operators() -> bool:
|
||||
return is_host() and not _dedicated_host
|
||||
|
||||
|
||||
func is_peer_operator(peer_id: int) -> bool:
|
||||
return bool(_operator_peer_ids.get(peer_id, false))
|
||||
|
||||
|
||||
func set_peer_operator(
|
||||
peer_id: int,
|
||||
fingerprint: String,
|
||||
enabled: bool,
|
||||
) -> bool:
|
||||
if not can_manage_operators() or peer_id <= 1:
|
||||
return false
|
||||
var record: PeerRegistry.PeerRecord = _registry.get_peer(peer_id)
|
||||
if (
|
||||
record == null
|
||||
or not record.identity_authenticated
|
||||
or record.identity_fingerprint != fingerprint
|
||||
):
|
||||
return false
|
||||
if enabled:
|
||||
_session_operator_fingerprints[fingerprint] = true
|
||||
else:
|
||||
_session_operator_fingerprints.erase(fingerprint)
|
||||
_set_operator_status(peer_id, enabled)
|
||||
return true
|
||||
|
||||
|
||||
func set_session_display_name(value: String) -> void:
|
||||
var cleaned: String = value.strip_edges().left(48)
|
||||
if cleaned.is_empty():
|
||||
|
|
@ -822,6 +882,7 @@ func _on_peer_disconnected(peer_id: int) -> void:
|
|||
_pending_authentication.erase(peer_id)
|
||||
_pending_identity_challenges.erase(peer_id)
|
||||
_authenticated_identity_cache.erase(peer_id)
|
||||
_operator_peer_ids.erase(peer_id)
|
||||
var recovery_attempt: String = _recovery_attempts.get(peer_id, "")
|
||||
if not recovery_attempt.is_empty():
|
||||
_recovery_attempts.erase(peer_id)
|
||||
|
|
@ -1136,6 +1197,11 @@ func submit_client_hello(data: Dictionary) -> void:
|
|||
NetworkProtocol.RejectionCode.MALFORMED_HANDSHAKE
|
||||
)
|
||||
return
|
||||
var operator_enabled: bool = _operator_for_fingerprint(
|
||||
str(identity["fingerprint"])
|
||||
)
|
||||
if operator_enabled:
|
||||
_operator_peer_ids[sender_id] = true
|
||||
var submitted_appearance := CharacterCustomizationCatalog.sanitized_snapshot(
|
||||
data["cosmetic_snapshot"]
|
||||
)
|
||||
|
|
@ -1180,9 +1246,12 @@ func submit_client_hello(data: Dictionary) -> void:
|
|||
)
|
||||
)
|
||||
receive_spawn_list.rpc_id(sender_id, _build_spawn_list())
|
||||
receive_operator_snapshot.rpc_id(sender_id, _operator_peer_id_snapshot())
|
||||
receive_peer_spawn.rpc(
|
||||
_make_spawn_entry(sender_id, display_name, spawn_transform)
|
||||
)
|
||||
if operator_enabled:
|
||||
receive_operator_status.rpc(sender_id, true)
|
||||
peer_authenticated.emit(sender_id, display_name)
|
||||
_emit_peer_count()
|
||||
|
||||
|
|
@ -1334,12 +1403,41 @@ func receive_peer_spawn(entry: Dictionary) -> void:
|
|||
func receive_peer_despawn(peer_id: int) -> void:
|
||||
if state != State.JOINED_CLIENT:
|
||||
return
|
||||
_operator_peer_ids.erase(peer_id)
|
||||
_registry.remove_peer(peer_id)
|
||||
_spawn_service.remove_peer(peer_id)
|
||||
peer_removed.emit(peer_id)
|
||||
_emit_peer_count()
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", 0)
|
||||
func receive_operator_snapshot(peer_ids: PackedInt32Array) -> void:
|
||||
if (
|
||||
state != State.JOINED_CLIENT
|
||||
or peer_ids.size() > get_session_max_players()
|
||||
):
|
||||
return
|
||||
_operator_peer_ids.clear()
|
||||
for peer_id: int in peer_ids:
|
||||
if peer_id > 1:
|
||||
_operator_peer_ids[peer_id] = true
|
||||
_update_local_operator()
|
||||
for peer_id: int in _operator_peer_ids:
|
||||
operator_status_changed.emit(peer_id, true)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", 0)
|
||||
func receive_operator_status(peer_id: int, enabled: bool) -> void:
|
||||
if state != State.JOINED_CLIENT or peer_id <= 1:
|
||||
return
|
||||
if enabled:
|
||||
_operator_peer_ids[peer_id] = true
|
||||
else:
|
||||
_operator_peer_ids.erase(peer_id)
|
||||
_update_local_operator()
|
||||
operator_status_changed.emit(peer_id, enabled)
|
||||
|
||||
|
||||
func _apply_spawn_entry(entry: Dictionary) -> void:
|
||||
if (
|
||||
typeof(entry.get("peer_id")) != TYPE_INT
|
||||
|
|
@ -1470,6 +1568,36 @@ func _sanitized_capabilities(value: Variant) -> PackedStringArray:
|
|||
return result
|
||||
|
||||
|
||||
func _operator_for_fingerprint(fingerprint: String) -> bool:
|
||||
return bool((
|
||||
_configured_operator_fingerprints
|
||||
if _dedicated_host
|
||||
else _session_operator_fingerprints
|
||||
).get(fingerprint, false))
|
||||
|
||||
|
||||
func _operator_peer_id_snapshot() -> PackedInt32Array:
|
||||
var result: PackedInt32Array = PackedInt32Array()
|
||||
for peer_id: int in _operator_peer_ids:
|
||||
result.append(peer_id)
|
||||
result.sort()
|
||||
return result
|
||||
|
||||
|
||||
func _set_operator_status(peer_id: int, enabled: bool) -> void:
|
||||
if enabled:
|
||||
_operator_peer_ids[peer_id] = true
|
||||
else:
|
||||
_operator_peer_ids.erase(peer_id)
|
||||
operator_status_changed.emit(peer_id, enabled)
|
||||
receive_operator_status.rpc(peer_id, enabled)
|
||||
|
||||
|
||||
func _update_local_operator() -> void:
|
||||
var local_peer_id: int = multiplayer.get_unique_id()
|
||||
_local_operator = bool(_operator_peer_ids.get(local_peer_id, false))
|
||||
|
||||
|
||||
func _verify_spawn_identity(entry: Dictionary) -> bool:
|
||||
var fingerprint := str(entry.get("identity_fingerprint", ""))
|
||||
var public_pem := NetworkIdentityCrypto.normalize_public_pem(
|
||||
|
|
@ -1929,5 +2057,8 @@ func _teardown_peer() -> void:
|
|||
_server_identity_fingerprint = ""
|
||||
_server_identity_public_key = ""
|
||||
_session_identity_keys.clear()
|
||||
_session_operator_fingerprints.clear()
|
||||
_operator_peer_ids.clear()
|
||||
_local_operator = false
|
||||
_host_port = 0
|
||||
_dedicated_host = false
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ var full_fingerprint := ""
|
|||
var compact_fingerprint := ""
|
||||
var display_name := ""
|
||||
var is_host := false
|
||||
var is_operator := false
|
||||
var is_local_player := false
|
||||
var continuity_state := ""
|
||||
var ping_to_host_ms := -1
|
||||
|
|
@ -13,4 +14,5 @@ var muted := false
|
|||
var blocked := false
|
||||
var can_kick := false
|
||||
var can_ban := false
|
||||
var can_manage_operator := false
|
||||
var revision := 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue