Multiplayer Authority Bug (XR)

Godot Version

Godot 4.3

Question

“How can I fix a desync issue where the server and a client have different views on which one has authority over a multiplayer object?”

Code, Log and Thoughts

I spawn an Pickable Object from the XR Tools Library and when a client grabs it i want the server to give the authority over that object to the client.

# Called by a client when it wants to pick up an object.
func request_authority(object_path):
    # Send the request to the server (ID 1) to get authority.
    rpc_id(1, "_server_set_authority", object_path)
    Log.print_to_server(str("Authority request for ", object_path))

# Called by a client when it wants to pick up an object.
func request_authority(object_path):
	# Send the request to the server (ID 1) to get authority.
	rpc_id(1, "_server_set_authority", object_path)
	Log.print_to_server(str("Authority request for ", object_path))

# Executed ONLY on the server to grant authority.
@rpc("any_peer")
func _server_set_authority(object_path):
	if not multiplayer.is_server():
		return
	# Get the ID of the client that sent the request.
	var sender_id = multiplayer.get_remote_sender_id()
	print("SERVER: Received authority request from client [", sender_id, "] for '", object_path, "'.")
	
	var object = get_node_or_null(object_path)
	if object:
		print("SERVER: Object found. Transferring authority to client [", sender_id, "].")
		# Transfer the authority for this object to the requesting client.
		object.set_multiplayer_authority(sender_id, true)
	else:
		print("SERVER ERROR: Object at path '", object_path, "' NOT found!")

So far this works and i get all the Prints which confirm that the Code is executed and no error occured.
I then put a print function in the pickable.gd file.

func _process(_delta):
	frame_counter += 1
	
	# (Send the live status to the server/Print  live status from server) every 60 frames 
	if frame_counter % 60 == 0:
		if not multiplayer.is_server():
			var has_authority = get_multiplayer_authority()
			Log.print_to_server(str("CLIENT: -> Authority: ", has_authority))
		else:
			var has_authority = get_multiplayer_authority()
			print(str("SERVER: -> Authority: ", has_authority))

The Output now confuses me.

Log:
CLIENT: → Authority: 1
SERVER: → Authority: (The Integer of the Client) for Example something like “330780423888”

So the server from his perspective sets the authority to the client but the client does not?
How can i fix that?

How are you spawning the object? Are you using a MultiPlayerSpawner?

The client can click on a button which triggers the following two functions:

func request_spawn_exhibit(id: int):
	rpc_id(1, "_server_spawn_exhibit", id)
	
@rpc("any_peer")
func _server_spawn_exhibit(id: int):
	# Sicherheitscheck: Nur der Server darf dies ausführen.
	if not multiplayer.is_server():
		return
	#one of two spawn places	
	var spawn = null
	if spawn1.empty:
		spawn = spawn1
	elif spawn2.empty:
		spawn = spawn2
	else:
		return
	
	var scene : PackedScene = load(Library.get_filename(id))
	
	var node : Exhibit = scene.instantiate()
	node.library_id = id
	node.scale_to_custom_size()
	node.position.y = 0.1
	#node.freeze = true
	node.gravity_scale = 0.1
	node.linear_damp = 0.5
	node.angular_damp = 0.5
	spawn.add_child(node)
	print("SERVER: Spawn Object: ", node.get_instance_id()," Path: ", node.get_path())

Pedestal1 and Pedestal2 are the spawn1 and spawn2 in the code and the objects get spawn for the client through the MultiplayerSpawner Exhibit1 and Exhibit2.

So i got some help and this seems to work though i dont get why.

# Called by a client when it wants to pick up an object.
func request_authority(object_path):
	# Send the request to the server (ID 1) to get authority.
	rpc_id(1, "_server_set_authority", object_path)
	Log.print_to_server(str("Authority request for ", object_path))

# Executed ONLY on the server to grant authority.
@rpc("any_peer")
func _server_set_authority(object_path):
	if not multiplayer.is_server():
		return
	# Get the ID of the client that sent the request.
	var sender_id = multiplayer.get_remote_sender_id()
	print("SERVER: Authority request from client [", sender_id, "] for '", object_path, "' received.")
	rpc("_apply_authority",object_path,sender_id)

@rpc("authority", "call_local")
func _apply_authority(object_path, sender_id):
	var object = get_node_or_null(object_path)
	if object:
		print("SERVER: Object found. Transferring authority to client [", sender_id, "].")
		object.set_multiplayer_authority(sender_id,true)
	else:
		print("SERVER ERROR: Object at path '", object_path, "' NOT found!")

To answer why, authority is controlled locally and does not propagate over the network automatically. So client and server need to independently agree who as authority.

In your original configuration only the server was asked to set authority on its instance, and the client did not do the same for itself.

The fix was that the server tells the client to change the authority for its instance.

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.