39DLL Tutorial

THIS ARTICLE IS UNDER CONSTRUCTION!

39DLL is a powerful DLL written by 39ster that allows access to Windows Sockets which allows you to make fast and reliable multiplayer games in Game Maker.

Getting Started
Open up DllScripts.gm6 which can be found in the DLL folder. Copy over 39dll.dll and src as well into your game's directory.

Constants
You will want to set a number of constants. They will be used to distinguish between the different types of message (eg. chat message, player position, etc.). Go to the Resources menu and then to Define Constants. Here are the constants you will want to set:

Messages
Messages are 39DLL's ways of communication between the server and clients. They are made up of different "sections" of memory (from our viewpoint) and are a little different than the way Game Maker handles its variables.

clearbuffer
This function simply removes all the data (if there is any) from the current buffer. It takes an optional argument that specifies a custom buffer, which we will not use.

sendmessage(sock)
This is a very important function: it sends the buffer through a certain socket. Like clearbuffer, it can specify a custom buffer.

receivemessage(sock)
This is the counterpart of sendmessage; it receives messages from a certain socket.

Writing Data
All of these functions write to the buffer. They can also specify a custom buffer. The most common thing to do with messages is to incorporate a Message ID into the message. These will simply tell the receiver what type of message was sent, eg. position, sprite, etc. Here are the most common write functions:
 * 1) writebyte: Writes a byte of memory to the buffer. Supports values 0 to 255 (integers).
 * 2) writeshort: Writes two bytes of memory to the buffer. Supports values -32512 to 32512 (integers).
 * 3) writeushort: Like writeshort, but cannot be negative. Thus, it can support values 0 to 65025 (integers).  Any data type that can be negative also doubles in capacity unsigned.
 * 4) writeint: Writes four bytes of memory to the buffer. Supports values -2114125312 to 2114125312 (integers).
 * 5) writefloat: Writes four bytes of memory to the buffer, but it supports decimals.
 * 6) writedouble: Writes eight bytes of memory to the buffer, and supports decimals. This will support any number Game Maker can.
 * 7) writestring: Writes string_length+1 bytes of memory to the buffer. Supports any length of string.

Reading Data
All of these functions read from the buffer. They, too, can specify a custom buffer. The important thing to remember is that you should read and write in the same order. There is a write function for every read function. Here is an example of how to write and read a single message:

Sending: clearbuffer; writeshort(x); writeshort(y); writebyte(sprite_index); writebyte(health); sendmessage(server); Recieving: recievemessage(client); x = readshort; y = readshort; sprite_index = readbyte; health = readbyte;

The Server
The server allows people to connect and play your game. It will handle all the different clients connected and deal with messages from the clients. Note: If you are hosting the server and connecting to people over the internet, you will need to port-forward.

Initializing the DLL
You must initialize the DLL before it can be used. To do this, create a new object (obj_server) and put this in the Create Event: dllinit(0,1,0); This function loads up 39DLL so it can be used in your game. It takes 3 arguments.
 * The first argument specifies the name of the DLL. Leave it as 0 unless you change the name of the DLL.  If so, the format is: "name.dll"
 * The second is if you want to use the socket functions of the DLL. Of course we do, so make it 1 (or true).
 * The third is if you want to use the encrypting and file functions. We don't need those now, so leave it as 0 (or false).

Setting up a connection
Now we have to set up a socket to listen to. listen = tcplisten(45645,4,1); This function will return the socket we are listening to so it can be used in tcpaccept. This function will return a value larger than 0 if it was successful, so use this code to check for errors. if listen <= 0 { show_message("Unable to listen to port 45645."); game_end; }
 * The first argument is the port number. Keep it between 10000 and 50000 for simplicity.
 * The second is the maximum amount of clients that can be accepted at a time. You may keep it low as usually only one person will try to connect at a time.  WARNING! This is not the maximum amount of players allowed in the game!
 * The third specifies non-blocking. If it is set to 0, the game will freeze if there are no attempted connections, which is not what we want, so set it to 1.

The Create Event should look something like this: //dll dllinit(0,1,0); //listening socket listen = tcplisten(45645,4,1); //error if listen <= 0 { show_message("Unable to listen to port 45645."); game_end; }

