4 import pyramid.httpexceptions
7 from pyramid.response import Response
8 from sqlalchemy import Sequence
9 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
10 from xonstat.d0_blind_id import d0_blind_id_verify
11 from xonstat.elo import process_elos
12 from xonstat.models import *
13 from xonstat.util import strip_colors, qfont_decode
15 log = logging.getLogger(__name__)
18 def is_blank_game(players):
19 """Determine if this is a blank game or not. A blank game is either:
21 1) a match that ended in the warmup stage, where accuracy events are not
24 2) a match in which no player made a positive or negative score AND was
27 r = re.compile(r'acc-.*-cnt-fired')
28 flg_nonzero_score = False
29 flg_acc_events = False
31 for events in players:
32 if is_real_player(events):
33 for (key,value) in events.items():
34 if key == 'scoreboard-score' and value != 0:
35 flg_nonzero_score = True
39 return not (flg_nonzero_score and flg_acc_events)
41 def get_remote_addr(request):
42 """Get the Xonotic server's IP address"""
43 if 'X-Forwarded-For' in request.headers:
44 return request.headers['X-Forwarded-For']
46 return request.remote_addr
49 def is_supported_gametype(gametype):
50 """Whether a gametype is supported or not"""
51 supported_game_types = ('duel', 'dm', 'ctf', 'tdm', 'kh',
52 'ka', 'ft', 'freezetag', 'nb', 'nexball')
54 if gametype in supported_game_types:
60 def verify_request(request):
62 (idfp, status) = d0_blind_id_verify(
63 sig=request.headers['X-D0-Blind-Id-Detached-Signature'],
65 postdata=request.body)
67 log.debug('\nidfp: {0}\nstatus: {1}'.format(idfp, status))
75 def num_real_players(player_events):
77 Returns the number of real players (those who played
78 and are on the scoreboard).
82 for events in player_events:
83 if is_real_player(events) and played_in_game(events):
89 def has_minimum_real_players(settings, player_events):
91 Determines if the collection of player events has enough "real" players
92 to store in the database. The minimum setting comes from the config file
93 under the setting xonstat.minimum_real_players.
95 flg_has_min_real_players = True
98 minimum_required_players = int(
99 settings['xonstat.minimum_required_players'])
101 minimum_required_players = 2
103 real_players = num_real_players(player_events)
105 if real_players < minimum_required_players:
106 flg_has_min_real_players = False
108 return flg_has_min_real_players
111 def verify_requests(settings):
113 Determines whether or not to verify requests using the blind_id algorithm
116 val_verify_requests = settings['xonstat.verify_requests']
117 if val_verify_requests == "true":
118 flg_verify_requests = True
120 flg_verify_requests = False
122 flg_verify_requests = True
124 return flg_verify_requests
127 def has_required_metadata(metadata):
129 Determines if a give set of metadata has enough data to create a game,
130 server, and map with.
132 flg_has_req_metadata = True
134 if 'T' not in metadata or\
135 'G' not in metadata or\
136 'M' not in metadata or\
137 'I' not in metadata or\
139 flg_has_req_metadata = False
141 return flg_has_req_metadata
144 def is_real_player(events):
146 Determines if a given set of events correspond with a non-bot
148 if not events['P'].startswith('bot'):
154 def played_in_game(events):
156 Determines if a given set of player events correspond with a player who
157 played in the game (matches 1 and scoreboardvalid 1)
159 if 'matches' in events and 'scoreboardvalid' in events:
165 def register_new_nick(session, player, new_nick):
167 Change the player record's nick to the newly found nick. Store the old
168 nick in the player_nicks table for that player.
170 session - SQLAlchemy database session factory
171 player - player record whose nick is changing
172 new_nick - the new nickname
174 # see if that nick already exists
175 stripped_nick = strip_colors(qfont_decode(player.nick))
177 player_nick = session.query(PlayerNick).filter_by(
178 player_id=player.player_id, stripped_nick=stripped_nick).one()
179 except NoResultFound, e:
180 # player_id/stripped_nick not found, create one
181 # but we don't store "Anonymous Player #N"
182 if not re.search('^Anonymous Player #\d+$', player.nick):
183 player_nick = PlayerNick()
184 player_nick.player_id = player.player_id
185 player_nick.stripped_nick = stripped_nick
186 player_nick.nick = player.nick
187 session.add(player_nick)
189 # We change to the new nick regardless
190 player.nick = new_nick
191 player.stripped_nick = strip_colors(qfont_decode(new_nick))
195 def update_fastest_cap(session, player_id, game_id, map_id, captime):
197 Check the fastest cap time for the player and map. If there isn't
198 one, insert one. If there is, check if the passed time is faster.
201 # we don't record fastest cap times for bots or anonymous players
205 # see if a cap entry exists already
206 # then check to see if the new captime is faster
208 cur_fastest_cap = session.query(PlayerCaptime).filter_by(
209 player_id=player_id, map_id=map_id).one()
211 # current captime is faster, so update
212 if captime < cur_fastest_cap.fastest_cap:
213 cur_fastest_cap.fastest_cap = captime
214 cur_fastest_cap.game_id = game_id
215 cur_fastest_cap.create_dt = datetime.datetime.utcnow()
216 session.add(cur_fastest_cap)
218 except NoResultFound, e:
219 # none exists, so insert
220 cur_fastest_cap = PlayerCaptime(player_id, game_id, map_id, captime)
221 session.add(cur_fastest_cap)
225 def get_or_create_server(session=None, name=None, hashkey=None, ip_addr=None,
228 Find a server by name or create one if not found. Parameters:
230 session - SQLAlchemy database session factory
231 name - server name of the server to be found or created
232 hashkey - server hashkey
235 # find one by that name, if it exists
236 server = session.query(Server).filter_by(name=name).one()
239 if server.hashkey != hashkey:
240 server.hashkey = hashkey
243 # store new IP address
244 if server.ip_addr != ip_addr:
245 server.ip_addr = ip_addr
249 if server.revision != revision:
250 server.revision = revision
253 log.debug("Found existing server {0}".format(server.server_id))
255 except MultipleResultsFound, e:
256 # multiple found, so also filter by hashkey
257 server = session.query(Server).filter_by(name=name).\
258 filter_by(hashkey=hashkey).one()
259 log.debug("Found existing server {0}".format(server.server_id))
261 except NoResultFound, e:
262 # not found, create one
263 server = Server(name=name, hashkey=hashkey)
266 log.debug("Created server {0} with hashkey {1}".format(
267 server.server_id, server.hashkey))
272 def get_or_create_map(session=None, name=None):
274 Find a map by name or create one if not found. Parameters:
276 session - SQLAlchemy database session factory
277 name - map name of the map to be found or created
280 # find one by the name, if it exists
281 gmap = session.query(Map).filter_by(name=name).one()
282 log.debug("Found map id {0}: {1}".format(gmap.map_id,
284 except NoResultFound, e:
285 gmap = Map(name=name)
288 log.debug("Created map id {0}: {1}".format(gmap.map_id,
290 except MultipleResultsFound, e:
291 # multiple found, so use the first one but warn
293 gmaps = session.query(Map).filter_by(name=name).order_by(
296 log.debug("Found map id {0}: {1} but found \
297 multiple".format(gmap.map_id, gmap.name))
302 def create_game(session=None, start_dt=None, game_type_cd=None,
303 server_id=None, map_id=None, winner=None, match_id=None,
306 Creates a game. Parameters:
308 session - SQLAlchemy database session factory
309 start_dt - when the game started (datetime object)
310 game_type_cd - the game type of the game being played
311 server_id - server identifier of the server hosting the game
312 map_id - map on which the game was played
313 winner - the team id of the team that won
315 seq = Sequence('games_game_id_seq')
316 game_id = session.execute(seq)
317 game = Game(game_id=game_id, start_dt=start_dt, game_type_cd=game_type_cd,
318 server_id=server_id, map_id=map_id, winner=winner)
319 game.match_id = match_id
322 game.duration = datetime.timedelta(seconds=int(round(float(duration))))
327 session.query(Game).filter(Game.server_id==server_id).\
328 filter(Game.match_id==match_id).one()
330 log.debug("Error: game with same server and match_id found! Ignoring.")
332 # if a game under the same server and match_id found,
333 # this is a duplicate game and can be ignored
334 raise pyramid.httpexceptions.HTTPOk('OK')
335 except NoResultFound, e:
336 # server_id/match_id combination not found. game is ok to insert
339 log.debug("Created game id {0} on server {1}, map {2} at \
340 {3}".format(game.game_id,
341 server_id, map_id, start_dt))
346 def get_or_create_player(session=None, hashkey=None, nick=None):
348 Finds a player by hashkey or creates a new one (along with a
349 corresponding hashkey entry. Parameters:
351 session - SQLAlchemy database session factory
352 hashkey - hashkey of the player to be found or created
353 nick - nick of the player (in case of a first time create)
356 if re.search('^bot#\d+$', hashkey) or re.search('^bot#\d+#', hashkey):
357 player = session.query(Player).filter_by(player_id=1).one()
358 # if we have an untracked player
359 elif re.search('^player#\d+$', hashkey):
360 player = session.query(Player).filter_by(player_id=2).one()
361 # else it is a tracked player
363 # see if the player is already in the database
364 # if not, create one and the hashkey along with it
366 hk = session.query(Hashkey).filter_by(
367 hashkey=hashkey).one()
368 player = session.query(Player).filter_by(
369 player_id=hk.player_id).one()
370 log.debug("Found existing player {0} with hashkey {1}".format(
371 player.player_id, hashkey))
377 # if nick is given to us, use it. If not, use "Anonymous Player"
378 # with a suffix added for uniqueness.
380 player.nick = nick[:128]
381 player.stripped_nick = strip_colors(qfont_decode(nick[:128]))
383 player.nick = "Anonymous Player #{0}".format(player.player_id)
384 player.stripped_nick = player.nick
386 hk = Hashkey(player_id=player.player_id, hashkey=hashkey)
388 log.debug("Created player {0} ({2}) with hashkey {1}".format(
389 player.player_id, hashkey, player.nick.encode('utf-8')))
393 def create_player_game_stat(session=None, player=None,
394 game=None, player_events=None):
396 Creates game statistics for a given player in a given game. Parameters:
398 session - SQLAlchemy session factory
399 player - Player record of the player who owns the stats
400 game - Game record for the game to which the stats pertain
401 player_events - dictionary for the actual stats that need to be transformed
404 # in here setup default values (e.g. if game type is CTF then
405 # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
406 # TODO: use game's create date here instead of now()
407 seq = Sequence('player_game_stats_player_game_stat_id_seq')
408 pgstat_id = session.execute(seq)
409 pgstat = PlayerGameStat(player_game_stat_id=pgstat_id,
410 create_dt=datetime.datetime.utcnow())
412 # set player id from player record
413 pgstat.player_id = player.player_id
415 #set game id from game record
416 pgstat.game_id = game.game_id
418 # all games have a score and every player has an alivetime
420 pgstat.alivetime = datetime.timedelta(seconds=0)
422 if game.game_type_cd == 'dm' or game.game_type_cd == 'tdm' or game.game_type_cd == 'duel':
426 elif game.game_type_cd == 'ctf':
432 pgstat.carrier_frags = 0
434 for (key,value) in player_events.items():
436 pgstat.nick = value[:128]
437 pgstat.stripped_nick = strip_colors(qfont_decode(pgstat.nick))
438 if key == 't': pgstat.team = int(value)
440 pgstat.rank = int(value)
441 # to support older servers who don't send scoreboardpos values
442 if pgstat.scoreboardpos is None:
443 pgstat.scoreboardpos = pgstat.rank
444 if key == 'alivetime':
445 pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
446 if key == 'scoreboard-drops': pgstat.drops = int(value)
447 if key == 'scoreboard-returns': pgstat.returns = int(value)
448 if key == 'scoreboard-fckills': pgstat.carrier_frags = int(value)
449 if key == 'scoreboard-pickups': pgstat.pickups = int(value)
450 if key == 'scoreboard-caps': pgstat.captures = int(value)
451 if key == 'scoreboard-score': pgstat.score = int(value)
452 if key == 'scoreboard-deaths': pgstat.deaths = int(value)
453 if key == 'scoreboard-kills': pgstat.kills = int(value)
454 if key == 'scoreboard-suicides': pgstat.suicides = int(value)
455 if key == 'scoreboard-captime':
456 pgstat.fastest_cap = datetime.timedelta(seconds=float(value)/100)
457 if key == 'avglatency': pgstat.avg_latency = float(value)
458 if key == 'teamrank': pgstat.teamrank = int(value)
459 if key == 'scoreboardpos': pgstat.scoreboardpos = int(value)
461 # check to see if we had a name, and if
462 # not use an anonymous handle
463 if pgstat.nick == None:
464 pgstat.nick = "Anonymous Player"
465 pgstat.stripped_nick = "Anonymous Player"
467 # otherwise process a nick change
468 elif pgstat.nick != player.nick and player.player_id > 2:
469 register_new_nick(session, player, pgstat.nick)
471 # if the player is ranked #1 and it is a team game, set the game's winner
472 # to be the team of that player
473 # FIXME: this is a hack, should be using the 'W' field (not present)
474 if pgstat.rank == 1 and pgstat.team:
475 game.winner = pgstat.team
483 def create_player_weapon_stats(session=None, player=None,
484 game=None, pgstat=None, player_events=None, game_meta=None):
486 Creates accuracy records for each weapon used by a given player in a
487 given game. Parameters:
489 session - SQLAlchemy session factory object
490 player - Player record who owns the weapon stats
491 game - Game record in which the stats were created
492 pgstat - Corresponding PlayerGameStat record for these weapon stats
493 player_events - dictionary containing the raw weapon values that need to be
495 game_meta - dictionary of game metadata (only used for stats version info)
499 # Version 1 of stats submissions doubled the data sent.
500 # To counteract this we divide the data by 2 only for
501 # POSTs coming from version 1.
503 version = int(game_meta['V'])
506 log.debug('NOTICE: found a version 1 request, halving the weapon stats...')
512 for (key,value) in player_events.items():
513 matched = re.search("acc-(.*?)-cnt-fired", key)
515 weapon_cd = matched.group(1)
516 seq = Sequence('player_weapon_stats_player_weapon_stats_id_seq')
517 pwstat_id = session.execute(seq)
518 pwstat = PlayerWeaponStat()
519 pwstat.player_weapon_stats_id = pwstat_id
520 pwstat.player_id = player.player_id
521 pwstat.game_id = game.game_id
522 pwstat.player_game_stat_id = pgstat.player_game_stat_id
523 pwstat.weapon_cd = weapon_cd
525 if 'n' in player_events:
526 pwstat.nick = player_events['n']
528 pwstat.nick = player_events['P']
530 if 'acc-' + weapon_cd + '-cnt-fired' in player_events:
531 pwstat.fired = int(round(float(
532 player_events['acc-' + weapon_cd + '-cnt-fired'])))
533 if 'acc-' + weapon_cd + '-fired' in player_events:
534 pwstat.max = int(round(float(
535 player_events['acc-' + weapon_cd + '-fired'])))
536 if 'acc-' + weapon_cd + '-cnt-hit' in player_events:
537 pwstat.hit = int(round(float(
538 player_events['acc-' + weapon_cd + '-cnt-hit'])))
539 if 'acc-' + weapon_cd + '-hit' in player_events:
540 pwstat.actual = int(round(float(
541 player_events['acc-' + weapon_cd + '-hit'])))
542 if 'acc-' + weapon_cd + '-frags' in player_events:
543 pwstat.frags = int(round(float(
544 player_events['acc-' + weapon_cd + '-frags'])))
547 pwstat.fired = pwstat.fired/2
548 pwstat.max = pwstat.max/2
549 pwstat.hit = pwstat.hit/2
550 pwstat.actual = pwstat.actual/2
551 pwstat.frags = pwstat.frags/2
554 pwstats.append(pwstat)
559 def parse_body(request):
561 Parses the POST request body for a stats submission
563 # storage vars for the request body
569 for line in request.body.split('\n'):
571 (key, value) = line.strip().split(' ', 1)
573 # Server (S) and Nick (n) fields can have international characters.
574 # We convert to UTF-8.
576 value = unicode(value, 'utf-8')
578 if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W' 'I' 'D':
579 game_meta[key] = value
582 # if we were working on a player record already, append
583 # it and work on a new one (only set team info)
584 if len(player_events) != 0:
585 players.append(player_events)
588 player_events[key] = value
591 (subkey, subvalue) = value.split(' ', 1)
592 player_events[subkey] = subvalue
594 player_events[key] = value
596 player_events[key] = value
598 # no key/value pair - move on to the next line
601 # add the last player we were working on
602 if len(player_events) > 0:
603 players.append(player_events)
605 return (game_meta, players)
608 def create_player_stats(session=None, player=None, game=None,
609 player_events=None, game_meta=None):
611 Creates player game and weapon stats according to what type of player
613 pgstat = create_player_game_stat(session=session,
614 player=player, game=game, player_events=player_events)
616 # fastest cap "upsert"
617 if game.game_type_cd == 'ctf' and pgstat.fastest_cap is not None:
618 update_fastest_cap(session, pgstat.player_id, game.game_id,
619 game.map_id, pgstat.fastest_cap)
621 # bots don't get weapon stats. sorry, bots!
622 if not re.search('^bot#\d+$', player_events['P']):
623 create_player_weapon_stats(session=session,
624 player=player, game=game, pgstat=pgstat,
625 player_events=player_events, game_meta=game_meta)
628 def stats_submit(request):
630 Entry handler for POST stats submissions.
633 # placeholder for the actual session
636 log.debug("\n----- BEGIN REQUEST BODY -----\n" + request.body +
637 "----- END REQUEST BODY -----\n\n")
639 (idfp, status) = verify_request(request)
640 if verify_requests(request.registry.settings):
642 log.debug("ERROR: Unverified request")
643 raise pyramid.httpexceptions.HTTPUnauthorized("Unverified request")
645 (game_meta, players) = parse_body(request)
647 if not has_required_metadata(game_meta):
648 log.debug("ERROR: Required game meta missing")
649 raise pyramid.httpexceptions.HTTPUnprocessableEntity("Missing game meta")
651 if not is_supported_gametype(game_meta['G']):
652 log.debug("ERROR: Unsupported gametype")
653 raise pyramid.httpexceptions.HTTPOk("OK")
655 if not has_minimum_real_players(request.registry.settings, players):
656 log.debug("ERROR: Not enough real players")
657 raise pyramid.httpexceptions.HTTPOk("OK")
659 if is_blank_game(players):
660 log.debug("ERROR: Blank game")
661 raise pyramid.httpexceptions.HTTPOk("OK")
663 # the "duel" gametype is fake
664 if num_real_players(players) == 2 and \
665 game_meta['G'] == 'dm':
666 game_meta['G'] = 'duel'
669 # fix for DTG, who didn't #ifdef WATERMARK to set the revision info
671 revision = game_meta['R']
675 #----------------------------------------------------------------------
676 # This ends the "precondition" section of sanity checks. All
677 # functions not requiring a database connection go ABOVE HERE.
678 #----------------------------------------------------------------------
679 session = DBSession()
681 server = get_or_create_server(session=session, hashkey=idfp,
682 name=game_meta['S'], revision=revision,
683 ip_addr=get_remote_addr(request))
685 gmap = get_or_create_map(session=session, name=game_meta['M'])
687 # duration is optional
689 duration = game_meta['D']
693 game = create_game(session=session,
694 start_dt=datetime.datetime.utcnow(),
695 #start_dt=datetime.datetime(
696 #*time.gmtime(float(game_meta['T']))[:6]),
697 server_id=server.server_id, game_type_cd=game_meta['G'],
698 map_id=gmap.map_id, match_id=game_meta['I'],
701 # find or create a record for each player
702 # and add stats for each if they were present at the end
704 for player_events in players:
705 if 'n' in player_events:
706 nick = player_events['n']
710 if 'matches' in player_events and 'scoreboardvalid' \
712 player = get_or_create_player(session=session,
713 hashkey=player_events['P'], nick=nick)
714 log.debug('Creating stats for %s' % player_events['P'])
715 create_player_stats(session=session, player=player, game=game,
716 player_events=player_events, game_meta=game_meta)
720 process_elos(game, session)
721 except Exception as e:
722 log.debug('Error (non-fatal): elo processing failed.')
725 log.debug('Success! Stats recorded.')
726 return Response('200 OK')
727 except Exception as e:
733 def parse_stats_submission(body):
735 Parses the POST request body for a stats submission
737 # storage vars for the request body
742 for line in body.split('\n'):
744 (key, value) = line.strip().split(' ', 1)
746 # Server (S) and Nick (n) fields can have international characters.
748 value = unicode(value, 'utf-8')
750 if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W' 'I' 'D' 'O':
751 game_meta[key] = value
754 # if we were working on a player record already, append
755 # it and work on a new one (only set team info)
757 players[events['P']] = events
763 (subkey, subvalue) = value.split(' ', 1)
764 events[subkey] = subvalue
770 # no key/value pair - move on to the next line
773 # add the last player we were working on
775 players[events['P']] = events
777 return (game_meta, players)
780 def submit_stats(request):
782 Entry handler for POST stats submissions.
785 # placeholder for the actual session
788 log.debug("\n----- BEGIN REQUEST BODY -----\n" + request.body +
789 "----- END REQUEST BODY -----\n\n")
791 (idfp, status) = verify_request(request)
792 if verify_requests(request.registry.settings):
794 log.debug("ERROR: Unverified request")
795 raise pyramid.httpexceptions.HTTPUnauthorized("Unverified request")
797 (game_meta, raw_players) = parse_stats_submission(request.body)
799 # only players present at the end of the match are eligible for stats
800 for rp in raw_players.values():
801 if not played_in_game(rp):
802 del raw_players[rp['P']]
804 revision = game_meta.get('R', 'unknown')
805 duration = game_meta.get('D', None)
807 #----------------------------------------------------------------------
808 # Precondition checks for ALL gametypes. These do not require a
809 # database connection.
810 #----------------------------------------------------------------------
811 if not is_supported_gametype(game_meta['G']):
812 log.debug("ERROR: Unsupported gametype")
813 raise pyramid.httpexceptions.HTTPOk("OK")
815 if not has_required_metadata(game_meta):
816 log.debug("ERROR: Required game meta missing")
817 raise pyramid.httpexceptions.HTTPUnprocessableEntity("Missing game meta")
819 if not has_minimum_real_players(request.registry.settings, raw_players.values()):
820 log.debug("ERROR: Not enough real players")
821 raise pyramid.httpexceptions.HTTPOk("OK")
823 if is_blank_game(raw_players.values()):
824 log.debug("ERROR: Blank game")
825 raise pyramid.httpexceptions.HTTPOk("OK")
827 # the "duel" gametype is fake
828 if num_real_players(raw_players.values()) == 2 and game_meta['G'] == 'dm':
829 game_meta['G'] = 'duel'
831 #----------------------------------------------------------------------
832 # Actual setup (inserts/updates) below here
833 #----------------------------------------------------------------------
834 session = DBSession()
836 game_type_cd = game_meta['G']
838 # All game types create Game, Server, Map, and Player records
840 server = get_or_create_server(
843 name = game_meta['S'],
845 ip_addr = get_remote_addr(request))
847 gmap = get_or_create_map(
849 name = game_meta['M'])
853 start_dt = datetime.datetime.utcnow(),
854 server_id = server.server_id,
855 game_type_cd = game_type_cd,
856 map_id = gmap.map_id,
857 match_id = game_meta['I'],
862 for events in raw_players.values():
863 player = get_or_create_player(
865 hashkey = events['P'],
866 nick = events.get('n', None))
868 pgstat = game_stats_handler(session, game_meta, game, server,
869 gmap, player, events)
871 players[events['P']] = player
872 pgstats[events['P']] = pgstat
875 log.debug('Success! Stats recorded.')
876 return Response('200 OK')
877 except Exception as e:
884 def game_stats_handler(session, game_meta, game, server, gmap, player, events):
885 """Game stats handler for all game types"""
887 # this is what we have to do to get partitioned records in - grab the
888 # sequence value first, then insert using the explicit ID (vs autogenerate)
889 seq = Sequence('player_game_stats_player_game_stat_id_seq')
890 pgstat_id = session.execute(seq)
891 pgstat = PlayerGameStat(player_game_stat_id=pgstat_id,
892 create_dt=datetime.datetime.utcnow())
894 # these fields should be on every pgstat record
895 pgstat.game_id = game.game_id
896 pgstat.player_id = player.player_id
897 pgstat.nick = events.get('n', 'Anonymous Player')[:128]
898 log.debug(pgstat.nick)
899 pgstat.stripped_nick = strip_colors(qfont_decode(pgstat.nick))
900 log.debug(qfont_decode(pgstat.nick))
901 log.debug(strip_colors(pgstat.nick))
902 pgstat.score = int(events.get('scoreboard-score', 0))
903 pgstat.alivetime = datetime.timedelta(seconds=int(round(float(events.get('alivetime', 0.0)))))
904 pgstat.rank = int(events.get('rank', None))
905 pgstat.scoreboardpos = int(events.get('scoreboardpos', pgstat.rank))
907 if pgstat.nick != player.nick \
908 and player.player_id > 2 \
909 and pgstat.nick != 'Anonymous Player':
910 register_new_nick(session, player, pgstat.nick)
914 # gametype-specific stuff is handled here. if passed to us, we store it
915 for (key,value) in events.items():
916 if key == 'wins': wins = True
917 if key == 't': pgstat.team = int(value)
918 if key == 'scoreboard-drops': pgstat.drops = int(value)
919 if key == 'scoreboard-returns': pgstat.returns = int(value)
920 if key == 'scoreboard-fckills': pgstat.carrier_frags = int(value)
921 if key == 'scoreboard-pickups': pgstat.pickups = int(value)
922 if key == 'scoreboard-caps': pgstat.captures = int(value)
923 if key == 'scoreboard-score': pgstat.score = int(value)
924 if key == 'scoreboard-deaths': pgstat.deaths = int(value)
925 if key == 'scoreboard-kills': pgstat.kills = int(value)
926 if key == 'scoreboard-suicides': pgstat.suicides = int(value)
927 if key == 'avglatency': pgstat.avg_latency = float(value)
929 if key == 'scoreboard-captime':
930 pgstat.fastest_cap = datetime.timedelta(seconds=float(value)/100)
931 if game.game_type_cd == 'ctf':
932 update_fastest_cap(session, player.player_id, game.game_id,
933 gmap.map_id, pgstat.fastest_cap)
935 # there is no "winning team" field, so we have to derive it
936 if wins and pgstat.team is not None and game.winner is None:
937 game.winner = pgstat.team