4 from pyramid.response import Response
5 from pyramid.view import view_config
6 from webhelpers.paginate import Page, PageURL
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
15 log = logging.getLogger(__name__)
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'}
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)
33 log.debug("testing logging; entered PlayerHandler.index()")
34 return {'players':players}
36 @view_config(renderer='player_info.mako')
37 def player_info(request):
38 player_id = request.matchdict['id']
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]
48 except Exception as e:
51 return {'player':player,
52 'recent_games':recent_games}
55 def player_game_index(request):
56 player_id = request.matchdict['player_id']
57 current_page = request.matchdict['page']
60 player = DBSession.query(Player).filter_by(player_id=player_id).one()
62 games_q = DBSession.query(PlayerGameStat, Game, Server, Map).\
63 filter(PlayerGameStat.player_id == player_id).\
64 filter(PlayerGameStat.game_id == Game.game_id).\
65 filter(Game.server_id == Server.server_id).\
66 filter(Game.map_id == Map.map_id).\
67 order_by(Game.game_id.desc())
69 games = Page(games_q, current_page, url=page_url)
72 except Exception as e:
77 return {'player':player,
81 def player_weapon_stats(request):
82 game_id = request.matchdict['game_id']
83 pgstat_id = request.matchdict['pgstat_id']
85 pwstats = DBSession.query(PlayerWeaponStat, Weapon).\
86 filter(PlayerWeaponStat.weapon_cd==Weapon.weapon_cd).\
87 filter_by(game_id=game_id).\
88 filter_by(player_game_stat_id=pgstat_id).\
89 order_by(Weapon.descr).\
92 except Exception as e:
94 return {'pwstats':pwstats}
97 ##########################################################################
98 # This is the game views area - only views pertaining to Xonotic
99 # games and their related information goes here
100 ##########################################################################
101 def game_index(request):
102 if 'page' in request.matchdict:
103 current_page = request.matchdict['page']
107 games_q = DBSession.query(Game, Server, Map).\
108 filter(Game.server_id == Server.server_id).\
109 filter(Game.map_id == Map.map_id).\
110 order_by(Game.game_id.desc())
112 games = Page(games_q, current_page, url=page_url)
114 return {'games':games}
117 def game_info(request):
118 game_id = request.matchdict['id']
122 (start_dt, game_type_cd, server_id, server_name, map_id, map_name) = \
123 DBSession.query("start_dt", "game_type_cd", "server_id",
124 "server_name", "map_id", "map_name").\
125 from_statement("select g.start_dt, g.game_type_cd, "
126 "g.server_id, s.name as server_name, g.map_id, "
127 "m.name as map_name "
128 "from games g, servers s, maps m "
129 "where g.game_id = :game_id "
130 "and g.server_id = s.server_id "
131 "and g.map_id = m.map_id").\
132 params(game_id=game_id).one()
134 player_game_stats = DBSession.query(PlayerGameStat).\
135 from_statement("select * from player_game_stats "
136 "where game_id = :game_id "
137 "order by score desc").\
138 params(game_id=game_id).all()
139 except Exception as inst:
147 player_game_stats = None
149 return {'notfound':notfound,
151 'game_type_cd':game_type_cd,
152 'server_id':server_id,
153 'server_name':server_name,
156 'player_game_stats':player_game_stats}
159 ##########################################################################
160 # This is the server views area - only views pertaining to Xonotic
161 # servers and their related information goes here
162 ##########################################################################
163 def server_info(request):
164 server_id = request.matchdict['id']
166 server = DBSession.query(Server).filter_by(server_id=server_id).one()
167 recent_games = DBSession.query(Game, Server, Map).\
168 filter(Game.server_id == server_id).\
169 filter(Game.server_id == Server.server_id).\
170 filter(Game.map_id == Map.map_id).\
171 order_by(Game.game_id.desc())[0:10]
173 except Exception as e:
176 return {'server':server,
177 'recent_games':recent_games}
180 def server_game_index(request):
181 server_id = request.matchdict['server_id']
182 current_page = request.matchdict['page']
185 server = DBSession.query(Server).filter_by(server_id=server_id).one()
187 games_q = DBSession.query(Game, Server, Map).\
188 filter(Game.server_id == server_id).\
189 filter(Game.server_id == Server.server_id).\
190 filter(Game.map_id == Map.map_id).\
191 order_by(Game.game_id.desc())
193 games = Page(games_q, current_page, url=page_url)
194 except Exception as e:
199 return {'games':games,
203 ##########################################################################
204 # This is the map views area - only views pertaining to Xonotic
205 # maps and their related information goes here
206 ##########################################################################
207 def map_info(request):
208 map_id = request.matchdict['id']
210 gmap = DBSession.query(Map).filter_by(map_id=map_id).one()
216 ##########################################################################
217 # This is the stats views area - only views pertaining to Xonotic
218 # statistics and its related information goes here
219 ##########################################################################
220 def get_or_create_server(session=None, name=None):
222 # find one by that name, if it exists
223 server = session.query(Server).filter_by(name=name).one()
224 log.debug("Found server id {0} with name {1}.".format(
225 server.server_id, server.name))
226 except NoResultFound, e:
227 server = Server(name=name)
230 log.debug("Created server id {0} with name {1}".format(
231 server.server_id, server.name))
232 except MultipleResultsFound, e:
233 # multiple found, so use the first one but warn
235 servers = session.query(Server).filter_by(name=name).order_by(
236 Server.server_id).all()
238 log.debug("Created server id {0} with name {1} but found \
240 server.server_id, server.name))
244 def get_or_create_map(session=None, name=None):
246 # find one by the name, if it exists
247 gmap = session.query(Map).filter_by(name=name).one()
248 log.debug("Found map id {0} with name {1}.".format(gmap.map_id,
250 except NoResultFound, e:
251 gmap = Map(name=name)
254 log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
256 except MultipleResultsFound, e:
257 # multiple found, so use the first one but warn
259 gmaps = session.query(Map).filter_by(name=name).order_by(
262 log.debug("Found map id {0} with name {1} but found \
263 multiple.".format(gmap.map_id, gmap.name))
267 def create_game(session=None, start_dt=None, game_type_cd=None,
268 server_id=None, map_id=None, winner=None):
269 game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
270 server_id=server_id, map_id=map_id, winner=winner)
273 log.debug("Created game id {0} on server {1}, map {2} at time \
274 {3} and on map {4}".format(game.game_id,
275 server_id, map_id, start_dt, map_id))
279 # search for a player and if found, create a new one (w/ hashkey)
280 def get_or_create_player(session=None, hashkey=None):
282 if re.search('^bot#\d+$', hashkey):
283 player = session.query(Player).filter_by(player_id=1).one()
284 # if we have an untracked player
285 elif re.search('^player#\d+$', hashkey):
286 player = session.query(Player).filter_by(player_id=2).one()
287 # else it is a tracked player
289 # see if the player is already in the database
290 # if not, create one and the hashkey along with it
292 hashkey = session.query(Hashkey).filter_by(
293 hashkey=hashkey).one()
294 player = session.query(Player).filter_by(
295 player_id=hashkey.player_id).one()
296 log.debug("Found existing player {0} with hashkey {1}.".format(
297 player.player_id, hashkey.hashkey))
302 hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
304 log.debug("Created player {0} with hashkey {1}.".format(
305 player.player_id, hashkey.hashkey))
309 def create_player_game_stat(session=None, player=None,
310 game=None, player_events=None):
312 # in here setup default values (e.g. if game type is CTF then
313 # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
314 # TODO: use game's create date here instead of now()
315 pgstat = PlayerGameStat(create_dt=datetime.datetime.now())
317 # set player id from player record
318 pgstat.player_id = player.player_id
320 #set game id from game record
321 pgstat.game_id = game.game_id
323 # all games have a score
326 if game.game_type_cd == 'dm':
330 elif game.game_type_cd == 'ctf':
336 pgstat.carrier_frags = 0
338 for (key,value) in player_events.items():
339 if key == 'n': pgstat.nick = value
340 if key == 't': pgstat.team = value
341 if key == 'rank': pgstat.rank = value
342 if key == 'alivetime':
343 pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
344 if key == 'scoreboard-drops': pgstat.drops = value
345 if key == 'scoreboard-returns': pgstat.returns = value
346 if key == 'scoreboard-fckills': pgstat.carrier_frags = value
347 if key == 'scoreboard-pickups': pgstat.pickups = value
348 if key == 'scoreboard-caps': pgstat.captures = value
349 if key == 'scoreboard-score': pgstat.score = value
350 if key == 'scoreboard-deaths': pgstat.deaths = value
351 if key == 'scoreboard-kills': pgstat.kills = value
352 if key == 'scoreboard-suicides': pgstat.suicides = value
354 # check to see if we had a name, and if
355 # not use the name from the player id
356 if pgstat.nick == None:
357 pgstat.nick = player.nick
365 def create_player_weapon_stats(session=None, player=None,
366 game=None, pgstat=None, player_events=None):
369 for (key,value) in player_events.items():
370 matched = re.search("acc-(.*?)-cnt-fired", key)
372 weapon_cd = matched.group(1)
373 pwstat = PlayerWeaponStat()
374 pwstat.player_id = player.player_id
375 pwstat.game_id = game.game_id
376 pwstat.player_game_stat_id = pgstat.player_game_stat_id
377 pwstat.weapon_cd = weapon_cd
379 if 'n' in player_events:
380 pwstat.nick = player_events['n']
382 pwstat.nick = player_events['P']
384 if 'acc-' + weapon_cd + '-cnt-fired' in player_events:
385 pwstat.fired = int(round(float(
386 player_events['acc-' + weapon_cd + '-cnt-fired'])))
387 if 'acc-' + weapon_cd + '-fired' in player_events:
388 pwstat.max = int(round(float(
389 player_events['acc-' + weapon_cd + '-fired'])))
390 if 'acc-' + weapon_cd + '-cnt-hit' in player_events:
391 pwstat.hit = int(round(float(
392 player_events['acc-' + weapon_cd + '-cnt-hit'])))
393 if 'acc-' + weapon_cd + '-hit' in player_events:
394 pwstat.actual = int(round(float(
395 player_events['acc-' + weapon_cd + '-hit'])))
396 if 'acc-' + weapon_cd + '-frags' in player_events:
397 pwstat.frags = int(round(float(
398 player_events['acc-' + weapon_cd + '-frags'])))
401 pwstats.append(pwstat)
406 def parse_body(request):
407 # storage vars for the request body
413 log.debug(request.body)
415 for line in request.body.split('\n'):
417 (key, value) = line.strip().split(' ', 1)
419 if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
420 game_meta[key] = value
423 # if we were working on a player record already, append
424 # it and work on a new one (only set team info)
425 if len(player_events) != 0:
426 players.append(player_events)
429 player_events[key] = value
432 (subkey, subvalue) = value.split(' ', 1)
433 player_events[subkey] = subvalue
435 player_events[key] = value
437 player_events[key] = value
439 # no key/value pair - move on to the next line
442 # add the last player we were working on
443 if len(player_events) > 0:
444 players.append(player_events)
446 return (game_meta, players)
449 def create_player_stats(session=None, player=None, game=None,
451 if 'joins' in player_events and 'matches' in player_events\
452 and 'scoreboardvalid' in player_events:
453 pgstat = create_player_game_stat(session=session,
454 player=player, game=game, player_events=player_events)
455 if not re.search('^bot#\d+$', player_events['P']):
456 create_player_weapon_stats(session=session,
457 player=player, game=game, pgstat=pgstat,
458 player_events=player_events)
461 @view_config(renderer='stats_submit.mako')
462 def stats_submit(request):
464 session = DBSession()
466 (game_meta, players) = parse_body(request)
468 # verify required metadata is present
469 if 'T' not in game_meta or\
470 'G' not in game_meta or\
471 'M' not in game_meta or\
472 'S' not in game_meta:
473 log.debug("Required game meta fields (T, G, M, or S) missing. "\
475 raise Exception("Required game meta fields (T, G, M, or S) missing.")
477 has_real_players = False
478 for player_events in players:
479 if not player_events['P'].startswith('bot'):
480 if 'joins' in player_events and 'matches' in player_events\
481 and 'scoreboardvalid' in player_events:
482 has_real_players = True
484 if not has_real_players:
485 raise Exception("No real players found. Stats ignored.")
487 server = get_or_create_server(session=session, name=game_meta['S'])
488 gmap = get_or_create_map(session=session, name=game_meta['M'])
491 winner = game_meta['W']
495 game = create_game(session=session,
496 start_dt=datetime.datetime(
497 *time.gmtime(float(game_meta['T']))[:6]),
498 server_id=server.server_id, game_type_cd=game_meta['G'],
499 map_id=gmap.map_id, winner=winner)
501 # find or create a record for each player
502 # and add stats for each if they were present at the end
504 for player_events in players:
505 player = get_or_create_player(session=session,
506 hashkey=player_events['P'])
507 log.debug('Creating stats for %s' % player_events['P'])
508 create_player_stats(session=session, player=player, game=game,
509 player_events=player_events)
512 log.debug('Success! Stats recorded.')
513 return Response('200 OK')
514 except Exception as e: