]> de.git.xonotic.org Git - xonotic/xonstat.git/blobdiff - xonstat/views.py
Add player accuracy tracking (!!!). Bots do not get recorded. Who cares about their...
[xonotic/xonstat.git] / xonstat / views.py
old mode 100644 (file)
new mode 100755 (executable)
index a32ac7b..3769cec
@@ -1,9 +1,12 @@
 import datetime
+import time
 import re
 from pyramid.response import Response
 from pyramid.view import view_config
 
 from xonstat.models import *
+from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
+
 
 import logging
 log = logging.getLogger(__name__)
@@ -28,52 +31,169 @@ def player_index(request):
 
 @view_config(renderer='player_info.mako')
 def player_info(request):
-    player = DBSession.query(Player).filter_by(player_id=p_player_id)
-    log.debug("testing logging; entered PlayerHandler.info()")
-    return {'player':player}
+    player_id = request.matchdict['id']
+    try:
+        player = DBSession.query(Player).filter_by(player_id=player_id).one()
+        recent_games = DBSession.query("game_id", "server_id", "server_name", 
+                "map_id", "map_name").\
+                from_statement("select g.game_id, s.server_id, s.name as server_name, m.map_id, m.name as map_name "
+                        "from player_game_stats gs, games g, servers s, maps m "
+                        "where gs.player_id=:player_id "
+                        "and gs.game_id = g.game_id "
+                        "and g.server_id = s.server_id "
+                        "and g.map_id = m.map_id "
+                        "order by g.start_dt desc "
+                        "limit 10 offset 1").\
+                        params(player_id=player_id).all()
+
+        log.debug(recent_games)
+    except Exception as e:
+        player = None
+        recent_games = None
+    return {'player':player, 'recent_games':recent_games}
+
+
+##########################################################################
+# This is the game views area - only views pertaining to Xonotic
+# games and their related information goes here
+##########################################################################
+def game_info(request):
+    game_id = request.matchdict['id']
+    try:
+        notfound = False
+
+        (start_dt, game_type_cd, server_id, server_name, map_id, map_name) = \
+        DBSession.query("start_dt", "game_type_cd", "server_id", 
+                "server_name", "map_id", "map_name").\
+                from_statement("select g.start_dt, g.game_type_cd, "
+                        "g.server_id, s.name as server_name, g.map_id, "
+                        "m.name as map_name "
+                        "from games g, servers s, maps m "
+                        "where g.game_id = :game_id "
+                        "and g.server_id = s.server_id "
+                        "and g.map_id = m.map_id").\
+                        params(game_id=game_id).one()
+
+        player_game_stats = DBSession.query(PlayerGameStat).\
+                from_statement("select * from player_game_stats "
+                        "where game_id = :game_id "
+                        "order by score desc").\
+                            params(game_id=game_id).all()
+    except Exception as inst:
+        notfound = True
+        start_dt = None
+        game_type_cd = None
+        server_id = None
+        server_name = None
+        map_id = None
+        map_name = None
+        player_game_stats = None
+
+    return {'notfound':notfound,
+            'start_dt':start_dt,
+            'game_type_cd':game_type_cd,
+            'server_id':server_id,
+            'server_name':server_name,
+            'map_id':map_id,
+            'map_name':map_name,
+            'player_game_stats':player_game_stats}
+
+
+##########################################################################
+# This is the server views area - only views pertaining to Xonotic
+# servers and their related information goes here
+##########################################################################
+def server_info(request):
+    server_id = request.matchdict['id']
+    try:
+        server = DBSession.query(Server).filter_by(server_id=server_id).one()
+        recent_games = DBSession.query("game_id", "server_id", "server_name", 
+                "map_id", "map_name").\
+                from_statement("select g.game_id, s.server_id, "
+                "s.name as server_name, m.map_id, m.name as map_name "
+                "from games g, servers s, maps m "
+                "where g.server_id=:server_id "
+                "and g.server_id = s.server_id "
+                "and g.map_id = m.map_id "
+                "order by g.start_dt desc "
+                "limit 10 offset 1").\
+                 params(server_id=server_id).all()
+
+    except Exception as e:
+        server = None
+        recent_games = None
+    return {'server':server,
+            'recent_games':recent_games}
+
+
+##########################################################################
+# This is the map views area - only views pertaining to Xonotic
+# maps and their related information goes here
+##########################################################################
+def map_info(request):
+    map_id = request.matchdict['id']
+    try:
+        gmap = DBSession.query(Map).filter_by(map_id=map_id).one()
+    except:
+        gmap = None
+    return {'gmap':gmap}
+
 
 ##########################################################################
 # This is the stats views area - only views pertaining to Xonotic
 # statistics and its related information goes here
 ##########################################################################
 def get_or_create_server(session=None, name=None):
