From: Jan Behrens Date: Tue, 22 Jan 2013 16:31:08 +0000 (+0100) Subject: Merge branch 'master' of http://git.xonotic.org/xonotic/xonstat into approved X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonstat.git;a=commitdiff_plain;h=d5733e0183d1c8c1683f060645477c0ec1368c6d;hp=dfe149e9fe51cf6dc84bb071bd9c86c19a9574bf Merge branch 'master' of http://git.xonotic.org/xonotic/xonstat into approved --- diff --git a/xonstat/__init__.py b/xonstat/__init__.py index 2d8fd1c..08946ad 100644 --- a/xonstat/__init__.py +++ b/xonstat/__init__.py @@ -56,9 +56,9 @@ def main(global_config, **settings): config.add_view(player_elo_info_json, route_name="player_elo_info_json", renderer="jsonp") config.add_route("player_accuracy", "/player/{id:\d+}/accuracy") - config.add_route("player_accuracy_json", "/player/{id:\d+}/accuracy.json") + #config.add_route("player_accuracy_json", "/player/{id:\d+}/accuracy.json") config.add_view(player_accuracy_json, route_name="player_accuracy", renderer="jsonp") - config.add_view(player_accuracy_json, route_name="player_accuracy_json", renderer="jsonp") + #config.add_view(player_accuracy_json, route_name="player_accuracy_json", renderer="jsonp") config.add_route("player_index", "/players") config.add_route("player_index_json", "/players.json") @@ -66,8 +66,12 @@ def main(global_config, **settings): config.add_view(player_index_json, route_name="player_index_json", renderer="jsonp") config.add_route("player_damage", "/player/{id:\d+}/damage") - config.add_view(player_damage_json, route_name="player_damage", - renderer="json") + config.add_view(player_damage_json, route_name="player_damage", renderer="jsonp") + + config.add_route("player_captimes", "/player/{id:\d+}/captimes") + config.add_route("player_captimes_json", "/player/{id:\d+}/captimes.json") + config.add_view(player_captimes, route_name="player_captimes", renderer="player_captimes.mako") + config.add_view(player_captimes_json, route_name="player_captimes_json", renderer="jsonp") # GAME ROUTES config.add_route("game_index", "/games") @@ -115,6 +119,11 @@ def main(global_config, **settings): config.add_view(map_info, route_name="map_info", renderer="map_info.mako") config.add_view(map_info_json, route_name="map_info_json", renderer="jsonp") + config.add_route("map_captimes", "/map/{id:\d+}/captimes") + config.add_route("map_captimes_json", "/map/{id:\d+}/captimes.json") + config.add_view(map_captimes, route_name="map_captimes", renderer="map_captimes.mako") + config.add_view(map_captimes_json, route_name="map_captimes_json", renderer="jsonp") + # SEARCH ROUTES config.add_route("search", "search") config.add_route("search_json", "search.json") diff --git a/xonstat/batch/badges/gen_badges.py b/xonstat/batch/badges/gen_badges.py index 44d73ae..770e42f 100644 --- a/xonstat/batch/badges/gen_badges.py +++ b/xonstat/batch/badges/gen_badges.py @@ -19,6 +19,9 @@ NUM_PLAYERS = None # we look for players who have activity within the past DELTA hours DELTA = 6 +VERBOSE = False + +INIFILE = None # keep this set to "None" # classic skin WITHOUT NAME - writes PNGs into "output//###.png" skin_classic = Skin( "", @@ -28,8 +31,21 @@ skin_classic = Skin( "", # more fancy skin [** WIP **]- writes PNGs into "output/archer/###.png" skin_archer = Skin( "archer", - bg = "background_archer-v1", - overlay = None, + #bg = "background_archer-v2_full", + bg = "background_archer-v3", + overlay = "", + nick_maxwidth = 265, + gametype_pos = (91,33), + nostats_pos = (91,59), + elo_pos = (91,47), + rank_pos = (91,58), + winp_pos = (509,20), + wins_pos = (508,35), + loss_pos = (508,45), + kdr_pos = (392,20), + kills_pos = (392,35), + deaths_pos = (392,45), + ptime_color = (0.05, 0.05, 0.1), ) # minimal skin - writes PNGs into "output/minimal/###.png" @@ -42,7 +58,7 @@ skin_minimal = Skin( "minimal", nick_fontsize = 16, nick_pos = (36,16), num_gametypes = 3, - nick_maxwidth = 300, + nick_maxwidth = 280, gametype_pos = (70,30), gametype_color = (0.0, 0.0, 0.0), gametype_text = "%s:", @@ -90,12 +106,17 @@ for arg in sys.argv[1:]: DELTA = 2**24 # large enough to enforce update, and doesn't result in errors elif arg == "test": NUM_PLAYERS = 100 + elif arg == "verbose": + VERBOSE = True else: - print """Usage: gen_badges.py [options] [skin list] + print """Usage: gen_badges.py [options] [skin list] Options: -force Force updating all badges (delta = 2^24) -test Limit number of players to 100 (for testing) + -verbose Show more verbose output -help Show this help text + Ini-File: + Name of a Pyramid ini-file to use (e.g. prodution.ini or development.ini). Skin list: Space-separated list of skins to use when creating badges. Available skins: classic, minimal, archer @@ -104,18 +125,25 @@ for arg in sys.argv[1:]: """ sys.exit(-1) else: - if arg == "classic": - skins.append(skin_classic) - elif arg == "minimal": - skins.append(skin_minimal) - elif arg == "archer": - skins.append(skin_archer) + if INIFILE == None: + INIFILE = arg + else: + if arg == "classic": + skins.append(skin_classic) + elif arg == "minimal": + skins.append(skin_minimal) + elif arg == "archer": + skins.append(skin_archer) if len(skins) == 0: - skins = [ skin_classic, skin_minimal ] + skins = [ skin_classic, skin_minimal, skin_archer ] + +if not INIFILE: + print "You must provide the name of an ini-file to use! Type 'gen_badges.py -h' for help." + sys.exit(-1) # environment setup -env = bootstrap('../../../development.ini') +env = bootstrap(INIFILE) req = env['request'] req.matchdict = {'id':3} @@ -142,7 +170,7 @@ else: filter(Player.active_ind == True).\ all() -playerdata = PlayerData() +playerdata = PlayerData if len(players) > 0: stop = datetime.now() @@ -163,11 +191,14 @@ if len(players) > 0: sstart = datetime.now() for sk in skins: - sk.render_image(playerdata, "output/%s/%d.png" % (str(sk), player_id[0])) + sk.render_image(playerdata.data, "output/%s/%d.png" % (str(sk), player_id[0])) sstop = datetime.now() td = sstop-sstart render_time += datetime_seconds(td) + if VERBOSE == True: + print player_id, unicode(playerdata.data['player'].nick) + stop = datetime.now() td = stop-start total_seconds = datetime_seconds(td) diff --git a/xonstat/batch/badges/img/background_archer-v2.png b/xonstat/batch/badges/img/background_archer-v2.png new file mode 100644 index 0000000..0ae73bd Binary files /dev/null and b/xonstat/batch/badges/img/background_archer-v2.png differ diff --git a/xonstat/batch/badges/img/background_archer-v3.png b/xonstat/batch/badges/img/background_archer-v3.png new file mode 100644 index 0000000..75b2483 Binary files /dev/null and b/xonstat/batch/badges/img/background_archer-v3.png differ diff --git a/xonstat/batch/badges/playerdata.py b/xonstat/batch/badges/playerdata.py index 0990208..e5b4046 100644 --- a/xonstat/batch/badges/playerdata.py +++ b/xonstat/batch/badges/playerdata.py @@ -1,6 +1,7 @@ import sqlalchemy as sa import sqlalchemy.sql.functions as func from xonstat.models import * +from xonstat.views.player import get_games_played, get_overall_stats, get_ranks, get_elos class PlayerData: @@ -16,6 +17,7 @@ class PlayerData: return self.data[key] return None + @classmethod def get_data(self, player_id): """Return player data as dict. @@ -25,87 +27,34 @@ class PlayerData: # total games # wins/losses # kills/deaths + # duel/dm/tdm/ctf elo + rank + 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) + ranks = get_ranks(player_id) + elos = get_elos(player_id) - player = DBSession.query(Player).filter(Player.player_id == player_id).one() - - games_played = DBSession.query( - Game.game_type_cd, func.count(), func.sum(PlayerGameStat.alivetime)).\ - 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 = {} - total_stats['games'] = 0 - total_stats['games_breakdown'] = {} # this is a dictionary inside a dictionary .. dictception? - total_stats['games_alivetime'] = {} - total_stats['gametypes'] = [] - for (game_type_cd, games, alivetime) in games_played: - total_stats['games'] += games - total_stats['gametypes'].append(game_type_cd) - total_stats['games_breakdown'][game_type_cd] = games - total_stats['games_alivetime'][game_type_cd] = alivetime - - (total_stats['kills'], total_stats['deaths'], total_stats['alivetime'],) = DBSession.query( - func.sum(PlayerGameStat.kills), - func.sum(PlayerGameStat.deaths), - func.sum(PlayerGameStat.alivetime)).\ - filter(PlayerGameStat.player_id == player_id).\ - one() - - (total_stats['wins'], total_stats['losses']) = DBSession.\ - query("wins", "losses").\ - from_statement( - "SELECT SUM(win) wins, SUM(loss) losses " - "FROM (SELECT g.game_id, " - " CASE " - " WHEN g.winner = pgs.team THEN 1 " - " WHEN pgs.rank = 1 THEN 1 " - " ELSE 0 " - " END win, " - " CASE " - " WHEN g.winner = pgs.team THEN 0 " - " WHEN pgs.rank = 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").\ - params(player_id=player_id).one() - - 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() + games_played_dict = {} + for game in games_played: + games_played_dict[game.game_type_cd] = game ranks_dict = {} - for gtc,rank,max_rank in ranks: - ranks_dict[gtc] = (rank, max_rank) - - elos = DBSession.query(PlayerElo).\ - filter_by(player_id=player_id).\ - order_by(PlayerElo.elo.desc()).\ - all() + for gt,rank in ranks.items(): + ranks_dict[gt] = (rank.rank, rank.max_rank) elos_dict = {} - for elo in elos: + for gt,elo in elos.items(): if elo.games >= 32: - elos_dict[elo.game_type_cd] = elo.elo + elos_dict[gt] = elo.elo self.data = { 'player':player, - 'total_stats':total_stats, + 'games_played':games_played_dict, + 'overall_stats':overall_stats, 'ranks':ranks_dict, 'elos':elos_dict, } + diff --git a/xonstat/batch/badges/skin.py b/xonstat/batch/badges/skin.py index 813594d..67d4826 100644 --- a/xonstat/batch/badges/skin.py +++ b/xonstat/batch/badges/skin.py @@ -67,8 +67,8 @@ class Skin: 'width': 560, 'height': 70, 'nick_fontsize': 20, - 'nick_pos': (56,18), - 'nick_maxwidth': 280, + 'nick_pos': (52,18), + 'nick_maxwidth': 270, 'gametype_fontsize':10, 'gametype_pos': (101,33), 'gametype_width': 94, @@ -140,7 +140,7 @@ class Skin: 'ptime_text': "Playing Time: %s", 'ptime_align': 0, } - + for k,v in params.items(): if self.params.has_key(k): self.params[k] = v @@ -191,14 +191,28 @@ class Skin: # setup variables - player = data.player - elos = data.elos - ranks = data.ranks - #games = data.total_stats['games'] - wins, losses = data.total_stats['wins'], data.total_stats['losses'] - games = wins + losses - kills, deaths = data.total_stats['kills'], data.total_stats['deaths'] - alivetime = data.total_stats['alivetime'] + player = data['player'] + elos = data['elos'] + ranks = data['ranks'] + games_played = data['games_played']['overall'] + overall_stats = data['overall_stats']['overall'] + + wins, losses, win_pct = games_played.wins, games_played.losses, games_played.win_pct + games = games_played.games + kills, deaths, kd_ratio = overall_stats.total_kills, overall_stats.total_deaths, overall_stats.k_d_ratio + alivetime = overall_stats.total_playing_time + + # make sorted list of gametypes + game_types = [] + num_games = 0 + for gt,info in data['games_played'].items(): + if gt == "overall": + continue + if info.games > num_games: + game_types.insert(0, gt) + else: + game_types.append(gt) + # build image @@ -207,7 +221,7 @@ class Skin: ctx = C.Context(surf) self.ctx = ctx ctx.set_antialias(C.ANTIALIAS_GRAY) - + # draw background if self.bg == None: if self.bgcolor != None: @@ -222,7 +236,7 @@ class Skin: try: # background texture bg = C.ImageSurface.create_from_png("img/%s.png" % self.bg) - + # tile image if bg: bg_w, bg_h = bg.get_width(), bg.get_height() @@ -252,17 +266,17 @@ class Skin: ## draw player's nickname with fancy colors - + # deocde nick, strip all weird-looking characters qstr = qfont_decode(player.nick).replace('^^', '^').replace(u'\x00', '') - chars = [] - for c in qstr: - # replace weird characters that make problems - TODO - if ord(c) < 128: - chars.append(c) - qstr = ''.join(chars) + #chars = [] + #for c in qstr: + # # replace weird characters that make problems - TODO + # if ord(c) < 128: + # chars.append(c) + #qstr = ''.join(chars) stripped_nick = strip_colors(qstr.replace(' ', '_')) - + # fontsize is reduced if width gets too large ctx.select_font_face(self.font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL) shrinknick = 0 @@ -275,8 +289,15 @@ class Skin: break # determine width of single whitespace for later use - xoff, yoff, tw, th = ctx.text_extents("_")[:4] + xoff, yoff, tw, th = ctx.text_extents("_ _")[:4] space_w = tw + xoff, yoff, tw, th = ctx.text_extents("__")[:4] + space_w -= tw + + # this hilarious code should determine the spacing between characters + sep_w = 0.25*space_w + if sep_w <= 0: + sep_w = 1 # split nick into colored segments xoffset = 0 @@ -292,11 +313,11 @@ class Skin: txt = parts[1] del parts[1] del parts[0] - + if not txt or len(txt) == 0: # only colorcode and no real text, skip this continue - + if tag: if tag.startswith('x'): r = int(tag[1] * 2, 16) / 255.0 @@ -314,20 +335,20 @@ class Skin: xoff, yoff, tw, th = ctx.text_extents(txt)[:4] ctx.set_source_rgb(r, g, b) ctx.move_to(self.nick_pos[0] + xoffset - xoff, self.nick_pos[1]) - ctx.show_text(txt) + ctx.show_text(txt.encode("utf-8")) tw += (len(txt)-len(txt.strip())) * space_w # account for lost whitespaces - xoffset += tw + 2 + xoffset += tw + sep_w ## print elos and ranks - + xoffset, yoffset = 0, 0 count = 0 - for gt in data.total_stats['gametypes'][:self.num_gametypes]: + for gt in game_types[:self.num_gametypes]: if not elos.has_key(gt) or not ranks.has_key(gt): continue count += 1 - + # re-align segments if less than max. gametypes are shown if count > 0: if count < self.num_gametypes: @@ -338,9 +359,9 @@ class Skin: else: xoffset += 0.5 * diff * self.gametype_width yoffset += 0.5 * diff * self.gametype_height - + # show a number gametypes the player has participated in - for gt in data.total_stats['gametypes'][:self.num_gametypes]: + for gt in game_types[:self.num_gametypes]: if not elos.has_key(gt) or not ranks.has_key(gt): continue @@ -384,19 +405,18 @@ class Skin: txt = "???" try: - ratio = float(wins)/games - txt = "%.2f%%" % round(ratio * 100, 2) + txt = "%.2f%%" % round(win_pct * 100, 2) except: - ratio = 0 - + win_pct = 0 + if self.winp_pos: - if ratio >= 0.5: - nr = 2*(ratio-0.5) + if win_pct >= 0.5: + nr = 2*(win_pct-0.5) r = nr*self.winp_colortop[0] + (1-nr)*self.winp_colormid[0] g = nr*self.winp_colortop[1] + (1-nr)*self.winp_colormid[1] b = nr*self.winp_colortop[2] + (1-nr)*self.winp_colormid[2] else: - nr = 2*ratio + nr = 2*win_pct r = nr*self.winp_colormid[0] + (1-nr)*self.winp_colorbot[0] g = nr*self.winp_colormid[1] + (1-nr)*self.winp_colorbot[1] b = nr*self.winp_colormid[2] + (1-nr)*self.winp_colorbot[2] @@ -424,24 +444,23 @@ class Skin: txt = self.kdtext_text self.set_font(self.kdtext_fontsize, self.kdtext_color) self.show_text(txt, self.kdtext_pos, self.kdtext_align) - + txt = "???" try: - ratio = float(kills)/deaths - txt = "%.3f" % round(ratio, 3) + txt = "%.3f" % round(kd_ratio, 3) except: - ratio = 0 + kd_ratio = 0 if self.kdr_pos: - if ratio >= 1.0: - nr = ratio-1.0 + if kd_ratio >= 1.0: + nr = kd_ratio-1.0 if nr > 1: nr = 1 r = nr*self.kdr_colortop[0] + (1-nr)*self.kdr_colormid[0] g = nr*self.kdr_colortop[1] + (1-nr)*self.kdr_colormid[1] b = nr*self.kdr_colortop[2] + (1-nr)*self.kdr_colormid[2] else: - nr = ratio + nr = kd_ratio r = nr*self.kdr_colormid[0] + (1-nr)*self.kdr_colorbot[0] g = nr*self.kdr_colormid[1] + (1-nr)*self.kdr_colorbot[1] b = nr*self.kdr_colormid[2] + (1-nr)*self.kdr_colorbot[2] diff --git a/xonstat/templates/map_captimes.mako b/xonstat/templates/map_captimes.mako new file mode 100644 index 0000000..1591cc4 --- /dev/null +++ b/xonstat/templates/map_captimes.mako @@ -0,0 +1,40 @@ +<%inherit file="base.mako"/> +<%namespace name="nav" file="nav.mako" /> + +<%block name="title"> +Map captimes + + + +
+
+ +

