• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

How to properly disconnect a player in a multiplayer game

Hello! So after all these years I'm finally learning how to use multiplayer and buffers in GameMaker!
I have followed the tutorials by GameMakerStation on YouTube however his tutorials don't really cover what to do when a player leaves the server. I have tried to use what I've learned from the connection code. My method is when the host receives the disconnect event it will get the socket of that player and send it to everyone else to delete that players object from their room. But for some reason that doesn't work with more than 2 players. Also I get and error when a player tried to reconnect (which I think might be related somehow?) Anyone with more knowledge on networking able to help me out?
Thanks!

GML:
var _type = async_load[? "type"];

if (_type = network_type_connect)
{
    var _socket = async_load[? "socket"];
    
    //send player info
    var _buffer = buffer_create(3,buffer_fixed,1);
    
    buffer_write(_buffer,buffer_u8,DATA.INIT_DATA);
    buffer_write(_buffer,buffer_u8,ds_list_size(clients));
    
    network_send_packet(_socket,_buffer,buffer_get_size(_buffer));
    buffer_delete(_buffer);
    
    //create player instance
    var _player = instance_create_layer(obj_start.x,obj_start.y,"Instances",obj_player);
    _player.player_id = ds_list_size(clients);
    _player.is_local = false;
    
    //let other clients know
    var _buffer = buffer_create(2,buffer_fixed,1);
    buffer_write(_buffer,buffer_u8,DATA.PLAYER_JOINED);
    buffer_write(_buffer,buffer_u8,_player.player_id);
    
    for (var i=0;i<ds_list_size(clients);i++)
    {
        var soc = clients[| i];
        
        if (soc < 0) continue;
        
        network_send_packet(soc, _buffer, buffer_get_size(_buffer));
    }
    buffer_delete(_buffer);
    
    //add to list
    ds_list_add(clients,_socket);
}

//Data
else if (_type == network_type_data)
{
    var _buffer = async_load[? "buffer"];
    
    buffer_seek(_buffer, buffer_seek_start,0);
    
    var _data = buffer_read(_buffer,buffer_u8);
    
    //init Data
    if (_data == DATA.INIT_DATA)
    {
        var _count = buffer_read(_buffer,buffer_u8);
        
        //set players id
        obj_player.player_id = _count;
        
        //create other players
        for (var i=0;i<_count;i++)
        {
            var _player = instance_create_layer(obj_start.x,obj_start.y,"Instances",obj_player);
            _player.player_id = i;
            _player.is_local = false;
        }
    }
    //player update
    else if (_data == DATA.PLAYER_UPDATE)
    {
        var pID = buffer_read(_buffer,buffer_u8);
        
        with (obj_player)
        {
            if (pID = player_id)
            {
                x = buffer_read(_buffer,buffer_s16);
                y = buffer_read(_buffer,buffer_s16);
                image_index = buffer_read(_buffer,buffer_u8);
            }
        }
        //server forwards player positions
        if (is_server)
        {
            for (var i=0; i<ds_list_size(clients);i++)
            {
                var soc = clients[| i];
                //make sure socket is not the one that sent the data
                if (soc < 0 || soc == async_load[? "id"]) continue;
                
                network_send_packet(soc,_buffer,buffer_get_size(_buffer));
            }
        }
    }
    //player joined
    else if (_data == DATA.PLAYER_JOINED)
    {
        var inst = instance_create_layer(0,0,"Instances",obj_player);
        inst.player_id = buffer_read(_buffer,buffer_u8);
        inst.is_local = false;
    }
    //player left game
    else if (_data == DATA.PLAYER_DISCONNECTED)
    {
        var _leavingPlayer = buffer_read(_buffer,buffer_u8);
        with (obj_player)
        {
            if (player_id == _leavingPlayer)
            {
                instance_destroy();
                exit;
            }
        }
    
    }
}