-    server = None
     try:
         # find one by that name, if it exists
         server = session.query(Server).filter_by(name=name).one()
         log.debug("Found server id {0} with name {1}.".format(
             server.server_id, server.name))
-    except:
-        # otherwise create a new one
+    except NoResultFound, e:
         server = Server(name=name)
         session.add(server)
         session.flush()
         log.debug("Created server id {0} with name {1}".format(
             server.server_id, server.name))
+    except MultipleResultsFound, e:
+        # multiple found, so use the first one but warn
+        log.debug(e)
+        servers = session.query(Server).filter_by(name=name).order_by(
+                Server.server_id).all()
+        server = servers[0]
+        log.debug("Created server id {0} with name {1} but found \
+                multiple".format(
+            server.server_id, server.name))
 
     return server
 
 def get_or_create_map(session=None, name=None):
-    gmap = None
     try:
         # find one by the name, if it exists
         gmap = session.query(Map).filter_by(name=name).one()
         log.debug("Found map id {0} with name {1}.".format(gmap.map_id, 
             gmap.name))
-    except:
-        # otherwise create a new one
+    except NoResultFound, e:
         gmap = Map(name=name)
         session.add(gmap)
         session.flush()
         log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
             gmap.name))
+    except MultipleResultsFound, e:
+        # multiple found, so use the first one but warn
+        log.debug(e)
+        gmaps = session.query(Map).filter_by(name=name).order_by(
+                Map.map_id).all()
+        gmap = gmaps[0]
+        log.debug("Found map id {0} with name {1} but found \
+                multiple.".format(gmap.map_id, gmap.name))
 
     return gmap
 
 def create_game(session=None, start_dt=None, game_type_cd=None, 
-        server_id=None, map_id=None):
+        server_id=None, map_id=None, winner=None):
     game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
-                server_id=server_id, map_id=map_id)
+                server_id=server_id, map_id=map_id, winner=winner)
     session.add(game)
     session.flush()
     log.debug("Created game id {0} on server {1}, map {2} at time \
@@ -84,9 +204,6 @@ def create_game(session=None, start_dt=None, game_type_cd=None,
 
 # search for a player and if found, create a new one (w/ hashkey)
 def get_or_create_player(session=None, hashkey=None):
-    # the player object we'll return
-    player = None
-
     # if we have a bot
     if re.search('^bot#\d+$', hashkey):
         player = session.query(Player).filter_by(player_id=1).one()
@@ -103,7 +220,7 @@ def get_or_create_player(session=None, hashkey=None):
             player = session.query(Player).filter_by(
                     player_id=hashkey.player_id).one()
             log.debug("Found existing player {0} with hashkey {1}.".format(
-                player.player_id, hashkey.hashkey)
+                player.player_id, hashkey.hashkey))
         except:
             player = Player()
             session.add(player)
@@ -111,7 +228,7 @@ def get_or_create_player(session=None, hashkey=None):
             hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
             session.add(hashkey)
             log.debug("Created player {0} with hashkey {1}.".format(
-                player.player_id, hashkey.hashkey)
+                player.player_id, hashkey.hashkey))
 
     return player
 
@@ -146,16 +263,19 @@ def create_player_game_stat(session=None, player=None,
 
     for (key,value) in player_events.items():
         if key == 'n': pgstat.nick = value
-        if key == 'total-drops': pgstat.drops = value
-        if key == 'total-returns': pgstat.returns = value
-        if key == 'total-fckills': pgstat.carrier_frags = value
-        if key == 'total-pickups': pgstat.pickups = value
-        if key == 'total-caps': pgstat.caps = value
-        if key == 'total-score': pgstat.score = value
-        if key == 'total-deaths': pgstat.deaths = value
-        if key == 'total-kills': pgstat.kills = value
-        if key == 'total-suicides': pgstat.suicides = value
-        # TODO: alivetime
+        if key == 't': pgstat.team = value
+        if key == 'rank': pgstat.rank = value
+        if key == 'alivetime': 
+            pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
+        if key == 'scoreboard-drops': pgstat.drops = value
+        if key == 'scoreboard-returns': pgstat.returns = value
+        if key == 'scoreboard-fckills': pgstat.carrier_frags = value
+        if key == 'scoreboard-pickups': pgstat.pickups = value
+        if key == 'scoreboard-caps': pgstat.captures = value
+        if key == 'scoreboard-score': pgstat.score = value
+        if key == 'scoreboard-deaths': pgstat.deaths = value
+        if key == 'scoreboard-kills': pgstat.kills = value
+        if key == 'scoreboard-suicides': pgstat.suicides = value
 
     # check to see if we had a name, and if 
     # not use the name from the player id
@@ -167,90 +287,150 @@ def create_player_game_stat(session=None, player=None,
 
     return pgstat
 
-@view_config(renderer='stats_submit.mako')
-def stats_submit(request):
-    session = DBSession()
 
-    # game meta information
+def create_player_weapon_stats(session=None, player=None, 
+        game=None, player_events=None):
+    pwstats = []
+
+    for (key,value) in player_events.items():
+        matched = re.search("acc-(.*?)-cnt-fired", key)
+        if matched:
+            log.debug("Matched key: {0}".format(key))
+            weapon_cd = matched.group(1)
+            pwstat = PlayerWeaponStat()
+            pwstat.player_id = player.player_id
+            pwstat.game_id = game.game_id
+            pwstat.weapon_cd = weapon_cd
+
+            if 'n' in player_events:
+                pwstat.nick = player_events['n']
+            else:
+                pwstat.nick = player_events['P']
+
+            if 'acc-' + weapon_cd + '-cnt-fired' in player_events:
+                pwstat.fired = int(round(float(
+                        player_events['acc-' + weapon_cd + '-cnt-fired'])))
+            if 'acc-' + weapon_cd + '-fired' in player_events:
+                pwstat.max = int(round(float(
+                        player_events['acc-' + weapon_cd + '-fired'])))
+            if 'acc-' + weapon_cd + '-cnt-hit' in player_events:
+                pwstat.hit = int(round(float(
+                        player_events['acc-' + weapon_cd + '-cnt-hit'])))
+            if 'acc-' + weapon_cd + '-hit' in player_events:
+                pwstat.actual = int(round(float(
+                        player_events['acc-' + weapon_cd + '-hit'])))
+            if 'acc-' + weapon_cd + '-frags' in player_events:
+                pwstat.frags = int(round(float(
+                        player_events['acc-' + weapon_cd + '-frags'])))
+
+            session.add(pwstat)
+            pwstats.append(pwstat)
+
+    return pwstats
+
+
+def parse_body(request):
+    # storage vars for the request body
     game_meta = {}
     player_events = {}
+    current_team = None
     players = []
     
-    # for troubleshooting issues...
     log.debug(request.body)
 
-    # main loop over each line of the stats body
     for line in request.body.split('\n'):
-        (key, value) = line.strip().split(' ', 1)
+        try:
+            (key, value) = line.strip().split(' ', 1)
     
-        # these are important keys needed to establish
-        # basic game meta information
-        if key in 'V' 'T' 'G' 'M' 'S' 'C':
-            game_meta[key] = value
+            if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
+                game_meta[key] = value
+
+            if key == 't':
+                current_team = value
     
-        # 'P' is a player record. After this key we will get a list
-        # of events for that player
-        if key == 'P':
-            # if we were working on a player record previously, append
-            # it and work on a new one
-            if len(player_events) != 0:
-                players.append(player_events)
-                player_events = {}
+            if key == 'P':
+                # if we were working on a player record already, append
+                # it and work on a new one (only set team info)
+                if len(player_events) != 0:
+                    players.append(player_events)
+                    player_events = {'t':current_team}
     
-            player_events[key] = value
+                player_events[key] = value
     
-        # 'e' is an event associated with the current 'P' record
-        # being processed
-        if key == 'e':
-            (subkey, subvalue) = value.split(' ', 1)
-            player_events[subkey] = subvalue
-
-        # 'n' is a player's name. it is not an event per se, 
-        # but it belongs on the player record
-        if key == 'n':
-            player_events[key] = value
+            if key == 'e':
+                (subkey, subvalue) = value.split(' ', 1)
+                player_events[subkey] = subvalue
+
+            if key == 'n':
+                player_events[key] = value
+        except:
+            # no key/value pair - move on to the next line
+            pass
     
     # add the last player we were working on
-    players.append(player_events)
-    
-    # verify required metadata is present
-    if 'T' not in game_meta or\
-        'G' not in game_meta or\
-        'M' not in game_meta or\
-        'S' not in game_meta:
-        log.debug("Required game meta fields (T, G, M, or S) missing. "\
-                "Can't continue.")
-        return {'msg':'Error processing the request.'}
-    
-    # find or create a record for the server
-    server = get_or_create_server(session=session, name=game_meta['S'])
+    if len(player_events) > 0:
+        players.append(player_events)
+
+    return (game_meta, players)
+
+
+@view_config(renderer='stats_submit.mako')
+def stats_submit(request):
+    try:
+        session = DBSession()
+
+        (game_meta, players) = parse_body(request)  
     
-    # find or create a record for the map
-    gmap = get_or_create_map(session=session, name=game_meta['M'])
+        # verify required metadata is present
+        if 'T' not in game_meta or\
+            'G' not in game_meta or\
+            'M' not in game_meta or\
+            'S' not in game_meta:
+            log.debug("Required game meta fields (T, G, M, or S) missing. "\
+                    "Can't continue.")
+            raise Exception("Required game meta fields (T, G, M, or S) missing.")
     
-    # create the game
-    # FIXME: don't use python now() here, convert from epoch T value
-    now = datetime.datetime.now()
-    game = create_game(session=session, start_dt=now, 
-            server_id=server.server_id,\
-            game_type_cd=game_meta['G'], map_id=gmap.map_id)
+        has_real_players = False
+        for player_events in players:
+            if not player_events['P'].startswith('bot'):
+                if 'joins' in player_events and 'matches' in player_events\
+                    and 'scoreboardvalid' in player_events:
+                    has_real_players = True
+
+        if not has_real_players:
+            raise Exception("No real players found. Stats ignored.")
+
+        server = get_or_create_server(session=session, name=game_meta['S'])
+        gmap = get_or_create_map(session=session, name=game_meta['M'])
+
+        if 'W' in game_meta:
+            winner = game_meta['W']
+        else:
+            winner = None
+
+        game = create_game(session=session, 
+                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, winner=winner)
     
-    # find or create a record for each player
-    # and add stats for each if they were present at the end
-    # of the game
-    has_real_players = False
-    for player_events in players:
-        if not player_events['P'].startswith('bot'):
-            has_real_players = True
-        player = get_or_create_player(session=session, 
-                hashkey=player_events['P'])
-        if 'joins' in player_events:
-            pgstat = create_player_game_stat(session=session, 
-                    player=player, game=game, player_events=player_events)
+        # find or create a record for each player
+        # and add stats for each if they were present at the end
+        # of the game
+        for player_events in players:
+            player = get_or_create_player(session=session, 
+                    hashkey=player_events['P'])
+            if 'joins' in player_events and 'matches' in player_events\
+                    and 'scoreboardvalid' in player_events:
+                pgstat = create_player_game_stat(session=session, 
+                        player=player, game=game, player_events=player_events)
+                if not player_events['P'].startswith('bot'):
+                    create_player_weapon_stats(session=session, 
+                            player=player, game=game, player_events=player_events)
     
-    if has_real_players:
         session.commit()
-        return {'msg':'Success! Stats recorded.'}
-    else:
+        log.debug('Success! Stats recorded.')
+        return Response('200 OK')
+    except Exception as e:
         session.rollback()
-        return {'msg':'No real players found. Stats ignored.'}
+        raise e