5 from pyramid.response import Response
\r
6 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
\r
7 from xonstat.models import *
\r
8 from xonstat.util import strip_colors
\r
10 log = logging.getLogger(__name__)
\r
13 def register_new_nick(session, player, new_nick):
\r
15 Change the player record's nick to the newly found nick. Store the old
\r
16 nick in the player_nicks table for that player.
\r
18 session - SQLAlchemy database session factory
\r
19 player - player record whose nick is changing
\r
20 new_nick - the new nickname
\r
22 # see if that nick already exists
\r
23 stripped_nick = strip_colors(player.nick)
\r
25 player_nick = session.query(PlayerNick).filter_by(
\r
26 player_id=player.player_id, stripped_nick=stripped_nick).one()
\r
27 except NoResultFound, e:
\r
28 # player_id/stripped_nick not found, create one
\r
29 # but we don't store "Anonymous Player #N"
\r
30 if not re.search('^Anonymous Player #\d+$', player.nick):
\r
31 player_nick = PlayerNick()
\r
32 player_nick.player_id = player.player_id
\r
33 player_nick.stripped_nick = stripped_nick
\r
34 player_nick.nick = player.nick
\r
35 session.add(player_nick)
\r
37 # We change to the new nick regardless
\r
38 log.debug('Changing nick from {0} to {1} for player {2}'.format(
\r
39 player.nick, new_nick, player.player_id))
\r
40 player.nick = new_nick
\r
44 def get_or_create_server(session=None, name=None):
\r
46 Find a server by name or create one if not found. Parameters:
\r
48 session - SQLAlchemy database session factory
\r
49 name - server name of the server to be found or created
\r
52 # find one by that name, if it exists
\r
53 server = session.query(Server).filter_by(name=name).one()
\r
54 log.debug("Found server id {0} with name {1}.".format(
\r
55 server.server_id, server.name))
\r
56 except NoResultFound, e:
\r
57 server = Server(name=name)
\r
60 log.debug("Created server id {0} with name {1}".format(
\r
61 server.server_id, server.name))
\r
62 except MultipleResultsFound, e:
\r
63 # multiple found, so use the first one but warn
\r
65 servers = session.query(Server).filter_by(name=name).order_by(
\r
66 Server.server_id).all()
\r
68 log.debug("Created server id {0} with name {1} but found \
\r
70 server.server_id, server.name))
\r
74 def get_or_create_map(session=None, name=None):
\r
76 Find a map by name or create one if not found. Parameters:
\r
78 session - SQLAlchemy database session factory
\r
79 name - map name of the map to be found or created
\r
82 # find one by the name, if it exists
\r
83 gmap = session.query(Map).filter_by(name=name).one()
\r
84 log.debug("Found map id {0} with name {1}.".format(gmap.map_id,
\r
86 except NoResultFound, e:
\r
87 gmap = Map(name=name)
\r
90 log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
\r
92 except MultipleResultsFound, e:
\r
93 # multiple found, so use the first one but warn
\r
95 gmaps = session.query(Map).filter_by(name=name).order_by(
\r
98 log.debug("Found map id {0} with name {1} but found \
\r
99 multiple.".format(gmap.map_id, gmap.name))
\r
104 def create_game(session=None, start_dt=None, game_type_cd=None,
\r
105 server_id=None, map_id=None, winner=None):
\r
107 Creates a game. Parameters:
\r
109 session - SQLAlchemy database session factory
\r
110 start_dt - when the game started (datetime object)
\r
111 game_type_cd - the game type of the game being played
\r
112 server_id - server identifier of the server hosting the game
\r
113 map_id - map on which the game was played
\r
114 winner - the team id of the team that won
\r
117 game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
\r
118 server_id=server_id, map_id=map_id, winner=winner)
\r
121 log.debug("Created game id {0} on server {1}, map {2} at time \
\r
122 {3} and on map {4}".format(game.game_id,
\r
123 server_id, map_id, start_dt, map_id))
\r
128 def get_or_create_player(session=None, hashkey=None, nick=None):
\r
130 Finds a player by hashkey or creates a new one (along with a
\r
131 corresponding hashkey entry. Parameters:
\r
133 session - SQLAlchemy database session factory
\r
134 hashkey - hashkey of the player to be found or created
\r
135 nick - nick of the player (in case of a first time create)
\r
138 if re.search('^bot#\d+$', hashkey):
\r
139 player = session.query(Player).filter_by(player_id=1).one()
\r
140 # if we have an untracked player
\r
141 elif re.search('^player#\d+$', hashkey):
\r
142 player = session.query(Player).filter_by(player_id=2).one()
\r
143 # else it is a tracked player
\r
145 # see if the player is already in the database
\r
146 # if not, create one and the hashkey along with it
\r
148 hashkey = session.query(Hashkey).filter_by(
\r
149 hashkey=hashkey).one()
\r
150 player = session.query(Player).filter_by(
\r
151 player_id=hashkey.player_id).one()
\r
152 log.debug("Found existing player {0} with hashkey {1}.".format(
\r
153 player.player_id, hashkey.hashkey))
\r
156 session.add(player)
\r
159 # if nick is given to us, use it. If not, use "Anonymous Player"
\r
160 # with a suffix added for uniqueness.
\r
162 player.nick = nick[:128]
\r
164 player.nick = "Anonymous Player #{0}".format(player.player_id)
\r
166 hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
\r
167 session.add(hashkey)
\r
168 log.debug("Created player {0} with hashkey {1}.".format(
\r
169 player.player_id, hashkey.hashkey))
\r
173 def create_player_game_stat(session=None, player=None,
\r
174 game=None, player_events=None):
\r
176 Creates game statistics for a given player in a given game. Parameters:
\r
178 session - SQLAlchemy session factory
\r
179 player - Player record of the player who owns the stats
\r
180 game - Game record for the game to which the stats pertain
\r
181 player_events - dictionary for the actual stats that need to be transformed
\r
184 # in here setup default values (e.g. if game type is CTF then
\r
185 # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
\r
186 # TODO: use game's create date here instead of now()
\r
187 pgstat = PlayerGameStat(create_dt=datetime.datetime.now())
\r
189 # set player id from player record
\r
190 pgstat.player_id = player.player_id
\r
192 #set game id from game record
\r
193 pgstat.game_id = game.game_id
\r
195 # all games have a score
\r
198 if game.game_type_cd == 'dm':
\r
201 pgstat.suicides = 0
\r
202 elif game.game_type_cd == 'ctf':
\r
204 pgstat.captures = 0
\r
208 pgstat.carrier_frags = 0
\r
210 for (key,value) in player_events.items():
\r
211 if key == 'n': pgstat.nick = value[:128]
\r
212 if key == 't': pgstat.team = value
\r
213 if key == 'rank': pgstat.rank = value
\r
214 if key == 'alivetime':
\r
215 pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
\r
216 if key == 'scoreboard-drops': pgstat.drops = value
\r
217 if key == 'scoreboard-returns': pgstat.returns = value
\r
218 if key == 'scoreboard-fckills': pgstat.carrier_frags = value
\r
219 if key == 'scoreboard-pickups': pgstat.pickups = value
\r
220 if key == 'scoreboard-caps': pgstat.captures = value
\r
221 if key == 'scoreboard-score': pgstat.score = value
\r
222 if key == 'scoreboard-deaths': pgstat.deaths = value
\r
223 if key == 'scoreboard-kills': pgstat.kills = value
\r
224 if key == 'scoreboard-suicides': pgstat.suicides = value
\r
226 # check to see if we had a name, and if
\r
227 # not use the name from the player id
\r
228 if pgstat.nick == None:
\r
229 pgstat.nick = player.nick
\r
231 # if the nick we end up with is different from the one in the
\r
232 # player record, change the nick to reflect the new value
\r
233 if pgstat.nick != player.nick and player.player_id > 1:
\r
234 log.debug('Registering new nick for {0}: {1}'.format(player.nick,
\r
236 register_new_nick(session, player, pgstat.nick)
\r
238 # if the player is ranked #1 and it is a team game, set the game's winner
\r
239 # to be the team of that player
\r
240 # FIXME: this is a hack, should be using the 'W' field (not present)
\r
241 if pgstat.rank == '1' and pgstat.team:
\r
242 log.debug('Found rank 1. Logging.')
\r
243 game.winner = pgstat.team
\r
246 session.add(pgstat)
\r
252 def create_player_weapon_stats(session=None, player=None,
\r
253 game=None, pgstat=None, player_events=None):
\r
255 Creates accuracy records for each weapon used by a given player in a
\r
256 given game. Parameters:
\r
258 session - SQLAlchemy session factory object
\r
259 player - Player record who owns the weapon stats
\r
260 game - Game record in which the stats were created
\r
261 pgstat - Corresponding PlayerGameStat record for these weapon stats
\r
262 player_events - dictionary containing the raw weapon values that need to be
\r
267 for (key,value) in player_events.items():
\r
268 matched = re.search("acc-(.*?)-cnt-fired", key)
\r
270 weapon_cd = matched.group(1)
\r
271 pwstat = PlayerWeaponStat()
\r
272 pwstat.player_id = player.player_id
\r
273 pwstat.game_id = game.game_id
\r
274 pwstat.player_game_stat_id = pgstat.player_game_stat_id
\r
275 pwstat.weapon_cd = weapon_cd
\r
277 if 'n' in player_events:
\r
278 pwstat.nick = player_events['n']
\r
280 pwstat.nick = player_events['P']
\r
282 if 'acc-' + weapon_cd + '-cnt-fired' in player_events:
\r
283 pwstat.fired = int(round(float(
\r
284 player_events['acc-' + weapon_cd + '-cnt-fired'])))
\r
285 if 'acc-' + weapon_cd + '-fired' in player_events:
\r
286 pwstat.max = int(round(float(
\r
287 player_events['acc-' + weapon_cd + '-fired'])))
\r
288 if 'acc-' + weapon_cd + '-cnt-hit' in player_events:
\r
289 pwstat.hit = int(round(float(
\r
290 player_events['acc-' + weapon_cd + '-cnt-hit'])))
\r
291 if 'acc-' + weapon_cd + '-hit' in player_events:
\r
292 pwstat.actual = int(round(float(
\r
293 player_events['acc-' + weapon_cd + '-hit'])))
\r
294 if 'acc-' + weapon_cd + '-frags' in player_events:
\r
295 pwstat.frags = int(round(float(
\r
296 player_events['acc-' + weapon_cd + '-frags'])))
\r
298 session.add(pwstat)
\r
299 pwstats.append(pwstat)
\r
304 def parse_body(request):
\r
306 Parses the POST request body for a stats submission
\r
308 # storage vars for the request body
\r
311 current_team = None
\r
314 log.debug(request.body)
\r
316 for line in request.body.split('\n'):
\r
318 (key, value) = line.strip().split(' ', 1)
\r
320 if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
\r
321 game_meta[key] = value
\r
324 # if we were working on a player record already, append
\r
325 # it and work on a new one (only set team info)
\r
326 if len(player_events) != 0:
\r
327 players.append(player_events)
\r
330 player_events[key] = value
\r
333 (subkey, subvalue) = value.split(' ', 1)
\r
334 player_events[subkey] = subvalue
\r
336 player_events[key] = value
\r
338 player_events[key] = value
\r
340 # no key/value pair - move on to the next line
\r
343 # add the last player we were working on
\r
344 if len(player_events) > 0:
\r
345 players.append(player_events)
\r
347 return (game_meta, players)
\r
350 def create_player_stats(session=None, player=None, game=None,
\r
351 player_events=None):
\r
353 Creates player game and weapon stats according to what type of player
\r
355 pgstat = create_player_game_stat(session=session,
\r
356 player=player, game=game, player_events=player_events)
\r
358 #TODO: put this into a config setting in the ini file?
\r
359 if not re.search('^bot#\d+$', player_events['P']):
\r
360 create_player_weapon_stats(session=session,
\r
361 player=player, game=game, pgstat=pgstat,
\r
362 player_events=player_events)
\r
365 def stats_submit(request):
\r
367 Entry handler for POST stats submissions.
\r
370 session = DBSession()
\r
372 (game_meta, players) = parse_body(request)
\r
374 # verify required metadata is present
\r
375 if 'T' not in game_meta or\
\r
376 'G' not in game_meta or\
\r
377 'M' not in game_meta or\
\r
378 'S' not in game_meta:
\r
379 log.debug("Required game meta fields (T, G, M, or S) missing. "\
\r
381 raise Exception("Required game meta fields (T, G, M, or S) missing.")
\r
384 for player_events in players:
\r
385 if not player_events['P'].startswith('bot'):
\r
386 # removing 'joins' here due to bug, but it should be here
\r
387 if 'matches' in player_events\
\r
388 and 'scoreboardvalid' in player_events:
\r
391 #TODO: put this into a config setting in the ini file?
\r
392 if real_players < 1:
\r
393 raise Exception("The number of real players is below the minimum. "\
\r
394 "Stats will be ignored.")
\r
396 server = get_or_create_server(session=session, name=game_meta['S'])
\r
397 gmap = get_or_create_map(session=session, name=game_meta['M'])
\r
399 if 'W' in game_meta:
\r
400 winner = game_meta['W']
\r
404 game = create_game(session=session,
\r
405 start_dt=datetime.datetime(
\r
406 *time.gmtime(float(game_meta['T']))[:6]),
\r
407 server_id=server.server_id, game_type_cd=game_meta['G'],
\r
408 map_id=gmap.map_id, winner=winner)
\r
410 # find or create a record for each player
\r
411 # and add stats for each if they were present at the end
\r
413 for player_events in players:
\r
414 if 'n' in player_events:
\r
415 nick = player_events['n']
\r
419 if 'matches' in player_events and 'scoreboardvalid' \
\r
421 player = get_or_create_player(session=session,
\r
422 hashkey=player_events['P'], nick=nick)
\r
423 log.debug('Creating stats for %s' % player_events['P'])
\r
424 create_player_stats(session=session, player=player, game=game,
\r
425 player_events=player_events)
\r
428 log.debug('Success! Stats recorded.')
\r
429 return Response('200 OK')
\r
430 except Exception as e:
\r