straywild/network/network_home_service.gd

956 lines
28 KiB
GDScript

class_name NetworkHomeService
extends Node
const NetworkHomeProtocolType = preload(
"res://network/network_home_protocol.gd"
)
const PlayerHomeStateType = preload("res://homes/player_home_state.gd")
signal local_transition_finished(accepted: bool, message: String)
signal local_space_changed(space_id: StringName, owner_fingerprint: String)
signal local_manifest_rejected(message: String)
const MAX_MANIFESTS: int = 128
var _session: NetworkSession
var _spawn_service: PlayerSpawnService
var _local_home: PlayerHomeStateType
var _save_manager: PlayerSaveManager
var _world_service: HomeWorldService
var _bag: PlayerBag
var _inventory_layout: PlayerInventoryLayout
var _hotbar: PlayerHotbar
var _manifests_by_fingerprint: Dictionary[String, Dictionary] = {}
var _fingerprint_by_peer: Dictionary[int, String] = {}
var _authorized_owned_counts: Dictionary[int, Dictionary] = {}
var _authorization_result_owner: Dictionary[String, int] = {}
var _next_interior_index: int = 0
var _suppress_local_submission: bool = false
var _publish_queued: bool = false
func setup(
session: NetworkSession,
spawn_service: PlayerSpawnService,
local_home: PlayerHomeStateType,
save_manager: PlayerSaveManager,
world_service: HomeWorldService,
bag: PlayerBag,
inventory_layout: PlayerInventoryLayout,
hotbar: PlayerHotbar,
) -> void:
_session = session
_spawn_service = spawn_service
_local_home = local_home
_save_manager = save_manager
_world_service = world_service
_bag = bag
_inventory_layout = inventory_layout
_hotbar = hotbar
if not _session.state_changed.is_connected(_on_session_state_changed):
_session.state_changed.connect(_on_session_state_changed)
if not _session.join_authenticated.is_connected(_on_join_authenticated):
_session.join_authenticated.connect(_on_join_authenticated)
if not _session.peer_authenticated.is_connected(_on_peer_authenticated):
_session.peer_authenticated.connect(_on_peer_authenticated)
if not _session.peer_removed.is_connected(_on_peer_removed):
_session.peer_removed.connect(_on_peer_removed)
if not _local_home.changed.is_connected(_on_local_home_changed):
_local_home.changed.connect(_on_local_home_changed)
func get_local_space() -> StringName:
if _session == null or not _session.is_gameplay_session_active():
return NetworkHomeProtocolType.WORLD_SPACE
return _session.get_peer_space(_session.get_local_peer_id())
func get_local_home_owner_fingerprint() -> String:
var space_id: String = String(get_local_space())
return space_id.trim_prefix("rv:") if space_id.begins_with("rv:") else ""
func can_local_decorate() -> bool:
return (
get_local_home_owner_fingerprint()
== _session.get_local_identity_fingerprint()
)
func get_authoritative_owned_count(
peer_id: int,
decor_id: StringName,
) -> int:
var authorized: Dictionary = _authorized_owned_counts.get(peer_id, {})
if authorized.has(String(decor_id)):
return int(authorized[String(decor_id)])
var fingerprint: String = _fingerprint_for_peer(peer_id)
var manifest: Dictionary = _manifests_by_fingerprint.get(fingerprint, {})
if manifest.is_empty():
return -1
var home: Dictionary = manifest.get("home", {})
var owned: Dictionary = home.get("owned_decor", {})
return int(owned.get(String(decor_id), 0))
func place_local_decor(
decor_id: StringName,
position: Vector3,
yaw_radians: float,
mode: PlayerHomeStateType.PlacementMode,
) -> String:
if (
not can_local_decorate()
or _bag == null
or _bag.get_quantity(decor_id) <= 0
or _world_service == null
):
return ""
var owner_fingerprint: String = (
_session.get_local_identity_fingerprint()
)
var normalized: Dictionary = (
_local_home.get_nearest_valid_placement_transform(
decor_id,
position,
yaw_radians,
mode,
"",
Callable(
self, "_candidate_clear_of_players"
).bind(owner_fingerprint, decor_id),
)
)
if normalized.is_empty():
return ""
var instance_id: String = _local_home.place_decor(
decor_id,
normalized["position"],
float(normalized["yaw"]),
mode,
)
if instance_id.is_empty():
return ""
if not _bag.remove_item(decor_id, 1):
_local_home.remove_placement(instance_id)
return ""
_save_manager.save_if_dirty()
return instance_id
func remove_local_decor(instance_id: String) -> bool:
if (
not can_local_decorate()
or _bag == null
or _inventory_layout == null
or _hotbar == null
):
return false
var placement: Dictionary = _local_home.get_placement(instance_id)
if placement.is_empty():
return false
var decor_id := StringName(str(placement["decor_id"]))
var target_hotbar_slot: int = _hotbar.find_item_slot(decor_id)
if target_hotbar_slot < 0:
target_hotbar_slot = _hotbar.find_first_empty_slot()
var prepared_hotbar_placement: bool = false
if (
target_hotbar_slot >= 0
and _inventory_layout.get_container(
PlayerInventoryLayout.EntryKind.ITEM,
decor_id,
) < 0
):
prepared_hotbar_placement = (
_inventory_layout.prepare_new_item_placement(
decor_id,
PlayerInventoryLayout.InventoryContainer.HOTBAR,
target_hotbar_slot,
)
)
var returned_to_inventory: bool = _bag.add_item(decor_id, 1)
if not returned_to_inventory:
if prepared_hotbar_placement:
_inventory_layout.cancel_prepared_item_placement(decor_id)
returned_to_inventory = _bag.add_item_to_storage(decor_id, 1)
if not returned_to_inventory:
return false
if target_hotbar_slot >= 0:
_hotbar.assign_item(target_hotbar_slot, decor_id)
_hotbar.select_slot(target_hotbar_slot)
if not _local_home.remove_placement(instance_id):
_bag.remove_item(decor_id, 1)
return false
_save_manager.save_if_dirty()
return true
func update_local_decor(
instance_id: String,
position: Vector3,
yaw_radians: float,
mode: PlayerHomeStateType.PlacementMode,
) -> bool:
var normalized: Dictionary = get_valid_local_decor_transform(
instance_id, position, yaw_radians, mode
)
if normalized.is_empty():
return false
if not _local_home.update_placement(
instance_id,
normalized["position"],
float(normalized["yaw"]),
mode,
):
return false
_save_manager.save_if_dirty()
return true
func get_valid_local_decor_transform(
instance_id: String,
position: Vector3,
yaw_radians: float,
mode: PlayerHomeStateType.PlacementMode,
) -> Dictionary:
if not can_local_decorate() or _world_service == null:
return {}
var placement: Dictionary = _local_home.get_placement(instance_id)
if placement.is_empty():
return {}
var decor_id := StringName(str(placement.get("decor_id", "")))
return _local_home.get_valid_update_transform(
instance_id,
position,
yaw_radians,
mode,
Callable(
self, "_candidate_clear_of_players"
).bind(
_session.get_local_identity_fingerprint(), decor_id
),
)
func _candidate_clear_of_players(
candidate: Dictionary,
owner_fingerprint: String,
decor_id: StringName,
) -> bool:
return (
_world_service != null
and _world_service.is_decor_placement_clear_of_players(
owner_fingerprint,
decor_id,
candidate["position"],
float(candidate["yaw"]),
)
)
func request_enter(owner_fingerprint: String) -> bool:
if (
_session == null
or not _session.is_gameplay_session_active()
or not _manifests_by_fingerprint.has(owner_fingerprint)
or get_local_space() != NetworkHomeProtocolType.WORLD_SPACE
):
return false
return _submit_transition(
NetworkHomeProtocolType.TransitionAction.ENTER,
owner_fingerprint,
)
func request_exit() -> bool:
var owner_fingerprint: String = get_local_home_owner_fingerprint()
if owner_fingerprint.is_empty():
return false
return _submit_transition(
NetworkHomeProtocolType.TransitionAction.EXIT,
owner_fingerprint,
)
func authorize_decor_purchase(
peer_id: int,
product_id: StringName,
resulting_count: int,
result_id: String,
) -> void:
if not _session.is_host() or peer_id <= 0 or result_id.is_empty():
return
var counts: Dictionary = _authorized_owned_counts.get(peer_id, {})
counts[String(product_id)] = resulting_count
_authorized_owned_counts[peer_id] = counts
_authorization_result_owner[result_id] = peer_id
func finalize_decor_purchase(
peer_id: int,
result_id: String,
applied: bool,
) -> void:
if _authorization_result_owner.get(result_id, 0) != peer_id:
return
_authorization_result_owner.erase(result_id)
if applied:
return
_authorized_owned_counts.erase(peer_id)
func refresh_world_presentations() -> void:
if _world_service == null:
return
_world_service.clear_presentations()
if not _world_service.is_world_ready():
return
if not _session.is_host():
for manifest: Dictionary in _manifests_by_fingerprint.values():
_world_service.apply_manifest(manifest)
return
var local_fingerprint: String = _session.get_local_identity_fingerprint()
if (
not _session.is_dedicated_host()
and not _manifests_by_fingerprint.has(local_fingerprint)
):
publish_local_home()
var fingerprints: Array[String] = []
fingerprints.assign(_manifests_by_fingerprint.keys())
fingerprints.sort()
var reserved: Array[Transform3D] = []
for fingerprint: String in fingerprints:
var manifest: Dictionary = _manifests_by_fingerprint[fingerprint]
var transform: Transform3D = _world_service.choose_exterior_transform(
int(manifest["owner_peer_id"]), reserved
)
reserved.append(transform)
manifest["exterior_transform"] = (
NetworkHomeProtocolType.transform_to_array(transform)
)
_manifests_by_fingerprint[fingerprint] = manifest
_broadcast_manifest(manifest)
for fingerprint: String in fingerprints:
var manifest: Dictionary = _manifests_by_fingerprint[fingerprint]
_spawn_peer_at_exterior(
int(manifest["owner_peer_id"]), fingerprint
)
func publish_local_home() -> bool:
if (
_session == null
or not _session.is_gameplay_session_active()
or _session.is_dedicated_host()
or _local_home == null
):
return false
if _session.is_host() and (
_world_service == null or not _world_service.is_world_ready()
):
return false
var home_data: Dictionary = _local_home.to_save_data()
var request: Dictionary = {
"request_id": _new_id("home"),
"session_id": _session.get_session_id(),
"owner_fingerprint": _session.get_local_identity_fingerprint(),
"home": home_data,
"digest": NetworkHomeProtocolType.manifest_digest(home_data),
}
request["sender_signature"] = _session.sign_local_action(
"portable_home_manifest",
NetworkHomeProtocolType.manifest_signature_fields(request),
)
if _session.is_host():
_process_manifest_request(_session.get_local_peer_id(), request)
else:
submit_home_manifest.rpc_id(1, request)
return true
@rpc(
"any_peer", "call_remote", "reliable",
NetworkHomeProtocolType.RELIABLE_CHANNEL,
)
func submit_home_manifest(data: Dictionary) -> void:
var sender_id: int = multiplayer.get_remote_sender_id()
if (
_session == null
or not _session.is_host()
or not _session.is_authenticated_peer(sender_id)
):
return
_process_manifest_request(sender_id, data)
func _process_manifest_request(peer_id: int, data: Dictionary) -> void:
if (
not NetworkHomeProtocolType.validate_manifest_request(data)
or str(data["session_id"]) != _session.get_session_id()
):
_reject_manifest(peer_id, "The RV update was invalid.")
return
var record := _session.get_peer_record(peer_id)
var fingerprint: String = str(data["owner_fingerprint"])
if (
record == null
or record.identity_fingerprint != fingerprint
or not _session.verify_peer_action(
peer_id,
"portable_home_manifest",
NetworkHomeProtocolType.manifest_signature_fields(data),
data["sender_signature"],
)
):
_reject_manifest(peer_id, "The RV owner could not be verified.")
return
var home_data: Dictionary = PlayerHomeStateType.sanitize_save_data(
data["home"]
)
var prior: Dictionary = _manifests_by_fingerprint.get(fingerprint, {})
var is_first_manifest: bool = prior.is_empty()
if not _valid_manifest_revision(peer_id, prior, home_data):
_reject_manifest(peer_id, "The RV update was rejected by the host.")
return
if not _moved_decor_clear_of_players(fingerprint, prior, home_data):
_reject_manifest(peer_id, "A player is in the way of that decor.")
return
var manifest: Dictionary
if prior.is_empty():
if _manifests_by_fingerprint.size() >= MAX_MANIFESTS:
_reject_manifest(peer_id, "This room cannot place another RV.")
return
var reserved: Array[Transform3D] = []
for existing: Dictionary in _manifests_by_fingerprint.values():
reserved.append(NetworkHomeProtocolType.array_to_transform(
existing["exterior_transform"]
))
var exterior_transform: Transform3D = (
_world_service.choose_exterior_transform(peer_id, reserved)
)
manifest = {
"owner_peer_id": peer_id,
"owner_fingerprint": fingerprint,
"home": home_data,
"exterior_transform": (
NetworkHomeProtocolType.transform_to_array(exterior_transform)
),
"interior_index": _next_interior_index,
}
_next_interior_index += 1
else:
manifest = prior.duplicate(true)
manifest["home"] = home_data
manifest["owner_peer_id"] = peer_id
_manifests_by_fingerprint[fingerprint] = manifest
_fingerprint_by_peer[peer_id] = fingerprint
_consume_owned_authorizations(peer_id, home_data)
_broadcast_manifest(manifest)
if is_first_manifest:
_spawn_peer_at_exterior(peer_id, fingerprint)
func _valid_manifest_revision(
peer_id: int,
prior_manifest: Dictionary,
home_data: Dictionary,
) -> bool:
if prior_manifest.is_empty():
# A portable RV is owned by the player's signed local save. A new host has
# no prior room ledger with which to compare its first manifest.
return true
var prior_home: Dictionary = prior_manifest["home"]
if (
int(home_data["revision"]) <= int(prior_home["revision"])
or int(home_data["tier"]) != int(prior_home["tier"])
or str(home_data["exterior_id"]) != str(prior_home["exterior_id"])
):
return false
var prior_owned: Dictionary = prior_home["owned_decor"]
var next_owned: Dictionary = home_data["owned_decor"]
var all_ids: Dictionary[String, bool] = {}
for value: Variant in prior_owned.keys():
all_ids[str(value)] = true
for value: Variant in next_owned.keys():
all_ids[str(value)] = true
var authorized: Dictionary = _authorized_owned_counts.get(peer_id, {})
for product_id: String in all_ids:
var prior_count: int = int(prior_owned.get(product_id, 0))
var next_count: int = int(next_owned.get(product_id, 0))
if prior_count == next_count:
continue
if next_count < prior_count or int(authorized.get(product_id, -1)) != next_count:
return false
return true
func _moved_decor_clear_of_players(
owner_fingerprint: String,
prior_manifest: Dictionary,
home_data: Dictionary,
) -> bool:
if _world_service == null:
return false
var prior_by_instance: Dictionary[String, Dictionary] = {}
if not prior_manifest.is_empty():
var prior_home: Dictionary = prior_manifest.get("home", {})
for value: Variant in prior_home.get("placements", []):
if typeof(value) != TYPE_DICTIONARY:
continue
var prior_placement: Dictionary = value
prior_by_instance[str(
prior_placement.get("instance_id", "")
)] = prior_placement
for value: Variant in home_data.get("placements", []):
if typeof(value) != TYPE_DICTIONARY:
return false
var placement: Dictionary = value
var instance_id: String = str(placement.get("instance_id", ""))
var prior_placement: Dictionary = prior_by_instance.get(
instance_id, {}
)
if (
not prior_placement.is_empty()
and _same_decor_transform(prior_placement, placement)
):
continue
var decor_id := StringName(str(placement.get("decor_id", "")))
var position: Vector3 = _placement_position(placement)
if (
not position.is_finite()
or not _world_service.is_decor_placement_clear_of_players(
owner_fingerprint,
decor_id,
position,
float(placement.get("yaw", 0.0)),
)
):
return false
return true
static func _same_decor_transform(
first: Dictionary,
second: Dictionary,
) -> bool:
if str(first.get("decor_id", "")) != str(second.get("decor_id", "")):
return false
var first_position: Vector3 = _placement_position(first)
var second_position: Vector3 = _placement_position(second)
return (
first_position.is_equal_approx(second_position)
and is_equal_approx(
float(first.get("yaw", 0.0)),
float(second.get("yaw", 0.0)),
)
)
static func _placement_position(placement: Dictionary) -> Vector3:
var values: Array = placement.get("position", [])
if values.size() != 3:
return Vector3.INF
return Vector3(
float(values[0]), float(values[1]), float(values[2])
)
func _consume_owned_authorizations(peer_id: int, home_data: Dictionary) -> void:
var authorized: Dictionary = _authorized_owned_counts.get(peer_id, {})
if authorized.is_empty():
return
var owned: Dictionary = home_data["owned_decor"]
for product_id: String in authorized.keys():
if int(owned.get(product_id, -1)) == int(authorized[product_id]):
authorized.erase(product_id)
if authorized.is_empty():
_authorized_owned_counts.erase(peer_id)
else:
_authorized_owned_counts[peer_id] = authorized
func _broadcast_manifest(manifest: Dictionary) -> void:
if _world_service != null and _world_service.is_world_ready():
_world_service.apply_manifest(manifest)
for peer_id: int in _session.get_authenticated_peer_ids():
if peer_id == 1:
continue
receive_home_manifest.rpc_id(peer_id, manifest)
@rpc(
"authority", "call_remote", "reliable",
NetworkHomeProtocolType.RELIABLE_CHANNEL,
)
func receive_home_manifest(data: Dictionary) -> void:
if not NetworkHomeProtocolType.validate_manifest_broadcast(data):
return
var fingerprint: String = str(data["owner_fingerprint"])
_manifests_by_fingerprint[fingerprint] = data.duplicate(true)
_fingerprint_by_peer[int(data["owner_peer_id"])] = fingerprint
if _world_service != null and _world_service.is_world_ready():
_world_service.apply_manifest(data)
func _reject_manifest(peer_id: int, message: String) -> void:
var fingerprint: String = _fingerprint_for_peer(peer_id)
var canonical: Dictionary = _manifests_by_fingerprint.get(fingerprint, {})
if peer_id == _session.get_local_peer_id():
_apply_manifest_rejection(message, canonical)
else:
receive_home_manifest_rejection.rpc_id(peer_id, message, canonical)
@rpc(
"authority", "call_remote", "reliable",
NetworkHomeProtocolType.RELIABLE_CHANNEL,
)
func receive_home_manifest_rejection(
message: String,
canonical: Dictionary,
) -> void:
_apply_manifest_rejection(message, canonical)
func _apply_manifest_rejection(message: String, canonical: Dictionary) -> void:
if (
canonical.is_empty()
or not NetworkHomeProtocolType.validate_manifest_broadcast(canonical)
):
local_manifest_rejected.emit(message)
return
_suppress_local_submission = true
_local_home.restore_from_save_data(canonical["home"])
_suppress_local_submission = false
_save_manager.save_if_dirty()
receive_home_manifest(canonical)
local_manifest_rejected.emit(message)
func _submit_transition(action: int, owner_fingerprint: String) -> bool:
var request: Dictionary = {
"request_id": _new_id("home_transition"),
"session_id": _session.get_session_id(),
"action": action,
"owner_fingerprint": owner_fingerprint,
}
request["sender_signature"] = _session.sign_local_action(
"portable_home_transition",
NetworkHomeProtocolType.transition_signature_fields(request),
)
if _session.is_host():
_process_transition_request(_session.get_local_peer_id(), request)
else:
submit_home_transition.rpc_id(1, request)
return true
@rpc(
"any_peer", "call_remote", "reliable",
NetworkHomeProtocolType.RELIABLE_CHANNEL,
)
func submit_home_transition(data: Dictionary) -> void:
var sender_id: int = multiplayer.get_remote_sender_id()
if (
not _session.is_host()
or not _session.is_authenticated_peer(sender_id)
):
return
_process_transition_request(sender_id, data)
func _process_transition_request(peer_id: int, data: Dictionary) -> void:
if (
not NetworkHomeProtocolType.validate_transition_request(data)
or str(data["session_id"]) != _session.get_session_id()
or not _session.verify_peer_action(
peer_id,
"portable_home_transition",
NetworkHomeProtocolType.transition_signature_fields(data),
data["sender_signature"],
)
):
_send_transition_rejection(peer_id, "The RV transition was invalid.")
return
var owner_fingerprint: String = str(data["owner_fingerprint"])
var action: int = int(data["action"])
var avatar: Player = _spawn_service.get_avatar(peer_id)
var manifest: Dictionary = _manifests_by_fingerprint.get(
owner_fingerprint, {}
)
if avatar == null or manifest.is_empty():
_send_transition_rejection(peer_id, "That RV is unavailable.")
return
if action == NetworkHomeProtocolType.TransitionAction.ENTER:
var record := _session.get_peer_record(peer_id)
var owner_is_local: bool = (
record != null and record.identity_fingerprint == owner_fingerprint
)
var privacy: int = int((manifest["home"] as Dictionary)["privacy"])
if (
(not owner_is_local and privacy != PlayerHomeStateType.Privacy.OPEN)
or _session.get_peer_space(peer_id)
!= NetworkHomeProtocolType.WORLD_SPACE
or not _world_service.is_avatar_near_exterior(
avatar, owner_fingerprint
)
):
_send_transition_rejection(peer_id, "You cannot enter that RV.")
return
_apply_host_transition(
peer_id,
NetworkHomeProtocolType.make_space_id(owner_fingerprint),
owner_fingerprint,
_world_service.get_interior_spawn_transform(
owner_fingerprint, peer_id
),
)
return
if (
_session.get_peer_space(peer_id)
!= NetworkHomeProtocolType.make_space_id(owner_fingerprint)
or not _world_service.is_avatar_near_interior_exit(
avatar, owner_fingerprint
)
):
_send_transition_rejection(peer_id, "Move closer to the RV door.")
return
_apply_host_transition(
peer_id,
NetworkHomeProtocolType.WORLD_SPACE,
owner_fingerprint,
_world_service.get_exterior_spawn_transform(owner_fingerprint),
)
func _spawn_peer_at_exterior(
peer_id: int,
owner_fingerprint: String,
) -> void:
if (
not _session.is_host()
or _session.get_peer_space(peer_id)
!= NetworkHomeProtocolType.WORLD_SPACE
or _spawn_service.get_avatar(peer_id) == null
):
return
_apply_host_transition(
peer_id,
NetworkHomeProtocolType.WORLD_SPACE,
owner_fingerprint,
_world_service.get_exterior_spawn_transform(owner_fingerprint),
)
func _apply_host_transition(
peer_id: int,
space_id: StringName,
owner_fingerprint: String,
transform: Transform3D,
) -> void:
var result: Dictionary = {
"peer_id": peer_id,
"space_id": String(space_id),
"owner_fingerprint": owner_fingerprint,
"transform": NetworkHomeProtocolType.transform_to_array(transform),
}
_apply_transition_result(result)
for recipient_id: int in _session.get_authenticated_peer_ids():
if recipient_id == 1:
continue
receive_home_transition.rpc_id(recipient_id, result)
_session.publish_authoritative_teleport(peer_id)
@rpc(
"authority", "call_remote", "reliable",
NetworkHomeProtocolType.RELIABLE_CHANNEL,
)
func receive_home_transition(data: Dictionary) -> void:
if (
typeof(data.get("peer_id")) != TYPE_INT
or typeof(data.get("space_id")) not in [TYPE_STRING, TYPE_STRING_NAME]
or not NetworkIdentityCrypto.valid_fingerprint(
data.get("owner_fingerprint")
)
):
return
var transform: Transform3D = NetworkHomeProtocolType.array_to_transform(
data.get("transform")
)
if transform == Transform3D():
return
_apply_transition_result(data)
func _apply_transition_result(data: Dictionary) -> void:
var peer_id: int = int(data["peer_id"])
var space_id := StringName(str(data["space_id"]))
if not _session.set_peer_space(peer_id, space_id):
return
var avatar: Player = _spawn_service.get_avatar(peer_id)
if avatar != null:
avatar.global_transform = NetworkHomeProtocolType.array_to_transform(
data["transform"]
)
avatar.velocity = Vector3.ZERO
if peer_id == _session.get_local_peer_id():
_world_service.set_local_space(space_id)
local_space_changed.emit(
space_id, str(data["owner_fingerprint"])
)
local_transition_finished.emit(true, "")
_refresh_peer_visibility()
func _send_transition_rejection(peer_id: int, message: String) -> void:
if peer_id == _session.get_local_peer_id():
local_transition_finished.emit(false, message)
else:
receive_home_transition_rejection.rpc_id(peer_id, message)
@rpc(
"authority", "call_remote", "reliable",
NetworkHomeProtocolType.RELIABLE_CHANNEL,
)
func receive_home_transition_rejection(message: String) -> void:
local_transition_finished.emit(
false, message.left(NetworkHomeProtocolType.MAX_MESSAGE_LENGTH)
)
func _refresh_peer_visibility() -> void:
var local_peer_id: int = _session.get_local_peer_id()
if local_peer_id <= 0:
return
for peer_id: int in _spawn_service.get_peer_ids():
if peer_id == local_peer_id:
continue
_spawn_service.set_peer_presentation_visible(
peer_id, _session.peers_share_space(local_peer_id, peer_id)
)
func _on_local_home_changed() -> void:
if _suppress_local_submission or _publish_queued:
return
_publish_queued = true
call_deferred("_publish_queued_home")
func _publish_queued_home() -> void:
_publish_queued = false
if _save_manager != null:
_save_manager.save_if_dirty()
publish_local_home()
func _on_join_authenticated() -> void:
publish_local_home()
func _on_peer_authenticated(peer_id: int, _display_name: String) -> void:
if not _session.is_host():
return
for manifest: Dictionary in _manifests_by_fingerprint.values():
receive_home_manifest.rpc_id(peer_id, manifest)
for existing_peer_id: int in _session.get_authenticated_peer_ids():
if existing_peer_id == peer_id:
continue
var avatar: Player = _spawn_service.get_avatar(existing_peer_id)
if avatar == null:
continue
var space_id: StringName = _session.get_peer_space(existing_peer_id)
var owner_fingerprint: String = (
String(space_id).trim_prefix("rv:")
if String(space_id).begins_with("rv:")
else _fingerprint_for_peer(existing_peer_id)
)
if owner_fingerprint.is_empty():
continue
receive_home_transition.rpc_id(peer_id, {
"peer_id": existing_peer_id,
"space_id": String(space_id),
"owner_fingerprint": owner_fingerprint,
"transform": NetworkHomeProtocolType.transform_to_array(
avatar.global_transform
),
})
func _on_peer_removed(peer_id: int) -> void:
var fingerprint: String = _fingerprint_by_peer.get(peer_id, "")
_fingerprint_by_peer.erase(peer_id)
_authorized_owned_counts.erase(peer_id)
if fingerprint.is_empty():
return
if _session.is_host():
for occupant_id: int in _session.get_authenticated_peer_ids():
if (
occupant_id == peer_id
or _session.get_peer_space(occupant_id)
!= NetworkHomeProtocolType.make_space_id(fingerprint)
):
continue
_apply_host_transition(
occupant_id,
NetworkHomeProtocolType.WORLD_SPACE,
fingerprint,
_world_service.get_exterior_spawn_transform(fingerprint),
)
_manifests_by_fingerprint.erase(fingerprint)
_world_service.remove_presentation(fingerprint)
if _session.is_host():
for recipient_id: int in _session.get_authenticated_peer_ids():
if recipient_id != 1:
receive_home_removed.rpc_id(recipient_id, fingerprint)
@rpc(
"authority", "call_remote", "reliable",
NetworkHomeProtocolType.RELIABLE_CHANNEL,
)
func receive_home_removed(owner_fingerprint: String) -> void:
if not NetworkIdentityCrypto.valid_fingerprint(owner_fingerprint):
return
_manifests_by_fingerprint.erase(owner_fingerprint)
_world_service.remove_presentation(owner_fingerprint)
func _on_session_state_changed(state: NetworkSession.State) -> void:
if state in [NetworkSession.State.PRIVATE_HOST, NetworkSession.State.OPEN_HOST]:
if (
not _session.is_dedicated_host()
and _world_service != null
and _world_service.is_world_ready()
):
publish_local_home()
return
if state in [
NetworkSession.State.INACTIVE,
NetworkSession.State.DISCONNECTING,
NetworkSession.State.CONNECTION_FAILED,
NetworkSession.State.SERVER_LOST,
]:
_manifests_by_fingerprint.clear()
_fingerprint_by_peer.clear()
_authorized_owned_counts.clear()
_authorization_result_owner.clear()
_next_interior_index = 0
_world_service.clear_presentations()
_world_service.set_local_space(NetworkHomeProtocolType.WORLD_SPACE)
local_space_changed.emit(NetworkHomeProtocolType.WORLD_SPACE, "")
func _fingerprint_for_peer(peer_id: int) -> String:
if _fingerprint_by_peer.has(peer_id):
return _fingerprint_by_peer[peer_id]
var record := _session.get_peer_record(peer_id)
return record.identity_fingerprint if record != null else ""
func _new_id(prefix: String) -> String:
return "%s:%s" % [
prefix, Crypto.new().generate_random_bytes(16).hex_encode(),
]