5 from pyramid.response import Response
\r
6 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
\r
7 from xonstat.models import *
\r
9 log = logging.getLogger(__name__)
\r
12 def get_or_create_server(session=None, name=None):
\r
14 Find a server by name or create one if not found. Parameters:
\r
16 session - SQLAlchemy database session factory
\r
17 name - server name of the server to be found or created
\r
20 # find one by that name, if it exists
\r
21 server = session.query(Server).filter_by(name=name).one()
\r
22 log.debug("Found server id {0} with name {1}.".format(
\r
23 server.server_id, server.name))
\r
24 except NoResultFound, e:
\r
25 server = Server(name=name)
\r
28 log.debug("Created server id {0} with name {1}".format(
\r
29 server.server_id, server.name))
\r
30 except MultipleResultsFound, e:
\r
31 # multiple found, so use the first one but warn
\r
33 servers = session.query(Server).filter_by(name=name).order_by(
\r
34 Server.server_id).all()
\r
36 log.debug("Created server id {0} with name {1} but found \
\r
38 server.server_id, server.name))
\r
42 def get_or_create_map(session=None, name=None):
\r
44 Find a map by name or create one if not found. Parameters:
\r
46 session - SQLAlchemy database session factory
\r
47 name - map name of the map to be found or created
\r
50 # find one by the name, if it exists
\r
51 gmap = session.query(Map).filter_by(name=name).one()
\r
52 log.debug("Found map id {0} with name {1}.".format(gmap.map_id,
\r
54 except NoResultFound, e:
\r
55 gmap = Map(name=name)
\r
58 log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
\r
60 except MultipleResultsFound, e:
\r
61 # multiple found, so use the first one but warn
\r
63 gmaps = session.query(Map).filter_by(name=name).order_by(
\r
66 log.debug("Found map id {0} with name {1} but found \
\r
67 multiple.".format(gmap.map_id, gmap.name))
\r
72 def create_game(session=None, start_dt=None, game_type_cd=None,
\r
73 server_id=None, map_id=None, winner=None):
\r
75 Creates a game. Parameters:
\r
77 session - SQLAlchemy database session factory
\r
78 start_dt - when the game started (datetime object)
\r
79 game_type_cd - the game type of the game being played
\r
80 server_id - server identifier of the server hosting the game
\r
81 map_id - map on which the game was played
\r
82 winner - the team id of the team that won
\r
85 game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
\r
86 server_id=server_id, map_id=map_id, winner=winner)
\r
89 log.debug("Created game id {0} on server {1}, map {2} at time \
\r
90 {3} and on map {4}".format(game.game_id,
\r
91 server_id, map_id, start_dt, map_id))
\r
96 def get_or_create_player(session=None, hashkey=None, nick=None):
\r
98 Finds a player by hashkey or creates a new one (along with a
\r
99 corresponding hashkey entry. Parameters:
\r
101 session - SQLAlchemy database session factory
\r
102 hashkey - hashkey of the player to be found or created
\r
103 nick - nick of the player (in case of a first time create)
\r
106 if re.search('^bot#\d+$', hashkey):
\r
107 player = session.query(Player).filter_by(player_id=1).one()
\r
108 # if we have an untracked player
\r
109 elif re.search('^player#\d+$', hashkey):
\r
110 player = session.query(Player).filter_by(player_id=2).one()
\r
111 # else it is a tracked player
\r
113 # see if the player is already in the database
\r
114 # if not, create one and the hashkey along with it
\r
116 hashkey = session.query(Hashkey).filter_by(
\r
117 hashkey=hashkey).one()
\r
118 player = session.query(Player).filter_by(
\r
119 player_id=hashkey.player_id).one()
\r
120 log.debug("Found existing player {0} with hashkey {1}.".format(
\r
121 player.player_id, hashkey.hashkey))
\r
128 session.add(player)
\r
130 hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
\r
131 session.add(hashkey)
\r
132 log.debug("Created player {0} with hashkey {1}.".format(
\r
133 player.player_id, hashkey.hashkey))
\r
137 def create_player_game_stat(session=None, player=None,
\r
138 game=None, player_events=None):
\r
140 Creates game statistics for a given player in a given game. Parameters:
\r
142 session - SQLAlchemy session factory
\r
143 player - Player record of the player who owns the stats
\r
144 game - Game record for the game to which the stats pertain
\r
145 player_events - dictionary for the actual stats that need to be transformed
\r
148 # in here setup default values (e.g. if game type is CTF then
\r
149 # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
\r
150 # TODO: use game's create date here instead of now()
\r
151 pgstat = PlayerGameStat(create_dt=datetime.datetime.now())
\r
153 # set player id from player record
\r
154 pgstat.player_id = player.player_id
\r
156 #set game id from game record
\r
157 pgstat.game_id = game.game_id
\r
159 # all games have a score
\r
162 if game.game_type_cd == 'dm':
\r
165 pgstat.suicides = 0
\r
166 elif game.game_type_cd == 'ctf':
\r
168 pgstat.captures = 0
\r
172 pgstat.carrier_frags = 0
\r
174 for (key,value) in player_events.items():
\r
175 if key == 'n': pgstat.nick = value
\r
176 if key == 't': pgstat.team = value
\r
177 if key == 'rank': pgstat.rank = value
\r
178 if key == 'alivetime':
\r
179 pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
\r
180 if key == 'scoreboard-drops': pgstat.drops = value
\r
181 if key == 'scoreboard-returns': pgstat.returns = value
\r
182 if key == 'scoreboard-fckills': pgstat.carrier_frags = value
\r
183 if key == 'scoreboard-pickups': pgstat.pickups = value
\r
184 if key == 'scoreboard-caps': pgstat.captures = value
\r
185 if key == 'scoreboard-score': pgstat.score = value
\r
186 if key == 'scoreboard-deaths': pgstat.deaths = value
\r
187 if key == 'scoreboard-kills': pgstat.kills = value
\r
188 if key == 'scoreboard-suicides': pgstat.suicides = value
\r
190 # check to see if we had a name, and if
\r
191 # not use the name from the player id
\r
192 if pgstat.nick == None:
\r
193 pgstat.nick = player.nick
\r
195 session.add(pgstat)
\r
201 def create_player_weapon_stats(session=None, player=None,
\r
202 game=None, pgstat=None, player_events=None):
\r
204 Creates accuracy records for each weapon used by a given player in a
\r
205 given game. Parameters:
\r
207 session - SQLAlchemy session factory object
\r
208 player - Player record who owns the weapon stats
\r
209 game - Game record in which the stats were created
\r
210 pgstat - Corresponding PlayerGameStat record for these weapon stats
\r
211 player_events - dictionary containing the raw weapon values that need to be
\r
216 for (key,value) in player_events.items():
\r
217 matched = re.search("acc-(.*?)-cnt-fired", key)
\r
219 weapon_cd = matched.group(1)
\r
220 pwstat = PlayerWeaponStat()
\r
221 pwstat.player_id = player.player_id
\r
222 pwstat.game_id = game.game_id
\r
223 pwstat.player_game_stat_id = pgstat.player_game_stat_id
\r
224 pwstat.weapon_cd = weapon_cd
\r
226 if 'n' in player_events:
\r
227 pwstat.nick = player_events['n']
\r
229 pwstat.nick = player_events['P']
\r
231 if 'acc-' + weapon_cd + '-cnt-fired' in player_events:
\r
232 pwstat.fired = int(round(float(
\r
233 player_events['acc-' + weapon_cd + '-cnt-fired'])))
\r
234 if 'acc-' + weapon_cd + '-fired' in player_events:
\r
235 pwstat.max = int(round(float(
\r
236 player_events['acc-' + weapon_cd + '-fired'])))
\r
237 if 'acc-' + weapon_cd + '-cnt-hit' in player_events:
\r
238 pwstat.hit = int(round(float(
\r
239 player_events['acc-' + weapon_cd + '-cnt-hit'])))
\r
240 if 'acc-' + weapon_cd + '-hit' in player_events:
\r
241 pwstat.actual = int(round(float(
\r
242 player_events['acc-' + weapon_cd + '-hit'])))
\r
243 if 'acc-' + weapon_cd + '-frags' in player_events:
\r
244 pwstat.frags = int(round(float(
\r
245 player_events['acc-' + weapon_cd + '-frags'])))
\r
247 session.add(pwstat)
\r
248 pwstats.append(pwstat)
\r
253 def parse_body(request):
\r
255 Parses the POST request body for a stats submission
\r
257 # storage vars for the request body
\r
260 current_team = None
\r
263 log.debug(request.body)
\r
265 for line in request.body.split('\n'):
\r
267 (key, value) = line.strip().split(' ', 1)
\r
269 if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
\r
270 game_meta[key] = value
\r
273 # if we were working on a player record already, append
\r
274 # it and work on a new one (only set team info)
\r
275 if len(player_events) != 0:
\r
276 players.append(player_events)
\r
279 player_events[key] = value
\r
282 (subkey, subvalue) = value.split(' ', 1)
\r
283 player_events[subkey] = subvalue
\r
285 player_events[key] = value
\r
287 player_events[key] = value
\r
289 # no key/value pair - move on to the next line
\r
292 # add the last player we were working on
\r
293 if len(player_events) > 0:
\r
294 players.append(player_events)
\r
296 return (game_meta, players)
\r
299 def create_player_stats(session=None, player=None, game=None,
\r
300 player_events=None):
\r
302 Creates player game and weapon stats according to what type of player
\r
304 if 'joins' in player_events and 'matches' in player_events\
\r
305 and 'scoreboardvalid' in player_events:
\r
306 pgstat = create_player_game_stat(session=session,
\r
307 player=player, game=game, player_events=player_events)
\r
308 if not re.search('^bot#\d+$', player_events['P']):
\r
309 create_player_weapon_stats(session=session,
\r
310 player=player, game=game, pgstat=pgstat,
\r
311 player_events=player_events)
\r
314 def stats_submit(request):
\r
316 Entry handler for POST stats submissions.
\r
319 session = DBSession()
\r
321 (game_meta, players) = parse_body(request)
\r
323 # verify required metadata is present
\r
324 if 'T' not in game_meta or\
\r
325 'G' not in game_meta or\
\r
326 'M' not in game_meta or\
\r
327 'S' not in game_meta:
\r
328 log.debug("Required game meta fields (T, G, M, or S) missing. "\
\r
330 raise Exception("Required game meta fields (T, G, M, or S) missing.")
\r
332 has_real_players = False
\r
333 for player_events in players:
\r
334 if not player_events['P'].startswith('bot'):
\r
335 if 'joins' in player_events and 'matches' in player_events\
\r
336 and 'scoreboardvalid' in player_events:
\r
337 has_real_players = True
\r
339 if not has_real_players:
\r
340 raise Exception("No real players found. Stats ignored.")
\r
342 server = get_or_create_server(session=session, name=game_meta['S'])
\r
343 gmap = get_or_create_map(session=session, name=game_meta['M'])
\r
345 if 'W' in game_meta:
\r
346 winner = game_meta['W']
\r
350 game = create_game(session=session,
\r
351 start_dt=datetime.datetime(
\r
352 *time.gmtime(float(game_meta['T']))[:6]),
\r
353 server_id=server.server_id, game_type_cd=game_meta['G'],
\r
354 map_id=gmap.map_id, winner=winner)
\r
356 # find or create a record for each player
\r
357 # and add stats for each if they were present at the end
\r
359 for player_events in players:
\r
360 if 'n' in player_events:
\r
361 nick = player_events['n']
\r
365 player = get_or_create_player(session=session,
\r
366 hashkey=player_events['P'], nick=nick)
\r
367 log.debug('Creating stats for %s' % player_events['P'])
\r
368 create_player_stats(session=session, player=player, game=game,
\r
369 player_events=player_events)
\r
372 log.debug('Success! Stats recorded.')
\r
373 return Response('200 OK')
\r
374 except Exception as e:
\r