From 14e30571beab151707851382d893d82c598a4b46 Mon Sep 17 00:00:00 2001 From: "Jan D. Behrens" Date: Sun, 5 Aug 2012 12:52:38 +0200 Subject: [PATCH] Restructured player_info page to now include more detailed stats for specific gametypes (currently duel,dm,tdm,ctf) The total_stats dictionary now also holds much more information than before, especially gametype-dependent info Also added "favorite server" line, showing on which server the player is most likely to be found This commit implements some of the suggestions from Feature Request #1039: Organizing Player page statistics - http://dev.xonotic.org/issues/1039 --- xonstat/templates/player_info.mako | 101 ++++++++++++++++---- xonstat/views/player.py | 147 +++++++++++++++++++++++++++-- 2 files changed, 221 insertions(+), 27 deletions(-) diff --git a/xonstat/templates/player_info.mako b/xonstat/templates/player_info.mako index 444d97f..5855157 100644 --- a/xonstat/templates/player_info.mako +++ b/xonstat/templates/player_info.mako @@ -189,18 +189,19 @@ Player Information Last Seen: ${recent_games[0][1].fuzzy_date()}
- Playing Time: ${total_stats['alivetime']} hours - % if total_stats['alivetime'] > total_stats['alivetime_month']: - % if total_stats['alivetime_month'] > total_stats['alivetime_week']: - (${total_stats['alivetime_month']} hours this month; ${total_stats['alivetime_week']} hours this week) + % if fav_server is not None: + Favorite Server: + <% srv_list = fav_server[:1] %> + % for srvinfo in srv_list: + % if srvinfo != srv_list[-1]: + <% delim = ", " %> % else: - (${total_stats['alivetime_month']} hours this month) + <% delim = "" %> % endif - % endif + ${srvinfo['name']}${delim} + % endfor
- - <% games_breakdown_str = ', '.join(["{0} {1}".format(ng, gt) for (gt, ng) in games_breakdown]) %> - Games Played: ${total_games} (${games_breakdown_str})
+ % endif % if fav_map is not None: Favorite Maps: @@ -218,7 +219,7 @@ Player Information % if fav_weapon is not None: Favorite Weapons: - <% wpn_list = fav_weapon[:2] %> + <% wpn_list = fav_weapon[:3] %> % for wpninfo in wpn_list: % if wpninfo != wpn_list[-1]: <% delim = ", " %> @@ -233,21 +234,88 @@ Player Information

- % if total_games > 0 and total_stats['wins'] is not None: - Win Percentage: ${round(float(total_stats['wins'])/total_games * 100, 2)}% (${total_stats['wins']} wins, ${total_games - total_stats['wins']} losses)
+ Playing Time: ${total_stats['alivetime']} hours + % if total_stats['alivetime'] > total_stats['alivetime_month']: + % if total_stats['alivetime_month'] > total_stats['alivetime_week']: + (${total_stats['alivetime_month']} hours this month; ${total_stats['alivetime_week']} hours this week) + % else: + (${total_stats['alivetime_month']} hours this month) + % endif + % endif +
+ + <% games_breakdown_str = ', '.join(["{0} {1}".format(ng, gt) for (gt, ng) in total_stats['games_breakdown'].items()]) %> + Games Played: ${total_stats['games']} (${games_breakdown_str})
+ + % if total_stats['games'] > 0 and total_stats['wins'] is not None: + Win Percentage: ${round(float(total_stats['wins'])/total_stats['games'] * 100, 2)}% (${total_stats['wins']} wins, ${total_stats['games'] - total_stats['wins']} losses)
% endif % if total_stats['kills'] > 0 and total_stats['deaths'] > 0: Kill Ratio: ${round(float(total_stats['kills'])/total_stats['deaths'], 3)} (${total_stats['kills']} kills, ${total_stats['deaths']} deaths, ${total_stats['suicides']} suicides)
% endif +

+
+ + +
+
+

