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
12 def is_real_player(events):
\r
15 if not events['P'].startswith('bot'):
\r
16 # removing 'joins' here due to bug, but it should be here
\r
17 if 'matches' in events and 'scoreboardvalid' in events:
\r
23 def register_new_nick(session, player, new_nick):
\r
25 Change the player record's nick to the newly found nick. Store the old
\r
26 nick in the player_nicks table for that player.
\r
28 session - SQLAlchemy database session factory
\r
29 player - player record whose nick is changing
\r
30 new_nick - the new nickname
\r
32 # see if that nick already exists
\r
33 stripped_nick = strip_colors(player.nick)
\r
35 player_nick = session.query(PlayerNick).filter_by(
\r
36 player_id=player.player_id, stripped_nick=stripped_nick).one()
\r
37 except NoResultFound, e:
\r
38 # player_id/stripped_nick not found, create one
\r
39 # but we don't store "Anonymous Player #N"
\r
40 if not re.search('^Anonymous Player #\d+$', player.nick):
\r
41 player_nick = PlayerNick()
\r
42 player_nick.player_id = player.player_id
\r
43 player_nick.stripped_nick = stripped_nick
\r
44 player_nick.nick = player.nick
\r
45 session.add(player_nick)
\r
47 # We change to the new nick regardless
\r
48 player.nick = new_nick
\r
52 def get_or_create_server(session=None, name=None):
\r
54 Find a server by name or create one if not found. Parameters:
\r
56 session - SQLAlchemy database session factory
\r
57 name - server name of the server to be found or created
\r
60 # find one by that name, if it exists
\r
61 server = session.query(Server).filter_by(name=name).one()
\r
62 log.debug("Found server id {0}: {1}".format(
\r
63 server.server_id, server.name.encode('utf-8')))
\r
64 except NoResultFound, e:
\r
65 server = Server(name=name)
\r
68 log.debug("Created server id {0}: {1}".format(
\r
69 server.server_id, server.name.encode('utf-8')))
\r
70 except MultipleResultsFound, e:
\r
71 # multiple found, so use the first one but warn
\r
73 servers = session.query(Server).filter_by(name=name).order_by(
\r
74 Server.server_id).all()
\r
76 log.debug("Created server id {0}: {1} but found \
\r
78 server.server_id, server.name.encode('utf-8')))
\r
82 def get_or_create_map(session=None, name=None):
\r
84 Find a map by name or create one if not found. Parameters:
\r
86 session - SQLAlchemy database session factory
\r
87 name - map name of the map to be found or created
\r
90 # find one by the name, if it exists
\r
91 gmap = session.query(Map).filter_by(name=name).one()
\r
92 log.debug("Found map id {0}: {1}".format(gmap.map_id,
\r
94 except NoResultFound, e:
\r
95 gmap = Map(name=name)
\r
98 log.debug("Created map id {0}: {1}".format(gmap.map_id,
\r
100 except MultipleResultsFound, e:
\r
101 # multiple found, so use the first one but warn
\r
103 gmaps = session.query(Map).filter_by(name=name).order_by(
\r
106 log.debug("Found map id {0}: {1} but found \
\r
107 multiple".format(gmap.map_id, gmap.name))
\r
112 def create_game(session=None, start_dt=None, game_type_cd=None,
\r
113 server_id=None, map_id=None, winner=None):
\r
115 Creates a game. Parameters:
\r
117 session - SQLAlchemy database session factory
\r
118 start_dt - when the game started (datetime object)
\r
119 game_type_cd - the game type of the game being played
\r
120 server_id - server identifier of the server hosting the game
\r
121 map_id - map on which the game was played
\r
122 winner - the team id of the team that won
\r
125 game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
\r
126 server_id=server_id, map_id=map_id, winner=winner)
\r
129 log.debug("Created game id {0} on server {1}, map {2} at \
\r
130 {3}".format(game.game_id,
\r
131 server_id, map_id, start_dt))
\r
136 def get_or_create_player(session=None, hashkey=None, nick=None):
\r
138 Finds a player by hashkey or creates a new one (along with a
\r
139 corresponding hashkey entry. Parameters:
\r
141 session - SQLAlchemy database session factory
\r
142 hashkey - hashkey of the player to be found or created
\r
143 nick - nick of the player (in case of a first time create)
\r
146 if re.search('^bot#\d+$', hashkey):
\r
147 player = session.query(Player).filter_by(player_id=1).one()
\r
148 # if we have an untracked player
\r
149 elif re.search('^player#\d+$', hashkey):
\r
150 player = session.query(Player).filter_by(player_id=2).one()
\r
151 # else it is a tracked player
\r
153 # see if the player is already in the database
\r
154 # if not, create one and the hashkey along with it
\r
156 hashkey = session.query(Hashkey).filter_by(
\r
157 hashkey=hashkey).one()
\r
158 player = session.query(Player).filter_by(
\r
159 player_id=hashkey.player_id).one()
\r
160 log.debug("Found existing player {0} with hashkey {1}".format(
\r
161 player.player_id, hashkey.hashkey))
\r
164 session.add(player)
\r
167 # if nick is given to us, use it. If not, use "Anonymous Player"
\r
168 # with a suffix added for uniqueness.
\r
170 player.nick = nick[:128]
\r
172 player.nick = "Anonymous Player #{0}".format(player.player_id)
\r
174 hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
\r
175 session.add(hashkey)
\r
176 log.debug("Created player {0} ({2}) with hashkey {1}".format(
\r
177 player.player_id, hashkey.hashkey, player.nick.encode('utf-8')))
\r
181 def create_player_game_stat(session=None, player=None,
\r
182 game=None, player_events=None):
\r
184 Creates game statistics for a given player in a given game. Parameters:
\r
186 session - SQLAlchemy session factory
\r
187 player - Player record of the player who owns the stats
\r
188 game - Game record for the game to which the stats pertain
\r
189 player_events - dictionary for the actual stats that need to be transformed
\r
192 # in here setup default values (e.g. if game type is CTF then
\r
193 # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
\r
194 # TODO: use game's create date here instead of now()
\r
195 pgstat = PlayerGameStat(create_dt=datetime.datetime.now())
\r
197 # set player id from player record
\r
198 pgstat.player_id = player.player_id
\r
200 #set game id from game record
\r
201 pgstat.game_id = game.game_id
\r
203 # all games have a score
\r
206 if game.game_type_cd == 'dm':
\r
209 pgstat.suicides = 0
\r
210 elif game.game_type_cd == 'ctf':
\r
212 pgstat.captures = 0
\r
216 pgstat.carrier_frags = 0
\r
218 for (key,value) in player_events.items():
\r
219 if key == 'n': pgstat.nick = value[:128]
\r
220 if key == 't': pgstat.team = value
\r
221 if key == 'rank': pgstat.rank = value
\r
222 if key == 'alivetime':
\r
223 pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
\r
224 if key == 'scoreboard-drops': pgstat.drops = value
\r
225 if key == 'scoreboard-returns': pgstat.returns = value
\r
226 if key == 'scoreboard-fckills': pgstat.carrier_frags = value
\r
227 if key == 'scoreboard-pickups': pgstat.pickups = value
\r
228 if key == 'scoreboard-caps': pgstat.captures = value
\r
229 if key == 'scoreboard-score': pgstat.score = value
\r
230 if key == 'scoreboard-deaths': pgstat.deaths = value
\r
231 if key == 'scoreboard-kills': pgstat.kills = value
\r
232 if key == 'scoreboard-suicides': pgstat.suicides = value
\r
234 # check to see if we had a name, and if
\r
235 # not use the name from the player id
\r
236 if pgstat.nick == None:
\r
237 pgstat.nick = player.nick
\r
239 # if the nick we end up with is different from the one in the
\r
240 # player record, change the nick to reflect the new value
\r
241 if pgstat.nick != player.nick and player.player_id > 1:
\r
242 register_new_nick(session, player, pgstat.nick)
\r
244 # if the player is ranked #1 and it is a team game, set the game's winner
\r
245 # to be the team of that player
\r
246 # FIXME: this is a hack, should be using the 'W' field (not present)
\r
247 if pgstat.rank == '1' and pgstat.team:
\r
248 game.winner = pgstat.team
\r
251 session.add(pgstat)
\r
257 def create_player_weapon_stats(session=None, player=None,
\r
258 game=None, pgstat=None, player_events=None):
\r
260 Creates accuracy records for each weapon used by a given player in a
\r
261 given game. Parameters:
\r
263 session - SQLAlchemy session factory object
\r
264 player - Player record who owns the weapon stats
\r
265 game - Game record in which the stats were created
\r
266 pgstat - Corresponding PlayerGameStat record for these weapon stats
\r
267 player_events - dictionary containing the raw weapon values that need to be
\r
272 for (key,value) in player_events.items():
\r
273 matched = re.search("acc-(.*?)-cnt-fired", key)
\r
275 weapon_cd = matched.group(1)
\r
276 pwstat = PlayerWeaponStat()
\r
277 pwstat.player_id = player.player_id
\r
278 pwstat.game_id = game.game_id
\r
279 pwstat.player_game_stat_id = pgstat.player_game_stat_id
\r
280 pwstat.weapon_cd = weapon_cd
\r
282 if 'n' in player_events:
\r
283 pwstat.nick = player_events['n']
\r
285 pwstat.nick = player_events['P']
\r
287 if 'acc-' + weapon_cd + '-cnt-fired' in player_events:
\r
288 pwstat.fired = int(round(float(
\r
289 player_events['acc-' + weapon_cd + '-cnt-fired'])))
\r
290 if 'acc-' + weapon_cd + '-fired' in player_events:
\r
291 pwstat.max = int(round(float(
\r
292 player_events['acc-' + weapon_cd + '-fired'])))
\r
293 if 'acc-' + weapon_cd + '-cnt-hit' in player_events:
\r
294 pwstat.hit = int(round(float(
\r
295 player_events['acc-' + weapon_cd + '-cnt-hit'])))
\r
296 if 'acc-' + weapon_cd + '-hit' in player_events:
\r
297 pwstat.actual = int(round(float(
\r
298 player_events['acc-' + weapon_cd + '-hit'])))
\r
299 if 'acc-' + weapon_cd + '-frags' in player_events:
\r
300 pwstat.frags = int(round(float(
\r
301 player_events['acc-' + weapon_cd + '-frags'])))
\r
303 session.add(pwstat)
\r
304 pwstats.append(pwstat)
\r
309 def parse_body(request):
\r
311 Parses the POST request body for a stats submission
\r
313 # storage vars for the request body
\r
316 current_team = None
\r
319 log.debug(request.body)
\r
321 for line in request.body.split('\n'):
\r
323 (key, value) = line.strip().split(' ', 1)
\r
325 # Server (S) and Nick (n) fields can have international characters.
\r
326 # We encode these as UTF-8.
\r
328 value = unicode(value, 'utf-8')
\r
330 if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
\r
331 game_meta[key] = value
\r
334 # if we were working on a player record already, append
\r
335 # it and work on a new one (only set team info)
\r
336 if len(player_events) != 0:
\r
337 players.append(player_events)
\r
340 player_events[key] = value
\r
343 (subkey, subvalue) = value.split(' ', 1)
\r
344 player_events[subkey] = subvalue
\r
346 player_events[key] = value
\r
348 player_events[key] = value
\r
350 # no key/value pair - move on to the next line
\r
353 # add the last player we were working on
\r
354 if len(player_events) > 0:
\r
355 players.append(player_events)
\r
357 return (game_meta, players)
\r
360 def create_player_stats(session=None, player=None, game=None,
\r
361 player_events=None):
\r
363 Creates player game and weapon stats according to what type of player
\r
365 pgstat = create_player_game_stat(session=session,
\r
366 player=player, game=game, player_events=player_events)
\r
368 #TODO: put this into a config setting in the ini file?
\r
369 if not re.search('^bot#\d+$', player_events['P']):
\r
370 create_player_weapon_stats(session=session,
\r
371 player=player, game=game, pgstat=pgstat,
\r
372 player_events=player_events)
\r
375 def stats_submit(request):
\r
377 Entry handler for POST stats submissions.
\r
380 session = DBSession()
\r
382 (game_meta, players) = parse_body(request)
\r
384 # verify required metadata is present
\r
385 if 'T' not in game_meta or\
\r
386 'G' not in game_meta or\
\r
387 'M' not in game_meta or\
\r
388 'S' not in game_meta:
\r
389 log.debug("Required game meta fields (T, G, M, or S) missing. "\
\r
391 raise Exception("Required game meta fields (T, G, M, or S) missing.")
\r
394 for events in players:
\r
395 if is_real_player(events):
\r
398 #TODO: put this into a config setting in the ini file?
\r
399 if real_players < 1:
\r
400 raise Exception("The number of real players is below the minimum. "\
\r
401 "Stats will be ignored.")
\r
403 server = get_or_create_server(session=session, name=game_meta['S'])
\r
404 gmap = get_or_create_map(session=session, name=game_meta['M'])
\r
406 if 'W' in game_meta:
\r
407 winner = game_meta['W']
\r
411 game = create_game(session=session,
\r
412 start_dt=datetime.datetime(
\r
413 *time.gmtime(float(game_meta['T']))[:6]),
\r
414 server_id=server.server_id, game_type_cd=game_meta['G'],
\r
415 map_id=gmap.map_id, winner=winner)
\r
417 # find or create a record for each player
\r
418 # and add stats for each if they were present at the end
\r
420 for player_events in players:
\r
421 if 'n' in player_events:
\r
422 nick = player_events['n']
\r
426 if 'matches' in player_events and 'scoreboardvalid' \
\r
428 player = get_or_create_player(session=session,
\r
429 hashkey=player_events['P'], nick=nick)
\r
430 log.debug('Creating stats for %s' % player_events['P'])
\r
431 create_player_stats(session=session, player=player, game=game,
\r
432 player_events=player_events)
\r
435 log.debug('Success! Stats recorded.')
\r
436 return Response('200 OK')
\r
437 except Exception as e:
\r