How does one use SteamMatchMakingServers anyway?
Disclaimer: This post discusses the Steam SDK API. However, it only mentions methods and functions which are available in the publicly listed API documentation at https://partner.steamgames.com/doc/api/ISteamGameServer. This documentation is available without signing an NDA, and there are also already multiple public dumps of the Steam SDK headers.
This post therefore does not reveal any new NDA'ed information that was previously unavailable. My intention is not to break any NDA by posting this, and it's being done in good faith to help people get started with Steam SDK development.
So you've worked on your Steam SDK powered multiplayer game for a while now, and you're thinking... wouldn't it be nice to add a server browser or something to the game? Or maybe you want to add matchmaking support to your game?
Regardless, you will end up stumbling across the SteamGameServer and SteamMatchMakingServers APIs, and if you are like me... you will be scratching your head.
In this first blog series part, we will discuss the SteamGameServer API and what is actually required in order to get your server to show up in the SteamMatchMakingServers queries.
This blog post assumes that you know how to use SteamMatchmakingServers. If not, have a look at the Steamworks SDK example project, Spacewar.
It's difficult to know where to even start this explanation. Maybe with a rant about the online documentation, which is outdated and wrong. For example, it lists the method EnableHeartbeats, which if you look in the actual headers you will find has been renamed to SetAdvertiseServerActive.
The description for this method is also very sparse in the online documentation. Reading the header file documentation, we can see the following:
Indicate whether you wish to be listed on the master server list and/or respond to server browser / LAN discovery packets. The server starts with this value set to false. You should set all relevant server parameters before enabling advertisement on the server.
Sounds simple enough, right? Except for the fact that "relevant server parameters" is extremely arbitrary, and makes it sound as if it's up to you as a user of the API to choose. That is wrong, as you will soon get to see.
Another thing that, as far as I know, is not mentioned explicitly anywhere is the fact that you need to call either ISteamGameServers::LogOn or ISteamGameServers::LogOnAnonymous in order to actually start receiving game server queries from the Steam master servers. It makes sense when you think about it, but the fact that you'll have to infer this yourself is less than ideal.
A high level overview of the two methods would be the following:
ISteamGameServers::LogOn/ISteamGameServers::LogOnAnonymous- "Hi Steam, I am a game server for game X, please notice me!"
ISteamGameServers::SetAdvertiseServerActive- Controls whether the game server will respond to queries from the Steam master servers
Basically, you can (and should) log on your game server to the Steam network, and you can do that without calling SetAdvertiseServerActive. However, calling SetAdvertiseServerActive without logging in is not going to work.
So now that we know the high level requirements for receiving and responding to server queries, let's analyze the detailed requirements. As mentioned before, the SetAdvertiseServerActive method mentions to set all "relevant" server parameters, but what about LogOn and LogOnAnonymous?
If we look at the header file once again, we can find the following:
Basic server data. These properties, if set, must be set before before calling LogOn. They may not be changed after logged in.
Not entirely clear and concise. If we want to set these, we'll have to do it before calling LogOn, that much is clear. But which of them are actually required? That's left to the user to figure out.
Anyway, these are the properties/methods:
- SetProduct
- SetGameDescription
- SetModDir
- SetDedicatedServer
Let's assume we need to set all of these. Many of them are extremely vague and unclear. For example, reading the documentation for SetProduct, we find this:
Game product identifier. This is currently used by the master server for version checking purposes. It's a required field, but will eventually will go away, and the AppID will be used for this purpose.
Okay, it's required. But what is the expected value? The documentation for SetGameDescription is almost as vague:
Description of the game. This is a required field and is displayed in the steam server browser....for now. This is a required field, but it will go away eventually, as the data should be determined from the AppID.
Love the "...for now". At least it explicitly mentions that it's required. But the most confusing of all of them is SetModDir:
If your game is a "mod," pass the string that identifies it. The default is an empty string, meaning this application is the original game, not a mod.
Once again very arbitrary. Do we have to set it? Logic would dictate that we do not, as the previous two explicitly mentioned being required, which this one does not. It also apparently has a default value. However, the online docs actually mention this:
NOTE: This is required for all game servers and can only be set before calling LogOn or LogOnAnonymous.
I can reveal to you that... it is in fact required, and as we'll get to see in later parts of this blog series, it has a very concrete purpose.
Moving on, looking at the "server state" properties, we can find the following:
- SetMaxPlayerCount
- SetBotPlayerCount
- SetServerName
- SetMapName
- SetPasswordProtected
- SetSpectatorPort
- SetSpectatorServerName
Let's add up everything we've read so far and write some code. Based on the bits and pieces of documentation available, one could assume that this code would be enough:
if (SteamErrMsg error; SteamGameServer_InitEx(0, 5037, 5038, eServerModeAuthentication, "0.1", &error))
{
return 1;
}
SteamGameServer()->SetProduct("game");
SteamGameServer()->SetGameDescription("my game");
SteamGameServer()->SetDedicatedServer(true);
SteamGameServer()->SetAdvertiseServerActive(true);
SteamGameServer()->LogOnAnonymous();
We have no "relevant" server properties to set, and the mod dir property seems optional. This should work right? Oh, how naive we are. If you tried this, you would find that your server will not show up in any query, and of course you will not get any error messages.
Looking at the Spacewar example project again, we can see that they are actually setting mod dir. So does that mean that we actually have to set it?
Having no idea what the expected value is, let's try to set it to something arbitrary:
SteamGameServer()->SetModDir("game");
Server still does not show up in queries? Hmmm... let's dig deeper into the Spacewar code.
After a while, you might realize that there's a missing piece to the puzzle. See, they have a separate method which they call every frame, and which sets a bunch of server properties which can change dynamically during the course of the server's lifetime.
Specifically, these are the 5 properties they set every frame:
- SetMaxPlayerCount
- SetPasswordProtected
- SetServerName
- SetBotPlayerCount
- SetMapName
They also use BUpdateUserData to update the user status for any players on the server.
Since this blog post is already long enough at this point, I will spare you the trial and error and tell you straight away. The required property is SetServerName. It does not matter if you are showing server name or not in your server browser. It does not matter that it's not explicitly marked as required anywhere in the documentation. It just is required.
And no, you do not have to call them every single frame like they do. It's enough to do it in the initial setup.
This leaves us with the following most minimal SteamGameServer API usage:
SteamGameServer()->SetModDir("game");
SteamGameServer()->SetProduct("game");
SteamGameServer()->SetGameDescription("my game");
SteamGameServer()->SetDedicatedServer(true);
SteamGameServer()->SetServerName("test server");
SteamGameServer()->SetAdvertiseServerActive(true);
SteamGameServer()->LogOnAnonymous();
Calling these methods should be enough to make your server show up in the SteamMatchmakingServers queries. If you actually want it to show up in the official Steam server browser however...? Well, that's a story for another blog post.