Setting up multiplayer

Godot Version

4.2.2

Question

I am starting multiplayer functionality and followed a few tutorials and read quite a bit at this point but am running into issues. The symptoms I have currently are this; 1-> Starting as a server or setting it up to click as server does not impact authority
2-> The host client gets full authority (I put this hear because when I instantiate them both at same time, they both are hosts until I click join, I later changed it to neither are hosting and start it lobby style but same errors creep up)
3-> lots of errors,

E 0:00:13:0280   _make_spawn_packet: Parameter "sync->get_replication_config_ptr()" is null.
  <C++ Source>   modules/multiplayer/scene_replication_interface.cpp:503 @ _make_spawn_packet()
E 0:00:13:0281   _send_raw: Condition "!p_buffer || p_size < 1" is true. Returning: ERR_INVALID_PARAMETER
  <C++ Source>   modules/multiplayer/scene_replication_interface.cpp:460 @ _send_raw()
(The two below this seem to be focused on my spawn? but it spawns them anways)
E 0:00:13:0282   origin_startup_player_manager.gd:41 @ peer_connected(): Parameter "sync->get_replication_config_ptr()" is null.
  <C++ Source>   modules/multiplayer/scene_replication_interface.cpp:503 @ _make_spawn_packet()
  <Stack Trace>  origin_startup_player_manager.gd:41 @ peer_connected()
E 0:00:13:0282   origin_startup_player_manager.gd:41 @ peer_connected(): Condition "!p_buffer || p_size < 1" is true. Returning: ERR_INVALID_PARAMETER
  <C++ Source>   modules/multiplayer/scene_replication_interface.cpp:460 @ _send_raw()
  <Stack Trace>  origin_startup_player_manager.gd:41 @ peer_connected()
(It repeats this error until I force disconnect or it crashes / which ever comes first)
E 0:00:13:0282   _send_sync: Condition "!sync || !sync->get_replication_config_ptr() || !_has_authority(sync)" is true. Continuing.
  <C++ Source>   modules/multiplayer/scene_replication_interface.cpp:814 @ _send_sync()

My code is probably very messy, I have it almost functional except for the authority issue but if thiers something confusing or conflicting ill try my best to answer questions.

Spawning in code

func peer_connected(id:int):
	print("Player "+str(id)+" Connected")
	var current_player= drone_body.instantiate()
	var current_camera= camera.instantiate()
	current_player.player=str(id)
	#spawning
	var pos :=Vector2.from_angle(randf()*2*PI)
	current_player.position=Vector3(pos.x*SPAWN_RANDOM*randf(),0,pos.y*SPAWN_RANDOM)
	current_player.name=str(id)
	$player_spawner.add_child(current_player,true)
	$player_spawner.add_child(current_camera,true)

Start up code (currently set up on button press)

##Local Server on Startup
func establish_connection_to_local_server():
	peer= ENetMultiplayerPeer.new()
	var error=peer.create_server(PORT,32)
	if error != OK:
		print("Connection to Server Failed:"+str(error))
		return
	peer.get_host().compress(ENetConnection.COMPRESS_RANGE_CODER)
	multiplayer.set_multiplayer_peer(peer)
	#SendPlayerInformation(name, multiplayer.get_unique_id())
	print("Setup Finished")

Join code (also button press)

##Join
func on_join_button_down():
	peer=ENetMultiplayerPeer.new()
	peer.create_client(ADDRESS, PORT)
	peer.get_host().compress(ENetConnection.COMPRESS_RANGE_CODER)
	multiplayer.set_multiplayer_peer(peer)
	print("Connection Joined")

Player code (the stuff that is impacted by id)

##Multiplayer setup
@onready var player_body_input = $PlayerInputSynchronizer

@export var player :=1:
	set(id):
		player=id
		$PlayerInputSynchronizer.set_multiplayer_authority(id)

My camera is technically spawned seperately from the body, but when I make it a child I still suffer from the same errors. This is probably a complex problem.

going to sleep~ but I do have a mixed update
I went back to a previous version and compared it to the new code, when I restored it to the old version the errors stayed the same but now its painfully obvious that it has something to do with my camera lacking authority?

func peer_connected(id:int):
	print("Player "+str(id)+" Connected")
	var current_player= drone_body.instantiate()
	var current_camera= camera.instantiate()
	current_player.player=str(id)
	#spawning
	var pos :=Vector2.from_angle(randf()*2*PI)
	current_player.position=Vector3(pos.x*SPAWN_RANDOM*randf(),0,pos.y*SPAWN_RANDOM)
	current_player.name=str(id)
	$player_spawner.add_child(current_player,true)
	$player_spawner.add_child(current_camera,true)

when I try to give my camera set_multiplayer_peer, or similar it really doesnt like it, and if I make the camera a child of the character body things get alot more messed up lol. any advice on how to get both spawners to have permission?

Your set authority looks wrong. There would be no way for the replication to take effect that way on the remote side. This is because after you give authority on the host, that player will wait for info from remote. Then the remote side is spawned waiting for the host to send info. They both wait on each other.

