Jan 27, 2016 - How to Write a LAN Server

Comments

Recently I had to write a LAN game server/client for C-Dogs SDL. I was surprised at how little information was available, so I thought I might document my solution here.

The Problem: Which IP to connect to?

Most people who have made, or attempted to make, network-multiplayer games will know that there are two ways to get two game instances to connect to each other:

  1. Directly, by entering the IP address of the other game instance, or
  2. Using an online “server list”, which is basically a list of IP addresses constantly updated

Most “game server browsers” actually download lists from several known locations, a fancy way of doing no. 2. But no. 2 boils down to picking an IP from a list, which the game then uses to connect to directly anyway - no. 1. The point is: to connect two games, you need the IP address.

In ages past there were dial-up modems where you could connect using phone numbers, or IPX which is similar to IP but has different addresses. The protocols have changed but the problem is the same.

But how do games see each other on a LAN? Somehow many LAN-enabled games can scan and find servers running on a LAN, and avoid having to enter IP addresses manually. They also do this without having to access a remote server list.

Broadcast Address

Fortunately, the solution is provided by the network itself: in IP networks, you can send messages to the broadcast address (255.255.255.255) and the network will pass the message on to all hosts on the network, that is, the LAN. Servers that understand the broadcast message can then reply back, alerting the broadcaster to their presence and address.

This is very simple in concept but takes a surprising amount of code to implement; IP broadcasts are only available for connectionless, datagram sockets, and most game network services either use TCP or a connection-based scheme to make things easier, so chances are you can’t just shoehorn the broadcast functionality into your existing network handler. Therefore, the LAN “scanner” service will have to run on a different port, using UDP. The procedure is this:

  1. The game server itself runs on port A, but a “listen” socket runs on a fixed port B.
  2. The client scans for LAN servers by broadcasting a UDP packet on port B.
  3. Servers receiving the broadcast scan reply back, optionally including the information that a game server is running on port A.
  4. The client, upon receiving the reply, connects normally to the IP and on port A.

Full credit for this solution goes to Lee Salzman, of Sauerbraten and ENet.

To illustrate, I’ll provide a code walkthrough by implementing a basic chat service using C and ENet, an excellent cross-platform game networking library. But the same approach will work for your chosen language/framework, because we’re dealing with socket networking for the most part. A full working server/client code example is available here: https://github.com/cxong/ENetLANChatServer

The Server

The chat server will run the following:

  1. Bind a UDP port for listening to broadcast scans
  2. Bind another socket for the chat server itself
  3. In a loop, we’ll check for:
    • Scans: we’ll reply with the chat server port
    • Chat server events: as we’re a chat server, we’ll just resend the text message to all clients

First, the initialisation:

enet_initialize();

A common mistake is to forget to initialise ENet itself; you’ll run into mysterious errors with the other ENet functions if you forget. Of course, like a good programmer you should always check your error values, but I’m omitting those in this post for brevity.

Next, start the listening socket, on port 34567 (pick any port that is likely to be unused):

ENetSocket listen = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
// Allow the port to be reused by other applications - this means we can run several servers at once
enet_socket_set_option(listen, ENET_SOCKOPT_REUSEADDR, 1);
ENetAddress addr;
addr.host = ENET_HOST_ANY;
addr.port = 34567;
enet_socket_bind(listen, &listenaddr);

Now start the chat server itself (supporting up to 16 clients):

ENetAddress addr;
addr.host = ENET_HOST_ANY;
addr.port = ENET_PORT_ANY;
ENetHost *host = enet_host_create(&addr, 16, 2, 0, 0);

Finally, in a loop, check for scans and also perform chat server logic. First the listener: since we’re dealing with raw sockets, we’re using the old select()/recv() combo to do non-blocking I/O.

// The following code belongs in a function; we're using return to step out

// Use select to see if there is data to read
ENetSocketSet set;
ENET_SOCKETSET_EMPTY(set);
ENET_SOCKETSET_ADD(set, listen);
if (enet_socketset_select(listen, &set, NULL, 0) <= 0) return;

