After the success of the TeeWorlds session at Barcamp Bournemouth 2 (read more about it here) I decided to set up a private TeeWorlds server for BUNIX members to use.
Everything worked great, even the map rotation was going, however since it's a low volume server, quite often you'd fire up TeeWorlds, go to join the BUNIX server only to find that there was no-one else in there. This was annoying.
Since TeeWorlds is open source software, I downloaded the source code and started digging through to find out how the server browser gets its information. I found the appropriate query, worked out what all the fields were and output the data as a simple XML file. The source is listed below, or it can be downloaded. You can see it in action here.
#!/usr/bin/python
from socket import *
from lxml import etree
class TeeworldsStatus:
address = ''
port = ''
data = {}
def __init__(self, address, port):
self.address = address
self.port = port
self._fetch_server_status()
def _fetch_server_status(self):
tw_s = socket(AF_INET, SOCK_DGRAM)
magic_request = chr(255)*10+'gief'
tw_s.sendto(magic_request, (self.address, self.port))
rtn_msg, server = tw_s.recvfrom(1024)
self._process_status_data(rtn_msg)
def _process_status_data(self, rtn_msg):
# strip leading chars
rtn_msg = rtn_msg[10:]
if rtn_msg[:4] == 'info':
info = rtn_msg[4:]
#fields are split by null characters
info_fields = info.split(chr(0))
self.data = {
'version': info_fields[0],
'name': info_fields[1],
'map': info_fields[2],
'gametype' : info_fields[3],
'map_progress' : info_fields[5],
'password' : info_fields[4],
'current_players': info_fields[6],
'max_players': info_fields[7]
}
# Remaining fields (exept the last one) are plaers and their scores
# so move in hops of 2 and extract player, score into a tuple
players = []
for idx in range(8, len(info_fields) -2, 2):
players.append((info_fields[idx], info_fields[idx+1]))
players = sorted(players, key=lambda player:player[1])
self.data['players'] = players
def get_status_xml(self):
server = etree.Element('server')
server.set('version', self.data['version'])
etree.SubElement(server, 'name').text = self.data['name']
etree.SubElement(server, 'map').text = self.data['map']
etree.SubElement(server, 'gametype').text = self.data['gametype']
etree.SubElement(server, 'map_progress').text = self.data['map_progress']
etree.SubElement(server, 'password').text = self.data['password']
etree.SubElement(server, 'current_players').text = self.data['current_players']
etree.SubElement(server, 'max_players').text = self.data['max_players']
players = etree.SubElement(server, 'players')
for name, score in self.data['players']:
player = etree.SubElement(players, 'player')
player.set('name', name)
player.set('score', score)
return etree.tostring(server, pretty_print=True)
tws = TeeworldsStatus("teeworlds.bunix.org.uk", 8080)
print("content-type:text/xml;charset=UTF-8\n")
print tws.get_status_xml()