//disconnect
else if (_type = network_type_disconnect)
{
    var _socket = async_load[? "socket"];
    //var _clientList = clients;
    var _leavingPlayerID = ds_list_find_index(clients,_socket);
    with (obj_player)
    {
        if (player_id == _leavingPlayerID)
        {
            instance_destroy();   
            exit;
        }
    }
    
    //let other clients know
    var _buffer = buffer_create(2,buffer_fixed,1);
    buffer_write(_buffer,buffer_u8,DATA.PLAYER_DISCONNECTED);
    buffer_write(_buffer,buffer_u8,_leavingPlayerID);
    
    for (var i=0;i<ds_list_size(clients);i++)
    {
        var soc = clients[| i];
        
        if (soc < 0) continue;
        
        network_send_packet(soc, _buffer, buffer_get_size(_buffer));
    }
    buffer_delete(_buffer);
    
    //remove from client list
    ds_list_delete(clients,_leavingPlayer);
}
 

Nidoking

Member
One thing that stands out immediately - surely, you want to remove the disconnected player BEFORE sending the disconnect message to all connected players. No use sending a message to the client that disconnected, and it may be causing your error. (Hard to say with all the detail of "and error" but no information on what the error is.)

Oh, and you're exiting the event when you destroy the player instance, so you're not even sending the message to any clients. You appear to be confusing "exit" with "break". They're not interchangeable.
 
One thing that stands out immediately - surely, you want to remove the disconnected player BEFORE sending the disconnect message to all connected players. No use sending a message to the client that disconnected, and it may be causing your error. (Hard to say with all the detail of "and error" but no information on what the error is.)

Oh, and you're exiting the event when you destroy the player instance, so you're not even sending the message to any clients. You appear to be confusing "exit" with "break". They're not interchangeable.
Wow I can't believe I mixed up break and exit! Thanks for pointing that out. 😅 I have been building it each time I run to test multiplayer in 2 windows so I haven't been able to use the debugger which I'm sure would have helped!😬
Thanks for you help I'll try this out when I'm back at my pc👍
 
Ok so I've tried something different and fixed the exit/break mixup. Now however I get this error as soon as a player leaves:

___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Async Event: Networking
for object obj_multiplayer_controller:

local variable _leavingPlayer(100011, -2147483648) not set before reading it.
at gml_Object_obj_multiplayer_controller_Other_68
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Object_obj_multiplayer_controller_Other_68 (line -1)

My current code looks like this:
GML:
var _type = async_load[? "type"];

if (_type = network_type_connect)
{
    var _socket = async_load[? "socket"];
   
    //send player info
    var _buffer = buffer_create(3,buffer_fixed,1);
   
    buffer_write(_buffer,buffer_u8,DATA.INIT_DATA);
    buffer_write(_buffer,buffer_u8,ds_list_size(clients));
   
    network_send_packet(_socket,_buffer,buffer_get_size(_buffer));
    buffer_delete(_buffer);
   
    //create player instance
    var _player = instance_create_layer(obj_start.x,obj_start.y,"Instances",obj_player);
    _player.player_id = ds_list_size(clients);
    _player.is_local = false;
   
    //let other clients know
    var _buffer = buffer_create(2,buffer_fixed,1);
    buffer_write(_buffer,buffer_u8,DATA.PLAYER_JOINED);
    buffer_write(_buffer,buffer_u8,_player.player_id);
   
    for (var i=0;i<ds_list_size(clients);i++)
    {
        var soc = clients[| i];
       
        if (soc < 0) continue;
       
        network_send_packet(soc, _buffer, buffer_get_size(_buffer));
    }
    buffer_delete(_buffer);
   
    //add to list
    ds_list_add(clients,_socket);
}