// Construct a data buffer (ENetBuffer) to receive the data
// Because we know exactly how big the scan packets will be (1 byte),
// we'll only set aside enough memory for that.
// If you want bigger payloads, adjust this to suit.
// If you want dynamic payloads, or you want to receive any payload just for the heck of it,
// use a suitably big buffer.
ENetAddress addr;
char buf;
ENetBuffer recvbuf;
recvbuf.data = &buf;
recvbuf.dataLength = 1;
if (enet_socket_receive(listen, &addr, &recvbuf, 1) <= 0) return;
// At this point we've received a packet; it would be a good idea to check its contents
// to make sure it's what we're looking for (like a magic number) and not random data,
// but all we're doing is sending a small reply so let's just do it, hooray for laziness!
// Reply to scanner client with the port of the server host
// We know the client address from the enet_socket_receive function
ENetBuffer replybuf;
replybuf.data = &host->address.port;
replybuf.dataLength = sizeof host->address.port;
enet_socket_send(listen, &addr, &replybuf, 1);

Then the chat server itself; as we’re going for maximum laziness simplicity, we’ll just broadcast the client’s data packet right back at all clients. Since there’s no validation or bounds checking, in a production environment this would be a gaping security hole :smile:

ENetEvent event;
if (enet_host_service(host, &event, 0) > 0)
{
	// Whenever a client connects or disconnects, broadcast a message
	// Whenever a client says something, broadcast it including
	// which client it was from
	char buf[256];
	switch (event.type)
	{
		case ENET_EVENT_TYPE_CONNECT:
			send_string(host, "New client connected!");
			break;
		case ENET_EVENT_TYPE_RECEIVE:
			sprintf(buf, "Client says: %s", event.packet->data);
			send_string(server.host, buf);
			break;
		case ENET_EVENT_TYPE_DISCONNECT:
			send_string(host, "Client disconnected :(");
			break;
		default:
			break;
	}
}

Don’t forget the helper function, send_string():

void send_string(ENetHost *host, char *s)
{
    // +1 for the null-terminator
	ENetPacket *packet = enet_packet_create(s, strlen(s) + 1, ENET_PACKET_FLAG_RELIABLE);
	enet_host_broadcast(host, 0, packet);
}

Finally, don’t forget to tear everything down at the end:

enet_socket_shutdown(listen, ENET_SOCKET_SHUTDOWN_READ_WRITE);
enet_socket_destroy(listen);
enet_host_destroy(host);
enet_deinitialize();

The Client

The client is conceptually simple:

  1. Send UDP broadcast scans to look for servers
  2. Once we’ve received a reply, connect to that IP as a normal ENetHost
  3. In a loop:
    • Get keys typed by the user; when they press enter send what they typed
    • Check for messages from the server; if there’s any print them out

It turns out that getting keyboard input and performing network I/O together is pretty hard; we’ll use rlutil, a sweet header file that contains some utility functions for console programs. We’ll only be using nb_getch(), to get keyboard hits in a non-blocking manner.

First, as always, initialisation:

enet_initialize();

Scan for the server, on port 34567 (where the server is listening)…

ENetSocket scanner = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
// We need to set a socket option in order to send to the broadcast address
enet_socket_set_option(scanner, ENET_SOCKOPT_BROADCAST, 1);
ENetAddress addr;
addr.host = ENET_HOST_BROADCAST;
addr.port = 34567;
// Send a dummy payload; you can make your own (larger) payload
// but make sure to update the server code if you do
char data = 42;
ENetBuffer sendbuf;
sendbuf.data = &data;
sendbuf.dataLength = 1;
enet_socket_send(scanner, &addr, &sendbuf, 1);

…and wait for the reply:

// Note that enet_socket_receive is blocking;
// for a non-blocking version use enet_socketset_select to check before receiving
ENetAddress addr;
enet_uint16 server_port;
ENetBuffer recvbuf;
recvbuf.data = &server_port;
recvbuf.dataLength = sizeof server_port;
enet_socket_receive(scanner, &addr, &recvbuf, 1);
// If the message is correct, we should have received sizeof(enet_uint16) worth of data
// Once again, error checking would be nice here, but omitted for brevity
addr.port = server_port;
// Now addr holds the exact host/port to connect to

