]> git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/views.py
Add player_game_index view, route, and template. It shows the recent games (paged...
[xonotic/xonstat.git] / xonstat / views.py
1 import datetime
2 import time
3 import re
4 from pyramid.response import Response
5 from pyramid.view import view_config
6 from webhelpers.paginate import Page, PageURL
7
8 from xonstat.models import *
9 from xonstat.util import page_url
10 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
11 from sqlalchemy import desc
12
13
14 import logging
15 log = logging.getLogger(__name__)
16
17 ##########################################################################
18 # This is the main index - the entry point to the entire site
19 ##########################################################################
20 @view_config(renderer='index.jinja2')
21 def main_index(request):
22     log.debug("testing logging; entered MainHandler.index()")
23     return {'project':'xonstat'}
24
25 ##########################################################################
26 # This is the player views area - only views pertaining to Xonotic players
27 # and their related information goes here
28 ##########################################################################
29 @view_config(renderer='player_index.mako')
30 def player_index(request):
31     players = DBSession.query(Player)
32
33     log.debug("testing logging; entered PlayerHandler.index()")
34     return {'players':players}
35
36 @view_config(renderer='player_info.mako')
37 def player_info(request):
38     player_id = request.matchdict['id']
39     try:
40         player = DBSession.query(Player).filter_by(player_id=player_id).one()
41         recent_games = DBSession.query(PlayerGameStat, Game, Server, Map).\
42                 filter(PlayerGameStat.player_id == player_id).\
43                 filter(PlayerGameStat.game_id == Game.game_id).\
44                 filter(Game.server_id == Server.server_id).\
45                 filter(Game.map_id == Map.map_id).\
46                 order_by(Game.game_id.desc())[0:10]
47
48         log.debug(recent_games)
49     except Exception as e:
50         player = None
51         recent_games = None
52     return {'player':player, 
53             'recent_games':recent_games}
54
55
56 def player_game_index(request):
57     player_id = request.matchdict['player_id']
58     current_page = request.matchdict['page']
59
60     try:
61         player = DBSession.query(Player).filter_by(player_id=player_id).one()
62
63         games_q = DBSession.query(PlayerGameStat, Game, Server, Map).\
64                 filter(PlayerGameStat.player_id == player_id).\
65                 filter(PlayerGameStat.game_id == Game.game_id).\
66                 filter(Game.server_id == Server.server_id).\
67                 filter(Game.map_id == Map.map_id).\
68                 order_by(Game.game_id.desc())
69
70         games = Page(games_q, current_page, url=page_url)
71
72         log.debug(games)
73         log.debug(player)
74         
75     except Exception as e:
76         player = None
77         games = None
78         raise e
79
80     return {'player':player,
81             'games':games}
82
83
84 ##########################################################################
85 # This is the game views area - only views pertaining to Xonotic
86 # games and their related information goes here
87 ##########################################################################
88 def game_index(request):
89     if 'page' in request.matchdict:
90         current_page = request.matchdict['page']
91     else:
92         current_page = 1
93
94     games_q = DBSession.query(Game, Server, Map).\
95             filter(Game.server_id == Server.server_id).\
96             filter(Game.map_id == Map.map_id).\
97             order_by(Game.game_id.desc())
98
99     games = Page(games_q, current_page, url=page_url)
100
101     log.debug(games)
102
103     return {'games':games}
104
105
106 def game_info(request):
107     game_id = request.matchdict['id']
108     try:
109         notfound = False
110
111         (start_dt, game_type_cd, server_id, server_name, map_id, map_name) = \
112         DBSession.query("start_dt", "game_type_cd", "server_id", 
113                 "server_name", "map_id", "map_name").\
114                 from_statement("select g.start_dt, g.game_type_cd, "
115                         "g.server_id, s.name as server_name, g.map_id, "
116                         "m.name as map_name "
117                         "from games g, servers s, maps m "
118                         "where g.game_id = :game_id "
119                         "and g.server_id = s.server_id "
120                         "and g.map_id = m.map_id").\
121                         params(game_id=game_id).one()
122
123         player_game_stats = DBSession.query(PlayerGameStat).\
124                 from_statement("select * from player_game_stats "
125                         "where game_id = :game_id "
126                         "order by score desc").\
127                             params(game_id=game_id).all()
128     except Exception as inst:
129         notfound = True
130         start_dt = None
131         game_type_cd = None
132         server_id = None
133         server_name = None
134         map_id = None
135         map_name = None
136         player_game_stats = None
137
138     return {'notfound':notfound,
139             'start_dt':start_dt,
140             'game_type_cd':game_type_cd,
141             'server_id':server_id,
142             'server_name':server_name,
143             'map_id':map_id,
144             'map_name':map_name,
145             'player_game_stats':player_game_stats}
146
147
148 ##########################################################################
149 # This is the server views area - only views pertaining to Xonotic
150 # servers and their related information goes here
151 ##########################################################################
152 def server_info(request):
153     server_id = request.matchdict['id']
154     try:
155         server = DBSession.query(Server).filter_by(server_id=server_id).one()
156         recent_games = DBSession.query(Game, Server, Map).\
157                 filter(Game.server_id == server_id).\
158                 filter(Game.server_id == Server.server_id).\
159                 filter(Game.map_id == Map.map_id).\
160                 order_by(Game.game_id.desc())[0:10]
161
162     except Exception as e:
163         server = None
164         recent_games = None
165     return {'server':server,
166             'recent_games':recent_games}
167
168
169 def server_game_index(request):
170     server_id = request.matchdict['server_id']
171     current_page = request.matchdict['page']
172
173     try:
174         server = DBSession.query(Server).filter_by(server_id=server_id).one()
175
176         games_q = DBSession.query(Game, Server, Map).\
177                 filter(Game.server_id == server_id).\
178                 filter(Game.server_id == Server.server_id).\
179                 filter(Game.map_id == Map.map_id).\
180                 order_by(Game.game_id.desc())
181
182         games = Page(games_q, current_page, url=page_url)
183         
184         log.debug("Server is:")
185         log.debug(server)
186         log.debug("Games is:")
187         log.debug(games)
188     except Exception as e:
189         server = None
190         games = None
191         raise e
192
193     return {'games':games,
194             'server':server}
195
196
197 ##########################################################################
198 # This is the map views area - only views pertaining to Xonotic
199 # maps and their related information goes here
200 ##########################################################################
201 def map_info(request):
202     map_id = request.matchdict['id']
203     try:
204         gmap = DBSession.query(Map).filter_by(map_id=map_id).one()
205     except:
206         gmap = None
207     return {'gmap':gmap}
208
209
210 ##########################################################################
211 # This is the stats views area - only views pertaining to Xonotic
212 # statistics and its related information goes here
213 ##########################################################################
214 def get_or_create_server(session=None, name=None):
215     try:
216         # find one by that name, if it exists
217         server = session.query(Server).filter_by(name=name).one()
218         log.debug("Found server id {0} with name {1}.".format(
219             server.server_id, server.name))
220     except NoResultFound, e:
221         server = Server(name=name)
222         session.add(server)
223         session.flush()
224         log.debug("Created server id {0} with name {1}".format(
225             server.server_id, server.name))
226     except MultipleResultsFound, e:
227         # multiple found, so use the first one but warn
228         log.debug(e)
229         servers = session.query(Server).filter_by(name=name).order_by(
230                 Server.server_id).all()
231         server = servers[0]
232         log.debug("Created server id {0} with name {1} but found \
233                 multiple".format(
234             server.server_id, server.name))
235
236     return server
237
238 def get_or_create_map(session=None, name=None):
239     try:
240         # find one by the name, if it exists
241         gmap = session.query(Map).filter_by(name=name).one()
242         log.debug("Found map id {0} with name {1}.".format(gmap.map_id, 
243             gmap.name))
244     except NoResultFound, e:
245         gmap = Map(name=name)
246         session.add(gmap)
247         session.flush()
248         log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
249             gmap.name))
250     except MultipleResultsFound, e:
251         # multiple found, so use the first one but warn
252         log.debug(e)
253         gmaps = session.query(Map).filter_by(name=name).order_by(
254                 Map.map_id).all()
255         gmap = gmaps[0]
256         log.debug("Found map id {0} with name {1} but found \
257                 multiple.".format(gmap.map_id, gmap.name))
258
259     return gmap
260
261 def create_game(session=None, start_dt=None, game_type_cd=None, 
262         server_id=None, map_id=None, winner=None):
263     game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
264                 server_id=server_id, map_id=map_id, winner=winner)
265     session.add(game)
266     session.flush()
267     log.debug("Created game id {0} on server {1}, map {2} at time \
268             {3} and on map {4}".format(game.game_id, 
269                 server_id, map_id, start_dt, map_id))
270
271     return game
272
273 # search for a player and if found, create a new one (w/ hashkey)
274 def get_or_create_player(session=None, hashkey=None):
275     # if we have a bot
276     if re.search('^bot#\d+$', hashkey):
277         player = session.query(Player).filter_by(player_id=1).one()
278     # if we have an untracked player
279     elif re.search('^player#\d+$', hashkey):
280         player = session.query(Player).filter_by(player_id=2).one()
281     # else it is a tracked player
282     else:
283         # see if the player is already in the database
284         # if not, create one and the hashkey along with it
285         try:
286             hashkey = session.query(Hashkey).filter_by(
287                     hashkey=hashkey).one()
288             player = session.query(Player).filter_by(
289                     player_id=hashkey.player_id).one()
290             log.debug("Found existing player {0} with hashkey {1}.".format(
291                 player.player_id, hashkey.hashkey))
292         except:
293             player = Player()
294             session.add(player)
295             session.flush()
296             hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
297             session.add(hashkey)
298             log.debug("Created player {0} with hashkey {1}.".format(
299                 player.player_id, hashkey.hashkey))
300
301     return player
302
303 def create_player_game_stat(session=None, player=None, 
304         game=None, player_events=None):
305
306     # in here setup default values (e.g. if game type is CTF then
307     # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
308     # TODO: use game's create date here instead of now()
309     pgstat = PlayerGameStat(create_dt=datetime.datetime.now())
310
311     # set player id from player record
312     pgstat.player_id = player.player_id
313
314     #set game id from game record
315     pgstat.game_id = game.game_id
316
317     # all games have a score
318     pgstat.score = 0
319
320     if game.game_type_cd == 'dm':
321         pgstat.kills = 0
322         pgstat.deaths = 0
323         pgstat.suicides = 0
324     elif game.game_type_cd == 'ctf':
325         pgstat.kills = 0
326         pgstat.captures = 0
327         pgstat.pickups = 0
328         pgstat.drops = 0
329         pgstat.returns = 0
330         pgstat.carrier_frags = 0
331
332     for (key,value) in player_events.items():
333         if key == 'n': pgstat.nick = value
334         if key == 't': pgstat.team = value
335         if key == 'rank': pgstat.rank = value
336         if key == 'alivetime': 
337             pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
338         if key == 'scoreboard-drops': pgstat.drops = value
339         if key == 'scoreboard-returns': pgstat.returns = value
340         if key == 'scoreboard-fckills': pgstat.carrier_frags = value
341         if key == 'scoreboard-pickups': pgstat.pickups = value
342         if key == 'scoreboard-caps': pgstat.captures = value
343         if key == 'scoreboard-score': pgstat.score = value
344         if key == 'scoreboard-deaths': pgstat.deaths = value
345         if key == 'scoreboard-kills': pgstat.kills = value
346         if key == 'scoreboard-suicides': pgstat.suicides = value
347
348     # check to see if we had a name, and if 
349     # not use the name from the player id
350     if pgstat.nick == None:
351         pgstat.nick = player.nick
352
353     session.add(pgstat)
354     session.flush()
355
356     return pgstat
357
358
359 def create_player_weapon_stats(session=None, player=None, 
360         game=None, player_events=None):
361     pwstats = []
362
363     for (key,value) in player_events.items():
364         matched = re.search("acc-(.*?)-cnt-fired", key)
365         if matched:
366             log.debug("Matched key: {0}".format(key))
367             weapon_cd = matched.group(1)
368             pwstat = PlayerWeaponStat()
369             pwstat.player_id = player.player_id
370             pwstat.game_id = game.game_id
371             pwstat.weapon_cd = weapon_cd
372
373             if 'n' in player_events:
374                 pwstat.nick = player_events['n']
375             else:
376                 pwstat.nick = player_events['P']
377
378             if 'acc-' + weapon_cd + '-cnt-fired' in player_events:
379                 pwstat.fired = int(round(float(
380                         player_events['acc-' + weapon_cd + '-cnt-fired'])))
381             if 'acc-' + weapon_cd + '-fired' in player_events:
382                 pwstat.max = int(round(float(
383                         player_events['acc-' + weapon_cd + '-fired'])))
384             if 'acc-' + weapon_cd + '-cnt-hit' in player_events:
385                 pwstat.hit = int(round(float(
386                         player_events['acc-' + weapon_cd + '-cnt-hit'])))
387             if 'acc-' + weapon_cd + '-hit' in player_events:
388                 pwstat.actual = int(round(float(
389                         player_events['acc-' + weapon_cd + '-hit'])))
390             if 'acc-' + weapon_cd + '-frags' in player_events:
391                 pwstat.frags = int(round(float(
392                         player_events['acc-' + weapon_cd + '-frags'])))
393
394             session.add(pwstat)
395             pwstats.append(pwstat)
396
397     return pwstats
398
399
400 def parse_body(request):
401     # storage vars for the request body
402     game_meta = {}
403     player_events = {}
404     current_team = None
405     players = []
406     
407     log.debug(request.body)
408
409     for line in request.body.split('\n'):
410         try:
411             (key, value) = line.strip().split(' ', 1)
412     
413             if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
414                 game_meta[key] = value
415
416             if key == 'P':
417                 # if we were working on a player record already, append
418                 # it and work on a new one (only set team info)
419                 if len(player_events) != 0:
420                     players.append(player_events)
421     
422                 player_events[key] = value
423
424             if key == 'e':
425                 (subkey, subvalue) = value.split(' ', 1)
426                 player_events[subkey] = subvalue
427             if key == 'n':
428                 player_events[key] = value
429             if key == 't':
430                 player_events[key] = value
431         except:
432             # no key/value pair - move on to the next line
433             pass
434     
435     # add the last player we were working on
436     if len(player_events) > 0:
437         players.append(player_events)
438
439     return (game_meta, players)
440
441
442 @view_config(renderer='stats_submit.mako')
443 def stats_submit(request):
444     try:
445         session = DBSession()
446
447         (game_meta, players) = parse_body(request)  
448     
449         # verify required metadata is present
450         if 'T' not in game_meta or\
451             'G' not in game_meta or\
452             'M' not in game_meta or\
453             'S' not in game_meta:
454             log.debug("Required game meta fields (T, G, M, or S) missing. "\
455                     "Can't continue.")
456             raise Exception("Required game meta fields (T, G, M, or S) missing.")
457     
458         has_real_players = False
459         for player_events in players:
460             if not player_events['P'].startswith('bot'):
461                 if 'joins' in player_events and 'matches' in player_events\
462                     and 'scoreboardvalid' in player_events:
463                     has_real_players = True
464
465         if not has_real_players:
466             raise Exception("No real players found. Stats ignored.")
467
468         server = get_or_create_server(session=session, name=game_meta['S'])
469         gmap = get_or_create_map(session=session, name=game_meta['M'])
470
471         if 'W' in game_meta:
472             winner = game_meta['W']
473         else:
474             winner = None
475
476         game = create_game(session=session, 
477                 start_dt=datetime.datetime(
478                     *time.gmtime(float(game_meta['T']))[:6]), 
479                 server_id=server.server_id, game_type_cd=game_meta['G'], 
480                 map_id=gmap.map_id, winner=winner)
481     
482         # find or create a record for each player
483         # and add stats for each if they were present at the end
484         # of the game
485         for player_events in players:
486             player = get_or_create_player(session=session, 
487                     hashkey=player_events['P'])
488             if 'joins' in player_events and 'matches' in player_events\
489                     and 'scoreboardvalid' in player_events:
490                 pgstat = create_player_game_stat(session=session, 
491                         player=player, game=game, player_events=player_events)
492                 if not player_events['P'].startswith('bot'):
493                     create_player_weapon_stats(session=session, 
494                             player=player, game=game, player_events=player_events)
495     
496         session.commit()
497         log.debug('Success! Stats recorded.')
498         return Response('200 OK')
499     except Exception as e:
500         session.rollback()
501         raise e