]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/views.py
Add basic views for player and game info, plus the supporting templates.
[xonotic/xonstat.git] / xonstat / views.py
1 import datetime
2 import re
3 from pyramid.response import Response
4 from pyramid.view import view_config
5
6 from xonstat.models import *
7 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
8
9
10 import logging
11 log = logging.getLogger(__name__)
12
13 ##########################################################################
14 # This is the main index - the entry point to the entire site
15 ##########################################################################
16 @view_config(renderer='index.jinja2')
17 def main_index(request):
18     log.debug("testing logging; entered MainHandler.index()")
19     return {'project':'xonstat'}
20
21 ##########################################################################
22 # This is the player views area - only views pertaining to Xonotic players
23 # and their related information goes here
24 ##########################################################################
25 @view_config(renderer='player_index.mako')
26 def player_index(request):
27     players = DBSession.query(Player)
28     log.debug("testing logging; entered PlayerHandler.index()")
29     return {'players':players}
30
31 @view_config(renderer='player_info.mako')
32 def player_info(request):
33     player_id = request.matchdict['id']
34     try:
35         player = DBSession.query(Player).filter_by(player_id=player_id).one()
36     except:
37         player = None
38     log.debug("entered player_info")
39     return {'player':player}
40
41
42 ##########################################################################
43 # This is the game views area - only views pertaining to Xonotic
44 # games and their related information goes here
45 ##########################################################################
46 def game_info(request):
47     game_id = request.matchdict['id']
48     try:
49         game = DBSession.query(Game).filter_by(game_id=game_id).one()
50     except:
51         game = None
52     log.debug("entered game_info")
53     return {'game':game}
54
55
56 ##########################################################################
57 # This is the stats views area - only views pertaining to Xonotic
58 # statistics and its related information goes here
59 ##########################################################################
60 def get_or_create_server(session=None, name=None):
61     try:
62         # find one by that name, if it exists
63         server = session.query(Server).filter_by(name=name).one()
64         log.debug("Found server id {0} with name {1}.".format(
65             server.server_id, server.name))
66     except NoResultFound, e:
67         server = Server(name=name)
68         session.add(server)
69         session.flush()
70         log.debug("Created server id {0} with name {1}".format(
71             server.server_id, server.name))
72     except MultipleResultsFound, e:
73         # multiple found, so use the first one but warn
74         log.debug(e)
75         servers = session.query(Server).filter_by(name=name).order_by(
76                 Server.server_id).all()
77         server = servers[0]
78         log.debug("Created server id {0} with name {1} but found \
79                 multiple".format(
80             server.server_id, server.name))
81
82     return server
83
84 def get_or_create_map(session=None, name=None):
85     try:
86         # find one by the name, if it exists
87         gmap = session.query(Map).filter_by(name=name).one()
88         log.debug("Found map id {0} with name {1}.".format(gmap.map_id, 
89             gmap.name))
90     except NoResultFound, e:
91         gmap = Map(name=name)
92         session.add(gmap)
93         session.flush()
94         log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
95             gmap.name))
96     except MultipleResultsFound, e:
97         # multiple found, so use the first one but warn
98         log.debug(e)
99         gmaps = session.query(Map).filter_by(name=name).order_by(
100                 Map.map_id).all()
101         gmap = gmaps[0]
102         log.debug("Found map id {0} with name {1} but found \
103                 multiple.".format(gmap.map_id, gmap.name))
104
105     return gmap
106
107 def create_game(session=None, start_dt=None, game_type_cd=None, 
108         server_id=None, map_id=None, winner=None):
109     game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
110                 server_id=server_id, map_id=map_id, winner=winner)
111     session.add(game)
112     session.flush()
113     log.debug("Created game id {0} on server {1}, map {2} at time \
114             {3} and on map {4}".format(game.game_id, 
115                 server_id, map_id, start_dt, map_id))
116
117     return game
118
119 # search for a player and if found, create a new one (w/ hashkey)
120 def get_or_create_player(session=None, hashkey=None):
121     # if we have a bot
122     if re.search('^bot#\d+$', hashkey):
123         player = session.query(Player).filter_by(player_id=1).one()
124     # if we have an untracked player
125     elif re.search('^player#\d+$', hashkey):
126         player = session.query(Player).filter_by(player_id=2).one()
127     # else it is a tracked player
128     else:
129         # see if the player is already in the database
130         # if not, create one and the hashkey along with it
131         try:
132             hashkey = session.query(Hashkey).filter_by(
133                     hashkey=hashkey).one()
134             player = session.query(Player).filter_by(
135                     player_id=hashkey.player_id).one()
136             log.debug("Found existing player {0} with hashkey {1}.".format(
137                 player.player_id, hashkey.hashkey))
138         except:
139             player = Player()
140             session.add(player)
141             session.flush()
142             hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
143             session.add(hashkey)
144             log.debug("Created player {0} with hashkey {1}.".format(
145                 player.player_id, hashkey.hashkey))
146
147     return player
148
149 def create_player_game_stat(session=None, player=None, 
150         game=None, player_events=None):
151
152     # in here setup default values (e.g. if game type is CTF then
153     # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
154     # TODO: use game's create date here instead of now()
155     pgstat = PlayerGameStat(create_dt=datetime.datetime.now())
156
157     # set player id from player record
158     pgstat.player_id = player.player_id
159
160     #set game id from game record
161     pgstat.game_id = game.game_id
162
163     # all games have a score
164     pgstat.score = 0
165
166     if game.game_type_cd == 'dm':
167         pgstat.kills = 0
168         pgstat.deaths = 0
169         pgstat.suicides = 0
170     elif game.game_type_cd == 'ctf':
171         pgstat.kills = 0
172         pgstat.captures = 0
173         pgstat.pickups = 0
174         pgstat.drops = 0
175         pgstat.returns = 0
176         pgstat.carrier_frags = 0
177
178     for (key,value) in player_events.items():
179         if key == 'n': pgstat.nick = value
180         if key == 't': pgstat.team = value
181         if key == 'rank': pgstat.rank = value
182         if key == 'alivetime': 
183             pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
184         if key == 'scoreboard-drops': pgstat.drops = value
185         if key == 'scoreboard-returns': pgstat.returns = value
186         if key == 'scoreboard-fckills': pgstat.carrier_frags = value
187         if key == 'scoreboard-pickups': pgstat.pickups = value
188         if key == 'scoreboard-caps': pgstat.captures = value
189         if key == 'scoreboard-score': pgstat.score = value
190         if key == 'scoreboard-deaths': pgstat.deaths = value
191         if key == 'scoreboard-kills': pgstat.kills = value
192         if key == 'scoreboard-suicides': pgstat.suicides = value
193
194     # check to see if we had a name, and if 
195     # not use the name from the player id
196     if pgstat.nick == None:
197         pgstat.nick = player.nick
198
199     session.add(pgstat)
200     session.flush()
201
202     return pgstat
203
204
205 def create_player_weapon_stats(session=None, player=None, 
206         game=None, player_events=None):
207     pwstats = []
208
209     for (key,value) in player_events.items():
210         matched = re.search("acc-(.*?)-cnt-fired", key)
211         if matched:
212             log.debug("Matched key: {0}".format(key))
213             weapon_cd = matched.group(1)
214             pwstat = PlayerWeaponStat()
215             pwstat.player_id = player.player_id
216             pwstat.game_id = game.game_id
217             pwstat.weapon_cd = weapon_cd
218             try:
219                 pwstat.max = int(player_events['acc-' + weapon_cd + '-fired'])
220             except:
221                 pwstat.max = 0
222             try:
223                 pwstat.actual = int(player_events['acc-' + weapon_cd + '-hit'])
224             except:
225                 pwstat.actual = 0
226             try:
227                 pwstat.fired = int(player_events['acc-' + weapon_cd + '-cnt-fired'])
228             except:
229                 pwstat.fired = 0
230             try:
231                 pwstat.hit = int(player_events['acc-' + weapon_cd + '-cnt-hit'])
232             except:
233                 pwstat.hit = 0
234             try:
235                 pwstat.frags = int(player_events['acc-' + weapon_cd + '-frags'])
236             except:
237                 pwstat.frags = 0
238
239             session.add(pwstat)
240             pwstats.append(pwstat)
241
242     return pwstats
243
244
245 def parse_body(request):
246     # storage vars for the request body
247     game_meta = {}
248     player_events = {}
249     current_team = None
250     players = []
251     
252     log.debug(request.body)
253
254     for line in request.body.split('\n'):
255         try:
256             (key, value) = line.strip().split(' ', 1)
257     
258             if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
259                 game_meta[key] = value
260
261             if key == 't':
262                 current_team = value
263     
264             if key == 'P':
265                 # if we were working on a player record already, append
266                 # it and work on a new one (only set team info)
267                 if len(player_events) != 0:
268                     players.append(player_events)
269                     player_events = {'t':current_team}
270     
271                 player_events[key] = value
272     
273             if key == 'e':
274                 (subkey, subvalue) = value.split(' ', 1)
275                 player_events[subkey] = subvalue
276
277             if key == 'n':
278                 player_events[key] = value
279         except:
280             # no key/value pair - move on to the next line
281             pass
282     
283     # add the last player we were working on
284     if len(player_events) > 0:
285         players.append(player_events)
286
287     return (game_meta, players)
288
289
290 @view_config(renderer='stats_submit.mako')
291 def stats_submit(request):
292     session = DBSession()
293
294     (game_meta, players) = parse_body(request)  
295     
296     # verify required metadata is present
297     if 'T' not in game_meta or\
298         'G' not in game_meta or\
299         'M' not in game_meta or\
300         'S' not in game_meta:
301         log.debug("Required game meta fields (T, G, M, or S) missing. "\
302                 "Can't continue.")
303         return {'msg':'Error processing the request.'}
304     
305     server = get_or_create_server(session=session, name=game_meta['S'])
306     gmap = get_or_create_map(session=session, name=game_meta['M'])
307
308     if 'W' in game_meta:
309         winner = game_meta['W']
310     else:
311         winner = None
312
313     # FIXME: don't use python now() here, convert from epoch T value
314     game = create_game(session=session, start_dt=datetime.datetime.now(), 
315             server_id=server.server_id, game_type_cd=game_meta['G'], 
316             map_id=gmap.map_id, winner=winner)
317     
318     # find or create a record for each player
319     # and add stats for each if they were present at the end
320     # of the game
321     has_real_players = False
322     for player_events in players:
323         if not player_events['P'].startswith('bot'):
324             has_real_players = True
325         player = get_or_create_player(session=session, 
326                 hashkey=player_events['P'])
327         if 'joins' in player_events and 'matches' in player_events\
328                  and 'scoreboardvalid' in player_events:
329             pgstat = create_player_game_stat(session=session, 
330                     player=player, game=game, player_events=player_events)
331             #pwstats = create_player_weapon_stats(session=session, 
332                     #player=player, game=game, player_events=player_events)
333     
334     if has_real_players:
335         session.commit()
336         log.debug('Success! Stats recorded.')
337         return {'msg':'Success! Stats recorded.'}
338     else:
339         session.rollback()
340         log.debug('No real players found. Stats ignored.')
341         return {'msg':'No real players found. Stats ignored.'}