+ % if total_stats['games_breakdown'].has_key('duel'): + Duel Stats: + % if total_stats['duel_wins'] is not None: + Win Percentage ${round(float(total_stats['duel_wins'])/total_stats['games_breakdown']['duel'] * 100, 2)}% (${total_stats['duel_wins']} wins, ${total_stats['games_breakdown']['duel'] - total_stats['duel_wins']} losses) + % endif + + % if total_stats['duel_kills'] > 0 and total_stats['duel_deaths'] > 0: + | Kill Ratio ${round(float(total_stats['duel_kills'])/total_stats['duel_deaths'], 3)} (${total_stats['duel_kills']} kills, ${total_stats['duel_deaths']} deaths, ${total_stats['duel_suicides']} suicides) + % endif +
+ % endif + + % if total_stats['games_breakdown'].has_key('dm'): + DM Stats: + % if total_stats['dm_wins'] is not None: + Win Percentage ${round(float(total_stats['dm_wins'])/total_stats['games_breakdown']['dm'] * 100, 2)}% (${total_stats['dm_wins']} wins, ${total_stats['games_breakdown']['dm'] - total_stats['dm_wins']} losses) + % endif + + % if total_stats['dm_kills'] > 0 and total_stats['dm_deaths'] > 0: + | Kill Ratio ${round(float(total_stats['dm_kills'])/total_stats['dm_deaths'], 3)} (${total_stats['dm_kills']} kills, ${total_stats['dm_deaths']} deaths, ${total_stats['dm_suicides']} suicides) + % endif +
+ % endif + + % if total_stats['games_breakdown'].has_key('tdm'): + TDM Stats: + % if total_stats['tdm_wins'] is not None: + Win Percentage ${round(float(total_stats['tdm_wins'])/total_stats['games_breakdown']['tdm'] * 100, 2)}% (${total_stats['tdm_wins']} wins, ${total_stats['games_breakdown']['tdm'] - total_stats['tdm_wins']} losses) + % endif + + % if total_stats['tdm_kills'] > 0 and total_stats['tdm_deaths'] > 0: + | Kill Ratio ${round(float(total_stats['tdm_kills'])/total_stats['tdm_deaths'], 3)} (${total_stats['tdm_kills']} kills, ${total_stats['tdm_deaths']} deaths, ${total_stats['tdm_suicides']} suicides) + % endif +
+ % endif + + % if total_stats['games_breakdown'].has_key('ctf'): + CTF Stats: + % if total_stats['ctf_wins'] is not None: + Win Percentage ${round(float(total_stats['ctf_wins'])/total_stats['games_breakdown']['ctf'] * 100, 2)}% (${total_stats['ctf_wins']} wins, ${total_stats['games_breakdown']['ctf'] - total_stats['ctf_wins']} losses) + % endif + + % if total_stats['ctf_pickups'] > 0 and total_stats['tdm_captures'] > 0: + | Cap Ratio ${round(float(total_stats['ctf_caps'])/total_stats['ctf_pickups'], 3)} (${total_stats['ctf_caps']} caps, ${total_stats['ctf_pickups']} pickups, ${total_stats['ctf_returns']} returns, ${total_stats['ctf_fckills']} fckills) + % endif +
+ % endif % if elos_display is not None and len(elos_display) > 0: Elo: - ${', '.join(elos_display)} -
- %if '*' in ', '.join(elos_display): - *preliminary Elo
+ ${elos_display} + %if '*' in elos_display: + *preliminary Elo %endif +
% endif % if ranks_display != '': @@ -257,7 +325,6 @@ Player Information