Typically the name of the node is set to the multiplayer id and sent by the host when using a MultiplayerSpawner. Then using the ready function, set the authority by checking its own name.

If you don’t want to use the name of the node to pass info you could potentially setup a custom spawn interface on the MultiplayerSpawner.

Im only 3 days into this so I don’t understand what replication means in this context?
from what you said it sounds like.
Authority---------Host
(wrong communication?)
Remote---------Player
so my remote and host arent communicating?
I tried setting the name of the node and sending it to throught the MultiplayerSpawner and it technically works, My cameras just for some reason do not like doing it and give errors that I am trying to resolve.
right now I imagine my setup looks like this
Server start
Host>-----> >Spawn< <—<Join

Sets authority here

Player>---------^ | ^--------<Player

Camera>–>(no authority set)<-<Camera

Both are trying to use the Multiplayer spawner. Either today or tommarrow I was gonna setup the “Join” button to scrub off .player(id) and apply a new one, but that doesnt resovle my own personal inflicted problem with my camera not having the proper authority. I just cant fathom why the player.name was able to change and not the camera(is it because they would have the same name?)
seems most videos and things I look at are using this method for giving authority.
What my goal is something like this
Host->Spawner->Set authority->spawn body->spawn camera (both with authority)
Join->Spawner->Set authority->spawn body->spawn camera (both with authority)
way simplified, but I seem to have messed up with the set authority part.
I dont know what you mean by remote side?
Trying to imagine it sounds like
Host->authority->player->remote-<-Spawn->Host?
I am curious what you mean by custom spawn interface?

A MultiplayerSpawner, other then the custom spawn, will spawn a default instance of the class and set the name.

It is then the MultiplayerSynchronizer responsibility to begin syncing state.

Authority needs to be agreed to by both client and host. Meaning they both need to be set to the same authority on owners and remotes.

Data only flows in one direction. Once authority is set, the node will only accept remote instance data synchronizations from the authority. If it isn’t the authority it will never send data, it will only receive.

All nodes default to host authority. Set authority is recursive by default, but not for new nodes under the same branch.

So to show you what you are doing.

Host sees Player join > host set auth > host spawn
... Host node waits for joined player (authed client) to send data

Player joins host > node is spawned for player by host
... New node instance defaults to server authority and waits for server to send data

# since authority controls direction of data both host and client instances are waiting for data from remote side.

As far as your camera goes, add the camera first and then set authority. Or in your case add the camera then set player id.

Finally, if you want a camera to spawn under the new player just like you composed it on the host. You will need to add the camera as a spawnable scene to the MultiplayerSpawner.

( MultiplayerSpawner should be able to handle nested scenes, but I haven’t tested it. But I have had nested spawners of spawned scenes work)

I have confirmed that the MultiplayerSpawner can spawn multiple things together. I dont have enough time to test everything tonight, but by sunday I should be able to take more of a look at what needs to be done. Free time is a luxary!

oh, it just occured to me that this is not a “lobby” stytle system.
It is a peer-peer host-client system but im kinda not the sharpest tool in the shed and did something that idk if it is weird or not.


var server_peer =ENetMultiplayerPeer.new()

##Gathering Hub
@onready var gathering_hub_spawner = get_node("/root/Origin/GatheringHub/gathering_hub_spawner") ##this might break when portal system is setup
##Spawn setup
var SPAWN_RANDOM = 3

func _ready():
	server_start()
	server_manager()

func server_start():
	if server_peer.create_server(SERVER_PORT) == OK:
		multiplayer.multiplayer_peer=server_peer
		print("Server Started")
	else:
		print("Server already running")

func server_manager():
	if not multiplayer.is_server():
		return
	multiplayer.peer_connected.connect(_add_player)
	multiplayer.peer_disconnected.connect(_remove_player)
	for id in multiplayer.get_peers():
		_add_player(id)
	if not OS.has_feature("dedicated_server"):
		_add_player(1)


func new_player_join():
	print("new player joined")
	server_peer.close()
	var client_peer=ENetMultiplayerPeer.new()
	client_peer.create_client(SERVER_IP,SERVER_PORT)
	multiplayer.multiplayer_peer=client_peer

##Adding Players
func _add_player(id:int):
	print("Player %s connected" % str(id))
	var operator = preload("res://assets/eye_drone.tscn").instantiate()
	operator.player=id
	operator.name=str(id)
	print(operator.name)
	#spawning
	var position :=Vector2.from_angle(randf()*2*PI)
	operator.position=Vector3(position.x*SPAWN_RANDOM*randf(),0,position.y*SPAWN_RANDOM)
	gathering_hub_spawner.add_child(operator,true)

##Removing Players
func _remove_player(id:int):
	if not gathering_hub_spawner.has_node(str(id)):
		return
	gathering_hub_spawner.get_node(str(id)).queue_free()
	print("Player %s disconnected" % id)

func _exit_tree():
	if not multiplayer.is_server():
		return
	multiplayer.peer_connected.disconnect(_add_player)
	multiplayer.peer_disconnected.disconnect(_remove_player)

at the very top the first thing the game does when it starts is it creates a server. This is intentional (probably not smart, but I didnt want a lobby based system).
When I open 2 clients it does give an expected error
Client 1 Host -----------------------Client 2 host
Server start------------------------- Server start (fail because port in use)
Body spawn in-(bodies move)-Body spawn in
Finished-------------------------------Click join
client 2 join(body spawn)----body spawn (different location)
body spawning different location might be bacuse spawn set to random?
body 1 still moves---------------body 2 is jerking(body 1 moves body 2 when host moves)
errors
E 0:00:05:0624 get_node: Node not found: “Origin/GatheringHub/gathering_hub_spawner/2135610537/ServerSynchronizer” (relative to “/root”).
<C++ Error> Method/function failed. Returning: nullptr
<C++ Source> scene/main/node.cpp:1651 @ get_node()
E 0:00:05:0624 process_simplify_path: Parameter “node” is null.
<C++ Source> modules/multiplayer/scene_cache_interface.cpp:110 @ process_simplify_path()
E 0:00:05:0624 get_node: Node not found: “Origin/GatheringHub/gathering_hub_spawner/2135610537/PlayerInputCameraSynchronizer” (relative to “/root”).
<C++ Error> Method/function failed. Returning: nullptr
<C++ Source> scene/main/node.cpp:1651 @ get_node()
E 0:00:05:0624 process_simplify_path: Parameter “node” is null.
<C++ Source> modules/multiplayer/scene_cache_interface.cpp:110 @ process_simplify_path()
What I dont get is it tries it fails to find the spawner for the camera? somehow?
I have attached the camera to the body at this point because the whole not giving authority thing, and its still doesnt like the camera apperently.

camera also started doing some weird things since I attached it


func _ready():
	if get_multiplayer_authority() == multiplayer.get_unique_id():
		pass
	else:
		set_process(false)
		set_process_input(false)

func get_camera_rotation_basis()->Basis:
	return camera_rotation.global_transform.basis

#
#func _input(event):
	#if event is InputEventMouseMotion:
		#rotate_camera(event.relative*camera_rotate_speed)
#
#func rotate_camera(move):
	#camera_base.rotate_y(-move.x)
	#camera_base.orthonormalize()
	#camera_rotation.rotation.x=clamp(camera_rotation.rotation.x+(camera_up_down_movement*move.y),camera_rotate_x_min,camera_rotate_x_max)

does the


func new_player_join():
	print("new player joined")
	server_peer.close()
	var client_peer=ENetMultiplayerPeer.new()
	client_peer.create_client(SERVER_IP,SERVER_PORT)
	multiplayer.multiplayer_peer=client_peer

server_peer.close() not do what I imagine it does?

its was so close to working at one point and then everything fell apart when I tried adding multiplayer lol.

image
image
I just checked the Remote debugger and it seems I have a hint as to the source of my problem.
Host and client connecting do not agree on the names. bizaree how its working like that.
the one that is the “client” has them numbered 1-2?
and the host is the one giving the unique id. Is this normal that client doesnt agree with host?
is this what you were talking about when you said the authority is set wrong and spawned node is waiting for host to send info? I feel like ive hit some kind of circular loop.

As far as the node names you have found the area of the issue. The node names (and NodePaths) need to match.

The only reason it chose 2 was probably because 1 was taken. Godot will add and/or incremented a number if node names collide. So it would seem the node name of “1” was used twice.

We need to see how you spawn the nodes.

I think i found the source of most of my issues. I had the idea since I was running both clients as Host first and then they both generate a body while they are host that might of been the root issue. So following the advice I just simply had the body delete itself before hosting (visual under).
Host->Spawn body->waiting->(still has body)+spawn new body->host controls body

Host->Spawn body->joining->(still had body)+generates error ->host controls body

So I now delete the body of the joining client and let it spawn into the host but seems I did a mistake.
right now it does a fun thing
Host->Spawn body->waiting->(still has body)+spawn new body->host controls body + extra body shows up but does nothing (joining client doesnt see it? only sees host)
image
Host->Spawn body->joining->(body delete)+sees host (no body spawn) ->host controls body
image

so client can now see host and host sees joining client but the body only spawns host side?
E 0:00:06:0862 get_node: Node not found: “Origin/GatheringHub/gathering_hub_spawner/473546440/ServerSynchronizer” (relative to “/root”).
<C++ Error> Method/function failed. Returning: nullptr
<C++ Source> scene/main/node.cpp:1651 @ get_node()
E 0:00:06:0862 process_simplify_path: Parameter “node” is null.
<C++ Source> modules/multiplayer/scene_cache_interface.cpp:110 @ process_simplify_path()

its a little bizzare to me that the spawn is working but only for the host
(relative spawn code below as a refresher)


func new_player_join():
	print("new player joined")
	server_peer.close()
	var client_peer=ENetMultiplayerPeer.new()
	client_peer.create_client(SERVER_IP,SERVER_PORT)
	multiplayer.multiplayer_peer=client_peer

