]> de.git.xonotic.org Git - xonotic/xonstat.git/blobdiff - xonstat/views/submission.py
Add a JSON Elo view to support autobalance.
[xonotic/xonstat.git] / xonstat / views / submission.py
index 0bbdf7e81d9a181c73c4108d78b56e2caa84e318..364babb67ff3419fcb34f704f78a1fc8687a5142 100644 (file)
@@ -31,7 +31,7 @@ def is_blank_game(players):
     for events in players:
         if is_real_player(events):
             for (key,value) in events.items():
-                if key == 'scoreboard-score' and value != '0':
+                if key == 'scoreboard-score' and value != 0:
                     flg_nonzero_score = True
                 if r.search(key):
                     flg_acc_events = True
@@ -107,6 +107,22 @@ def has_minimum_real_players(settings, player_events):
     return flg_has_min_real_players
 
 
+def verify_requests(settings):
+    """
+    Determines whether or not to verify requests using the blind_id algorithm
+    """
+    try:
+        val_verify_requests = settings['xonstat.verify_requests']
+        if val_verify_requests == "true":
+            flg_verify_requests = True
+        else:
+            flg_verify_requests = False
+    except:
+        flg_verify_requests = True
+
+    return flg_verify_requests
+
+
 def has_required_metadata(metadata):
     """
     Determines if a give set of metadata has enough data to create a game,
@@ -155,7 +171,7 @@ def register_new_nick(session, player, new_nick):
     new_nick - the new nickname
     """
     # see if that nick already exists
-    stripped_nick = strip_colors(player.nick)
+    stripped_nick = strip_colors(qfont_decode(player.nick))
     try:
         player_nick = session.query(PlayerNick).filter_by(
             player_id=player.player_id, stripped_nick=stripped_nick).one()
@@ -165,16 +181,46 @@ def register_new_nick(session, player, new_nick):
         if not re.search('^Anonymous Player #\d+$', player.nick):
             player_nick = PlayerNick()
             player_nick.player_id = player.player_id
-            player_nick.stripped_nick = player.stripped_nick
+            player_nick.stripped_nick = stripped_nick
             player_nick.nick = player.nick
             session.add(player_nick)
 
     # We change to the new nick regardless
     player.nick = new_nick
-    player.stripped_nick = strip_colors(new_nick)
+    player.stripped_nick = strip_colors(qfont_decode(new_nick))
     session.add(player)
 
 
+def update_fastest_cap(session, player_id, game_id,  map_id, captime):
+    """
+    Check the fastest cap time for the player and map. If there isn't
+    one, insert one. If there is, check if the passed time is faster.
+    If so, update!
+    """
+    # we don't record fastest cap times for bots or anonymous players
+    if player_id <= 2:
+        return
+
+    # see if a cap entry exists already
+    # then check to see if the new captime is faster
+    try:
+        cur_fastest_cap = session.query(PlayerCaptime).filter_by(
+            player_id=player_id, map_id=map_id).one()
+
+        # current captime is faster, so update
+        if captime < cur_fastest_cap.fastest_cap:
+            cur_fastest_cap.fastest_cap = captime
+            cur_fastest_cap.game_id = game_id
+            cur_fastest_cap.create_dt = datetime.datetime.utcnow()
+            session.add(cur_fastest_cap)
+
+    except NoResultFound, e:
+        # none exists, so insert
+        cur_fastest_cap = PlayerCaptime(player_id, game_id, map_id, captime)
+        session.add(cur_fastest_cap)
+        session.flush()
+
+
 def get_or_create_server(session=None, name=None, hashkey=None, ip_addr=None,
         revision=None):
     """
@@ -253,7 +299,8 @@ def get_or_create_map(session=None, name=None):
 
 
 def create_game(session=None, start_dt=None, game_type_cd=None, 
-        server_id=None, map_id=None, winner=None, match_id=None):
+        server_id=None, map_id=None, winner=None, match_id=None,
+        duration=None):
     """
     Creates a game. Parameters:
 
@@ -270,6 +317,11 @@ def create_game(session=None, start_dt=None, game_type_cd=None,
                 server_id=server_id, map_id=map_id, winner=winner)
     game.match_id = match_id
 
