]> git.xonotic.org Git - xonotic/xonstat.git/commitdiff
Merge branch 'master' of github.com:antzucaro/XonStat
authorAnt Zucaro <azucaro@gmail.com>
Sat, 16 Jun 2012 13:53:08 +0000 (09:53 -0400)
committerAnt Zucaro <azucaro@gmail.com>
Sat, 16 Jun 2012 13:53:08 +0000 (09:53 -0400)
Conflicts:
xonstat/views/__init__.py
xonstat/views/player.py

1  2 
xonstat/__init__.py
xonstat/templates/player_info.mako
xonstat/views/__init__.py
xonstat/views/player.py

diff --combined xonstat/__init__.py
index 79f7f527bc64c066cc77d75b766725ab4f0176f2,2e45a0853d4d47e1815a28f8c1e3a6a37ed44476..3160b7583c2cba36fd2ab88e5b4168ddd952d36d
mode 100755,100644..100644
@@@ -2,7 -2,7 +2,7 @@@ import sqlahelpe
  from pyramid.config import Configurator
  from sqlalchemy import engine_from_config
  from xonstat.models import initialize_db
- from xonstat.views import * 
+ from xonstat.views import *
  
  def main(global_config, **settings):
      """ This function returns a Pyramid WSGI application.
  
      # ROOT ROUTE
      config.add_route("main_index", "/")
-     config.add_view(main_index, route_name="main_index",
-         renderer="main_index.mako")
+     config.add_view(main_index, route_name="main_index", renderer="main_index.mako")
  
      # MAIN SUBMISSION ROUTE
      config.add_route("stats_submit", "stats/submit")
      config.add_view(stats_submit, route_name="stats_submit")
  
      # PLAYER ROUTES
-     config.add_route("player_game_index",
-             "/player/{player_id:\d+}/games")
-     config.add_view(player_game_index, route_name="player_game_index",
-         renderer="player_game_index.mako")
+     config.add_route("player_game_index", "/player/{player_id:\d+}/games")
+     config.add_view(player_game_index, route_name="player_game_index", renderer="player_game_index.mako")
  
      config.add_route("player_index", "/players")
-     config.add_view(player_index, route_name="player_index",
-         renderer="player_index.mako")
+     config.add_view(player_index, route_name="player_index", renderer="player_index.mako")
  
      config.add_route("player_info", "/player/{id:\d+}")
-     config.add_view(player_info, route_name="player_info",
-         renderer="player_info.mako")
+     config.add_view(player_info, route_name="player_info", renderer="player_info.mako")
  
-     config.add_route("player_accuracy", "/player/{id:\d+}/accuracy")
-     config.add_view(player_accuracy_json, route_name="player_accuracy",
-         renderer="json")
+     config.add_route("player_accuracy",      "/player/{id:\d+}/accuracy")
+     config.add_route("player_accuracy_json", "/player/{id:\d+}/accuracy.json")
+     config.add_view(player_accuracy_json, route_name="player_accuracy",      renderer="json")
+     config.add_view(player_accuracy_json, route_name="player_accuracy_json", renderer="json")
  
 +    config.add_route("player_damage", "/player/{id:\d+}/damage")
 +    config.add_view(player_damage_json, route_name="player_damage",
 +        renderer="json")
 +
      # GAME ROUTES
      config.add_route("game_index", "/games")
-     config.add_view(game_index, route_name="game_index",
-         renderer="game_index.mako")
+     config.add_view(game_index, route_name="game_index", renderer="game_index.mako")
  
      config.add_route("game_info", "/game/{id:\d+}")
-     config.add_view(game_info, route_name="game_info",
-         renderer="game_info.mako")
+     config.add_view(game_info, route_name="game_info", renderer="game_info.mako")
  
      config.add_route("rank_index", "/ranks/{game_type_cd:ctf|dm|tdm|duel}")
-     config.add_view(rank_index, route_name="rank_index",
-         renderer="rank_index.mako")
+     config.add_view(rank_index, route_name="rank_index", renderer="rank_index.mako")
  
      # SERVER ROUTES
      config.add_route("server_index", "/servers")
-     config.add_view(server_index, route_name="server_index",
-         renderer="server_index.mako")
+     config.add_view(server_index, route_name="server_index", renderer="server_index.mako")
  
-     config.add_route("server_game_index",
-         "/server/{server_id:\d+}/games/page/{page:\d+}")
-     config.add_view(server_game_index, route_name="server_game_index",
-         renderer="server_game_index.mako")
+     config.add_route("server_game_index", "/server/{server_id:\d+}/games/page/{page:\d+}")
+     config.add_view(server_game_index, route_name="server_game_index", renderer="server_game_index.mako")
  
      config.add_route("server_info", "/server/{id:\d+}")
-     config.add_view(server_info, route_name="server_info",
-         renderer="server_info.mako")
+     config.add_view(server_info, route_name="server_info", renderer="server_info.mako")
  
      # MAP ROUTES
+     config.add_route("map_index",      "/maps")
      config.add_route("map_index_json", "/maps.json")
-     config.add_view(map_index_json, route_name="map_index_json",
-         renderer="json")
-     config.add_route("map_index", "/maps")
-     config.add_view(map_index, route_name="map_index",
-         renderer="map_index.mako")
+     config.add_view(map_index,      route_name="map_index",      renderer="map_index.mako")
+     config.add_view(map_index_json, route_name="map_index_json", renderer="json")
  
      config.add_route("map_info", "/map/{id:\d+}")
-     config.add_view(map_info, route_name="map_info",
-         renderer="map_info.mako")
+     config.add_view(map_info, route_name="map_info", renderer="map_info.mako")
  
      # SEARCH ROUTES
      config.add_route("search", "search")
-     config.add_view(search, route_name="search",
-         renderer="search.mako")
+     config.add_view(search, route_name="search", renderer="search.mako")
  
      return config.make_wsgi_app()
index 24ed9056b4fbc77d9bfb97c88e598bf2338b37d2,8216f7554ecd000e0bee6600f80233628bbacf0b..24ed9056b4fbc77d9bfb97c88e598bf2338b37d2
mode 100755,100644..100644
@@@ -13,7 -13,6 +13,7 @@@ ${nav.nav('players')
        <script type="text/javascript">
        $(function () {
  
 +          // plot the accuracy graph
            function plot_acc_graph(data) {
                var games = new Array();
                var avgs = new Array();
@@@ -24,7 -23,7 +24,7 @@@
                    avgs[i] = [i, data.avg];
                    accs[i] = [i, data.accs[i][1]];
                    game_link = '/game/' + data.accs[i][0];
 -                  j = 20 - i;
 +                  j = data.games - i;
                    games[i] = [i, '<a href="' + game_link + '">' + j + '</a>'];
                }
  
                });
            }
  
 +          // plot the damage graph
 +          function plot_dmg_graph(data) {
 +              var games = new Array();
 +              var avgs = new Array();
 +              var dmgs = new Array();
 +
 +              var i=0;
 +              for(i=0; i < data.games; i++) {
 +                  avgs[i] = [i, data.avg];
 +                  dmgs[i] = [i, data.dmgs[i][1]];
 +                  game_link = '/game/' + data.dmgs[i][0];
 +                  j = data.games - i;
 +                  games[i] = [i, '<a href="' + game_link + '">' + j + '</a>'];
 +              }
 +
 +              $.plot(
 +                  $("#dmg-graph"), 
 +                  [ { label: 'average', data: avgs, hoverable: true, clickable: false }, 
 +                    { label: 'efficiency', data: dmgs, lines: {show:true}, points: {show:false}, hoverable: true, clickable: true }, ],
 +                  { yaxis: {ticks: 10, min: 0 },
 +                    xaxis: {ticks: games},
 +                    grid: { hoverable: true, clickable: true },
 +              });
 +          }
 +
            function showTooltip(x, y, contents) {
              $('<div id="tooltip">' + contents + '</div>').css( {
                  position: 'absolute',
                if (item) {
                    if (previousPoint != item.dataIndex) {
                      previousPoint = item.dataIndex;
 -                    
 +
                      $("#tooltip").remove();
                      var x = item.datapoint[0].toFixed(2),
                          y = item.datapoint[1].toFixed(2);
 -                    
 +
                      showTooltip(item.pageX, item.pageY, y + "%");
                    }
                }
                }
            });
  
 -          $.ajax({
 -              url: '${request.route_url("player_accuracy", id=player.player_id)}',
 -              method: 'GET',
 -              dataType: 'json',
 -              success: plot_acc_graph
 +          $('#dmg-graph').bind("plothover", function (event, pos, item) {
 +              if (item) {
 +                  if (previousPoint != item.dataIndex) {
 +                    previousPoint = item.dataIndex;
 +
 +                    $("#tooltip").remove();
 +                    var x = item.datapoint[0].toFixed(2),
 +                        y = item.datapoint[1].toFixed(2);
 +
 +                    showTooltip(item.pageX, item.pageY, y);
 +                  }
 +              }
 +              else {
 +                  $("#tooltip").remove();
 +                  previousPoint = null;
 +              }
            });
  
 +          // bind click events to the weapon images
            $(".acc-weap").click(function () {
                var dataurl = $(this).find('a').attr('href');
  
 -              $('.weapon-active').removeClass('weapon-active');
 +              $('.accuracy-nav').find('.weapon-active').removeClass('weapon-active');
                $(this).addClass('weapon-active');
  
                $.ajax({
                    success: plot_acc_graph
                });
            });
 +
 +          $(".dmg-weap").click(function () {
 +              var dataurl = $(this).find('a').attr('href');
 +
 +              $('.damage-nav').find('.weapon-active').removeClass('weapon-active');
 +              $(this).addClass('weapon-active');
 +
 +              $.ajax({
 +                  url: dataurl,
 +                  method: 'GET',
 +                  dataType: 'json',
 +                  success: plot_dmg_graph
 +              });
 +          });
 +
 +          // populate the graphs with the default weapons
 +          $.ajax({
 +              url: '${request.route_url("player_accuracy", id=player.player_id)}',
 +              method: 'GET',
 +              dataType: 'json',
 +              success: plot_acc_graph
 +          });
 +
 +          $.ajax({
 +              url: '${request.route_url("player_damage", id=player.player_id)}',
 +              method: 'GET',
 +              dataType: 'json',
 +              success: plot_dmg_graph
 +          });
 +
 +
        })
        </script>
      % endif
@@@ -201,14 -132,14 +201,14 @@@ Player Informatio
  </div>
  
  
 -% if accs is not None:
 +% if 'nex' in recent_weapons or 'rifle' in recent_weapons or 'minstanex' in recent_weapons or 'uzi' in recent_weapons or 'shotgun' in recent_weapons:
  <div class="row">
    <div class="span10">
      <h3>Accuracy</h3>
      <div id="acc-graph" class="flot" style="width:800px; height:200px;">
      </div>
  
 -    <div class="weapon-nav">
 +    <div class="weapon-nav accuracy-nav">
        <ul>
          % if 'nex' in recent_weapons:
          <li>
  % endif
  
  
 +% if 'rocketlauncher' in recent_weapons or 'grenadelauncher' in recent_weapons or 'electro' in recent_weapons or 'crylink' in recent_weapons or 'laser' in recent_weapons:
 +<div class="row">
 +  <div class="span10">
 +    <h3>Damage Efficiency</h3>
 +    <div id="dmg-graph" class="flot" style="width:800px; height:200px;">
 +    </div>
 +
 +    <div class="weapon-nav damage-nav">
 +      <ul>
 +        % if 'rocketlauncher' in recent_weapons:
 +        <li>
 +          <div class="dmg-weap weapon-active">
 +            <img src="${request.static_url("xonstat:static/images/rocketlauncher.png")}" />
 +            <p><small>Rocket</small></p>
 +            <a href="${request.route_url('player_damage', id=player.player_id, _query={'weapon':'rocketlauncher'})}" title="Show rocket launcher efficiency"></a>
 +          </div>
 +        </li>
 +        % endif
 +
 +        % if 'grenadelauncher' in recent_weapons:
 +        <li>
 +          <div class="dmg-weap">
 +            <img src="${request.static_url("xonstat:static/images/grenadelauncher.png")}" />
 +            <p><small>Mortar</small></p>
 +            <a href="${request.route_url('player_damage', id=player.player_id, _query={'weapon':'grenadelauncher'})}" title="Show mortar damage efficiency"></a>
 +          </div>
 +        </li>
 +        % endif
 +
 +        % if 'electro' in recent_weapons:
 +        <li>
 +          <div class="dmg-weap">
 +            <img src="${request.static_url("xonstat:static/images/electro.png")}" />
 +            <p><small>Electro</small></p>
 +            <a href="${request.route_url('player_damage', id=player.player_id, _query={'weapon':'electro'})}" title="Show electro damage efficiency"></a>
 +          </div>
 +        </li>
 +        % endif
 +
 +        % if 'crylink' in recent_weapons:
 +        <li>
 +          <div class="dmg-weap">
 +            <img src="${request.static_url("xonstat:static/images/crylink.png")}" />
 +            <p><small>Crylink</small></p>
 +            <a href="${request.route_url('player_damage', id=player.player_id, _query={'weapon':'crylink'})}" title="Show crylink damage efficiency"></a>
 +          </div>
 +        </li>
 +        % endif
 +
 +        % if 'hagar' in recent_weapons:
 +        <li>
 +          <div class="dmg-weap">
 +            <img src="${request.static_url("xonstat:static/images/hagar.png")}" />
 +            <p><small>Hagar</small></p>
 +            <a href="${request.route_url('player_damage', id=player.player_id, _query={'weapon':'hagar'})}" title="Show hagar damage efficiency"></a>
 +          </div>
 +        </li>
 +        % endif
 +
 +        % if 'laser' in recent_weapons:
 +        <li>
 +          <div class="dmg-weap">
 +            <img src="${request.static_url("xonstat:static/images/laser.png")}" />
 +            <p><small>Laser</small></p>
 +            <a href="${request.route_url('player_damage', id=player.player_id, _query={'weapon':'laser'})}" title="Show laser damage efficiency"></a>
 +          </div>
 +        </li>
 +        % endif
 +
 +      </ul>
 +    </div>
 +
 +  </div>
 +</div>
 +% endif
 +
 +
  ##### RECENT GAMES (v2) ####
  % if recent_games:
  <div class="row">
index 45a91b58c860e0b200c6fdbf967e1e0fcd01d378,e7f6e389dfa1c32cf43bba4da96e9437b129a815..525d2f9d353357a1edf22b543962e4aa33866c07
mode 100755,100644..100644
@@@ -1,8 -1,9 +1,8 @@@
- from xonstat.views.submission import stats_submit\r
- from xonstat.views.player import player_index, player_info, player_game_index\r
- from xonstat.views.player import player_accuracy_json, player_damage_json\r
- from xonstat.views.game import game_index, game_info, rank_index\r
- from xonstat.views.map import map_info, map_index, map_index_json\r
- from xonstat.views.server import server_info, server_game_index, server_index\r
- from xonstat.views.search import search_q, search\r
- from xonstat.views.main import main_index\r
+ from xonstat.views.submission import stats_submit
+ from xonstat.views.player import player_index, player_info, player_game_index
 -from xonstat.views.player import player_accuracy_json
 -from xonstat.views.game   import game_index, game_info, rank_index
 -from xonstat.views.map    import map_info, map_index
 -from xonstat.views.map    import map_index_json
++from xonstat.views.player import player_accuracy_json, player_damage_json
++from xonstat.views.game import game_index, game_info, rank_index
++from xonstat.views.map import map_info, map_index, map_index_json
+ from xonstat.views.server import server_info, server_game_index, server_index
+ from xonstat.views.search import search_q, search
 -from xonstat.views.main   import main_index
++from xonstat.views.main import main_index
diff --combined xonstat/views/player.py
index 82acf169d437a4c19b81dfe88fa2eed85599dd08,02e3cd3360a9b5437f29c470e0f03d615ae0b2c5..fe322a78f8767c220009370d5be14a7f4834c5d7
mode 100755,100644..100644
- import datetime\r
- import json\r
- import logging\r
- import re\r
- import sqlalchemy as sa\r
- import sqlalchemy.sql.functions as func\r
- import time\r
- from pyramid.response import Response\r
- from pyramid.url import current_route_url\r
- from sqlalchemy import desc, distinct\r
- from webhelpers.paginate import Page, PageURL\r
- from xonstat.models import *\r
- from xonstat.util import page_url\r
\r
- log = logging.getLogger(__name__)\r
\r
\r
- def _player_index_data(request):\r
-     if request.params.has_key('page'):\r
-         current_page = request.params['page']\r
-     else:\r
-         current_page = 1\r
\r
-     try:\r
-         player_q = DBSession.query(Player).\\r
-                 filter(Player.player_id > 2).\\r
-                 filter(Player.active_ind == True).\\r
-                 filter(sa.not_(Player.nick.like('Anonymous Player%'))).\\r
-                 order_by(Player.player_id.desc())\r
\r
-         players = Page(player_q, current_page, items_per_page=10, url=page_url)\r
\r
-     except Exception as e:\r
-         players = None\r
-         raise e\r
\r
-     return {'players':players\r
-            }\r
\r
\r
- def player_index(request):\r
-     """\r
-     Provides a list of all the current players.\r
-     """\r
-     return _player_index_data(request)\r
\r
\r
- def _get_games_played(player_id):\r
-     """\r
-     Provides a breakdown by gametype of the games played by player_id.\r
\r
-     Returns a tuple containing (total_games, games_breakdown), where\r
-     total_games is the absolute number of games played by player_id\r
-     and games_breakdown is an array containing (game_type_cd, # games)\r
-     """\r
-     games_played = DBSession.query(Game.game_type_cd, func.count()).\\r
-             filter(Game.game_id == PlayerGameStat.game_id).\\r
-             filter(PlayerGameStat.player_id == player_id).\\r
-             group_by(Game.game_type_cd).\\r
-             order_by(func.count().desc()).all()\r
\r
-     total = 0\r
-     for (game_type_cd, games) in games_played:\r
-         total += games\r
\r
-     return (total, games_played)\r
\r
\r
- # TODO: should probably factor the above function into this one such that\r
- # total_stats['ctf_games'] is the count of CTF games and so on...\r
- def _get_total_stats(player_id):\r
-     """\r
-     Provides aggregated stats by player_id.\r
\r
-     Returns a dict with the keys 'kills', 'deaths', 'alivetime'.\r
\r
-     kills = how many kills a player has over all games\r
-     deaths = how many deaths a player has over all games\r
-     alivetime = how long a player has played over all games\r
\r
-     If any of the above are None, they are set to 0.\r
-     """\r
-     total_stats = {}\r
-     (total_stats['kills'], total_stats['deaths'], total_stats['alivetime']) = DBSession.\\r
-             query("total_kills", "total_deaths", "total_alivetime").\\r
-             from_statement(\r
-                 "select sum(kills) total_kills, "\r
-                 "sum(deaths) total_deaths, "\r
-                 "sum(alivetime) total_alivetime "\r
-                 "from player_game_stats "\r
-                 "where player_id=:player_id"\r
-             ).params(player_id=player_id).one()\r
\r
-     (total_stats['wins'],) = DBSession.\\r
-             query("total_wins").\\r
-             from_statement(\r
-                 "select count(*) total_wins "\r
-                 "from games g, player_game_stats pgs "\r
-                 "where g.game_id = pgs.game_id "\r
-                 "and player_id=:player_id "\r
-                 "and (g.winner = pgs.team or pgs.rank = 1)"\r
-             ).params(player_id=player_id).one()\r
\r
-     for (key,value) in total_stats.items():\r
-         if value == None:\r
-             total_stats[key] = 0\r
\r
-     return total_stats\r
\r
\r
- def get_accuracy_stats(player_id, weapon_cd, games):\r
-     """\r
-     Provides accuracy for weapon_cd by player_id for the past N games.\r
-     """\r
-     # Reaching back 90 days should give us an accurate enough average\r
-     # We then multiply this out for the number of data points (games) to\r
-     # create parameters for a flot graph\r
-     try:\r
-         raw_avg = DBSession.query(func.sum(PlayerWeaponStat.hit),\r
-                 func.sum(PlayerWeaponStat.fired)).\\r
-                 filter(PlayerWeaponStat.player_id == player_id).\\r
-                 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\\r
-                 one()\r
\r
-         avg = round(float(raw_avg[0])/raw_avg[1]*100, 2)\r
\r
-         # Determine the raw accuracy (hit, fired) numbers for $games games\r
-         # This is then enumerated to create parameters for a flot graph\r
-         raw_accs = DBSession.query(PlayerWeaponStat.game_id, \r
-             PlayerWeaponStat.hit, PlayerWeaponStat.fired).\\r
-                 filter(PlayerWeaponStat.player_id == player_id).\\r
-                 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\\r
-                 order_by(PlayerWeaponStat.game_id.desc()).\\r
-                 limit(games).\\r
-                 all()\r
\r
-         # they come out in opposite order, so flip them in the right direction\r
-         raw_accs.reverse()\r
\r
-         accs = []\r
-         for i in range(len(raw_accs)):\r
-             accs.append((raw_accs[i][0], round(float(raw_accs[i][1])/raw_accs[i][2]*100, 2)))\r
-     except:\r
-         accs = []\r
-         avg = 0.0\r
\r
-     return (avg, accs)\r
\r
\r
- def get_damage_stats(player_id, weapon_cd, games):\r
-     """\r
-     Provides damage info for weapon_cd by player_id for the past N games.\r
-     """\r
-     try:\r
-         raw_avg = DBSession.query(func.sum(PlayerWeaponStat.actual),\r
-                 func.sum(PlayerWeaponStat.hit)).\\r
-                 filter(PlayerWeaponStat.player_id == player_id).\\r
-                 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\\r
-                 one()\r
\r
-         avg = round(float(raw_avg[0])/raw_avg[1], 2)\r
\r
-         # Determine the damage efficiency (hit, fired) numbers for $games games\r
-         # This is then enumerated to create parameters for a flot graph\r
-         raw_dmgs = DBSession.query(PlayerWeaponStat.game_id, \r
-             PlayerWeaponStat.actual, PlayerWeaponStat.hit).\\r
-                 filter(PlayerWeaponStat.player_id == player_id).\\r
-                 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\\r
-                 order_by(PlayerWeaponStat.game_id.desc()).\\r
-                 limit(games).\\r
-                 all()\r
\r
-         # they come out in opposite order, so flip them in the right direction\r
-         raw_dmgs.reverse()\r
\r
-         dmgs = []\r
-         for i in range(len(raw_dmgs)):\r
-             # try to derive, unless we've hit nothing then set to 0!\r
-             try:\r
-                 dmg = round(float(raw_dmgs[i][1])/raw_dmgs[i][2], 2)\r
-             except:\r
-                 dmg = 0.0\r
\r
-             dmgs.append((raw_dmgs[i][0], dmg))\r
-     except Exception as e:\r
-         dmgs = []\r
-         avg = 0.0\r
\r
-     return (avg, dmgs)\r
\r
\r
- def _player_info_data(request):\r
-     player_id = int(request.matchdict['id'])\r
-     if player_id <= 2:\r
-         player_id = -1;\r
\r
-     try:\r
-         player = DBSession.query(Player).filter_by(player_id=player_id).\\r
-                 filter(Player.active_ind == True).one()\r
\r
-         # games played, alivetime, wins, kills, deaths\r
-         total_stats = _get_total_stats(player.player_id)\r
\r
-         # games breakdown - N games played (X ctf, Y dm) etc\r
-         (total_games, games_breakdown) = _get_games_played(player.player_id)\r
\r
\r
-         # friendly display of elo information and preliminary status\r
-         elos = DBSession.query(PlayerElo).filter_by(player_id=player_id).\\r
-                 filter(PlayerElo.game_type_cd.in_(['ctf','duel','dm'])).\\r
-                 order_by(PlayerElo.elo.desc()).all()\r
\r
-         elos_display = []\r
-         for elo in elos:\r
-             if elo.games > 32:\r
-                 str = "{0} ({1})"\r
-             else:\r
-                 str = "{0}* ({1})"\r
\r
-             elos_display.append(str.format(round(elo.elo, 3),\r
-                 elo.game_type_cd))\r
\r
-         # which weapons have been used in the past 90 days\r
-         # and also, used in 5 games or more?\r
-         back_then = datetime.datetime.utcnow() - datetime.timedelta(days=90)\r
-         recent_weapons = []\r
-         for weapon in DBSession.query(PlayerWeaponStat.weapon_cd, func.count()).\\r
-                 filter(PlayerWeaponStat.player_id == player_id).\\r
-                 filter(PlayerWeaponStat.create_dt > back_then).\\r
-                 group_by(PlayerWeaponStat.weapon_cd).\\r
-                 having(func.count() > 4).\\r
-                 all():\r
-                     recent_weapons.append(weapon[0])\r
\r
-         # recent games table, all data\r
-         recent_games = DBSession.query(PlayerGameStat, Game, Server, Map).\\r
-                 filter(PlayerGameStat.player_id == player_id).\\r
-                 filter(PlayerGameStat.game_id == Game.game_id).\\r
-                 filter(Game.server_id == Server.server_id).\\r
-                 filter(Game.map_id == Map.map_id).\\r
-                 order_by(Game.game_id.desc())[0:10]\r
\r
-     except Exception as e:\r
-         player = None\r
-         elos_display = None\r
-         total_stats = None\r
-         recent_games = None\r
-         total_games = None\r
-         games_breakdown = None\r
-         recent_weapons = []\r
\r
-     return {'player':player,\r
-             'elos_display':elos_display,\r
-             'recent_games':recent_games,\r
-             'total_stats':total_stats,\r
-             'total_games':total_games,\r
-             'games_breakdown':games_breakdown,\r
-             'recent_weapons':recent_weapons,\r
-             }\r
\r
\r
- def player_info(request):\r
-     """\r
-     Provides detailed information on a specific player\r
-     """\r
-     return _player_info_data(request)\r
\r
\r
- def _player_game_index_data(request):\r
-     player_id = request.matchdict['player_id']\r
\r
-     if request.params.has_key('page'):\r
-         current_page = request.params['page']\r
-     else:\r
-         current_page = 1\r
\r
-     try:\r
-         games_q = DBSession.query(Game, Server, Map).\\r
-             filter(PlayerGameStat.game_id == Game.game_id).\\r
-             filter(PlayerGameStat.player_id == player_id).\\r
-             filter(Game.server_id == Server.server_id).\\r
-             filter(Game.map_id == Map.map_id).\\r
-             order_by(Game.game_id.desc())\r
\r
-         games = Page(games_q, current_page, items_per_page=10, url=page_url)\r
\r
-         pgstats = {}\r
-         for (game, server, map) in games:\r
-             pgstats[game.game_id] = DBSession.query(PlayerGameStat).\\r
-                     filter(PlayerGameStat.game_id == game.game_id).\\r
-                     order_by(PlayerGameStat.rank).\\r
-                     order_by(PlayerGameStat.score).all()\r
\r
-     except Exception as e:\r
-         player = None\r
-         games = None\r
\r
-     return {'player_id':player_id,\r
-             'games':games,\r
-             'pgstats':pgstats}\r
\r
\r
- def player_game_index(request):\r
-     """\r
-     Provides an index of the games in which a particular\r
-     player was involved. This is ordered by game_id, with\r
-     the most recent game_ids first. Paginated.\r
-     """\r
-     return _player_game_index_data(request)\r
\r
\r
- def _player_accuracy_data(request):\r
-     player_id = request.matchdict['id']\r
-     allowed_weapons = ['nex', 'rifle', 'shotgun', 'uzi', 'minstanex']\r
-     weapon_cd = 'nex'\r
-     games = 20\r
\r
-     if request.params.has_key('weapon'):\r
-         if request.params['weapon'] in allowed_weapons:\r
-             weapon_cd = request.params['weapon']\r
\r
-     if request.params.has_key('games'):\r
-         try:\r
-             games = request.params['games']\r
\r
-             if games < 0:\r
-                 games = 20\r
-             if games > 50:\r
-                 games = 50\r
-         except:\r
-             games = 20\r
\r
-     (avg, accs) = get_accuracy_stats(player_id, weapon_cd, games)\r
\r
-     # if we don't have enough data for the given weapon\r
-     if len(accs) < games:\r
-         games = len(accs)\r
\r
-     return {\r
-             'player_id':player_id, \r
-             'player_url':request.route_url('player_info', id=player_id), \r
-             'weapon':weapon_cd, \r
-             'games':games, \r
-             'avg':avg, \r
-             'accs':accs\r
-             }\r
\r
\r
- def player_accuracy_json(request):\r
-     """\r
-     Provides a JSON response representing the accuracy for the given weapon.\r
\r
-     Parameters:\r
-        weapon = which weapon to display accuracy for. Valid values are 'nex',\r
-                 'shotgun', 'uzi', and 'minstanex'.\r
-        games = over how many games to display accuracy. Can be up to 50.\r
-     """\r
-     return _player_accuracy_data(request)\r
\r
\r
- def _player_damage_data(request):\r
-     player_id = request.matchdict['id']\r
-     allowed_weapons = ['grenadelauncher', 'electro', 'crylink', 'hagar',\r
-             'rocketlauncher', 'laser']\r
-     weapon_cd = 'laser'\r
-     games = 20\r
\r
-     if request.params.has_key('weapon'):\r
-         if request.params['weapon'] in allowed_weapons:\r
-             weapon_cd = request.params['weapon']\r
\r
-     if request.params.has_key('games'):\r
-         try:\r
-             games = request.params['games']\r
\r
-             if games < 0:\r
-                 games = 20\r
-             if games > 50:\r
-                 games = 50\r
-         except:\r
-             games = 20\r
\r
-     (avg, dmgs) = get_damage_stats(player_id, weapon_cd, games)\r
\r
-     # if we don't have enough data for the given weapon\r
-     if len(dmgs) < games:\r
-         games = len(dmgs)\r
\r
-     return {\r
-             'player_id':player_id, \r
-             'player_url':request.route_url('player_info', id=player_id), \r
-             'weapon':weapon_cd, \r
-             'games':games, \r
-             'avg':avg, \r
-             'dmgs':dmgs\r
-             }\r
\r
\r
- def player_damage_json(request):\r
-     """\r
-     Provides a JSON response representing the damage for the given weapon.\r
\r
-     Parameters:\r
-        weapon = which weapon to display damage for. Valid values are\r
-          'grenadelauncher', 'electro', 'crylink', 'hagar', 'rocketlauncher',\r
-          'laser'.\r
-        games = over how many games to display damage. Can be up to 50.\r
-     """\r
-     return _player_damage_data(request)\r
+ import datetime
+ import json
+ import logging
+ import re
+ import sqlalchemy as sa
+ import sqlalchemy.sql.functions as func
+ import time
+ from pyramid.response import Response
+ from pyramid.url import current_route_url
+ from sqlalchemy import desc, distinct
+ from webhelpers.paginate import Page, PageURL
+ from xonstat.models import *
+ from xonstat.util import page_url
+ log = logging.getLogger(__name__)
+ def _player_index_data(request):
+     if request.params.has_key('page'):
+         current_page = request.params['page']
+     else:
+         current_page = 1
+     try:
+         player_q = DBSession.query(Player).\
+                 filter(Player.player_id > 2).\
+                 filter(Player.active_ind == True).\
+                 filter(sa.not_(Player.nick.like('Anonymous Player%'))).\
+                 order_by(Player.player_id.desc())
+         players = Page(player_q, current_page, items_per_page=10, url=page_url)
+     except Exception as e:
+         players = None
+         raise e
+     return {'players':players
+            }
+ def player_index(request):
+     """
+     Provides a list of all the current players.
+     """
+     return _player_index_data(request)
+ def _get_games_played(player_id):
+     """
+     Provides a breakdown by gametype of the games played by player_id.
+     Returns a tuple containing (total_games, games_breakdown), where
+     total_games is the absolute number of games played by player_id
+     and games_breakdown is an array containing (game_type_cd, # games)
+     """
+     games_played = DBSession.query(Game.game_type_cd, func.count()).\
+             filter(Game.game_id == PlayerGameStat.game_id).\
+             filter(PlayerGameStat.player_id == player_id).\
+             group_by(Game.game_type_cd).\
+             order_by(func.count().desc()).all()
+     total = 0
+     for (game_type_cd, games) in games_played:
+         total += games
+     return (total, games_played)
+ # TODO: should probably factor the above function into this one such that
+ # total_stats['ctf_games'] is the count of CTF games and so on...
+ def _get_total_stats(player_id):
+     """
+     Provides aggregated stats by player_id.
+     Returns a dict with the keys 'kills', 'deaths', 'alivetime'.
+     kills = how many kills a player has over all games
+     deaths = how many deaths a player has over all games
+     alivetime = how long a player has played over all games
+     If any of the above are None, they are set to 0.
+     """
+     total_stats = {}
+     (total_stats['kills'], total_stats['deaths'], total_stats['alivetime']) = DBSession.\
+             query("total_kills", "total_deaths", "total_alivetime").\
+             from_statement(
+                 "select sum(kills) total_kills, "
+                 "sum(deaths) total_deaths, "
+                 "sum(alivetime) total_alivetime "
+                 "from player_game_stats "
+                 "where player_id=:player_id"
+             ).params(player_id=player_id).one()
+     (total_stats['wins'],) = DBSession.\
+             query("total_wins").\
+             from_statement(
+                 "select count(*) total_wins "
+                 "from games g, player_game_stats pgs "
+                 "where g.game_id = pgs.game_id "
+                 "and player_id=:player_id "
+                 "and (g.winner = pgs.team or pgs.rank = 1)"
+             ).params(player_id=player_id).one()
+     for (key,value) in total_stats.items():
+         if value == None:
+             total_stats[key] = 0
+     return total_stats
+ def get_accuracy_stats(player_id, weapon_cd, games):
+     """
+     Provides accuracy for weapon_cd by player_id for the past N games.
+     """
+     # Reaching back 90 days should give us an accurate enough average
+     # We then multiply this out for the number of data points (games) to
+     # create parameters for a flot graph
+     try:
+         raw_avg = DBSession.query(func.sum(PlayerWeaponStat.hit),
+                 func.sum(PlayerWeaponStat.fired)).\
+                 filter(PlayerWeaponStat.player_id == player_id).\
+                 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
+                 one()
+         avg = round(float(raw_avg[0])/raw_avg[1]*100, 2)
+         # Determine the raw accuracy (hit, fired) numbers for $games games
+         # This is then enumerated to create parameters for a flot graph
+         raw_accs = DBSession.query(PlayerWeaponStat.game_id, 
+             PlayerWeaponStat.hit, PlayerWeaponStat.fired).\
+                 filter(PlayerWeaponStat.player_id == player_id).\
+                 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
+                 order_by(PlayerWeaponStat.game_id.desc()).\
+                 limit(games).\
+                 all()
+         # they come out in opposite order, so flip them in the right direction
+         raw_accs.reverse()
+         accs = []
+         for i in range(len(raw_accs)):
+             accs.append((raw_accs[i][0], round(float(raw_accs[i][1])/raw_accs[i][2]*100, 2)))
+     except:
+         accs = []
+         avg = 0.0
+     return (avg, accs)
++def get_damage_stats(player_id, weapon_cd, games):
++    """
++    Provides damage info for weapon_cd by player_id for the past N games.
++    """
++    try:
++        raw_avg = DBSession.query(func.sum(PlayerWeaponStat.actual),
++                func.sum(PlayerWeaponStat.hit)).\
++                filter(PlayerWeaponStat.player_id == player_id).\
++                filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
++                one()
++
++        avg = round(float(raw_avg[0])/raw_avg[1], 2)
++
++        # Determine the damage efficiency (hit, fired) numbers for $games games
++        # This is then enumerated to create parameters for a flot graph
++        raw_dmgs = DBSession.query(PlayerWeaponStat.game_id, 
++            PlayerWeaponStat.actual, PlayerWeaponStat.hit).\
++                filter(PlayerWeaponStat.player_id == player_id).\
++                filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
++                order_by(PlayerWeaponStat.game_id.desc()).\
++                limit(games).\
++                all()
++
++        # they come out in opposite order, so flip them in the right direction
++        raw_dmgs.reverse()
++
++        dmgs = []
++        for i in range(len(raw_dmgs)):
++            # try to derive, unless we've hit nothing then set to 0!
++            try:
++                dmg = round(float(raw_dmgs[i][1])/raw_dmgs[i][2], 2)
++            except:
++                dmg = 0.0
++
++            dmgs.append((raw_dmgs[i][0], dmg))
++    except Exception as e:
++        dmgs = []
++        avg = 0.0
++
++    return (avg, dmgs)
++
++
+ def _player_info_data(request):
+     player_id = int(request.matchdict['id'])
+     if player_id <= 2:
+         player_id = -1;
+     try:
+         player = DBSession.query(Player).filter_by(player_id=player_id).\
+                 filter(Player.active_ind == True).one()
+         # games played, alivetime, wins, kills, deaths
+         total_stats = _get_total_stats(player.player_id)
+         # games breakdown - N games played (X ctf, Y dm) etc
+         (total_games, games_breakdown) = _get_games_played(player.player_id)
+         # friendly display of elo information and preliminary status
+         elos = DBSession.query(PlayerElo).filter_by(player_id=player_id).\
+                 filter(PlayerElo.game_type_cd.in_(['ctf','duel','dm'])).\
+                 order_by(PlayerElo.elo.desc()).all()
+         elos_display = []
+         for elo in elos:
+             if elo.games > 32:
+                 str = "{0} ({1})"
+             else:
+                 str = "{0}* ({1})"
+             elos_display.append(str.format(round(elo.elo, 3),
+                 elo.game_type_cd))
+         # which weapons have been used in the past 90 days
+         # and also, used in 5 games or more?
+         back_then = datetime.datetime.utcnow() - datetime.timedelta(days=90)
+         recent_weapons = []
+         for weapon in DBSession.query(PlayerWeaponStat.weapon_cd, func.count()).\
+                 filter(PlayerWeaponStat.player_id == player_id).\
+                 filter(PlayerWeaponStat.create_dt > back_then).\
+                 group_by(PlayerWeaponStat.weapon_cd).\
+                 having(func.count() > 4).\
+                 all():
+                     recent_weapons.append(weapon[0])
+         # recent games table, all data
+         recent_games = DBSession.query(PlayerGameStat, Game, Server, Map).\
+                 filter(PlayerGameStat.player_id == player_id).\
+                 filter(PlayerGameStat.game_id == Game.game_id).\
+                 filter(Game.server_id == Server.server_id).\
+                 filter(Game.map_id == Map.map_id).\
+                 order_by(Game.game_id.desc())[0:10]
+     except Exception as e:
+         player = None
+         elos_display = None
+         total_stats = None
+         recent_games = None
+         total_games = None
+         games_breakdown = None
+         recent_weapons = []
+     return {'player':player,
+             'elos_display':elos_display,
+             'recent_games':recent_games,
+             'total_stats':total_stats,
+             'total_games':total_games,
+             'games_breakdown':games_breakdown,
+             'recent_weapons':recent_weapons,
+             }
+ def player_info(request):
+     """
+     Provides detailed information on a specific player
+     """
+     return _player_info_data(request)
+ def _player_game_index_data(request):
+     player_id = request.matchdict['player_id']
+     if request.params.has_key('page'):
+         current_page = request.params['page']
+     else:
+         current_page = 1
+     try:
+         games_q = DBSession.query(Game, Server, Map).\
+             filter(PlayerGameStat.game_id == Game.game_id).\
+             filter(PlayerGameStat.player_id == player_id).\
+             filter(Game.server_id == Server.server_id).\
+             filter(Game.map_id == Map.map_id).\
+             order_by(Game.game_id.desc())
+         games = Page(games_q, current_page, items_per_page=10, url=page_url)
+         pgstats = {}
+         for (game, server, map) in games:
+             pgstats[game.game_id] = DBSession.query(PlayerGameStat).\
+                     filter(PlayerGameStat.game_id == game.game_id).\
+                     order_by(PlayerGameStat.rank).\
+                     order_by(PlayerGameStat.score).all()
+     except Exception as e:
+         player = None
+         games = None
+     return {'player_id':player_id,
+             'games':games,
+             'pgstats':pgstats}
+ def player_game_index(request):
+     """
+     Provides an index of the games in which a particular
+     player was involved. This is ordered by game_id, with
+     the most recent game_ids first. Paginated.
+     """
+     return _player_game_index_data(request)
+ def _player_accuracy_data(request):
+     player_id = request.matchdict['id']
+     allowed_weapons = ['nex', 'rifle', 'shotgun', 'uzi', 'minstanex']
+     weapon_cd = 'nex'
+     games = 20
+     if request.params.has_key('weapon'):
+         if request.params['weapon'] in allowed_weapons:
+             weapon_cd = request.params['weapon']
+     if request.params.has_key('games'):
+         try:
+             games = request.params['games']
+             if games < 0:
+                 games = 20
+             if games > 50:
+                 games = 50
+         except:
+             games = 20
+     (avg, accs) = get_accuracy_stats(player_id, weapon_cd, games)
+     # if we don't have enough data for the given weapon
+     if len(accs) < games:
+         games = len(accs)
+     return {
+             'player_id':player_id, 
+             'player_url':request.route_url('player_info', id=player_id), 
+             'weapon':weapon_cd, 
+             'games':games, 
+             'avg':avg, 
+             'accs':accs
+             }
+ def player_accuracy_json(request):
+     """
+     Provides a JSON response representing the accuracy for the given weapon.
+     Parameters:
+        weapon = which weapon to display accuracy for. Valid values are 'nex',
+                 'shotgun', 'uzi', and 'minstanex'.
+        games = over how many games to display accuracy. Can be up to 50.
+     """
+     return _player_accuracy_data(request)
++
++
++def _player_damage_data(request):
++    player_id = request.matchdict['id']
++    allowed_weapons = ['grenadelauncher', 'electro', 'crylink', 'hagar',
++            'rocketlauncher', 'laser']
++    weapon_cd = 'laser'
++    games = 20
++
++    if request.params.has_key('weapon'):
++        if request.params['weapon'] in allowed_weapons:
++            weapon_cd = request.params['weapon']
++
++    if request.params.has_key('games'):
++        try:
++            games = request.params['games']
++
++            if games < 0:
++                games = 20
++            if games > 50:
++                games = 50
++        except:
++            games = 20
++
++    (avg, dmgs) = get_damage_stats(player_id, weapon_cd, games)
++
++    # if we don't have enough data for the given weapon
++    if len(dmgs) < games:
++        games = len(dmgs)
++
++    return {
++            'player_id':player_id, 
++            'player_url':request.route_url('player_info', id=player_id), 
++            'weapon':weapon_cd, 
++            'games':games, 
++            'avg':avg, 
++            'dmgs':dmgs
++            }
++
++
++def player_damage_json(request):
++    """
++    Provides a JSON response representing the damage for the given weapon.
++
++    Parameters:
++       weapon = which weapon to display damage for. Valid values are
++         'grenadelauncher', 'electro', 'crylink', 'hagar', 'rocketlauncher',
++         'laser'.
++       games = over how many games to display damage. Can be up to 50.
++    """
++    return _player_damage_data(request)