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