//Data
else if (_type == network_type_data)
{
    var _buffer = async_load[? "buffer"];
   
    buffer_seek(_buffer, buffer_seek_start,0);
   
    var _data = buffer_read(_buffer,buffer_u8);
   
    //init Data
    if (_data == DATA.INIT_DATA)
    {
        var _count = buffer_read(_buffer,buffer_u8);
       
        //set players id
        obj_player.player_id = _count;
       
        //create other players
        for (var i=0;i<_count;i++)
        {
            var _player = instance_create_layer(obj_start.x,obj_start.y,"Instances",obj_player);
            _player.player_id = i;
            _player.is_local = false;
        }
    }
    //player update
    else if (_data == DATA.PLAYER_UPDATE)
    {
        var pID = buffer_read(_buffer,buffer_u8);
       
        with (obj_player)
        {
            if (pID = player_id)
            {
                x = buffer_read(_buffer,buffer_s16);
                y = buffer_read(_buffer,buffer_s16);
                image_index = buffer_read(_buffer,buffer_u8);
            }
        }
        //server forwards player positions
        if (is_server)
        {
            for (var i=0; i<ds_list_size(clients);i++)
            {
                var soc = clients[| i];
                //make sure socket is not the one that sent the data
                if (soc < 0 || soc == async_load[? "id"]) continue;
               
                network_send_packet(soc,_buffer,buffer_get_size(_buffer));
            }
        }
    }
    //player joined
    else if (_data == DATA.PLAYER_JOINED)
    {
        var inst = instance_create_layer(0,0,"Instances",obj_player);
        inst.player_id = buffer_read(_buffer,buffer_u8);
        inst.is_local = false;
    }
    //player left game
    else if (_data == DATA.PLAYER_DISCONNECTED)
    {
        var _leavingPlayer = buffer_read(_buffer,buffer_u8);
        with (obj_player)
        {
            if (player_id == ds_list_find_index(clients,_leavingPlayer))
            {
                instance_destroy();
                break;
            }
        }
   
    }
}

//disconnect
else if (_type = network_type_disconnect)
{
    var _socket = async_load[? "socket"];
    //var _clientList = clients;
    var _leavingPlayerID = ds_list_find_index(clients,_socket);
    with (obj_player)
    {
        if (player_id == _leavingPlayerID)
        {
            instance_destroy();  
            break;
        }
    }
   
    //let other clients know
    var _buffer = buffer_create(2,buffer_fixed,1);
    buffer_write(_buffer,buffer_u8,DATA.PLAYER_DISCONNECTED);
    buffer_write(_buffer,buffer_u8,_socket);
   
    //remove from client list
    ds_list_delete(clients,_leavingPlayer);
   
    //send data  
    for (var i=0;i<ds_list_size(clients);i++)
    {
        var soc = clients[| i];
       
        if (soc < 0) continue;
       
        network_send_packet(soc, _buffer, buffer_get_size(_buffer));
    }
    buffer_delete(_buffer);
}
I changed it so that the server sends all the clients the disconnecting socket. The clients then find the object with the id relating to that socket in their clients list and destroy it.
So far all the other multiplayer seems to work fine, just this not working when disconnecting. Maybe there is a different method altogether I should use?
 

Sotsog

Member
I notice that you are probably using Matharoo's networking code as a base which means that my own code is structurally similar. I got disconnect to work for at least 4 players. So if you or someone else still need help, here is my solution. I still haven't worked out how to properly remove the disconnected player from the Clients List however.

Code block: Server detect player disconnect and forward the info to all clients.
GML:
else if (type == network_type_disconnect)
    {
    var _cliSockID = async_load[? "socket"];
    
    for (var i=0; i<ds_list_size(clientsList); i++)
        {
        if (clientsList[| i] == _cliSockID)
            {
            var pID = i;
            with (oPlayer)
                {
                if (pID == playerID) instance_destroy();
                }
            }
        }
    #region INFORM ALL CLIENTS THAT A CLIENT HAS LEFT THE SERVER
            var bufferPlayerLeft = buffer_create(2, buffer_fixed, 1);
    
            buffer_write(bufferPlayerLeft, buffer_u8, DATA.PLAYER_DISCONNECT);
            buffer_write(bufferPlayerLeft, buffer_u8, pID);
    
            for (var i=0; i < ds_list_size(clientsList); i++)
            {
                var _cliSockID = clientsList[| i];
            
                if (_cliSockID < 0) continue;
        
                network_send_packet(_cliSockID, bufferPlayerLeft, buffer_get_size(bufferPlayerLeft));
            }
            buffer_delete(bufferPlayerLeft);
    #endregion
    }
Code block: And then for the clients to receive and interpret the packet.
GML:
    #region BUFFER TYPE: PLAYER_DISCONNECT
        if (data == DATA.PLAYER_DISCONNECT)
            {
            var pIDdisc = buffer_read(buffer,buffer_u8);
            with (oPlayer)
                {
                if (pIDdisc == playerID) instance_destroy();
                }
            }
    #endregion
 

