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