##Adding Players
func _add_player(id:int):
	print("Player %s connected" % str(id))
	var operator = preload("res://assets/eye_drone.tscn").instantiate()
	operator.player=id
	operator.name=str(id)
	print(operator.name)
	#spawning
	var position :=Vector2.from_angle(randf()*2*PI)
	operator.position=Vector3(position.x*SPAWN_RANDOM*randf(),0,position.y*SPAWN_RANDOM)
	gathering_hub_spawner.add_child(operator,true)

If the multiplayer was setup incorrectly it woulndt be working host side, but it is… just some reason the client isnt recieving a body? I did an oops somewhere lol. according to my prints I see
Player 1 connected
1
Player 473546440 connected
473546440
new player joined
(this is where I noticed only 2 bodies in host and 1 (the host) in client)
Player 1 connected
1
Player 473546440 disconnected
Player 1121668252 connected
1121668252
(I was disconnecting and reconnecting to see if it would handle disconnects properly and it was removing the body from the disconnect and giving a new one to the new connection just fine)
new player joined
Player 1 connected
1
Player 1121668252 disconnected
Player 1013957905 connected
1013957905
(is it weird that player 1(host) says connected ← I am assuming this is the clients perspective-> and so it creates a body for the host)
(the older player that disconnects body gets destroyed host side(as intended))
(The new body is only generated host side and the client never recieves its body)
That is where the problem currently is at.

it… did not take me long to figure out what my problem was … your gonna laugh but it was really really dumb. In the multiplayer spawner I forgot to add the PackedScene to it… so it HAD the ability to spawn, just it didnt have anything TO spawn. It is fixed now fully and working 100% as intended~ thanks for the help with the trouble shooting.
The problem is resolved (mostly my own errors), but I am a little confused. Why did the client that joined the host camera automatically moved to the host? It shouldnt of had authority on that camera at all? Does it not care authority if it has nothing else around? I just thought the screen would go blank. Little things like that throw me off, but I am curious.

1 Like

Does it not care authority if it has nothing else around?

Authority only really matters for rpc calls. A client can modify and delete anything it wants, but it will not effect the authority’s state.

I assume it’s because you had a player existing before the switch to multiplayer. It could have been the hosts camera setting itself to the current camera on the client. Or the existing client camera still working after the switch. (I think you modifed the switch due to the name conflict)

A lot of things were at play not sure where one issue stopped and one started.

I’m glad it’s working

2 Likes

turns out, it wasnt set up correctly lol
I opened up 4 clients and and a problem showed up.
E 0:01:22:0012 on_spawn_receive: Condition “parent->has_node(name)” is true. Returning: ERR_INVALID_DATA
<C++ Source> modules/multiplayer/scene_replication_interface.cpp:604 @ on_spawn_receive()

it doesnt show up on host +1, but host on +2 and this error shows up 1 time (for all cliencts except the host, and on +3 it shows the error 2 times for all clients except the host)
all bodies spawn but things get weird

Host sees movement of all clients but all clients only see the movement of the host + thier own

I created a chatbox just to type and have a bigger picture.
what I think is happening is rather odd. The host starts off as 1 (they all technically do). A new one joins in, loses its 1 number and gets unique id (all good, this is why I thought it was done and good). add client 2 and Host + client 1 have the same name now? add client 3 and host does the same thing but client 1 and 2 all have matching names with client 3 being the only unique.

to me this tells me thier names are changing after being set up but according to Godot they still have the same names
I started doing print functions and added some new code to try and narrow down to problem once and for all

##Adding Players
func _add_player(id:int):
	print("Player %s connected" % str(id))
	var operator = preload("res://assets/eye_drone.tscn").instantiate()
	operator.player=id
	operator.name=str(id)
	OperatorManager.operator_name=operator.name
	OperatorManager.call_deferred("set_operator_name")
	#spawning
	var position :=Vector2.from_angle(randf()*2*PI)
	operator.position=Vector3(position.x*SPAWN_RANDOM*randf(),0,position.y*SPAWN_RANDOM)
	gathering_hub_spawner.add_child(operator,true)
	print(Players)
##Server Calls
func connected_to_server():
	print("Connceted To Server")
	SendPlayerInformation.rpc_id(1,"",multiplayer.get_unique_id())

func connection_failed():
	print("Connection Failed")

##Operator Management
@rpc("any_peer","call_local")
func SendPlayerInformation(name,id):
	if !MultiplayerManager.Players.has(id):
		MultiplayerManager.Players[id]={
			"name" : name,
			"id" : id
		}
		if multiplayer.is_server:
			for i in MultiplayerManager.Players:
				SendPlayerInformation.rpc(MultiplayerManager.Players[i],name,i)