Sotsog

Member
Ok so I've tried something different and fixed the exit/break mixup. Now however I get this error as soon as a player leaves:

___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Async Event: Networking
for object obj_multiplayer_controller:

local variable _leavingPlayer(100011, -2147483648) not set before reading it.
at gml_Object_obj_multiplayer_controller_Other_68
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Object_obj_multiplayer_controller_Other_68 (line -1)

My current code looks like this:
GML:
var _type = async_load[? "type"];

if (_type = network_type_connect)
{
    var _socket = async_load[? "socket"];
  
    //send player info
    var _buffer = buffer_create(3,buffer_fixed,1);
  
    buffer_write(_buffer,buffer_u8,DATA.INIT_DATA);
    buffer_write(_buffer,buffer_u8,ds_list_size(clients));
  
    network_send_packet(_socket,_buffer,buffer_get_size(_buffer));
    buffer_delete(_buffer);
  
    //create player instance
    var _player = instance_create_layer(obj_start.x,obj_start.y,"Instances",obj_player);
    _player.player_id = ds_list_size(clients);
    _player.is_local = false;
  
    //let other clients know
    var _buffer = buffer_create(2,buffer_fixed,1);
    buffer_write(_buffer,buffer_u8,DATA.PLAYER_JOINED);
    buffer_write(_buffer,buffer_u8,_player.player_id);
  
    for (var i=0;i<ds_list_size(clients);i++)
    {
        var soc = clients[| i];
      
        if (soc < 0) continue;
      
        network_send_packet(soc, _buffer, buffer_get_size(_buffer));
    }
    buffer_delete(_buffer);
  
    //add to list
    ds_list_add(clients,_socket);
}

//Data
else if (_type == network_type_data)
{
    var _buffer = async_load[? "buffer"];
  
    buffer_seek(_buffer, buffer_seek_start,0);
  
    var _data = buffer_read(_buffer,buffer_u8);
  
    //init Data
    if (_data == DATA.INIT_DATA)
    {
        var _count = buffer_read(_buffer,buffer_u8);
      
        //set players id
        obj_player.player_id = _count;
      
        //create other players
        for (var i=0;i<_count;i++)
        {
            var _player = instance_create_layer(obj_start.x,obj_start.y,"Instances",obj_player);
            _player.player_id = i;
            _player.is_local = false;
        }
    }
    //player update
    else if (_data == DATA.PLAYER_UPDATE)
    {
        var pID = buffer_read(_buffer,buffer_u8);
      
        with (obj_player)
        {
            if (pID = player_id)
            {
                x = buffer_read(_buffer,buffer_s16);
                y = buffer_read(_buffer,buffer_s16);
                image_index = buffer_read(_buffer,buffer_u8);
            }
        }
        //server forwards player positions
        if (is_server)
        {
            for (var i=0; i<ds_list_size(clients);i++)
            {
                var soc = clients[| i];
                //make sure socket is not the one that sent the data
                if (soc < 0 || soc == async_load[? "id"]) continue;
              
                network_send_packet(soc,_buffer,buffer_get_size(_buffer));
            }
        }
    }
    //player joined
    else if (_data == DATA.PLAYER_JOINED)
    {
        var inst = instance_create_layer(0,0,"Instances",obj_player);
        inst.player_id = buffer_read(_buffer,buffer_u8);
        inst.is_local = false;
    }
    //player left game
    else if (_data == DATA.PLAYER_DISCONNECTED)
    {
        var _leavingPlayer = buffer_read(_buffer,buffer_u8);
        with (obj_player)
        {
            if (player_id == ds_list_find_index(clients,_leavingPlayer))
            {
                instance_destroy();
                break;
            }
        }
  
    }
}

