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 player.nick = new_nick
\r
42 def get_or_create_server(session=None, name=None):
\r
44 Find a server by name or create one if not found. Parameters:
\r
46 session - SQLAlchemy database session factory
\r
47 name - server name of the server to be found or created
\r
50 # find one by that name, if it exists
\r
51 server = session.query(Server).filter_by(name=name).one()
\r
52 log.debug("Found server id {0} with name {1}.".format(
\r
53 server.server_id, server.name))
\r
54 except NoResultFound, e:
\r
55 server = Server(name=name)
\r
58 log.debug("Created server id {0} with name {1}".format(
\r
59 server.server_id, server.name))
\r
60 except MultipleResultsFound, e:
\r
61 # multiple found, so use the first one but warn
\r
63 servers = session.query(Server).filter_by(name=name).order_by(
\r
64 Server.server_id).all()
\r
66 log.debug("Created server id {0} with name {1} but found \
\r
68 server.server_id, server.name))
\r
72 def get_or_create_map(session=None, name=None):
\r
74 Find a map by name or create one if not found. Parameters:
\r
76 session - SQLAlchemy database session factory
\r
77 name - map name of the map to be found or created
\r
80 # find one by the name, if it exists
\r
81 gmap = session.query(Map).filter_by(name=name).one()
\r
82 log.debug("Found map id {0} with name {1}.".format(gmap.map_id,
\r
84 except NoResultFound, e:
\r
85 gmap = Map(name=name)
\r
88 log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
\r
90 except MultipleResultsFound, e:
\r
91 # multiple found, so use the first one but warn
\r
93 gmaps = session.query(Map).filter_by(name=name).order_by(
\r
96 log.debug("Found map id {0} with name {1} but found \
\r
97 multiple.".format(gmap.map_id, gmap.name))
\r
102 def create_game(session=None, start_dt=None, game_type_cd=None,
\r
103 server_id=None, map_id=None, winner=None):
\r
105 Creates a game. Parameters:
\r
107 session - SQLAlchemy database session factory
\r
108 start_dt - when the game started (datetime object)
\r
109 game_type_cd - the game type of the game being played
\r
110 server_id - server identifier of the server hosting the game
\r
111 map_id - map on which the game was played
\r
112 winner - the team id of the team that won
\r
115 game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
\r
116 server_id=server_id, map_id=map_id, winner=winner)
\r
119 log.debug("Created game id {0} on server {1}, map {2} at time \
\r
120 {3} and on map {4}".format(game.game_id,
\r
121 server_id, map_id, start_dt, map_id))
\r
126 def get_or_create_player(session=None, hashkey=None, nick=None):
\r
128 Finds a player by hashkey or creates a new one (along with a
\r
129 corresponding hashkey entry. Parameters:
\r
131 session - SQLAlchemy database session factory
\r
132 hashkey - hashkey of the player to be found or created
\r
133 nick - nick of the player (in case of a first time create)
\r
136 if re.search('^bot#\d+$', hashkey):
\r
137 player = session.query(Player).filter_by(player_id=1).one()
\r
138 # if we have an untracked player
\r
139 elif re.search('^player#\d+$', hashkey):
\r
140 player = session.query(Player).filter_by(player_id=2).one()
\r
141 # else it is a tracked player
\r
143 # see if the player is already in the database
\r
144 # if not, create one and the hashkey along with it
\r
146 hashkey = session.query(Hashkey).filter_by(
\r
147 hashkey=hashkey).one()
\r
148 player = session.query(Player).filter_by(
\r
149 player_id=hashkey.player_id).one()
\r
150 log.debug("Found existing player {0} with hashkey {1}.".format(
\r
151 player.player_id, hashkey.hashkey))
\r
154 session.add(player)
\r
157 # if nick is given to us, use it. If not, use "Anonymous Player"
\r
158 # with a suffix added for uniqueness.
\r
160 player.nick = nick[:128]
\r
162 player.nick = "Anonymous Player #{0}".format(player.player_id)
\r
164 hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
\r
165 session.add(hashkey)
\r
166 log.debug("Created player {0} with hashkey {1}.".format(
\r
167 player.player_id, hashkey.hashkey))
\r
171 def create_player_game_stat(session=None, player=None,
\r
172 game=None, player_events=None):
\r
174 Creates game statistics for a given player in a given game. Parameters:
\r
176 session - SQLAlchemy session factory
\r
177 player - Player record of the player who owns the stats
\r
178 game - Game record for the game to which the stats pertain
\r
179 player_events - dictionary for the actual stats that need to be transformed
\r
182 # in here setup default values (e.g. if game type is CTF then
\r
183 # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
\r
184 # TODO: use game's create date here instead of now()
\r
185 pgstat = PlayerGameStat(create_dt=datetime.datetime.now())
\r
187 # set player id from player record
\r
188 pgstat.player_id = player.player_id
\r
190 #set game id from game record
\r
191 pgstat.game_id = game.game_id
\r
193 # all games have a score
\r
196 if game.game_type_cd == 'dm':
\r
199 pgstat.suicides = 0
\r
200 elif game.game_type_cd == 'ctf':
\r
202 pgstat.captures = 0
\r
206 pgstat.carrier_frags = 0
\r
208 for (key,value) in player_events.items():
\r
209 if key == 'n': pgstat.nick = value[:128]
\r
210 if key == 't': pgstat.team = value
\r
211 if key == 'rank': pgstat.rank = value
\r
212 if key == 'alivetime':
\r
213 pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
\r
214 if key == 'scoreboard-drops': pgstat.drops = value
\r
215 if key == 'scoreboard-returns': pgstat.returns = value
\r
216 if key == 'scoreboard-fckills': pgstat.carrier_frags = value
\r
217 if key == 'scoreboard-pickups': pgstat.pickups = value
\r
218 if key == 'scoreboard-caps': pgstat.captures = value
\r
219 if key == 'scoreboard-score': pgstat.score = value
\r
220 if key == 'scoreboard-deaths': pgstat.deaths = value
\r
221 if key == 'scoreboard-kills': pgstat.kills = value
\r
222 if key == 'scoreboard-suicides': pgstat.suicides = value
\r
224 # check to see if we had a name, and if
\r
225 # not use the name from the player id
\r
226 if pgstat.nick == None:
\r
227 pgstat.nick = player.nick
\r
229 # if the nick we end up with is different from the one in the
\r
230 # player record, change the nick to reflect the new value
\r
231 if pgstat.nick != player.nick and player.player_id > 1:
\r
232 log.debug('Registering new nick for {0}: {1}'.format(player.nick,
\r
234 register_new_nick(session, player, pgstat.nick)
\r
236 # if the player is ranked #1 and it is a team game, set the game's winner
\r
237 # to be the team of that player
\r
238 # FIXME: this is a hack, should be using the 'W' field (not present)
\r
239 if pgstat.rank == '1' and pgstat.team:
\r
240 log.debug('Found rank 1. Logging.')
\r
241 game.winner = pgstat.team
\r
244 session.add(pgstat)
\r
250 def create_player_weapon_stats(session=None, player=None,
\r
251 game=None, pgstat=None, player_events=None):
\r
253 Creates accuracy records for each weapon used by a given player in a
\r
254 given game. Parameters:
\r
256 session - SQLAlchemy session factory object
\r
257 player - Player record who owns the weapon stats
\r
258 game - Game record in which the stats were created
\r
259 pgstat - Corresponding PlayerGameStat record for these weapon stats
\r
260 player_events - dictionary containing the raw weapon values that need to be
\r
265 for (key,value) in player_events.items():
\r
266 matched = re.search("acc-(.*?)-cnt-fired", key)
\r
268 weapon_cd = matched.group(1)
\r
269 pwstat = PlayerWeaponStat()
\r
270 pwstat.player_id = player.player_id
\r
271 pwstat.game_id = game.game_id
\r
272 pwstat.player_game_stat_id = pgstat.player_game_stat_id
\r
273 pwstat.weapon_cd = weapon_cd
\r
275 if 'n' in player_events:
\r
276 pwstat.nick = player_events['n']
\r
278 pwstat.nick = player_events['P']
\r
280 if 'acc-' + weapon_cd + '-cnt-fired' in player_events:
\r
281 pwstat.fired = int(round(float(
\r
282 player_events['acc-' + weapon_cd + '-cnt-fired'])))
\r
283 if 'acc-' + weapon_cd + '-fired' in player_events:
\r
284 pwstat.max = int(round(float(
\r
285 player_events['acc-' + weapon_cd + '-fired'])))
\r
286 if 'acc-' + weapon_cd + '-cnt-hit' in player_events:
\r
287 pwstat.hit = int(round(float(
\r
288 player_events['acc-' + weapon_cd + '-cnt-hit'])))
\r
289 if 'acc-' + weapon_cd + '-hit' in player_events:
\r
290 pwstat.actual = int(round(float(
\r
291 player_events['acc-' + weapon_cd + '-hit'])))
\r
292 if 'acc-' + weapon_cd + '-frags' in player_events:
\r
293 pwstat.frags = int(round(float(
\r
294 player_events['acc-' + weapon_cd + '-frags'])))
\r
296 session.add(pwstat)
\r
297 pwstats.append(pwstat)
\r
302 def parse_body(request):
\r
304 Parses the POST request body for a stats submission
\r
306 # storage vars for the request body
\r
309 current_team = None
\r
312 log.debug(request.body)
\r
314 for line in request.body.split('\n'):
\r
316 (key, value) = line.strip().split(' ', 1)
\r
318 # Server (S) and Nick (n) fields can have international characters.
\r
319 # We encode these as UTF-8.
\r
321 value = unicode(value, 'utf-8')
\r
323 if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
\r
324 game_meta[key] = value
\r
327 # if we were working on a player record already, append
\r
328 # it and work on a new one (only set team info)
\r
329 if len(player_events) != 0:
\r
330 players.append(player_events)
\r
333 player_events[key] = value
\r
336 (subkey, subvalue) = value.split(' ', 1)
\r
337 player_events[subkey] = subvalue
\r
339 player_events[key] = value
\r
341 player_events[key] = value
\r
343 # no key/value pair - move on to the next line
\r
346 # add the last player we were working on
\r
347 if len(player_events) > 0:
\r
348 players.append(player_events)
\r
350 return (game_meta, players)
\r
353 def create_player_stats(session=None, player=None, game=None,
\r
354 player_events=None):
\r
356 Creates player game and weapon stats according to what type of player
\r
358 pgstat = create_player_game_stat(session=session,
\r
359 player=player, game=game, player_events=player_events)
\r
361 #TODO: put this into a config setting in the ini file?
\r
362 if not re.search('^bot#\d+$', player_events['P']):
\r
363 create_player_weapon_stats(session=session,
\r
364 player=player, game=game, pgstat=pgstat,
\r
365 player_events=player_events)
\r
368 def stats_submit(request):
\r
370 Entry handler for POST stats submissions.
\r
373 session = DBSession()
\r
375 (game_meta, players) = parse_body(request)
\r
377 # verify required metadata is present
\r
378 if 'T' not in game_meta or\
\r
379 'G' not in game_meta or\
\r
380 'M' not in game_meta or\
\r
381 'S' not in game_meta:
\r
382 log.debug("Required game meta fields (T, G, M, or S) missing. "\
\r
384 raise Exception("Required game meta fields (T, G, M, or S) missing.")
\r
387 for player_events in players:
\r
388 if not player_events['P'].startswith('bot'):
\r
389 # removing 'joins' here due to bug, but it should be here
\r
390 if 'matches' in player_events\
\r
391 and 'scoreboardvalid' in player_events:
\r
394 #TODO: put this into a config setting in the ini file?
\r
395 if real_players < 1:
\r
396 raise Exception("The number of real players is below the minimum. "\
\r
397 "Stats will be ignored.")
\r
399 server = get_or_create_server(session=session, name=game_meta['S'])
\r
400 gmap = get_or_create_map(session=session, name=game_meta['M'])
\r
402 if 'W' in game_meta:
\r
403 winner = game_meta['W']
\r
407 game = create_game(session=session,
\r
408 start_dt=datetime.datetime(
\r
409 *time.gmtime(float(game_meta['T']))[:6]),
\r
410 server_id=server.server_id, game_type_cd=game_meta['G'],
\r
411 map_id=gmap.map_id, winner=winner)
\r
413 # find or create a record for each player
\r
414 # and add stats for each if they were present at the end
\r
416 for player_events in players:
\r
417 if 'n' in player_events:
\r
418 nick = player_events['n']
\r
422 if 'matches' in player_events and 'scoreboardvalid' \
\r
424 player = get_or_create_player(session=session,
\r
425 hashkey=player_events['P'], nick=nick)
\r
426 log.debug('Creating stats for %s' % player_events['P'])
\r
427 create_player_stats(session=session, player=player, game=game,
\r
428 player_events=player_events)
\r
431 log.debug('Success! Stats recorded.')
\r
432 return Response('200 OK')
\r
433 except Exception as e:
\r