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

index 79f7f527bc64c066cc77d75b766725ab4f0176f2,2e45a0853d4d47e1815a28f8c1e3a6a37ed44476..3160b7583c2cba36fd2ab88e5b4168ddd952d36d
mode 100755,100644..100644
@@@ -28,39 -27,29 +27,33 @@@ def main(global_config, **settings)
      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")
index 24ed9056b4fbc77d9bfb97c88e598bf2338b37d2,8216f7554ecd000e0bee6600f80233628bbacf0b..24ed9056b4fbc77d9bfb97c88e598bf2338b37d2
mode 100755,100644..100644
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
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)