${map.name}

+

Back to map info page

+ +

Fastest flag capture times:

+ + + + + + + + + + + + + % for ct in captimes: + + + + + + + % endfor + +
GameCaptimeNickServerDate
view${ct.fastest_cap.total_seconds()} seconds${ct.player_nick_html|n}${ct.server_name}${ct.create_dt_fuzzy}
+ +
+
diff --git a/xonstat/templates/player_captimes.mako b/xonstat/templates/player_captimes.mako new file mode 100644 index 0000000..96d8ff7 --- /dev/null +++ b/xonstat/templates/player_captimes.mako @@ -0,0 +1,41 @@ +<%inherit file="base.mako"/> +<%namespace name="nav" file="nav.mako" /> + +<%block name="title"> +Player captimes + + +
+
+ +

${player.nick_html_colors()|n}

+

Back to player info page

+ +

Fastest flag capture times:

+ + + + + + + ## + + + + + + + % for ct in captimes: + + + + ## + + + + % endfor + +
GameCaptimeNickMapServerDate
view${ct.fastest_cap.total_seconds()} seconds${ct.html_nick|n}${ct.map_name}${ct.server_name}${ct.create_dt_fuzzy}
+ +
+
diff --git a/xonstat/views/__init__.py b/xonstat/views/__init__.py index b497dd5..f238d7a 100644 --- a/xonstat/views/__init__.py +++ b/xonstat/views/__init__.py @@ -5,6 +5,7 @@ from xonstat.views.player import player_index_json, player_info_json from xonstat.views.player import player_game_index_json, player_accuracy_json from xonstat.views.player import player_damage_json, player_hashkey_info_json from xonstat.views.player import player_hashkey_info_text, player_elo_info_json +from xonstat.views.player import player_captimes, player_captimes_json from xonstat.views.game import game_index, game_info, rank_index from xonstat.views.game import game_index_json, game_info_json, rank_index_json @@ -12,6 +13,7 @@ from xonstat.views.game import game_finder from xonstat.views.map import map_info, map_index from xonstat.views.map import map_info_json, map_index_json +from xonstat.views.map import map_captimes, map_captimes_json from xonstat.views.server import server_info, server_game_index, server_index from xonstat.views.server import server_info_json, server_game_index_json diff --git a/xonstat/views/map.py b/xonstat/views/map.py index 6ecbf37..4c65a2d 100644 --- a/xonstat/views/map.py +++ b/xonstat/views/map.py @@ -38,7 +38,7 @@ def map_index(request): def map_index_json(request): """ - Provides a JSON-serialized list of all the current maps. + Provides a JSON-serialized list of all the current maps. """ view_data = _map_index_data(request) @@ -50,7 +50,7 @@ def map_index_json(request): def _map_info_data(request): map_id = request.matchdict['id'] - try: + try: leaderboard_lifetime = int( request.registry.settings['xonstat.leaderboard_lifetime']) except: @@ -77,7 +77,7 @@ def _map_info_data(request): filter(Game.game_id == PlayerGameStat.game_id).\ filter(Game.map_id == map_id).\ filter(Player.player_id > 2).\ - filter(PlayerGameStat.create_dt > + filter(PlayerGameStat.create_dt > (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\ order_by(expr.desc(func.sum(PlayerGameStat.score))).\ group_by(Player.nick).\ @@ -87,13 +87,13 @@ def _map_info_data(request): for (player_id, nick, score) in top_scorers] # top players by playing time - top_players = DBSession.query(Player.player_id, Player.nick, + top_players = DBSession.query(Player.player_id, Player.nick, func.sum(PlayerGameStat.alivetime)).\ filter(Player.player_id == PlayerGameStat.player_id).\ filter(Game.game_id == PlayerGameStat.game_id).\ filter(Game.map_id == map_id).\ filter(Player.player_id > 2).\ - filter(PlayerGameStat.create_dt > + filter(PlayerGameStat.create_dt > (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\ order_by(expr.desc(func.sum(PlayerGameStat.alivetime))).\ group_by(Player.nick).\ @@ -103,11 +103,11 @@ def _map_info_data(request): for (player_id, nick, score) in top_players] # top servers using/playing this map - top_servers = DBSession.query(Server.server_id, Server.name, + top_servers = DBSession.query(Server.server_id, Server.name, func.count(Game.game_id)).\ filter(Game.server_id == Server.server_id).\ filter(Game.map_id == map_id).\ - filter(Game.create_dt > + filter(Game.create_dt > (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\ order_by(expr.desc(func.count(Game.game_id))).\ group_by(Server.name).\ @@ -162,3 +162,66 @@ def map_info_json(request): List the information stored about a given map. JSON. """ return [{'status':'not implemented'}] + + +def map_captimes_data(request): + map_id = int(request.matchdict['id']) + + MapCaptimes = namedtuple('PlayerCaptimes', ['fastest_cap', 'create_dt', 'create_dt_epoch', 'create_dt_fuzzy', + 'player_id', 'player_nick', 'player_nick_stripped', 'player_nick_html', + 'game_id', 'server_id', 'server_name']) + + dbquery = DBSession.query('fastest_cap', 'create_dt', 'player_id', 'game_id', + 'server_id', 'server_name', 'player_nick').\ + from_statement( + "SELECT ct.fastest_cap, " + "ct.create_dt, " + "ct.player_id, " + "ct.game_id, " + "g.server_id, " + "s.name server_name, " + "pgs.nick player_nick " + "FROM player_map_captimes ct, " + "games g, " + "maps m, " + "servers s, " + "player_game_stats pgs " + "WHERE ct.map_id = :map_id " + "AND g.game_id = ct.game_id " + "AND g.server_id = s.server_id " + "AND m.map_id = ct.map_id " + "AND pgs.player_id = ct.player_id " + "AND pgs.game_id = ct.game_id " + "ORDER BY ct.fastest_cap " + ).params(map_id=map_id).all() + + mmap = DBSession.query(Map).filter_by(map_id=map_id).one() + + map_captimes = [] + for row in dbquery: + map_captimes.append(MapCaptimes( + 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, + player_nick=row.player_nick, + player_nick_stripped=strip_colors(row.player_nick), + player_nick_html=html_colors(row.player_nick), + game_id=row.game_id, + server_id=row.server_id, + server_name=row.server_name, + )) + + return { + 'captimes':map_captimes, + 'map_id':map_id, + 'map_url':request.route_url('map_info', id=map_id), + 'map':mmap, + } + +def map_captimes(request): + return map_captimes_data(request) + +def map_captimes_json(request): + return map_captimes_data(request) diff --git a/xonstat/views/player.py b/xonstat/views/player.py index 24a6f26..33b4774 100644 --- a/xonstat/views/player.py +++ b/xonstat/views/player.py @@ -12,7 +12,7 @@ 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, to_json, pretty_date, datetime_seconds +from xonstat.util import page_url, to_json, pretty_date, datetime_seconds, html_colors from xonstat.views.helpers import RecentGame, recent_games_q log = logging.getLogger(__name__) @@ -272,7 +272,7 @@ def get_fav_maps(player_id, game_type_cd=None): 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 @@ -302,7 +302,7 @@ def get_ranks(player_id): 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").\ @@ -412,7 +412,7 @@ def get_accuracy_stats(player_id, weapon_cd, games): # 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, + raw_accs = DBSession.query(PlayerWeaponStat.game_id, PlayerWeaponStat.hit, PlayerWeaponStat.fired).\ filter(PlayerWeaponStat.player_id == player_id).\ filter(PlayerWeaponStat.weapon_cd == weapon_cd).\ @@ -448,7 +448,7 @@ def get_damage_stats(player_id, weapon_cd, games): # 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, + raw_dmgs = DBSession.query(PlayerWeaponStat.game_id, PlayerWeaponStat.actual, PlayerWeaponStat.hit).\ filter(PlayerWeaponStat.player_id == player_id).\ filter(PlayerWeaponStat.weapon_cd == weapon_cd).\ @@ -526,7 +526,7 @@ def player_info_json(request): """ # All player_info fields are converted into JSON-formattable dictionaries - player_info = player_info_data(request) + player_info = player_info_data(request) player = player_info['player'].to_dict() @@ -646,11 +646,11 @@ def player_accuracy_data(request): 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, + 'player_id':player_id, + 'player_url':request.route_url('player_info', id=player_id), + 'weapon':weapon_cd, + 'games':games, + 'avg':avg, 'accs':accs } @@ -703,11 +703,11 @@ def player_damage_data(request): 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, + 'player_id':player_id, + 'player_url':request.route_url('player_info', id=player_id), + 'weapon':weapon_cd, + 'games':games, + 'avg':avg, 'dmgs':dmgs } @@ -868,3 +868,69 @@ def player_elo_info_json(request): 'version': 1, 'elos': elos, }] + +def player_captimes_data(request): + player_id = int(request.matchdict['id']) + if player_id <= 2: + player_id = -1; + + #player_captimes = DBSession.query(PlayerCaptime).\ + # filter(PlayerCaptime.player_id==player_id).\ + # order_by(PlayerCaptime.fastest_cap).\ + # all() + + 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)