Getting a list of public servers for your own game can be a trial for a lot of users, unless you know the secret tips and tricks of BYOND.
Although I mention it all the time, a lot of people haven't yet realised that most BYOND pages come with a text mode version, which allows you to read data very, very easily.
View the text mode of this page.
There was a forum post that explained the text format feature, but it was in the old Features forum that was dissolved when the feature tracker was added. It may be buried in the release notes, but I'm too lazy to find it. So I'll just summarise it here.
You can affix ;format=text to the end of the URL for any user pages and user hubs. This will give you a list of information in text format. If you attach &long=true to the end of it, more in-depth information will be given (screenshot links, medal information etc, blog posts). You can further expand it by adding &num_posts=# to get more posts about it.
Now the really cool thing about this seemingly unknown feature is that it displays text in a readable BYOND Savefile format. If you check out the comments in the savefile reference entry you'll see a comment explaining this. It's with these very features that we'll be able to grab a list of server information.
First and foremost, we'll be storing the server information in a datum. This, at least in my opinion, makes this all drastically easier to deal with.
Seeing as I don't have an active game of my own to test this stuff with, I'll use NEStalgia for my example.
Taking into account all the data there, we know we'll need the following fields to grab all the server information we want.
- url
- server_version
- status
- players
- guests
- host
Time to do some programmin'.
server
var
url = null
version = null
status = null
guests = 0
host = "Unknown"
list
players = new/list
New(url, version, status, guests, host, players)
src.url = url //
src.version = version // These three attributes are present no matter what their value.
src.players = players //
src.status = status ? status : null // (I think)
src.guests = guests ? guests : 0 // These three attributes are excluded if they have no value.
src.host = host ? host : "Unknown" //
To make life easier for ourselves, we'll be populating the datum info in datum/New(). Saves time and effort on our part. Now for actually grabbing the server information, we'll need a proc to grab the data and parse the info.
proc
t_get_servers(hub)
if(!hub) CRASH("No hub supplied for t_get_servers()")
var/http[] = world.Export("http://www.byond.com/hub/[hub];format=text")
if(!http || http["CONTENT-TYPE"] != "text/plain; charset=ISO-8859-1")
world.log << "ERROR: Hub \"[hub]\" doesn't exist or BYOND Hub is currently unavailable."
return FALSE
. = new/list // All error checks have passed, so now we can set the default return to a list.
var/savefile/server = new
server.ImportText("/", file2text(http["CONTENT"]))
server.cd = "/world"
for(var/i in server.dir)
server.cd = "/world/[i]"
var/server/S = new(server["url"], server["server_version"], server["status"], server["guests"], server["host"], server["players"])
. += S
Because this proc uses world.Export(), it's not a very good idea to use it a lot. As it'll hang your world until it's completed it's task. There are ways to get around this with spawn(), but I'll leave it to those commenting to explain how and why.
-- Extra Reading.
What the scripts above do is simple, one is a world/proc which grabs a list of server info from the BYOND hub and stuffs the different server info it gathers into a separate datum and returns a list for the user to use as they see fit. This is really, all that needs to be done.
But some out there are pedantic about this kind of thing, and may want or require more data from the hub (list of medals, list of posts about the game, all that sort of thing). I've no intentions of writing a full library to provide this kind of functionality today (I'm waaay too tired for that). But what I will do is open the door for others now, and finish it off in my own time later on. To achieve this, we're going to want another datum to accompany our server datum, we'll name it "hub".
Ideally, all the separated data would have it's own datum. One for medals, one for servers, one for posts and one for general information. This kind of info I'll expand upon in my next post. For now, I'll just settle with general information and add the rest in later.
If we take a look at the long format of the NEStalgia page, we'll see the different fields we want:
- type
- title
- path
- short_desc
- long_desc
- author
- version
- banner
- icon
- small_icon
- single_player
- multi_player
- video
Arguably we'd also want the waiting list in there (personally, the waiting list should be part of /general and I honestly have no idea why it isn't). But I digress. It's also assumable that we already know the type. But to assume is to make a mockery of fact, so we wont assume what anyone will be using this library for.
hub
var
h_type = null // Type is an existing read-only variable, attempting to redefine it will cause a compiler error.
title = null
path = null
short_desc = null
long_desc = null
author = null
version = null
banner = null
icon = null
small_icon = null
single_player = FALSE
multi_player = FALSE
video = null
list
servers = new/list
Now to populate the hub datum itself. Unlike the server datum where we just plugged the data into New(), there are, in my opinion, too many variables to do this effectively. So we'll resort to trickery and espionage to achieve this result. Simply meaning, we'll loop through the datum variables (which, with exception to type, have the same name as the data from the website) and if they match, apply the value of the website var to the datum var, like so:
hub
var
h_type = null // Type is an existing read-only variable, attempting to redefine it will cause a compiler error.
title = null
path = null
short_desc = null
long_desc = null
author = null
version = null
banner = null
icon = null
small_icon = null
single_player = FALSE
multi_player = FALSE
video = null
list
servers = new/list
New(hub)
if(!hub) CRASH("No hub supplied for new/hub")
var/http[] = world.Export("http://www.byond.com/hub/[hub];format=text&long=true")
if(!http || http["CONTENT-TYPE"] != "text/plain; charset=ISO-8859-1")
world.log << "ERROR: Hub \"[hub]\" doesn't exist or BYOND Hub is currently unavailable."
return FALSE
var/savefile/info = new
info.ImportText("/", file2text(http["CONTENT"]))
info.cd = "/general"
// Populate the above variables.
for(var/i in info.dir)
if(i == "type") src.h_type = info["type"]
else
if(i in src.vars) src.vars[i] = info[i]
// Populate servers list.
info.cd = "/world"
for(var/i in info.dir)
info.cd = "/world/[i]"
var/server/S = new(info["url"], info["server_version"], info["status"], info["guests"], info["host"], info["players"])
src.servers += S
And there you have it. That'll give a full list of server information, and the general hub info. As you can see from my example, adding in the rest of the stuff would be pretty easy, I'll get around to doing so at a later date.
The library for this article can be found: Here.
If you have any comments on how to improve all this, I'll be happy to hear them.