// But first, shut down the scanner because we're done with it
enet_socket_shutdown(scanner, ENET_SOCKET_SHUTDOWN_READ_WRITE);
enet_socket_destroy(scanner);

Now that we have the server’s address (addr), start our chat client host:

ENetHost *host = enet_host_create(NULL, 1, 2, 0, 0);
ENetPeer *peer = enet_host_connect(host, &addr, 2, 0);
// Wait for 5 seconds for connection to succeed
ENetEvent event;
if (enet_host_service(host, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT)
{
	printf("Connected\n");
}

The chat client is now connected; in a loop, check for the user’s typed input, collecting the keys and sending them off on enter/return:

// Keep a keyboard buffer outside the main loop
char buf[256];

// ... inside the loop

int k = nb_getch();
if (k == KEY_ENTER || k == '\r')
{
	// If we have something to say, say it to the server
	if (strlen(buf) > 0)
	{
		send_string(peer, buf);
	}
	memset(buf, 0, sizeof buf);
	printf("\n");
}
else if (k > 0 && strlen(buf) < 255)
{
	// Hold on to our message until we press enter
	buf[strlen(buf)] = (char)k;
	// Print the typed character out; otherwise the user doesn't know what was typed
	printf("%c", k);
}

The send_string() helper function is different for the client because we’re only sending to the server, but otherwise it’s exactly the same:

void send_string(ENetPeer *peer, char *s)
{
	ENetPacket *packet = enet_packet_create(s, strlen(s) + 1, ENET_PACKET_FLAG_RELIABLE);
	enet_peer_send(peer, 0, packet);
}

We also need to check for server messages, and print whatever we get:

ENetEvent event;
if (enet_host_service(host, &event, 0) > 0 && event.type == ENET_EVENT_TYPE_RECEIVE)
{
	printf("%s\n", event.packet->data);
}

Don’t forget to tear down everything at the end:

enet_peer_disconnect_now(peer, 0);
enet_host_destroy(host);
enet_deinitialize();

The End

Phew! Hope that all makes sense. As I’ve said, the concept is simple but the code is verbose because we’re pretty deep into socket code, with all the select()s, recv()s and socket options, thinly wrapped in ENet which makes it slightly less verbose. There’s a working server/client project available here, with the proper error checking mess for you to get started. Good luck!

May 4, 2015 - Ammo is a Resource

Comments

The latest release of C-Dogs SDL is ostensibly about the addition of deathmatch mode, and although it was the original goal and most notable feature, development took twice as long as previous releases, and largely for one reason: ammo. Before this, C-Dogs didn’t have ammo, or in other words, C-Dogs had infinite ammo. How could ammo be that hard to implement; surely it’s just an extra counter? I though the same, but I soon realised this:

Ammo is a resource.

And that leads to a heap of things related to resource management. The player must now contend with many more decisions:

  • Which gun should I use? (some guns have rarer ammo)
  • How should I engage enemies? (close-up saves ammo but is riskier; farther is safer but wastes ammo)
  • Should I run away altogether and save ammo?
  • Should I explore, and gamble on finding more ammo or having to use it up on new enemies?

And as a designer, you now have to contend with:

  • Making sure the player is not too starved, or over-supplied
  • Resetting the player with the right amount of ammo at key points
  • Being careful not to railroad the player into a few play styles due to ammo supply

Of course there are great rewards for implementing ammo. It adds a layer of depth via the decision-making. It gives players an incentive to use different guns, as overusing their favourites will make them run out of ammo. But it’s also an entire game mechanic, and for one that’s so old and common, it’s surprisingly hard to get right.

doom

Games like these are too easy with lots of ammo, too hard without. Ammo placement can make or break a map.

Cyberdogs had ammo!

Fortunately, ammo in games is older than dirt, so there’s plenty of examples of how ammo can work, when it doesn’t, and what to watch out for. Most C-Dogs fans know that the game is actually a sequel to the very similar Cyberdogs, and it had ammo! But the two also play differently. Cyberdogs is slow, tense and methodical; enemies are very dangerous up close, pop out unexpectedly and you had to make your shots count. C-Dogs is sometimes like this too, but it is also fast and frantic, owing to new mechanics like sliding, open spaces, and of course - no ammo. Many players prefer Cyberdogs over C-Dogs for this reason.

cyberdogs armoury

How does Cyberdogs solve the ammo resource problem, that is, make sure the player’s getting enough ammo, and not too much? Armoury. In between levels, the player can spend cash to buy guns and ammo, cash that is earned from completing objectives and killing enemies in the previous level. In a way it is an elegant solution, by fobbing off the problem to the player, who is now responsible for how much ammo to buy. This also acts as a meta-game: too little ammo is dangerous, but too much and you don’t have enough cash to buy the goodies like extra lives and bigger guns. This mechanic is quite widespread too, from shops in RPGs to loadout screens in mech games.

But herein lies the problem: we’re attempting to balance one mechanic by piling on more mechanics! Wasn’t the point to make sure the player gets the right amount of ammo? Now we’ve added a store, buying/selling, cash and maybe more. Wouldn’t this just make things more complex and hard to balance? Let’s look at the ammo resource cycle here:

  • Use ammo to kill enemies
  • Kills earn cash
  • Use cash to buy ammo

Can’t we just cut out the middleman - cash - and keep things simple?

Sources and Sinks

Not quite. The cycle misses some important details: you can earn more money by completing more objectives, and you can also spend money on (expensive) guns. These are crucial to keeping the Cyberdogs ammo/cash system balanced. In other words, sources make sure the economy is never low on cash since players can always go out of their way to obtain more, and sinks make sure there’s never an overabundance of cash, by having big expensive items that the player can save up towards.

Once again, we can find examples in other genres, notably in RPGs, where there are plenty of sources and sinks to keep the cash economy healthy. If the player is short on cash, she can always farm for more gold, or explore dungeons more thoroughly to find expensive loot to sell. Many RPGs also have inventory limitations, which limits the amount of loot you can carry back to a store and sell; if the player is low on cash then she could make repeated trips to sell all the loot. On the other hand, if the player has too much cash, she could upgrade her equipment, which tends to be quite expensive. Most RPGs also feature magical or rare equipment that is obscenely expensive but provide marginal improvements over regular equipment. This Gamasutra article on Diablo’s item system shows how all these aspects work together to create a sophisticated economy. In summary, most of your gear was from loot, shop equipment was extremely expensive, but still worthwhile since the loot was random and often made the player miss out on a particular kind of equipment.

So it is important to have multiple sources and sinks in your economy, and also to make these alternates less desirable, otherwise they distract from the main game. For example, grinding, farming and shuttling loot are reliable money-makers but they are often tedious and less fun than fighting enemies or completing objectives.

ff6 shop

No matter how much you grind, you will barely be able to afford everything here. Inflation really messes with savings.

There’s another lesson from RPGs too, and that is to grow prices superlinearly. That is, for bigger and better items, increase their price much more than usual. Ever wonder why the wooden sword costs 10G, the iron sword 100G, and the silver sword 1000G, even though each one is only a little better than the last? This is to stop players from hoarding cash and making later segments of the game too easy because they can buy the best gear in one go. It’s a neat trick to prevent snowballing and make balancing much easier, by making those upgrades reliable sinks.

A Faustian Bargain

It seems we are stuck between two imperfect options. If we want a simple, ammo-only system, it is prone to shortages or surpluses, one that must be fixed by limiting player freedom, railroading them into a golden path. It is no accident that modern shooters rarely have ammo supply problems, but are also linear experiences. Alternately, if we want player freedom, non-linear gameplay, exploration, then we are forced to contend with the supply problems, which are neatly balanced with an economy. In return, we end up with an entire system which both the developers and players must manage.

But is this necessarily a bad thing? Players often enjoy managing resources as a meta-game, and there’s no denying the satisfaction of earning and spending these virtual trinkets, the clickety-clack sound when picking things up, watching virtual counters increment. When deciding on game features, it is important not to avoid complexity for its own sake, and to keep the greater goal in mind: making fun or enjoyable games. So the take-home message is this: be careful about adding features, because you may end up adding an entire system and meta-game!

Mar 12, 2015 - Killer music

Comments

Music in games is often relegated to the background, little more than filling the silence, pasted on as an afterthought. Recently I’ve contribute to the open source match-3 puzzle game FreeBlocks, and the surprising quality of the music reminded me of the value of music, that it can not only enhance but elevate the game to new heights.

Good music

But first let me show an example of music done right: the main theme of Enter the Dragon. I love music but there’s so much bad and badly-used music out there, and most people don’t care. I have a depressing anecdote where a jazz musician played a gig for a large dinner party, and received the following feedback from the client:

You guys were perfect! We hardly noticed you were playing.

People didn’t care about what they played, let alone how they played it. They only cared that there was something to fill the silence. Most people, from games to movies, treat music the same way. All it had to do was fit and stay unobtrusive; who cares whether it was “memorable” or “moving”? E.g. scoring a desert level? Just find some vaguely-Arabic music and call it a day!

Well Enter the Dragon cared. The main theme tells you exactly what this movie is about, with not a hint of subtlety, and all in the first minute no less. All you have to do is pay attention. Try for yourself: see if you can figure out what the movie is about, by listening to the music alone:

Kiais and Asian instruments? Kung fu. Blending into electric bass? Possibly set in some East-meets-West locale. Funky wah-wah guitar? Blaxploitation. Diminished chord? Something sinister, possibly involving spies and conspiracies. Yes, everything in the music tells you something about the movie, in no uncertain terms. It gets better if you watch and listen, as the music changes right on cue to what’s happening on screen - the stars’ names, their first appearances, changes in the scene. If you were to guess that this is the work of a master composer, you’d be right.

To take a gaming example, one of my favourite video game soundtracks is Sonic 3, due in no small part to collaboration with world-class musicians. For example, stage 1 of the game, Angel Island Zone, takes place in a tropical jungle environment, so the music is suitably cheery and Carribean with steel drums and xylophones. At the end of Act 1 however, the jungle is set on fire, with the level palette changed to an omnious orange and brown, an excellent signal for the ramp-up in difficulty. What’s really clever though is the music: Act 2 has a different track, but listen and compare the two tracks:

Notice anything? The two are versions of the same piece, one in a major key and one in minor! They have essentially the same structure too. The music is saying, “Act 2 is like Act 1 only darker and harder”, which is also what the graphics are saying. The same compositional technique is used in stage 2, where the sleeker and faster Act 2 (with its jets and speed wheels) is reflected in its music.

Match 3 harmony

Moving back to FreeBlocks; I was looking for additional music for the game and found this excellent piece by pixelsphere, and by sheer coincidence the music happened to be in the same scale and tune to the sound effects, which gives the game a very calming, musical quality. Listen and judge for yourself:

Although music theory - and sound design in general - is often overlooked in games, it can be used to give an extra amount of polish that makes a game stand out. Candy Crush Saga is a notable example, where all its sound effects are in the same scale.

Listen to the xylophone play an arpeggio as consecutive matches are made.

Prolific sound designer Aaron Marks puts his musical background to use:

I definitely have some techniques where I ‘tune’ sound elements to be either pleasant or something a little more harsh. I also tend to pay attention to the music that is accompanying a scene or game level and often tune the sound effect to a note which blends well or I’ll go the opposite direction and detune it, especially for evil characters and their weapons, for example. source

All this really goes to show that, yes music is overlooked, but all it takes is a little extra effort to craft something that’s special and above the norm. For me personally, this was proven in our game for Global Game Jam 2015, where I worked with the talented sound and music designer Matthew Chin. In addition to creating a killer music track, we decided to have two layers of the track, one calm and one frenetic, and fade dynamically between the two based on the action on-screen. The effort paid off in the form of taking the best audio prize; listen for yourself:

So next time, hit up your sound designer and make some killer music!