My prints start off like this
Server already running
Player 1 connected
{ }
Server already running
Player 1 connected
{ }
Server Started
Player 1 connected
{ 1: { “name”: “”, “id”: 1 } }
Server already running
Player 1 connected
{ }
but toward the end my print looks more like
Player 1511927008 connected
{ 1: { “name”: “”, “id”: 1 }, 195040876: { “name”: “”, “id”: 195040876 }, 441155578: { “name”: “”, “id”: 441155578 } }
new player joined
Connceted To Server
Player 1 connected
{ }
Player 195040876 connected
{ }
Player 441155578 connected
{ }
to me this sort of makes sense that the host will have all the names and id (wierd the names arent changing so That might be the source of my issues, but the Unique ID are also not unique… so thats a bundle of fun. Is it a problem the clients dont know about eachother? I think that explains why they only see the bodies but dont share syncronization data with anyone but the host.

E 0:02:26:0296 _process_rpc: RPC - ‘Node(MultiplayerManager.gd)::SendPlayerInformation’: Method expected 2 arguments, but called with 3
<C++ Source> modules/multiplayer/scene_rpc_interface.cpp:290 @ _process_rpc()

also have this error?

func connected_to_server():
	print("Connceted To Server")
	SendPlayerInformation.rpc_id(1,"",multiplayer.get_unique_id())

The thing im following uses 3 tags here and it doesnt complain for them.
(at this point I am trying to follow a tutorial to try and increase my understanding but seems my attempts just are narrowing the problems)
I still seem to be failing to set the name properly? I tried putting it in an index but it just shows the problems ive been having more obviously.

Need to use rpc_id here.

I found some time to mess with the code today. I still am so confused about the whole authority thing. I have a body being generated by the spawner, and according to the prints they are being named correctly. All clients (host included) see all bodies but only the host moves (the clients arent moved by the host now though). The camera of the clients moves automatically to the host. I put in an ingame chat to see what is going on with the whole authority thing.

add operator:1
operator ID:1
Operator added with name:1and authority ID:1
1
add operator:750019725
operator ID:750019725
Operator added with name:750019725and authority ID:750019725
1
add operator:1367095416
operator ID:1367095416
Operator added with name:1367095416and authority ID:1367095416
1
add operator:1885263129
operator ID:1885263129
Operator added with name:1885263129and authority ID:1885263129
1
my prints basically are taken multiple points throughout just to confirm that the process is working. What is weird is that the bodies are being set correctly, but the bodies are unable to move?
In fact when I was using the chat box with the ID attached to the chat it was empty (empty text : random text here) comprated to the host (1:random text). The host then would change to 750019725:text, then 1367095416:text, etc. The host seems to take the authority instead of the clients? while the bodies which I thought was getting the authority didn’t.
Got a new fun error which basically tells me Authority isnt being set:
E 0:00:30:0495 on_replication_start: The MultiplayerSynchronizer at path “/root/Origin/GatheringHub/gathering_hub_spawner/97566485/PlayerInputSynchronizer” is unable to process the pending spawn since it has no network ID. This might happen when changing the multiplayer authority during the “_ready” callback. Make sure to only change the authority of multiplayer synchronizers during “_enter_tree” or the “_spawn_custom” callback of their multiplayer spawner.
<C++ Error> Condition “pending_sync_net_ids.is_empty()” is true. Returning: ERR_INVALID_DATA
<C++ Source> modules/multiplayer/scene_replication_interface.cpp:244 @ on_replication_start()

Basically its saying my input synchronizer is wrong? but I cant find it.
I am posting the full code, It is probably barely functioning with how much I deleted, and readded things. So some parts might not be needed/functional. I appreciate your help on this so far, I imagine If I don’t have it working by the end of the week, im just gonna abandon the multiplayer part and move on the the next thing to experiment on.

MultiplayerManager code (fun)

extends Node
##type "netstat -aon" on cmd to find open (listening) ports

##Operator setup
var Operator_name : String

##Multiplayer setup
@export var SERVER_PORT = 2004
@export var SERVER_IP= "127.0.0.1"

var server_peer =ENetMultiplayerPeer.new()

##Gathering Hub
@onready var gathering_hub_spawner = get_node("/root/Origin/GatheringHub/gathering_hub_spawner")

func _ready():
	pass

func server_start():
	if not multiplayer.is_server():return
	server_peer.create_server(SERVER_PORT)
	multiplayer.multiplayer_peer=server_peer
	multiplayer.peer_connected.connect(peer_connected)
	multiplayer.peer_disconnected.connect(peer_disconnected)
	multiplayer.connected_to_server.connect(connected_to_server)
	multiplayer.connection_failed.connect(connection_failed)
	for id in multiplayer.get_peers():
		GameManager.call_deferred("add_operator", id)
	if not OS.has_feature("dedicated_server"):GameManager.call("add_operator",1)

##Server and Client Calls
func peer_connected(operator_id):
	GameManager.call("add_operator", operator_id)

func peer_disconnected(operator_id):
	GameManager.call("delete_body", operator_id)

##Client Calls
func connected_to_server():
	pass

func connection_failed():
	pass

##Operator Management
func on_join_button_down():
	server_peer.close()
	var client_peer=ENetMultiplayerPeer.new()
	client_peer.create_client(SERVER_IP,SERVER_PORT)
	multiplayer.multiplayer_peer=client_peer

##Cleaning
#delete body
func erase_corruption():
	var corruption=get_tree().get_nodes_in_group("current body")
	for corrupt in corruption:
		corrupt.queue_free()

#disconnect when leaving
func _exit_tree():
	if not multiplayer.is_server():
		return
	multiplayer.peer_connected.disconnect(peer_connected)
	multiplayer.peer_disconnected.disconnect(peer_disconnected)
	multiplayer.connected_to_server.disconnect(connected_to_server)
	multiplayer.connection_failed.disconnect(connection_failed)

GameManagercode (personally idk why I seperated it from multiplayer code?)

extends Node

var operator_ids = []
var local_operator

var SPAWN_RANDOM = 3
##Gathering Hub
@onready var gathering_hub_spawner = get_node("/root/Origin/GatheringHub/gathering_hub_spawner") ##this might break when portal system is setup

func add_operator(operator_id):
	print("add operator:"+str(operator_id))
	add_body(operator_id)

func add_body(operator_id):
	print("operator ID:", operator_id)
	operator_ids.append(operator_id)
	var operator = preload("res://assets/eye_drone.tscn").instantiate()
	operator.operator=operator_id
	operator.name=str(operator_id)
	var position :=Vector2.from_angle(randf()*2*PI)
	operator.position=Vector3(position.x*SPAWN_RANDOM*randf(),0,position.y*SPAWN_RANDOM)
	gathering_hub_spawner.add_child(operator,true)
	operator.set_multiplayer_authority(operator_id)
	print("Operator added with name:", operator.name,"and authority ID:", operator.get_multiplayer_authority())
	OperatorManager.operator_name=operator.name
	OperatorManager.call_deferred("set_operator_name")
	
	if operator_id == multiplayer.get_unique_id():
		local_operator=operator

func delete_body(operator_id):
	if !gathering_hub_spawner.has_node(str(operator_id)):
		return
	gathering_hub_spawner.get_node(str(operator_id)).queue_free()

@rpc
func add_new_operator(new_operator_id):
	add_operator(new_operator_id)

@rpc
func add_previous_operator(operator_ids):
	for operator_id in operator_ids:
		add_operator(operator_id)

Character Body 3D code (its refered to as Operator because im cringe I guess, and bored lol. trying to use words I might be more familiar with to try and understand this better)

extends CharacterBody3D
## 18.51 video tutorial started getting weird as fuck
# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
##New varialbes (delete extra or unnessassary ones)
var DIRECTION_INTERPOLATE_SPEED=1
var MOTION_INTERPOLATE_SPEED=10
var ROTATION_INTERPOLATE_SPEED=10

var motion=Vector2()
var root_motion=Transform3D()
var orientation=Transform3D()
##Operator
#body
@export var current_body : CharacterBody3D
@export var current_body_model : MeshInstance3D
var forward_direction=Vector3()
#camera

##movement
@export_range(0,100,1) var move_speed:float=50
@export_range(0,100,1) var jump_speed:float=5
##camera

##keyboard
var keyboard_is_rotating_camera:bool=false
## mouse
var mouse_last_position:Vector2=Vector2.ZERO
var mouse_is_rotating_camera:bool=false

##Multiplayer setup
@export var operator :int:
	set(operator_id):
		operator=operator_id
		$PlayerInputSynchronizer.set_multiplayer_authority(operator_id)

@onready var player_input = $PlayerInputSynchronizer

##Delete if fail
func _physics_process(delta):
	if !is_multiplayer_authority():
		return
	movement(delta)
	_apply_input(delta)

# Add the gravity
	if not is_on_floor():
		velocity.y -= gravity * delta
##Inputs
func _apply_input(delta:float):
#movement
	motion=motion.lerp(player_input.input_direction, MOTION_INTERPOLATE_SPEED*delta)

	set_velocity(velocity)
	set_up_direction(Vector3.UP)
	move_and_slide()
	#rpc("remote_set_position", global_position) #this rpc exists because the ServerSyncrhonizer and player input syncronizer are not... connecting?
#cleanup
	#orientation.origin=Vector3()
	#orientation=orientation.orthonormalized()
	#current_body_model.global_transform.basis=orientation.basis
#
#@rpc("unreliable")
#func remote_set_position(authority_position):
	#global_position = authority_position

##Movement
#body input
func movement(delta:float)->void:
	var velocity_direction:Vector3=Vector3.ZERO
	velocity_direction=(transform.basis*Vector3(player_input.input_direction.x,velocity.y,player_input.input_direction.y)).normalized()
	if velocity_direction:
		velocity.x=velocity_direction.x*move_speed*delta
		velocity.y=velocity_direction.y*move_speed*delta
		position+=velocity_direction.normalized()*move_speed*delta #This is the important one!
	current_body.rotation.y=player_input.get_camera_rotation()

player input synchronizer code (still says player instead of operater, its old code)

extends MultiplayerSynchronizer


##Body
var input_direction :=Vector2()

##Camera
@export var camera_base : Node3D
@export var camera_rotation : Node3D
@export var camera : Camera3D
##camera dynamics
@export_range(0.1,10,.1) var camera_smoothing:float=10.0
#camera zoom
var camera_zoom_direction:float=0
@export var camera_zoom_start:float = 5
@export_range(0,100,1) var camera_zoom_speed = 5
@export_range(0,100,0.01) var camera_zoom_min = 0
@export_range(0,100,1) var camera_zoom_max = 10
@export_range(0,2,.1) var camera_zoom_damp:float= .92 #this needs to be less then 1.0
#camera rotate
var camera_rotate_direction:int=0
@export_range(0,10,0.1) var camera_rotate_speed:float= 0.20
@export_range(0,20,1) var camera_rotate_y_speed:float=2
@export_range(0,10,1) var camera_rotate_x_min:float= deg_to_rad(-89.9)
@export_range(0,10,1) var camera_rotate_x_max:float= deg_to_rad(89.9)
##Checks
# mouse
var mouse_last_position:Vector2=Vector2.ZERO
var can_camera_mouse_rotate:bool=true
var mouse_is_rotating_camera:bool=false
#keyboard
var keyboard_is_rotating_camera:bool=true
#camera
var can_camera_move:bool=true
var can_camera_target_move:bool=false
var can_camera_zoom:bool=true
var can_camera_rotate:bool=false
var can_camera_rotate_y:bool=true
var can_camera_rotate_x:bool=true

func _enter_tree():
##Checks Authority
	if get_multiplayer_authority() == multiplayer.get_unique_id():
		camera.make_current()
	else:
		set_process(false)
		set_process_input(false)

func _process(delta):
	body_controller()
	camera_controller(delta)
	is_search_bar_selected()

func body_controller():
	if can_camera_move:
		input_direction=Input.get_vector("moveleft","moveright","moveforward","movebackward")

##Camera
func camera_controller(delta):
	if !can_camera_target_move:
		camera_zoom(delta)
		camera_rotate(delta)
		camera_mouse_rotate(delta)
	if is_mouse_over_menu():
		can_camera_zoom=false
	else:
		can_camera_zoom=true

##Inputs (Keyboard and/or Mouse)
#unhandled input
func _unhandled_input(event)->void:
#zoom
	if event.is_action("zoomin"):
		camera_zoom_direction=-1
	if event.is_action("zoomout"):
		camera_zoom_direction=1
#rotate from keyboard
	if event.is_action_pressed("camerarotateright"):
		camera_rotate_direction=-1
		keyboard_is_rotating_camera=true
	if event.is_action_pressed("camerarotateleft"):
		camera_rotate_direction=1
		keyboard_is_rotating_camera=true
	elif event.is_action_released("camerarotateright") or event.is_action_released("camerarotateleft"):
		keyboard_is_rotating_camera=false
#rotate from mouse
	if event.is_action_pressed("cameramouserotate"):
		mouse_last_position=get_viewport().get_mouse_position()
		mouse_is_rotating_camera=true
	elif event.is_action_released("cameramouserotate"):
		mouse_is_rotating_camera=false

##Camera Zoom
#currently broken
func camera_zoom(delta:float)->void:
	if !can_camera_zoom:
		return
	var new_zoom:float=clamp(camera.position.z+camera_zoom_speed*camera_zoom_direction*delta,camera_zoom_min,camera_zoom_max)
	camera.position.z=new_zoom
	camera_zoom_direction*=camera_zoom_damp

##Camera Rotation
#camera rotate position (y axis)
func camera_rotate(delta:float)->void:
	if !can_camera_rotate or !keyboard_is_rotating_camera:
#print("camera rotate locked") This will spam messages because keyboard_is_rotating_camera unless pressed
		return
#to rotate
	camera_rotate_y(delta, camera_rotate_direction)
	print (camera_rotate_direction)

#camera rotate base (y axis)
func camera_rotate_y(delta:float, direction:float)->void:
	if !can_camera_rotate_y:
		print("camera rotate y locked")
		return
	camera_base.rotation.y+=direction*camera_rotate_y_speed*delta #this turned off stops it from left/right

#camera rotate gimble (x axis)
func camera_rotate_x(delta:float, direction:float)->void:
	if !can_camera_rotate_x:
		print("camera rotate x locked")
		return
	var new_rotation_x:float=camera_rotation.rotation.x #turn off these 4 lines of code and it stops rotating up/down
	new_rotation_x-=direction*camera_rotate_speed*delta
	new_rotation_x=clamp(new_rotation_x,camera_rotate_x_min,camera_rotate_x_max)
	camera_rotation.rotation.x=new_rotation_x

#camera mouse rotate function
func camera_mouse_rotate(delta:float)->void:
	if !can_camera_mouse_rotate or !mouse_is_rotating_camera:
		#print("mouse rotated locked") #it will SPAM messages because mouse_is_rotating_camera function is turned OFF unless pressed
		return
	var mouse_offset:Vector2=get_viewport().get_mouse_position()
	mouse_offset=mouse_offset-mouse_last_position
	mouse_last_position=get_viewport().get_mouse_position()
	camera_rotate_y(delta,mouse_offset.x)
	camera_rotate_x(delta,mouse_offset.y)

func get_camera_rotation_basis()->Basis:
	return camera_base.global_transform.basis

func get_camera_rotation():
	return camera_base.rotation.y

##Menus
func _set_process_input(enable:bool)->void:
	if can_camera_move:
		can_camera_move=enable

#menu search
func is_search_bar_selected():
	var search_bar=get_node_or_null("/root/GatheringHub/main_screen_hud/seed_system_menu_settings/system_menu/Primary Container/primary_path/direction/root_menu_text_display/menu_display_text")
	if search_bar!=null:
		if search_bar.has_focus():
			_set_process_input(false)
		else:
			_set_process_input(true)
	return false

#menu inputs
func is_mouse_over_menu():
	var system_menu=get_node_or_null("/root/GatheringHub/main_screen_hud/seed_system_menu_settings/system_menu/Primary Container")
	var mouse_position=get_viewport().get_mouse_position()
	if system_menu!=null:
		var menu_rectangle=system_menu.get_global_rect()
		if menu_rectangle.has_point(mouse_position):
			return true
	var branch_selection_group_nodes=get_tree().get_nodes_in_group("branch_bud_selection")
	for node in branch_selection_group_nodes:
		var container=node.find_child("PanelContainer")
		if container!=null:
			var panel_rect=container.get_global_rect()
			if panel_rect.has_point(mouse_position):
				return true
	return false

I can’t parse all this.

Let’s work on debugging strategies.

Just so you are aware when running multiple windows from the editor they will share the output for print statements, it will be hard to separate messages unless they have identifying information which game instance they come from.

Errors will be separated by session, but it is almost impossible to know which session is used for host and client.

One solution is to run one instance from command line and the other with the editor.

Those errors are probably important, but it will require knowing if the client or the host is producing them. And also some time reading Godot source code to understand the issue.

… I did end up looking at it some. There is a lot going on. From what I see you have an operator that is given full authority to the client. Body and input. And at least for the physics process only the client will be able to operate it. Replication should then be sending position to the other peers to perceive the movement…

You are potentially checking too early for the client instances authority will still be host auth at this point. Client instance operator_id will not have been set yet? (Unless there is code, not shown, setting the operator_id on the spawned client instance?)

Remember a spawned node will not have any modifications to state made by host when spawned. This means operator_id will be its saved/default value in the inspector.

Setting node name does not automatically set authority. You need to use that name to set authority. Usually in the ready function of the node.

Set_multiplayer_authority(name.to_int())

I watched a few videos and found this https://www.youtube.com/watch?v=w3aMRJWXsSE ← Most other tutorials Did basically the same thing we have been talking about with authority and such but this video did 1 thing different.


func _on_join_button_down():
	MultiplayerManager.erase_corruption()
	MultiplayerManager.on_join_button_down()
	print(get_tree())
	get_tree().change_scene_to_file("res://Origin.tscn")

func _on_invite_button_down():
	MultiplayerManager.erase_corruption()
	MultiplayerManager.server_start()
	get_tree().change_scene_to_file("res://Origin.tscn")

The whole "erase_corruption() thing… is a little redundant now. It was my attempt at deleting the old body of the client so the server can give it a new one. what is interesting is that I can get_tree().change_scene_to_file(“res://Origin.tscn”)<- this code here “resets” the world on both the clients and host and somehow now it works just fine? I think its because technically its no longer
Host-World 1-add new body clients
Host-World 1-Client 1 joins -cant find spawner(error)add new body clients(does it anyways?)-authority problem, host has control
Host-World 1-Client 2 joins -cant find spawner(error)add new body clients(does it anyways?)-authority problem, host has control
Host-World 1-Client 3 joins -cant find spawner(error)add new body clients(does it anyways?)-authority problem, host has control
looking at it this way it seems that the spawner was trying to work in the server, but the spawn was trying to work in the clients at same time? Server spawned something for the clients to use, but clients failed to spawn. because clients failed to spawn they had no authority. Host had empty shells for bodies.
Now I have it set up more like this
Host-World 1->new client joins<generates client bodies (no authority issues)
Host-World 1-join-<deletes personal world-<joins host World 1 -receives body 2 (get authority)
Host-World 1-join<deletes personal world-<joins host World 1 -receives body 3 (get authority)
Host-World 1-join<deletes personal world-<joins host World 1-receives body 4 (get authority)
With this I beleive it is finally resolved.
Thanks for the help, learned quiet a bit about trouble shooting. Using chat box even making the clients print thier unique_ID so I can tell where the bodies are being generated from (its how I figured out the problem)
cant beleive it took just… 1 line of code to figure it out.
I am curious though, why did the clients not automatically join the server world? Why did they try to keep thier own? Most of the tutorials didnt talk about how it functioned, but the get_tree().change_scene_to_file(“res://Origin.tscn”) fixed my problem. Just 1 line of code :upside_down_face:.
Thanks for the help! I really appreciate it.