Accepting new players
We now need to check if someone is trying to connect to the game, and then accept them. In the Step Event of obj_server, we use this code: var sock; sock = tcpaccept(listen,1); if sock <= 0 exit; This function returns the socket of the client trying to connect, or a value <= 0 if no one tried to connect. We will now create a client object for the connecting player. First, we will make a script.
 * The first argument is the listening socket. This will always be the value returned by tcplisten.
 * The second also specifies non-blocking. In this case it will freeze the game if no message is being received.  We do not want this; set it to 1.

get_mid var tid,taken; tid = 0; taken = 1; while(taken) { tid += 1; with obj_client if mid != tid { taken = 0; break; } } return tid; This script will return an unused id for the client to use. These ids will be used to send messages to certain players only. They will always be at least 1.

Now, put this in the same event: var o,tid; tid = get_mid; o = instance_create(0,0,obj_client); o.mid = tid; o.client = sock;

This will create a new client object when a player connects. The new object will hold the socket and id of the player.

Next, we will send the id to the player. clearbuffer; writebyte(MSG_ACCEPT); writebyte(tid); sendmessage(sock); This is quite straight-forward: the first byte specifies what the contents of the message are, and the next byte holds the id of the player. Finally, the message is sent to the player that just connected.

The Step Event should look something like this: //listen for clients var sock; sock = tcpaccept(listen,1); if sock <= 0 exit; //accept the client var o,tid; tid = get_mid; o = instance_create(0,0,obj_client); o.mid = tid; o.client = sock; //send the client his id clearbuffer; writebyte(MSG_ACCEPT); writebyte(tid); sendmessage(sock);

Receiving messages from the clients
If you haven't done so already, make a new object (obj_client). This object will store the id and socket of the corresponding player, as well as receive messages from the player.

Make a new script. Its purpose will be to send a message to certain clients. It can send it to all, only x and all but x (0,1,2). The spec argument will be optional (used for x in only and but) and take the mid of the player.

sendext(to,spec) var to,xx; to = argument0; xx = argument1; with obj_client switch(to) { case 0: //all sendmessage(client); break; case 1: //only if mid == xx  sendmessage(client); break; case 2: //all but if mid != xx  sendmessage(client); break; }

So if you wanted to send a message to every client but the object's, use: sendext(2,mid);

The following code is the biggest and most complicated of the codes: it is the code responsible for receiving messages from the players. The structure is identical when the player recieves messages from the server. var size,msgid; while(1) { size = receivemessage(client); //no message if size <= 0 break; First, we start an infinite loop to receive messages. A variable size is then set to the size of the received message. If it is less than or equal to 0, no message was received.

The next part of the code reads the messages: msgid = readbyte; //contents of message switch(msgid) { //read messages here } } This code will read the byte containing a pointer describing the message's contents and then execute different code depending on the value of the pointer. Remember this? It was set to MSG_ACCEPT when we first used it to send the id.

The code that goes in //read messages here is just a simple case: case id //code break;

You will repeat this multiple times, once for each type of message that will be received.

The MSG_LEAVE message will be sent when a player leaves (that is, closes their game). It will destroy the client handling object and forward the id of the player that left to the other players so the "ghost" player of the player that left will be destroyed. case MSG_LEAVE: //tell the other players our client left clearbuffer; writebyte(MSG_LEAVE); writebyte(mid); sendext(2,mid); break;

The MSG_POSITION message will be sent containing the x and y of the player who sent it. We will forward these positions and our id to the other players so that the "ghost" players on their screen will display the position of the other players. case MSG_POSITION: var xx,yy; xx = readshort; yy = readshort; //relay information to the other players clearbuffer; writebyte(MSG_POSITION); writebyte(mid); writeshort(xx); writeshort(yy); sendext(2,mid); break;

Here is the full receiving messages code: //receive messages var size,msgid; while(1) { size = receivemessage(client); //no message if size <= 0 break; //message id msgid = readbyte; switch(msgid) { //the player left case MSG_LEAVE: //tell the other players our client left clearbuffer; writebyte(MSG_LEAVE); writebyte(mid); sendext(2,mid); break; //player x,y case MSG_POSITION: var xx,yy; xx = readshort; yy = readshort; //relay information to the other players clearbuffer; writebyte(MSG_POSITION); writebyte(mid); writeshort(xx); writeshort(yy); sendext(2,mid); break; } }

One more thing you should do is put obj_server into a room.

We're done with the server for now
We have now finished the server! Go ahead and save a copy as server.gmk. When this tutorial is updated it will add a basic client that can move, chat, and be seen by the other players.