I have genuinely not found a solution for this anywhere and I feel like the solution potentially may be simple but for the life of me I can’t figure it out.
The problem is that when I use an rpc to get player information it just sends it to the players own array, rather than the hosts and I’m so confused why, I may just be going about it the wrong way entirely
func _add_player_to_game(id: int):
Global.player_joined.emit()
print("Player %s joined the game!" % str(id))
var player_to_add = multiplayer_scene.instantiate()
player_to_add.player_id = id
SendPlayerInformation.rpc(Global.Username,player_to_add.player_id)
@rpc("any_peer")
func SendPlayerInformation(name, id):
name = Global.Username
if !GlobalMissionManager.Players.has(id):
GlobalMissionManager.Players[id] ={
"name": name,
"id": id,
"level": Global.Player_Level
}
if multiplayer.is_server():
for i in GlobalMissionManager.Players:
SendPlayerInformation.rpc(GlobalMissionManager.Players[i].name, i)
The code probably has alot of unnecessary fluff because of the frantic tweaking I’ve done
but on both the host and client I have a button which prints out the player information array and they just print out their own information rather than adding it to a collective array like it does if I remove the rpc, however if I remove the rpc everyones information but the ID stays the same, I am genuinely so confused, if I need to elaborate please say and I will
Okay so I’ve made a bit of progress but now it’s down to sharing the data amongst peers,
Here is how I’ve changed my code
func _add_player_to_game(id: int):
print("Player %s joined the game!" % str(id))
var player_to_add = multiplayer_scene.instantiate()
player_to_add.player_id = id
SendPlayerInformation(Global.Username, player_to_add.player_id)
Update_Info.rpc()
@rpc("any_peer")
func SendPlayerInformation(name, id):
if !GlobalMissionManager.Players.has(id):
GlobalMissionManager.Players[id] ={
"name": name,
"id": id,
"level": 1
}
if multiplayer.is_server():
for i in GlobalMissionManager.Players:
SendPlayerInformation.rpc(GlobalMissionManager.Players[i].name, i)
@rpc("call_local", "any_peer")
func Update_Info():
for i in GlobalMissionManager.Players:
if GlobalMissionManager.Players[i].id == multiplayer.get_unique_id():
GlobalMissionManager.Players[i].name = Global.Username
GlobalMissionManager.Players[i].level = Global.Player_Level
Rather than the send player information being done per peer it is sent out via the host and then using ‘Update _Info()’ it updates the info accordingly, however now I’m stuck on how to change everyone elses Dictionary as well, for context GlobalMissionManager.Players is a dictionary. I have a feeling it is because there may be something I just cant figure out about RPCs but I have no clue
Assuming this function is run only when a player connects to the server (and thus only on the server) your RPC is set to "any_peer" but it’s only ever called by the server and thus (probably) the authority, so "any_peer" is unnecessary; since the RPC annotation does not have "call_local" the function will not be called locally (on the server) only called on connected clients.
You will have to RPC with any data you want to send as arguments, between those parenthesis is your gateway to the world wide web. So on this line you are sending the server’s Global.Username, and this player_to_add.player_id to everyone else connected.
Upon recieving the RPC the connected clients override name with their own Global.Username, then store their own Global.Player_Level into the dictionary. The last if is never run because they are not the server, and it is not set to "call_local".
It sounds like you want to flip who is RPC’ing who here. The players should be using SendPlayerInformation.rpc(Global.Username) when they connected to a server, or when most appropriate after connecting to a server, such as _ready() on the next scene. Then you can use get_remote_sender_id() to find the id of who sent the RPC.
Hey! I’m super appreciative of the response and I’ll provide further my full script for multiplayer, it’s pretty messy and likely has alot of unnecessary code because I’ve completely thrown myself into the deep end here and don’t completely understand everything
extends Node
const SERVER_PORT = 8080
const SERVER_IP = "127.0.0.1"
var multiplayer_scene = preload("res://Scenes/player.tscn")
var multiplayer_peer: ENetMultiplayerPeer = ENetMultiplayerPeer.new()
var _players_spawn_node
var player_list = {}
func become_host():
print("Starting host!")
multiplayer_peer.create_server(SERVER_PORT)
multiplayer.multiplayer_peer = multiplayer_peer
multiplayer.peer_connected.connect(_add_player_to_game)
multiplayer.peer_disconnected.connect(_del_player)
if not OS.has_feature("dedicated_server"):
_add_player_to_game(1)
func join_as_client(lobby_id):
print("Player joining")
multiplayer_peer.create_client(SERVER_IP, SERVER_PORT)
multiplayer.multiplayer_peer = multiplayer_peer
func _add_player_to_game(id: int):
print("Player %s joined the game!" % str(id))
var player_to_add = multiplayer_scene.instantiate()
player_to_add.player_id = id
SendPlayerInformation(Global.Username, player_to_add.player_id)
rpc("Update_Info")
func _del_player(id: int):
print("Player %s left the game!" %id)
if not _players_spawn_node.has_node(str(id)):
return
_players_spawn_node.get_node(str(id)).queue_free()
@rpc("any_peer")
func SendPlayerInformation(name, id):
if !GlobalMissionManager.Players.has(id):
GlobalMissionManager.Players[id] ={
"name": name,
"id": id,
"level": 1
}
if multiplayer.is_server():
for i in GlobalMissionManager.Players:
SendPlayerInformation.rpc(GlobalMissionManager.Players[i].name, i)
@rpc("any_peer", "call_remote", "reliable")
func Update_Info():
for i in GlobalMissionManager.Players:
if GlobalMissionManager.Players[i].id == multiplayer.get_unique_id():
GlobalMissionManager.set_multiplayer_authority(multiplayer.get_unique_id())
GlobalMissionManager.Players[i].name = Global.Username
GlobalMissionManager.Players[i].level = Global.Player_Level
to my knowledge ‘_add_player_to_game’ is run by everyone who joins, I may be wrong though, also if I ever set ‘SendPlayerInformation’ to “call_local” it just closes the second I press host without an error message
in its current state the update info actually works but only for the peer that it comes from, it is not updated for any of the other peers or host
This is because it’s infinitely recursive, the RPC layer is messing with error messages but normally you should recieve a stack overflow, the function calls itself for every key in the Players dictionary
This is because it’s infinitely recursive, the RPC layer is messing with error messages but normally you should recieve a stack overflow, the function calls itself for every key in the Players dictionary
Okay that makes total sense, although attempting to remove it and replace it with “call_local” breaks my code due to it not being re-sent to the others
if multiplayer.peer_connected.connect(_add_player_to_game) doesn’t call it from the peers, how would I be able to call specific functions from the connected peers? sorry I’m still really new to multiplayer programming
Oh and I want to add GlobalMissionManager is an autoload
That’s part of why I think you want to change who is RPC’ing whom here. The clients should RPC when they connect to the server, not the server RPCing when a client connects; the server also does not have the client’s data to send to others.
The peer_connected signal does exist and can be connected to clients, but you only connect it in your become_host function so only the host is running the _add_player_to_game function. You can also connect it in your join_as_client function, but I do not believe it will help you in this scenario.
Sorry for being so slow with this, how would I go about changing who RPCs who? Would it be by connecting the peer connected signal to the ‘join_as_client’ function or something else entirely
And if I were to do this would it work in a Peer to Peer game?
Currently only your server is sending the RPC; you need clients to send a RPC with their data. I would recommend adding a @rpc("any_peer") function to your global mission manager for adding player data. With "any_peer" anyone can call the RPC regardless of authority and it will replicate to all other players like a normal .rpc call.
# in your global mission manager
@rpc("any_peer")
func SendPlayerInformation(name: String) -> void:
var id := get_remote_sender_id()
GlobalMissionManager.Players[id] ={
"name": name,
"id": id,
"level": 1
}
# in your player script/scene.
func _ready() -> void:
GlobalMissionManager.SendPlayerInformation.rpc(Global.Username)
# or if you do not instantiate a scene for the players (it seems like you do,
# but do not add it as a child) you can connect to `connected_to_server`
func join_as_client(lobby_id):
print("Player joining")
multiplayer_peer.create_client(SERVER_IP, SERVER_PORT)
multiplayer.multiplayer_peer = multiplayer_peer
var send_data: Callable = GlobalMissionManager.SendPlayerInformation.rpc.bind(Global.Username)
multiplayer.connected_to_server.connect(send_data, CONNECT_ONE_SHOT)
because I get an error if I do var send_data: Callable = GlobalMissionManager.SendPlayerInformation.rpc(Global.Username) or var send_data: Callable = GlobalMissionManager.SendPlayerInformation.rpc.bind(Global.Username)
now whenever a player joins the game they get their own dictionary and don’t send it to the host which is a massive problem because the host needs it the player data to be able to spawn everyones character. Is the problem because I had to change the format? Or is there a way for the host to collect the info?
the error for var send_data: Callable = GlobalMissionManager.SendPlayerInformation.rpc.bind(Global.Username) is:
‘Error at (32, 74): Cannot find property “rpc” on base “Callable”’
could it be the version of Godot I’m using?
Oh and the error for just doing var send_data: Callable = GlobalMissionManager.SendPlayerInformation.rpc(Global.Username) is:
‘Error at (32, 31): cannot get return value of call to “rpc()” because it returns “void”’
I tested this and it left the clients with a completely empty dictionary unfortunately, I need them to all have identical dictionaries that they all can effect. But I’m really appreciating the help
Thinking about it more the CONNECT_ONE_SHOT shouldn’t be there, maybe I wanted to avoid N^2 client RPCs every time someone connects. Maybe the clients don’t trigger peer_connected either, worth throwing a print statement on it to see.
Check if the server has a full dictionary; then the next step is sending that dictionary to newly connecting players, after they submit their information to the server. you can use rpc_id() to only send to the new player id.
Oh yeah I have print statements, it’s how I know the state of the host or clients dictionaries and the host doesn’t have a full dictionary, but you are actually completely correct multiplayer.peer_connected.connect() hasn’t been working at all. I just did:
I think this is a great option for submitting user information to the server.
I see your vision now with using the same function for server ⇆ client exchanges. I think this will work for what you want? The client submits it’s information, including to itself, then the server sends it to the other players.
@rpc("any_peer")
func SendPlayerInformation(name, id):
GlobalMissionManager.Players[id] ={
"name": name,
"id": id,
"level": 1,
}
if multiplayer.is_server():
# Send new information to all clients
SendPlayerInformation.rpc(name, id)
for i in GlobalMissionManager.Players:
# Send all stored server data to new client, may be better to
# have a separate function for sending the entire dictionary
# instead of entry by entry
var player_info: Dictionary = GlobalMissionManager.Players[i]
SendPlayerInformation.rpc_id(id, player_info.name)
# in client connection
multiplayer.connected_to_server.connect(func test() -> void:
var id := multiplayer.get_unique_id()
# Send our information to server
GlobalMissionManager.SendPlayerInformation.rpc_id(1, Global.Username, id)
)
YOU’RE A LEGEND! THANK YOU SO MUCH! I just did a very very small edit to that code and now it works! genuinely thank you so much!
@rpc("any_peer")
func SendPlayerInformation(name: String, id, level) -> void:
if !GlobalMissionManager.Players.has(id):
GlobalMissionManager.Players[id] ={
"name": name,
"id": id,
"level": level
}
if multiplayer.is_server():
SendPlayerInformation.rpc(name, id)
for i in GlobalMissionManager.Players:
var player_info: Dictionary = GlobalMissionManager.Players[i]
SendPlayerInformation.rpc(player_info.name, player_info.id, player_info.level)
I ended it with ordinary RPC so it gave it to everyone and I tested it with 4 players so it works and tested level and it’s expandable! OMG thank you soooo much you’re a life saver