- % 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:
diff --git a/xonstat/views/player.py b/xonstat/views/player.py index 443e9d5..7491902 100644 --- a/xonstat/views/player.py +++ b/xonstat/views/player.py @@ -54,6 +54,8 @@ def player_index_json(request): def _get_games_played(player_id): """ + DEPRECATED: Now included in _get_total_stats() + Provides a breakdown by gametype of the games played by player_id. Returns a tuple containing (total_games, games_breakdown), where @@ -73,14 +75,14 @@ def _get_games_played(player_id): 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'. + games = how many games a player has played + games_breakdown = how many games of given type a player has played (dictionary) kills = how many kills a player has over all games deaths = how many deaths a player has over all games suicides = how many suicides a player has over all games @@ -96,7 +98,24 @@ def _get_total_stats(player_id): one_month_ago = datetime.datetime.utcnow() - datetime.timedelta(days=30) total_stats = {} - (total_stats['kills'], total_stats['deaths'], total_stats['suicides'], total_stats['alivetime'],) = DBSession.query( + + 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_stats['games'] = 0 + total_stats['games_breakdown'] = {} # this is a dictionary inside a dictionary .. dictception? + for (game_type_cd, games) in games_played: + total_stats['games'] += games + total_stats['games_breakdown'][game_type_cd] = games + + # more fields can be added here, e.g. 'collects' for kh games + (total_stats['kills'], total_stats['deaths'], total_stats['suicides'], + total_stats['alivetime'],) = DBSession.query( func.sum(PlayerGameStat.kills), func.sum(PlayerGameStat.deaths), func.sum(PlayerGameStat.suicides), @@ -122,6 +141,60 @@ def _get_total_stats(player_id): filter(PlayerGameStat.player_id == player_id).\ filter(Game.winner == PlayerGameStat.team or PlayerGameStat.rank == 1).\ one() + + (total_stats['duel_wins'],) = DBSession.query( + func.count("*")).\ + filter(Game.game_id == PlayerGameStat.game_id).\ + filter(Game.game_type_cd == "duel").\ + filter(PlayerGameStat.player_id == player_id).\ + filter(PlayerGameStat.rank == 1).\ + one() + + (total_stats['duel_kills'], total_stats['duel_deaths'], total_stats['duel_suicides'],) = DBSession.query( + func.sum(PlayerGameStat.kills), + func.sum(PlayerGameStat.deaths), + func.sum(PlayerGameStat.suicides)).\ + filter(Game.game_id == PlayerGameStat.game_id).\ + filter(Game.game_type_cd == "duel").\ + filter(PlayerGameStat.player_id == player_id).\ + one() + + (total_stats['dm_wins'],) = DBSession.query( + func.count("*")).\ + filter(Game.game_id == PlayerGameStat.game_id).\ + filter(Game.game_type_cd == "dm" or Game.game_type_cd == "tdm").\ + filter(PlayerGameStat.player_id == player_id).\ + filter(PlayerGameStat.rank == 1).\ + one() + + (total_stats['dm_kills'], total_stats['dm_deaths'], total_stats['dm_suicides'],) = DBSession.query( + func.sum(PlayerGameStat.kills), + func.sum(PlayerGameStat.deaths), + func.sum(PlayerGameStat.suicides)).\ + filter(Game.game_id == PlayerGameStat.game_id).\ + filter(Game.game_type_cd == "dm" or Game.game_type_cd == "tdm").\ + filter(PlayerGameStat.player_id == player_id).\ + one() + + (total_stats['ctf_wins'],) = DBSession.query( + func.count("*")).\ + filter(Game.game_id == PlayerGameStat.game_id).\ + filter(Game.game_type_cd == "ctf").\ + filter(PlayerGameStat.player_id == player_id).\ + filter(PlayerGameStat.rank == 1).\ + one() + + (total_stats['ctf_caps'], total_stats['ctf_pickups'], total_stats['ctf_drops'], + total_stats['ctf_returns'], total_stats['ctf_fckills'],) = DBSession.query( + func.sum(PlayerGameStat.captures), + func.sum(PlayerGameStat.pickups), + func.sum(PlayerGameStat.drops), + func.sum(PlayerGameStat.returns), + func.sum(PlayerGameStat.carrier_frags)).\ + filter(Game.game_id == PlayerGameStat.game_id).\ + filter(Game.game_type_cd == "ctf").\ + filter(PlayerGameStat.player_id == player_id).\ + one() for (key,value) in total_stats.items(): if value == None: @@ -191,6 +264,37 @@ def _get_fav_weapon(player_id): return fav_weapon +def _get_fav_server(player_id): + """ + Get the player's favorite server. The favorite server is defined + as the server that he or she has played on the most in the past + 90 days. + + Returns a sequence of dictionaries with keys for the server's name and id. + The sequence holds the most-used servers in decreasing order. + """ + # 90 day window + back_then = datetime.datetime.utcnow() - datetime.timedelta(days=90) + + raw_fav_server = DBSession.query(Server.name, Server.server_id).\ + filter(Game.game_id == PlayerGameStat.game_id).\ + filter(Game.server_id == Server.server_id).\ + filter(PlayerGameStat.player_id == player_id).\ + filter(PlayerGameStat.create_dt > back_then).\ + group_by(Server.name, Server.server_id).\ + order_by(func.count().desc()).\ + all() + + fav_server = [] + for srv_e in raw_fav_server: + entry = {} + entry['name'] = srv_e[0] + entry['id'] = srv_e[1] + fav_server.append(entry) + + return fav_server + + def _get_rank(player_id): """ Get the player's rank as well as the total number of ranks. @@ -304,7 +408,8 @@ def player_info_data(request): 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) + # DEPRECATED: included in total_stats, see above + # (total_games, games_breakdown) = _get_games_played(player.player_id) # favorite map from the past 90 days try: @@ -318,12 +423,19 @@ def player_info_data(request): except: fav_weapon = None + # favorite server from the past 90 days + try: + fav_server = _get_fav_server(player.player_id) + except: + fav_server = None + # 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 = [] + elos_dict = {} for elo in elos: if elo.games > 32: str = "{0} ({1})" @@ -332,11 +444,18 @@ def player_info_data(request): elos_display.append(str.format(round(elo.elo, 3), elo.game_type_cd)) + elos_dict[elo.game_type_cd] = round(elo.elo, 3) + elos_display = ', '.join(elos_display) # get current rank information ranks = _get_rank(player_id) - ranks_display = ', '.join(["{1} of {2} ({0})".format(gtc, rank, - max_rank) for gtc, rank, max_rank in ranks]) + + ranks_display = [] + ranks_dict = {} + for gtc,rank,max_rank in ranks: + ranks_display.append("{1} of {2} ({0})".format(gtc, rank, max_rank)) + ranks_dict[gtc] = (rank, max_rank) + ranks_display = ', '.join(ranks_display) # which weapons have been used in the past 90 days @@ -361,25 +480,33 @@ def player_info_data(request): except Exception as e: player = None + elos = None elos_display = None total_stats = None recent_games = None - total_games = None - games_breakdown = None + # DEPRECATED: included in total_stats, see above + #total_games = None + #games_breakdown = None recent_weapons = [] fav_map = None fav_weapon = None + fav_server = None + ranks = None ranks_display = None; return {'player':player, + 'elos':elos_dict, 'elos_display':elos_display, 'recent_games':recent_games, 'total_stats':total_stats, - 'total_games':total_games, - 'games_breakdown':games_breakdown, + # DEPRECATED: included in total_stats, see above + #'total_games':total_games, + #'games_breakdown':games_breakdown, 'recent_weapons':recent_weapons, 'fav_map':fav_map, 'fav_weapon':fav_weapon, + 'fav_server':fav_server, + 'ranks':ranks_dict, 'ranks_display':ranks_display, } -- 2.39.2