//disconnect
else if (_type = network_type_disconnect)
{
    var _socket = async_load[? "socket"];
    //var _clientList = clients;
    var _leavingPlayerID = ds_list_find_index(clients,_socket);
    with (obj_player)
    {
        if (player_id == _leavingPlayerID)
        {
            instance_destroy(); 
            break;
        }
    }
  
    //let other clients know
    var _buffer = buffer_create(2,buffer_fixed,1);
    buffer_write(_buffer,buffer_u8,DATA.PLAYER_DISCONNECTED);
    buffer_write(_buffer,buffer_u8,_socket);
  
    //remove from client list
    ds_list_delete(clients,_leavingPlayer);
  
    //send data 
    for (var i=0;i<ds_list_size(clients);i++)
    {
        var soc = clients[| i];
      
        if (soc < 0) continue;
      
        network_send_packet(soc, _buffer, buffer_get_size(_buffer));
    }
    buffer_delete(_buffer);
}
I changed it so that the server sends all the clients the disconnecting socket. The clients then find the object with the id relating to that socket in their clients list and destroy it.
So far all the other multiplayer seems to work fine, just this not working when disconnecting. Maybe there is a different method altogether I should use?
Do the Clients really have access to the Clients List though? Isn't it only defined for the server? If I am right then that is one reason why your code block for DATA.PLAYER_DISCONNECTED is not working.
 

Sotsog

Member
They should have *A* list of clients. What else is the player interacting with?
Thanks for replying to a revived thread, and please correct me if I'm wrong. I think the answer is that the local player is interacting with other player objects based solely on player.ids and not via a ds_list. Perhaps it can be considered *a* list of sorts. I'll try my best to explain what I see...
  1. A client connects to the server. Server adds client socket to "clientsList" and sends an updated total player count (=ds_list_size(clientsList)) to all clients.
  2. Each client, including the server client, creates an instance of object "player" and assigns player.id = total player count. There is a mechanism to ensure that each client can only control their own player object. Non-local player objects positions are updated using player.ids, see DATA.PLAYER_UPDATE in original post.
  3. When a player disconnects, the server checks disconnecting socket and looks up player id from clientsList.
  4. Server sends out player.id of the disconnected player to all clients. Player object instance with corresponding player.id is destroyed locally on both server and clients.
There's no ds_list_delete call anywhere. Where did you expect the entry in the list to be deleted if you weren't deleting the entry from the list?
I didn't say that I expected it to happen in my code. I agree that the entry should be deleted from the ds_list.

I did some testing on ds_list_delete and can now say this.
  • If: MyList = [-1,1,2,3].
  • Then: pos = ds_list_find_index(clientsList,1) returns pos = 1 (index position of value 1);
  • So that: ds_list_delete(MyList,pos) returns MyList = [0, 2, 3].
  • Doing that again: pos = ds_list_find_index(clientsList,2) returns pos = 1 (index position of value 2);
  • So that: ds_list_delete(MyList,pos) returns MyList = [0, 3].
In conclusion the index shifting stumped me. Funny how that works... So I figured it out. This is where ds_list_delete goes. I just had to make sure to delete the index position with stored value player.id (guh).
GML:
else if (type == network_type_disconnect)
    {
    var _cliSockID = async_load[? "socket"];
    discText = true;    // show on oGUI
    var pID = -2;

    #region DESTROY DISCONNECTED PLAYER INSTANCE FROM SERVER CLIENT
    for (var i=0; i<ds_list_size(clientsList); i++)
        {
        if (clientsList[| i] == _cliSockID)
            {
            pID = _cliSockID;
            with (oPlayer)
                {
                if (pID == playerID)
                    {
                    instance_destroy();
                    }
                }
            }
        }
    #endregion

    var pos = ds_list_find_index(clientsList,pID);
    ds_list_delete(clientsList,pos);
   
    #region INFORM ALL CLIENTS THAT A CLIENT HAS LEFT THE SERVER
        var bufferPlayerLeft = buffer_create(2, buffer_fixed, 1);

        buffer_write(bufferPlayerLeft, buffer_u8, DATA.PLAYER_DISCONNECT);
        buffer_write(bufferPlayerLeft, buffer_u8, _cliSockID);

        for (var i=0; i < ds_list_size(clientsList); i++)
        {
            var _cliSockID = clientsList[| i];
       
            if (_cliSockID < 0) continue;
   
            network_send_packet(_cliSockID, bufferPlayerLeft, buffer_get_size(bufferPlayerLeft));
        }
        buffer_delete(bufferPlayerLeft);
    #endregion

    }
 
Last edited:
Top