phoenixdev

Creative Commons License
phoenixdev.co.uk is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.

Teeworlds Monitoring Script

Thursday, 15th April 2010

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()