Sections
Timeline
Sub-Sections
Last Change
Annotate
Revision Log
Download
Plain Text
Original Format
Metanav
Preferences
About Trac
Links
Slowchop Studios
Gerald Kaszuba
Advertisement

root/pyquake3/trunk/pyquake3.py

Revision 3, 4.5 kB (checked in by gak, 19 months ago)

added GPL license

Line 
1"""
2Python Quake 3 Library
3http://misc.slowchop.com/misc/wiki/pyquake3
4Copyright (C) 2006-2007 Gerald Kaszuba
5
6This program is free software; you can redistribute it and/or
7modify it under the terms of the GNU General Public License
8as published by the Free Software Foundation; either version 2
9of the License, or (at your option) any later version.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with this program; if not, write to the Free Software
18Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19"""
20
21import socket
22import re
23
24class Player:
25        def __init__(self, name, frags, ping, address=None, bot=-1):
26                self.name = name
27                self.frags = frags
28                self.ping = ping
29                self.address = address
30                self.bot = bot
31        def __str__(self):
32                return self.name
33        def __repr__(self):
34                return str(self)
35
36class PyQuake3:
37        packet_prefix = '\xff' * 4
38        player_reo = re.compile(r'^(\d+) (\d+) "(.*)"')
39        def __init__(self, server, rcon_password=''):
40                self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
41                self.set_server(server)
42                self.set_rcon_password(rcon_password)
43        def set_server(self, server):
44                try:
45                        self.address, self.port = server.split(':')
46                except:
47                        raise Exception('Server address must be in the format of \
48                                        "address:port"')
49                self.port = int(self.port)
50                self.s.connect((self.address, self.port))
51        def get_address(self):
52                return '%s:%s' % (self.address, self.port)
53        def set_rcon_password(self, rcon_password):
54                self.rcon_password = rcon_password
55        def send_packet(self, data):
56                self.s.send('%s%s\n' % (self.packet_prefix, data))
57        def recv(self, timeout=1):
58                self.s.settimeout(timeout)
59                try:
60                        return self.s.recv(4096)
61                except socket.error, e:
62                        raise Exception('Error receiving the packet: %s' % \
63                                        e[1])
64        def command(self, cmd, timeout=1, retries=3):
65                while retries:
66                        self.send_packet(cmd)
67                        try:
68                                data = self.recv(timeout)
69                        except:
70                                data = None
71                        if data:
72                                return self.parse_packet(data)
73                        retries -= 1
74                raise Exception('Server response timed out')
75        def rcon(self, cmd):
76                r = self.command('rcon "%s" %s' % (self.rcon_password, cmd))
77                if r[1] == 'No rconpassword set on the server.\n' or r[1] == \
78                                'Bad rconpassword.\n':
79                        raise Exception(r[1][:-1])
80                return r
81        def parse_packet(self, data):
82                if data.find(self.packet_prefix) != 0:
83                        raise Exception('Malformed packet')
84                first_line_length = data.find('\n')
85                if first_line_length == -1:
86                        raise Exception('Malformed packet')
87                response_type = data[len(self.packet_prefix):first_line_length]
88                response_data = data[first_line_length+1:]
89                return response_type, response_data
90        def parse_status(self, data):
91                split = data[1:].split('\\')
92                values = dict(zip(split[::2], split[1::2]))
93                # if there are \n's in one of the values, it's the list of players
94                for var, val in values.items():
95                        pos = val.find('\n')
96                        if pos == -1:
97                                continue
98                        split = val.split('\n', 1)
99                        values[var] = split[0]
100                        self.parse_players(split[1])
101                return values
102        def parse_players(self, data):
103                self.players = []
104                for player in data.split('\n'):
105                        if not player:
106                                continue
107                        match = self.player_reo.match(player)
108                        if not match:
109                                print 'couldnt match', player
110                                continue
111                        frags, ping, name = match.groups()
112                        self.players.append(Player(name, frags, ping))
113        def update(self):
114                cmd, data = self.command('getstatus')
115                self.vars = self.parse_status(data)
116        def rcon_update(self):
117                cmd, data = self.rcon('status')
118                lines = data.split('\n')
119                players = lines[3:]
120                self.players = []
121                for p in players:
122                        while p.find('  ') != -1:
123                                p = p.replace('  ', ' ')
124                        while p.find(' ') == 0:
125                                p = p[1:]
126                        if p == '':
127                                continue
128                        p = p.split(' ')
129                        self.players.append(Player(p[3][:-2], p[0], p[1], p[5], p[6]))
130
131if __name__ == '__main__':
132        q = PyQuake3('localhost:27960','hello')
133        q.update()
134        print 'The name of %s is %s, running map %s with %s player(s).' % \
135                        (q.get_address(), q.vars['sv_hostname'], \
136                        q.vars['mapname'], len(q.players))
137        for player in q.players:
138                print '%s with %s frags and a %sms ping' % (player.name, \
139                                player.frags, player.ping)
140        q.rcon_update()
141        for player in q.players:
142                print '%s has an address of %s' % (player.name, player.address)
143       
Note: See TracBrowser for help on using the browser.