+    try:
+        game.duration = datetime.timedelta(seconds=int(round(float(duration))))
+    except:
+        pass
+
     try:
         session.query(Game).filter(Game.server_id==server_id).\
                 filter(Game.match_id==match_id).one()
@@ -282,6 +334,7 @@ def create_game(session=None, start_dt=None, game_type_cd=None,
     except NoResultFound, e:
         # server_id/match_id combination not found. game is ok to insert
         session.add(game)
+        session.flush()
         log.debug("Created game id {0} on server {1}, map {2} at \
                 {3}".format(game.game_id, 
                     server_id, map_id, start_dt))
@@ -324,7 +377,7 @@ def get_or_create_player(session=None, hashkey=None, nick=None):
             # with a suffix added for uniqueness.
             if nick:
                 player.nick = nick[:128]
-                player.stripped_nick = strip_colors(nick[:128])
+                player.stripped_nick = strip_colors(qfont_decode(nick[:128]))
             else:
                 player.nick = "Anonymous Player #{0}".format(player.player_id)
                 player.stripped_nick = player.nick
@@ -361,8 +414,9 @@ def create_player_game_stat(session=None, player=None,
     #set game id from game record
     pgstat.game_id = game.game_id
 
-    # all games have a score
+    # all games have a score and every player has an alivetime
     pgstat.score = 0
+    pgstat.alivetime = datetime.timedelta(seconds=0)
 
     if game.game_type_cd == 'dm' or game.game_type_cd == 'tdm' or game.game_type_cd == 'duel':
         pgstat.kills = 0
@@ -377,7 +431,9 @@ def create_player_game_stat(session=None, player=None,
         pgstat.carrier_frags = 0
 
     for (key,value) in player_events.items():
-        if key == 'n': pgstat.nick = value[:128]
+        if key == 'n': 
+            pgstat.nick = value[:128]
+            pgstat.stripped_nick = strip_colors(qfont_decode(pgstat.nick))
         if key == 't': pgstat.team = int(value)
         if key == 'rank': pgstat.rank = int(value)
         if key == 'alivetime': 
@@ -391,6 +447,9 @@ def create_player_game_stat(session=None, player=None,
         if key == 'scoreboard-deaths': pgstat.deaths = int(value)
         if key == 'scoreboard-kills': pgstat.kills = int(value)
         if key == 'scoreboard-suicides': pgstat.suicides = int(value)
+        if key == 'scoreboard-captime':
+            pgstat.fastest_cap = datetime.timedelta(seconds=float(value)/100)
+        if key == 'avglatency': pgstat.avg_latency = float(value)
 
     # check to see if we had a name, and if
     # not use an anonymous handle
@@ -415,7 +474,7 @@ def create_player_game_stat(session=None, player=None,
 
 
 def create_player_weapon_stats(session=None, player=None, 
-        game=None, pgstat=None, player_events=None):
+        game=None, pgstat=None, player_events=None, game_meta=None):
     """
     Creates accuracy records for each weapon used by a given player in a
     given game. Parameters:
@@ -426,9 +485,23 @@ def create_player_weapon_stats(session=None, player=None,
     pgstat - Corresponding PlayerGameStat record for these weapon stats
     player_events - dictionary containing the raw weapon values that need to be
         transformed
+    game_meta - dictionary of game metadata (only used for stats version info)
     """
     pwstats = []
 
+    # Version 1 of stats submissions doubled the data sent.
+    # To counteract this we divide the data by 2 only for
+    # POSTs coming from version 1.
+    try:
+        version = int(game_meta['V'])
+        if version == 1:
+            is_doubled = True
+            log.debug('NOTICE: found a version 1 request, halving the weapon stats...')
+        else:
+            is_doubled = False
+    except:
+        is_doubled = False
+
     for (key,value) in player_events.items():
         matched = re.search("acc-(.*?)-cnt-fired", key)
         if matched:
@@ -463,6 +536,13 @@ def create_player_weapon_stats(session=None, player=None,
                 pwstat.frags = int(round(float(
                         player_events['acc-' + weapon_cd + '-frags'])))
 
+            if is_doubled:
+                pwstat.fired = pwstat.fired/2
+                pwstat.max = pwstat.max/2
+                pwstat.hit = pwstat.hit/2
+                pwstat.actual = pwstat.actual/2
+                pwstat.frags = pwstat.frags/2
+
             session.add(pwstat)
             pwstats.append(pwstat)
 
@@ -488,7 +568,7 @@ def parse_body(request):
             if key in 'S' 'n':
                 value = unicode(value, 'utf-8')
 
-            if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W' 'I':
+            if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W' 'I' 'D':
                 game_meta[key] = value
 
             if key == 'P':
@@ -519,18 +599,23 @@ def parse_body(request):
 
 
 def create_player_stats(session=None, player=None, game=None, 
-        player_events=None):
+        player_events=None, game_meta=None):
     """
     Creates player game and weapon stats according to what type of player
     """
     pgstat = create_player_game_stat(session=session, 
         player=player, game=game, player_events=player_events)
 
-    #TODO: put this into a config setting in the ini file?
+    # fastest cap "upsert"
+    if game.game_type_cd == 'ctf' and pgstat.fastest_cap is not None:
+        update_fastest_cap(session, pgstat.player_id, game.game_id, 
+                game.map_id, pgstat.fastest_cap)
+
+    # bots don't get weapon stats. sorry, bots!
     if not re.search('^bot#\d+$', player_events['P']):
         create_player_weapon_stats(session=session, 
             player=player, game=game, pgstat=pgstat,
-            player_events=player_events)
+            player_events=player_events, game_meta=game_meta)
 
 
 def stats_submit(request):
@@ -538,15 +623,19 @@ def stats_submit(request):
     Entry handler for POST stats submissions.
     """
     try:
+        # placeholder for the actual session
+        session = None
+
         log.debug("\n----- BEGIN REQUEST BODY -----\n" + request.body +
                 "----- END REQUEST BODY -----\n\n")
 
         (idfp, status) = verify_request(request)
-        if not idfp:
-            log.debug("ERROR: Unverified request")
-            raise pyramid.httpexceptions.HTTPUnauthorized("Unverified request")
+        if verify_requests(request.registry.settings):
+            if not idfp:
+                log.debug("ERROR: Unverified request")
+                raise pyramid.httpexceptions.HTTPUnauthorized("Unverified request")
 
-        (game_meta, players) = parse_body(request)  
+        (game_meta, players) = parse_body(request)
 
         if not has_required_metadata(game_meta):
             log.debug("ERROR: Required game meta missing")
@@ -588,14 +677,19 @@ def stats_submit(request):
 
         gmap = get_or_create_map(session=session, name=game_meta['M'])
 
-        # FIXME: use the gmtime instead of utcnow() when the timezone bug is
-        # fixed
+        # duration is optional
+        if 'D' in game_meta:
+            duration = game_meta['D']
+        else:
+            duration = None
+
         game = create_game(session=session, 
                 start_dt=datetime.datetime.utcnow(),
                 #start_dt=datetime.datetime(
                     #*time.gmtime(float(game_meta['T']))[:6]), 
                 server_id=server.server_id, game_type_cd=game_meta['G'], 
-                   map_id=gmap.map_id, match_id=game_meta['I'])
+                   map_id=gmap.map_id, match_id=game_meta['I'],
+                   duration=duration)
 
         # find or create a record for each player
         # and add stats for each if they were present at the end
@@ -612,7 +706,7 @@ def stats_submit(request):
                     hashkey=player_events['P'], nick=nick)
                 log.debug('Creating stats for %s' % player_events['P'])
                 create_player_stats(session=session, player=player, game=game, 
-                        player_events=player_events)
+                        player_events=player_events, game_meta=game_meta)
 
         # update elos
         try:
@@ -624,5 +718,6 @@ def stats_submit(request):
         log.debug('Success! Stats recorded.')
         return Response('200 OK')
     except Exception as e:
-        session.rollback()
+        if session:
+            session.rollback()
         return e