Hi, I want to get some parameters from the client side on disconnection (for whatever cause, maybe he lost connection, maybe he quit). The parameters I want to pass into the server are stored on the client’s scriptable object. I tried using public override void OnNetworkDespawn(), but it’s triggering too late, so the code inside is not reaching the [ServerRpc].
I also tried the private void OnClientDisconnect(ulong clientId) callback, but only gets triggered on the Host/Server, so there’s no way for me to pass in the data before the client disconnects.
Does anyone have any idea on how to accomplish this?
You have to do that BEFORE the client disconnected.
You gave the best example yourself: “lost connection”. So the client got disconnected because someone pulled the plug on the client’s router. How do you think that client is supposed to send data to the server? It simply cannot.
If you need to persist state, you have to either allow the players to manually persist it, or send it to the server at certain points in time (eg “autosave”, for example on level completion or on a timer) and if it’s an MMO style game you have to continuously update the player’s persistant data, be it kill count, world position, hitpoints. Though you may relax some parameters to update only every couple seconds, such as world position not needing to be persisted 50 times per second, it suffices to do so every 1-5 seconds.
Moreover, MMO style would actually keep the player in the world on the server-side even if the client’s cable got pulled, so the server keeps on simulating the world with that client not doing anything and teammates wondering. This usually leads to the avatar’s death when getting disconnected during combat.
I understand. In my case, I guess I can pass the parameters/data back to the server at the beginning (OnClientConnect) since it is data that will remain immutable throughout.
The reason I didn’t do this is because I still don’t know how to assign data to clients on the server side. I believe it should be somehow basic, but I didn’t come up with anything useful. I haven’t tried it, but I guess I could do something like: “OnClientConnect” I assign the data to that client, and then send a ServerRpc to give that data.
The problem would be: How on Earth can I store this data on the server, do I use the client’s ulong that is passed in on the “OnClientConnect” callback? But if so, I don’t know how to do something like “this ulong gets this data”. And then “OnClientDisconnect” compare the ulong against something to know which client is disconnecting, and therefore do some action with its data (already available on the server side).
Btw it also triggers on the client. You probably have that hooked up incorrectly.
Yes, you would use that ulong clientId for identification while the client is connected (including disconnect callback on server side).
If you need custom identification, such as making sure a player that disconnects and joins again is recognized as a previous (specific) player, then you’d have to find a way for that client to generate a unique identifier.
This could be an automatically generated GUID in its simplest form that gets sent to the server on connection approval and that GUID becomes the key for the Dictionary<GUID, PlayerData> the server uses to store client data. That dictionary could of course also be a database interface or some other way of persisting data.
Alternatively it could be a user account, usually email and (salted, hashed) password.
Could I use a dictionary for this?
For instance, “OnConnectionApproval” I have this line
var clientId = request.ClientNetworkId;
Net.LogInfo($"=> OnConnectionApproval({clientId})");
I guess I could then add this clientId to a dictionary and assign a custom data (in my case I want it to be an integer withdrawn from a list). I’m not sure though what’s the proper way to compare this dictionary’s clientId to the “OnClientDisconnect’s ulong clientId” on disconnection. Maybe something like
private void OnClientDisconnected(ulong clientId)
{
foreach (string key in (myDictionary))
{
if (clientId == myDictionary.Key)
{
//then use something like
"myDictionary.Key.GetValue"
// I haven't used dictionaries yet so this is just
// a pseudocode
}
}
}
So, only 2 things:
(1) is this a proper way to do it? Or is there a simpler way? Also, if I wanted to assign and store more than one parameter, I believe a dictionary is not the way to go
(2) this is regarding:
I don’t know what I might have done wrong, the setup is simple, I have a NetworkBehaviour that subscribes to the callbacks on start:
private void AddNetworkManagerCallbacks()
{
var netMan = NetworkManager.Singleton;
if (netMan != null)
{
Debug.Log("RegisteringCALLBACKS");
// ensure we never register callbacks twice
RemoveNetworkManagerCallbacks();
netMan.OnClientConnectedCallback += OnClientConnected;
netMan.OnClientDisconnectCallback += OnClientDisconnect;
}
}
private void RemoveNetworkManagerCallbacks()
{
var netMan = NetworkManager.Singleton;
if (netMan != null)
{
netMan.OnClientConnectedCallback -= OnClientConnected;
netMan.OnClientDisconnectCallback -= OnClientDisconnect;
}
}
And then, this is the “OnClientDisconnect” callback:
private void OnClientDisconnect(ulong clientId)
{
if (IsServer) return;
Debug.LogWarning($"OwnerClientId({OwnerClientId}) [clientId({clientId})] IS DISCONNECTING and giving back its ID");
ReturningNetworkIDToList_ServerRpc(networkPlayerID - 1);
}
In the Start() method? Maybe try OnEnable, Awake or OnNetworkSpawn. Netcode mentions the execution order somewhere in the docs, when the callbacks run and how the order is different between client and server and I can’t remember it right now.
NetworkManager.Singleton may be null when called at the wrong time (ie too early).
As to the Dictionary: yes, key is the ulong clientId but keep in mind that it will change for a client that disconnects, then reconnects. And to store more than one value, just make a class or struct PlayerData that has all the fields you need to persist and use that as the Dictionary value.
On your project, the “NetcodeBootstrap” script subscribes to this on Start, I assume to give the NetworkManager.Singleton time to be created and therefore not to be null. In fact, I tried this on Awake and it never subscribed.
Hmmm … on second thought … of course, Netcode mentions this in the docs too: you don’t get events for things that were initiated by the local instance. So a client connecting is something the client initiated and thus it may not receive OnClientConnected for itself, only for other clients. I know I ran into this with OnClientDisconnected.
That’s why I made this class for receiving disconnect events from the UI buttons that kick/disconnect clients: