X-Git-Url: http://de.git.xonotic.org/?a=blobdiff_plain;ds=sidebyside;f=xonstat%2Fviews%2Fplayer.py;h=fa8cde44ded4cfe90d161388130546a6ae45de59;hb=26ffe42bb07e46d3dd9f938aa8315b47bfc7fa56;hp=af1337d3fc44a64b3eb796472437268b30703784;hpb=349dd5c4cdd51b406f58f817291e8f98e1da60a3;p=xonotic%2Fxonstat.git diff --git a/xonstat/views/player.py b/xonstat/views/player.py old mode 100755 new mode 100644 index af1337d..fa8cde4 --- a/xonstat/views/player.py +++ b/xonstat/views/player.py @@ -1,316 +1,996 @@ -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 _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) +import datetime +import logging +import pyramid.httpexceptions +import sqlalchemy as sa +import sqlalchemy.sql.functions as func +from calendar import timegm +from collections import namedtuple +from webhelpers.paginate import Page +from xonstat.models import * +from xonstat.util import page_url, to_json, pretty_date, datetime_seconds +from xonstat.util import is_cake_day, verify_request +from xonstat.views.helpers import RecentGame, recent_games_q + +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=25, 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 player_index_json(request): + """ + Provides a list of all the current players. JSON. + """ + return [{'status':'not implemented'}] + + +def get_games_played(player_id): + """ + Provides a breakdown by gametype of the games played by player_id. + + Returns a list of namedtuples with the following members: + - game_type_cd + - games + - wins + - losses + - win_pct + + The list itself is ordered by the number of games played + """ + GamesPlayed = namedtuple('GamesPlayed', ['game_type_cd', 'games', 'wins', + 'losses', 'win_pct']) + + raw_games_played = DBSession.query('game_type_cd', 'wins', 'losses').\ + from_statement( + "SELECT game_type_cd, " + "SUM(win) wins, " + "SUM(loss) losses " + "FROM (SELECT g.game_id, " + "g.game_type_cd, " + "CASE " + "WHEN g.winner = pgs.team THEN 1 " + "WHEN pgs.scoreboardpos = 1 THEN 1 " + "ELSE 0 " + "END win, " + "CASE " + "WHEN g.winner = pgs.team THEN 0 " + "WHEN pgs.scoreboardpos = 1 THEN 0 " + "ELSE 1 " + "END loss " + "FROM games g, " + "player_game_stats pgs " + "WHERE g.game_id = pgs.game_id " + "AND pgs.player_id = :player_id) win_loss " + "GROUP BY game_type_cd " + ).params(player_id=player_id).all() + + games_played = [] + overall_games = 0 + overall_wins = 0 + overall_losses = 0 + for row in raw_games_played: + games = row.wins + row.losses + overall_games += games + overall_wins += row.wins + overall_losses += row.losses + win_pct = float(row.wins)/games * 100 + + games_played.append(GamesPlayed(row.game_type_cd, games, row.wins, + row.losses, win_pct)) + + try: + overall_win_pct = float(overall_wins)/overall_games * 100 + except: + overall_win_pct = 0.0 + + games_played.append(GamesPlayed('overall', overall_games, overall_wins, + overall_losses, overall_win_pct)) + + # sort the resulting list by # of games played + games_played = sorted(games_played, key=lambda x:x.games) + games_played.reverse() + return games_played + + +def get_overall_stats(player_id): + """ + Provides a breakdown of stats by gametype played by player_id. + + Returns a dictionary of namedtuples with the following members: + - total_kills + - total_deaths + - k_d_ratio + - last_played (last time the player played the game type) + - last_played_epoch (same as above, but in seconds since epoch) + - last_played_fuzzy (same as above, but in relative date) + - total_playing_time (total amount of time played the game type) + - total_playing_time_secs (same as the above, but in seconds) + - total_pickups (ctf only) + - total_captures (ctf only) + - cap_ratio (ctf only) + - total_carrier_frags (ctf only) + - game_type_cd + - game_type_descr + + The key to the dictionary is the game type code. There is also an + "overall" game_type_cd which sums the totals and computes the total ratios. + """ + OverallStats = namedtuple('OverallStats', ['total_kills', 'total_deaths', + 'k_d_ratio', 'last_played', 'last_played_epoch', 'last_played_fuzzy', + 'total_playing_time', 'total_playing_time_secs', 'total_pickups', 'total_captures', 'cap_ratio', + 'total_carrier_frags', 'game_type_cd', 'game_type_descr']) + + raw_stats = DBSession.query('game_type_cd', 'game_type_descr', + 'total_kills', 'total_deaths', 'last_played', 'total_playing_time', + 'total_pickups', 'total_captures', 'total_carrier_frags').\ + from_statement( + "SELECT g.game_type_cd, " + "gt.descr game_type_descr, " + "Sum(pgs.kills) total_kills, " + "Sum(pgs.deaths) total_deaths, " + "Max(pgs.create_dt) last_played, " + "Sum(pgs.alivetime) total_playing_time, " + "Sum(pgs.pickups) total_pickups, " + "Sum(pgs.captures) total_captures, " + "Sum(pgs.carrier_frags) total_carrier_frags " + "FROM games g, " + "cd_game_type gt, " + "player_game_stats pgs " + "WHERE g.game_id = pgs.game_id " + "AND g.game_type_cd = gt.game_type_cd " + "AND pgs.player_id = :player_id " + "GROUP BY g.game_type_cd, game_type_descr " + "UNION " + "SELECT 'overall' game_type_cd, " + "'Overall' game_type_descr, " + "Sum(pgs.kills) total_kills, " + "Sum(pgs.deaths) total_deaths, " + "Max(pgs.create_dt) last_played, " + "Sum(pgs.alivetime) total_playing_time, " + "Sum(pgs.pickups) total_pickups, " + "Sum(pgs.captures) total_captures, " + "Sum(pgs.carrier_frags) total_carrier_frags " + "FROM player_game_stats pgs " + "WHERE pgs.player_id = :player_id " + ).params(player_id=player_id).all() + + # to be indexed by game_type_cd + overall_stats = {} + + for row in raw_stats: + # individual gametype ratio calculations + try: + k_d_ratio = float(row.total_kills)/row.total_deaths + except: + k_d_ratio = None + + try: + cap_ratio = float(row.total_captures)/row.total_pickups + except: + cap_ratio = None + + # everything else is untouched or "raw" + os = OverallStats(total_kills=row.total_kills, + total_deaths=row.total_deaths, + k_d_ratio=k_d_ratio, + last_played=row.last_played, + last_played_epoch=timegm(row.last_played.timetuple()), + last_played_fuzzy=pretty_date(row.last_played), + total_playing_time=row.total_playing_time, + total_playing_time_secs=int(datetime_seconds(row.total_playing_time)), + total_pickups=row.total_pickups, + total_captures=row.total_captures, + cap_ratio=cap_ratio, + total_carrier_frags=row.total_carrier_frags, + game_type_cd=row.game_type_cd, + game_type_descr=row.game_type_descr) + + overall_stats[row.game_type_cd] = os + + # We have to edit "overall" stats to exclude deaths in CTS. + # Although we still want to record deaths, they shouldn't + # count towards the overall K:D ratio. + if 'cts' in overall_stats: + os = overall_stats['overall'] + + try: + k_d_ratio = float(os.total_kills)/(os.total_deaths - overall_stats['cts'].total_deaths) + except: + k_d_ratio = None + + non_cts_deaths = os.total_deaths - overall_stats['cts'].total_deaths + + + overall_stats['overall'] = OverallStats( + total_kills = os.total_kills, + total_deaths = non_cts_deaths, + k_d_ratio = k_d_ratio, + last_played = os.last_played, + last_played_epoch = os.last_played_epoch, + last_played_fuzzy = os.last_played_fuzzy, + total_playing_time = os.total_playing_time, + total_playing_time_secs = os.total_playing_time_secs, + total_pickups = os.total_pickups, + total_captures = os.total_captures, + cap_ratio = os.cap_ratio, + total_carrier_frags = os.total_carrier_frags, + game_type_cd = os.game_type_cd, + game_type_descr = os.game_type_descr) + + return overall_stats + + +def get_fav_maps(player_id, game_type_cd=None): + """ + Provides a breakdown of favorite maps by gametype. + + Returns a dictionary of namedtuples with the following members: + - game_type_cd + - map_name (map name) + - map_id + - times_played + + The favorite map is defined as the map you've played the most + for the given game_type_cd. + + The key to the dictionary is the game type code. There is also an + "overall" game_type_cd which is the overall favorite map. This is + defined as the favorite map of the game type you've played the + most. The input parameter game_type_cd is for this. + """ + FavMap = namedtuple('FavMap', ['map_name', 'map_id', 'times_played', 'game_type_cd']) + + raw_favs = DBSession.query('game_type_cd', 'map_name', + 'map_id', 'times_played').\ + from_statement( + "SELECT game_type_cd, " + "name map_name, " + "map_id, " + "times_played " + "FROM (SELECT g.game_type_cd, " + "m.name, " + "m.map_id, " + "Count(*) times_played, " + "Row_number() " + "OVER ( " + "partition BY g.game_type_cd " + "ORDER BY Count(*) DESC, m.map_id ASC) rank " + "FROM games g, " + "player_game_stats pgs, " + "maps m " + "WHERE g.game_id = pgs.game_id " + "AND g.map_id = m.map_id " + "AND pgs.player_id = :player_id " + "GROUP BY g.game_type_cd, " + "m.map_id, " + "m.name) most_played " + "WHERE rank = 1 " + "ORDER BY times_played desc " + ).params(player_id=player_id).all() + + fav_maps = {} + overall_fav = None + for row in raw_favs: + fv = FavMap(map_name=row.map_name, + map_id=row.map_id, + times_played=row.times_played, + game_type_cd=row.game_type_cd) + + # if we aren't given a favorite game_type_cd + # then the overall favorite is the one we've + # played the most + if overall_fav is None: + fav_maps['overall'] = fv + overall_fav = fv.game_type_cd + + # otherwise it is the favorite map from the + # favorite game_type_cd (provided as a param) + # and we'll overwrite the first dict entry + if game_type_cd == fv.game_type_cd: + fav_maps['overall'] = fv + + fav_maps[row.game_type_cd] = fv + + return fav_maps + + +def get_ranks(player_id): + """ + Provides a breakdown of the player's ranks by game type. + + Returns a dictionary of namedtuples with the following members: + - game_type_cd + - rank + - max_rank + + The key to the dictionary is the game type code. There is also an + "overall" game_type_cd which is the overall best rank. + """ + Rank = namedtuple('Rank', ['rank', 'max_rank', 'percentile', 'game_type_cd']) + + raw_ranks = DBSession.query("game_type_cd", "rank", "max_rank").\ + from_statement( + "select pr.game_type_cd, pr.rank, overall.max_rank " + "from player_ranks pr, " + "(select game_type_cd, max(rank) max_rank " + "from player_ranks " + "group by game_type_cd) overall " + "where pr.game_type_cd = overall.game_type_cd " + "and player_id = :player_id " + "order by rank").\ + params(player_id=player_id).all() + + ranks = {} + found_top_rank = False + for row in raw_ranks: + rank = Rank(rank=row.rank, + max_rank=row.max_rank, + percentile=100 - 100*float(row.rank-1)/(row.max_rank-1), + game_type_cd=row.game_type_cd) + + + if not found_top_rank: + ranks['overall'] = rank + found_top_rank = True + elif rank.percentile > ranks['overall'].percentile: + ranks['overall'] = rank + + ranks[row.game_type_cd] = rank + + return ranks; + + +def get_elos(player_id): + """ + Provides a breakdown of the player's elos by game type. + + Returns a dictionary of namedtuples with the following members: + - player_id + - game_type_cd + - games + - elo + + The key to the dictionary is the game type code. There is also an + "overall" game_type_cd which is the overall best rank. + """ + raw_elos = DBSession.query(PlayerElo).filter_by(player_id=player_id).\ + order_by(PlayerElo.elo.desc()).all() + + elos = {} + found_max_elo = False + for row in raw_elos: + if not found_max_elo: + elos['overall'] = row + found_max_elo = True + + elos[row.game_type_cd] = row + + return elos + + +def get_recent_games(player_id): + """ + Provides a list of recent games for a player. Uses the recent_games_q helper. + """ + # recent games played in descending order + rgs = recent_games_q(player_id=player_id, force_player_id=True).limit(10).all() + recent_games = [RecentGame(row) for row in rgs] + + return recent_games + + +def get_recent_weapons(player_id): + """ + Returns the weapons that have been used in the past 90 days + and also used in 5 games or more. + """ + cutoff = 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 > cutoff).\ + group_by(PlayerWeaponStat.weapon_cd).\ + having(func.count() > 4).\ + all(): + recent_weapons.append(weapon[0]) + + return recent_weapons + + +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 = get_games_played(player_id) + overall_stats = get_overall_stats(player_id) + fav_maps = get_fav_maps(player_id) + elos = get_elos(player_id) + ranks = get_ranks(player_id) + recent_games = get_recent_games(player_id) + recent_weapons = get_recent_weapons(player_id) + cake_day = is_cake_day(player.create_dt) + + except Exception as e: + player = None + games_played = None + overall_stats = None + fav_maps = None + elos = None + ranks = None + recent_games = None + recent_weapons = [] + cake_day = False + ## do not raise exceptions here (only for debugging) + # raise e + + return {'player':player, + 'games_played':games_played, + 'overall_stats':overall_stats, + 'fav_maps':fav_maps, + 'elos':elos, + 'ranks':ranks, + 'recent_games':recent_games, + 'recent_weapons':recent_weapons, + 'cake_day':cake_day, + } + + +def player_info(request): + """ + Provides detailed information on a specific player + """ + return player_info_data(request) + + +def player_info_json(request): + """ + Provides detailed information on a specific player. JSON. + """ + + # All player_info fields are converted into JSON-formattable dictionaries + player_info = player_info_data(request) + + player = player_info['player'].to_dict() + + games_played = {} + for game in player_info['games_played']: + games_played[game.game_type_cd] = to_json(game) + + overall_stats = {} + for gt,stats in player_info['overall_stats'].items(): + overall_stats[gt] = to_json(stats) + + elos = {} + for gt,elo in player_info['elos'].items(): + elos[gt] = to_json(elo.to_dict()) + + ranks = {} + for gt,rank in player_info['ranks'].items(): + ranks[gt] = to_json(rank) + + fav_maps = {} + for gt,mapinfo in player_info['fav_maps'].items(): + fav_maps[gt] = to_json(mapinfo) + + recent_games = [] + for game in player_info['recent_games']: + recent_games.append(to_json(game)) + + #recent_weapons = player_info['recent_weapons'] + + return [{ + 'player': player, + 'games_played': games_played, + 'overall_stats': overall_stats, + 'fav_maps': fav_maps, + 'elos': elos, + 'ranks': ranks, + 'recent_games': recent_games, + # 'recent_weapons': recent_weapons, + 'recent_weapons': ['not implemented'], + }] + #return [{'status':'not implemented'}] + + +def player_game_index_data(request): + player_id = request.matchdict['player_id'] + + game_type_cd = None + game_type_descr = None + + if request.params.has_key('type'): + game_type_cd = request.params['type'] + try: + game_type_descr = DBSession.query(GameType.descr).\ + filter(GameType.game_type_cd == game_type_cd).\ + one()[0] + except Exception as e: + pass + + else: + game_type_cd = None + game_type_descr = None + + if request.params.has_key('page'): + current_page = request.params['page'] + else: + current_page = 1 + + try: + player = DBSession.query(Player).\ + filter_by(player_id=player_id).\ + filter(Player.active_ind == True).\ + one() + + rgs_q = recent_games_q(player_id=player.player_id, + force_player_id=True, game_type_cd=game_type_cd) + + games = Page(rgs_q, current_page, items_per_page=20, url=page_url) + + # replace the items in the canned pagination class with more rich ones + games.items = [RecentGame(row) for row in games.items] + + games_played = get_games_played(player_id) + + except Exception as e: + player = None + games = None + game_type_cd = None + game_type_descr = None + games_played = None + + return { + 'player_id':player.player_id, + 'player':player, + 'games':games, + 'game_type_cd':game_type_cd, + 'game_type_descr':game_type_descr, + 'games_played':games_played, + } + + +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_game_index_json(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. JSON. + """ + return [{'status':'not implemented'}] + + +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(request): + """ + Provides the accuracy for the given weapon. (JSON only) + """ + return player_accuracy_data(request) + + +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 = 'rocketlauncher' + 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) + + +def player_hashkey_info_data(request): + (idfp, status) = verify_request(request) + + # if config is to *not* verify requests and we get nothing back, this + # query will return nothing and we'll 404. + try: + player = DBSession.query(Player).\ + filter(Player.player_id == Hashkey.player_id).\ + filter(Player.active_ind == True).\ + filter(Hashkey.hashkey == idfp).one() + + games_played = get_games_played(player.player_id) + overall_stats = get_overall_stats(player.player_id) + fav_maps = get_fav_maps(player.player_id) + elos = get_elos(player.player_id) + ranks = get_ranks(player.player_id) + + except Exception as e: + raise pyramid.httpexceptions.HTTPNotFound + + return {'player':player, + 'hashkey':hashkey, + 'games_played':games_played, + 'overall_stats':overall_stats, + 'fav_maps':fav_maps, + 'elos':elos, + 'ranks':ranks, + } + + +def player_hashkey_info_json(request): + """ + Provides detailed information on a specific player. JSON. + """ + + # All player_info fields are converted into JSON-formattable dictionaries + player_info = player_hashkey_info_data(request) + + player = player_info['player'].to_dict() + + games_played = {} + for game in player_info['games_played']: + games_played[game.game_type_cd] = to_json(game) + + overall_stats = {} + for gt,stats in player_info['overall_stats'].items(): + overall_stats[gt] = to_json(stats) + + elos = {} + for gt,elo in player_info['elos'].items(): + elos[gt] = to_json(elo.to_dict()) + + ranks = {} + for gt,rank in player_info['ranks'].items(): + ranks[gt] = to_json(rank) + + fav_maps = {} + for gt,mapinfo in player_info['fav_maps'].items(): + fav_maps[gt] = to_json(mapinfo) + + return [{ + 'version': 1, + 'player': player, + 'games_played': games_played, + 'overall_stats': overall_stats, + 'fav_maps': fav_maps, + 'elos': elos, + 'ranks': ranks, + }] + + +def player_hashkey_info_text(request): + """ + Provides detailed information on a specific player. Plain text. + """ + # UTC epoch + now = timegm(datetime.datetime.utcnow().timetuple()) + + # All player_info fields are converted into JSON-formattable dictionaries + player_info = player_hashkey_info_data(request) + + # gather all of the data up into aggregate structures + player = player_info['player'] + games_played = player_info['games_played'] + overall_stats = player_info['overall_stats'] + elos = player_info['elos'] + ranks = player_info['ranks'] + fav_maps = player_info['fav_maps'] + + # one-offs for things needing conversion for text/plain + player_joined = timegm(player.create_dt.timetuple()) + alivetime = int(datetime_seconds(overall_stats['overall'].total_playing_time)) + + # this is a plain text response, if we don't do this here then + # Pyramid will assume html + request.response.content_type = 'text/plain' + + return { + 'version': 1, + 'now': now, + 'player': player, + 'hashkey': player_info['hashkey'], + 'player_joined': player_joined, + 'games_played': games_played, + 'overall_stats': overall_stats, + 'alivetime': alivetime, + 'fav_maps': fav_maps, + 'elos': elos, + 'ranks': ranks, + } + + +def player_elo_info_data(request): + """ + Provides elo information on a specific player. Raw data is returned. + """ + hashkey = request.matchdict['hashkey'] + try: + player = DBSession.query(Player).\ + filter(Player.player_id == Hashkey.player_id).\ + filter(Player.active_ind == True).\ + filter(Hashkey.hashkey == hashkey).one() + + elos = get_elos(player.player_id) + + except Exception as e: + log.debug(e) + raise pyramid.httpexceptions.HTTPNotFound + + return {'elos':elos} + + +def player_elo_info_json(request): + """ + Provides elo information on a specific player. JSON. + """ + elo_info = player_elo_info_data(request) + + elos = {} + for gt, elo in elo_info['elos'].items(): + elos[gt] = to_json(elo.to_dict()) + + return [{ + 'version': 1, + 'elos': elos, + }] + +def player_captimes_data(request): + player_id = int(request.matchdict['id']) + if player_id <= 2: + player_id = -1; + + PlayerCaptimes = namedtuple('PlayerCaptimes', ['fastest_cap', 'create_dt', 'create_dt_epoch', 'create_dt_fuzzy', + 'player_id', 'game_id', 'map_id', 'map_name', 'server_id', 'server_name']) + + dbquery = DBSession.query('fastest_cap', 'create_dt', 'player_id', 'game_id', 'map_id', + 'map_name', 'server_id', 'server_name').\ + from_statement( + "SELECT ct.fastest_cap, " + "ct.create_dt, " + "ct.player_id, " + "ct.game_id, " + "ct.map_id, " + "m.name map_name, " + "g.server_id, " + "s.name server_name " + "FROM player_map_captimes ct, " + "games g, " + "maps m, " + "servers s " + "WHERE ct.player_id = :player_id " + "AND g.game_id = ct.game_id " + "AND g.server_id = s.server_id " + "AND m.map_id = ct.map_id " + #"ORDER BY ct.fastest_cap " + "ORDER BY ct.create_dt desc" + ).params(player_id=player_id).all() + + player = DBSession.query(Player).filter_by(player_id=player_id).one() + + player_captimes = [] + for row in dbquery: + player_captimes.append(PlayerCaptimes( + fastest_cap=row.fastest_cap, + create_dt=row.create_dt, + create_dt_epoch=timegm(row.create_dt.timetuple()), + create_dt_fuzzy=pretty_date(row.create_dt), + player_id=row.player_id, + game_id=row.game_id, + map_id=row.map_id, + map_name=row.map_name, + server_id=row.server_id, + server_name=row.server_name, + )) + + return { + 'captimes':player_captimes, + 'player_id':player_id, + 'player_url':request.route_url('player_info', id=player_id), + 'player':player, + } + +def player_captimes(request): + return player_captimes_data(request) + +def player_captimes_json(request): + return player_captimes_data(request)