def __init__(self, row): self.fastest_cap = row.fastest_cap self.create_dt = row.create_dt self.create_dt_epoch = timegm(row.create_dt.timetuple()) self.create_dt_fuzzy = pretty_date(row.create_dt) self.player_id = row.player_id self.player_nick = row.player_nick self.player_nick_stripped = strip_colors(row.player_nick) self.player_nick_html = html_colors(row.player_nick) self.game_id = row.game_id self.server_id = row.server_id self.server_name = row.server_name
def get_or_create_player(session=None, hashkey=None, nick=None): """ Finds a player by hashkey or creates a new one (along with a corresponding hashkey entry. Parameters: session - SQLAlchemy database session factory hashkey - hashkey of the player to be found or created nick - nick of the player (in case of a first time create) """ # if we have a bot if re.search('^bot#\d+$', hashkey) or re.search('^bot#\d+#', hashkey): player = session.query(Player).filter_by(player_id=1).one() # if we have an untracked player elif re.search('^player#\d+$', hashkey): player = session.query(Player).filter_by(player_id=2).one() # else it is a tracked player else: # see if the player is already in the database # if not, create one and the hashkey along with it try: hk = session.query(Hashkey).filter_by( hashkey=hashkey).one() player = session.query(Player).filter_by( player_id=hk.player_id).one() log.debug("Found existing player {0} with hashkey {1}".format( player.player_id, hashkey)) except: player = Player() player.create_dt = datetime.datetime.utcnow() session.add(player) session.flush() # if nick is given to us, use it. If not, use "Anonymous Player" # with a suffix added for uniqueness. if nick: player.nick = nick[:128] player.stripped_nick = strip_colors(qfont_decode(nick[:128])) else: player.nick = "Anonymous Player #{0}".format(player.player_id) player.stripped_nick = player.nick hk = Hashkey(player_id=player.player_id, hashkey=hashkey) session.add(hk) log.debug("Created player {0} ({2}) with hashkey {1}".format( player.player_id, hashkey, player.nick.encode('utf-8'))) return player
def get_or_create_player(session, hashkey, nick, untrackedPlayerDict): """ Finds a player by hashkey or creates a new one (along with a corresponding hashkey entry. Parameters: session - SQLAlchemy database session factory hashkey - hashkey of the player to be found or created nick - nick of the player (in case of a first time create) untrackedPlayerCount - current counter of untracked/anonymous players in the match. returns: (player data record, updated untrackedPlayerCount) """ # the getOrCreatePlayer stored procedure looks at the privacy_match_hist setting and # returns null if a player deleted himself and wants to stay untracked. # For tracked players it may update the nick and aliases, unless the player is anonymous # and it returns the player_id stripped_nick = strip_colors(qfont_decode(nick[:128])) pid = session.execute( expr.func.getOrCreatePlayer(hashkey, nick, stripped_nick)).scalar() defaultNick = "Untracked Player {0}" if pid is not None: player = session.query(Player).filter_by(player_id=pid).one() if player.privacy_match_hist != 3: # allow storing match history log.debug("Found existing player {0} with hashkey {1}".format( player.player_id, hashkey)) return player defaultNick = "Anonymous Player {0}" # map untracked or anonymous player to the next available placeholder Player if hashkey in untrackedPlayerDict: return untrackedPlayerDict[hashkey] player = Player() untrackedPlayerCount = len(untrackedPlayerDict) + 1 player.player_id = -untrackedPlayerCount player.nick = defaultNick.format(untrackedPlayerCount) player.stripped_nick = player.nick untrackedPlayerDict[hashkey] = player log.debug(("Found " + defaultNick + " with steam-id {1}").format( untrackedPlayerCount, hashkey)) return player
def register_new_nick(session, player, new_nick): """ Change the player record's nick to the newly found nick. Store the old nick in the player_nicks table for that player. session - SQLAlchemy database session factory player - player record whose nick is changing new_nick - the new nickname """ # see if that nick already exists stripped_nick = strip_colors(player.nick) try: player_nick = session.query(PlayerNick).filter_by( player_id=player.player_id, stripped_nick=stripped_nick).one() except NoResultFound, e: # player_id/stripped_nick not found, create one # but we don't store "Anonymous Player #N" if not re.search('^Anonymous Player #\d+$', player.nick): player_nick = PlayerNick() player_nick.player_id = player.player_id player_nick.stripped_nick = player.stripped_nick player_nick.nick = player.nick session.add(player_nick)
def register_new_nick(session, player, new_nick): """ Change the player record's nick to the newly found nick. Store the old nick in the player_nicks table for that player. session - SQLAlchemy database session factory player - player record whose nick is changing new_nick - the new nickname """ # see if that nick already exists stripped_nick = strip_colors(qfont_decode(player.nick)) try: player_nick = session.query(PlayerNick).filter_by( player_id=player.player_id, stripped_nick=stripped_nick).one() except NoResultFound, e: # player_id/stripped_nick not found, create one # but we don't store "Anonymous Player #N" if not re.search('^Anonymous Player #\d+$', player.nick): player_nick = PlayerNick() player_nick.player_id = player.player_id player_nick.stripped_nick = stripped_nick player_nick.nick = player.nick session.add(player_nick)
def create_player(session, events): """ Creates a new player from the list of events. :param session: SQLAlchemy session :param events: Dict of player events from the submission :return: Player """ player = Player() session.add(player) session.flush() nick = events.get('n', None) if nick: player.nick = nick[:128] player.stripped_nick = strip_colors(qfont_decode(player.nick)) else: player.nick = "Anonymous Player #{0}".format(player.player_id) player.stripped_nick = player.nick hk = Hashkey(player_id=player.player_id, hashkey=events.get('P', None)) session.add(hk) return player
def nick_stripped(self): if self.nick is None: return "Anonymous Player" else: return strip_colors(self.nick)
try: player_nick = session.query(PlayerNick).filter_by( player_id=player.player_id, stripped_nick=stripped_nick).one() except NoResultFound, e: # player_id/stripped_nick not found, create one # but we don't store "Anonymous Player #N" if not re.search('^Anonymous Player #\d+$', player.nick): player_nick = PlayerNick() player_nick.player_id = player.player_id player_nick.stripped_nick = player.stripped_nick player_nick.nick = player.nick session.add(player_nick) # We change to the new nick regardless player.nick = new_nick player.stripped_nick = strip_colors(new_nick) session.add(player) def get_or_create_server(session=None, name=None, hashkey=None): """ Find a server by name or create one if not found. Parameters: session - SQLAlchemy database session factory name - server name of the server to be found or created hashkey - server hashkey """ try: # find one by that name, if it exists server = session.query(Server).filter_by(name=name).one()
def create_game_stat(session, game_meta, game, server, gmap, player, events): """Game stats handler for all game types""" game_type_cd = game.game_type_cd pgstat = create_default_game_stat(session, game_type_cd) # these fields should be on every pgstat record pgstat.game_id = game.game_id pgstat.player_id = player.player_id pgstat.nick = events.get('n', 'Anonymous Player')[:128] pgstat.stripped_nick = strip_colors(qfont_decode(pgstat.nick)) pgstat.score = int(round(float(events.get('scoreboard-score', 0)))) pgstat.alivetime = datetime.timedelta(seconds=int(round(float(events.get('alivetime', 0.0))))) pgstat.rank = int(events.get('rank', None)) pgstat.scoreboardpos = int(events.get('scoreboardpos', pgstat.rank)) if pgstat.nick != player.nick \ and player.player_id > 2 \ and pgstat.nick != 'Anonymous Player': register_new_nick(session, player, pgstat.nick) wins = False # gametype-specific stuff is handled here. if passed to us, we store it for (key,value) in events.items(): if key == 'wins': wins = True if key == 't': pgstat.team = int(value) if key == 'scoreboard-drops': pgstat.drops = int(value) if key == 'scoreboard-returns': pgstat.returns = int(value) if key == 'scoreboard-fckills': pgstat.carrier_frags = int(value) if key == 'scoreboard-pickups': pgstat.pickups = int(value) if key == 'scoreboard-caps': pgstat.captures = int(value) if key == 'scoreboard-score': pgstat.score = int(round(float(value))) if key == 'scoreboard-deaths': pgstat.deaths = int(value) if key == 'scoreboard-kills': pgstat.kills = int(value) if key == 'scoreboard-suicides': pgstat.suicides = int(value) if key == 'scoreboard-objectives': pgstat.collects = int(value) if key == 'scoreboard-captured': pgstat.captures = int(value) if key == 'scoreboard-released': pgstat.drops = int(value) if key == 'scoreboard-fastest': pgstat.fastest = datetime.timedelta(seconds=float(value)/100) if key == 'scoreboard-takes': pgstat.pickups = int(value) if key == 'scoreboard-ticks': pgstat.drops = int(value) if key == 'scoreboard-revivals': pgstat.revivals = int(value) if key == 'scoreboard-bctime': pgstat.time = datetime.timedelta(seconds=int(value)) if key == 'scoreboard-bckills': pgstat.carrier_frags = int(value) if key == 'scoreboard-losses': pgstat.drops = int(value) if key == 'scoreboard-pushes': pgstat.pushes = int(value) if key == 'scoreboard-destroyed': pgstat.destroys = int(value) if key == 'scoreboard-kckills': pgstat.carrier_frags = int(value) if key == 'scoreboard-lives': pgstat.lives = int(value) if key == 'scoreboard-goals': pgstat.captures = int(value) if key == 'scoreboard-faults': pgstat.drops = int(value) if key == 'scoreboard-laps': pgstat.laps = int(value) if key == 'avglatency': pgstat.avg_latency = float(value) if key == 'scoreboard-captime': pgstat.fastest = datetime.timedelta(seconds=float(value)/100) if game.game_type_cd == 'ctf': update_fastest_cap(session, player.player_id, game.game_id, gmap.map_id, pgstat.fastest, game.mod) # there is no "winning team" field, so we have to derive it if wins and pgstat.team is not None and game.winner is None: game.winner = pgstat.team session.add(game) session.add(pgstat) return pgstat
try: player_nick = session.query(PlayerNick).filter_by( player_id=player.player_id, stripped_nick=stripped_nick).one() except NoResultFound, e: # player_id/stripped_nick not found, create one # but we don't store "Anonymous Player #N" if not re.search('^Anonymous Player #\d+$', player.nick): player_nick = PlayerNick() player_nick.player_id = player.player_id player_nick.stripped_nick = stripped_nick player_nick.nick = player.nick session.add(player_nick) # We change to the new nick regardless player.nick = new_nick player.stripped_nick = strip_colors(qfont_decode(new_nick)) session.add(player) def update_fastest_cap(session, player_id, game_id, map_id, captime, mod): """ Check the fastest cap time for the player and map. If there isn't one, insert one. If there is, check if the passed time is faster. If so, update! """ # we don't record fastest cap times for bots or anonymous players if player_id <= 2: return # see if a cap entry exists already # then check to see if the new captime is faster
def create_game_stat(session, game_meta, game, server, gmap, player, events): """Game stats handler for all game types""" game_type_cd = game.game_type_cd pgstat = create_default_game_stat(session, game_type_cd) # these fields should be on every pgstat record pgstat.game_id = game.game_id pgstat.create_dt = datetime.datetime.utcnow() pgstat.player_id = player.player_id pgstat.nick = events.get( 'n', 'Anonymous Player')[:128] if player.player_id >= 0 else player.nick pgstat.stripped_nick = strip_colors(qfont_decode( pgstat.nick)) if player.player_id >= 0 else player.stripped_nick pgstat.score = int(round(float(events.get('scoreboard-score', 0)))) pgstat.alivetime = datetime.timedelta( seconds=int(round(float(events.get('alivetime', 0.0))))) pgstat.rank = int(events.get('rank', None)) pgstat.scoreboardpos = int(events.get('scoreboardpos', pgstat.rank)) if pgstat.nick != player.nick \ and player.player_id > 2 \ and pgstat.nick != 'Anonymous Player': register_new_nick(session, player, pgstat.nick) wins = False # gametype-specific stuff is handled here. if passed to us, we store it for (key, value) in events.items(): if key == 'wins': wins = True if key == 't': pgstat.team = int(value) if key == 'scoreboard-drops': pgstat.drops = int(value) if key == 'scoreboard-returns': pgstat.returns = int(value) if key == 'scoreboard-fckills': pgstat.carrier_frags = int(value) if key == 'scoreboard-pickups': pgstat.pickups = int(value) if key == 'scoreboard-caps': pgstat.captures = int(value) if key == 'scoreboard-score': pgstat.score = int(round(float(value))) if key == 'scoreboard-deaths': pgstat.deaths = int(value) if key == 'scoreboard-kills': pgstat.kills = int(value) if key == 'scoreboard-suicides': pgstat.suicides = int(value) if key == 'scoreboard-objectives': pgstat.collects = int(value) if key == 'scoreboard-captured': pgstat.captures = int(value) if key == 'scoreboard-released': pgstat.drops = int(value) if key == 'scoreboard-fastest': pgstat.fastest = datetime.timedelta(seconds=float(value) / 100) if key == 'scoreboard-takes': pgstat.pickups = int(value) if key == 'scoreboard-ticks': pgstat.drops = int(value) if key == 'scoreboard-revivals': pgstat.revivals = int(value) if key == 'scoreboard-bctime': pgstat.time = datetime.timedelta(seconds=int(value)) if key == 'scoreboard-bckills': pgstat.carrier_frags = int(value) if key == 'scoreboard-losses': pgstat.drops = int(value) if key == 'scoreboard-pushes': pgstat.pushes = int(value) if key == 'scoreboard-destroyed': pgstat.destroys = int(value) if key == 'scoreboard-kckills': pgstat.carrier_frags = int(value) if key == 'scoreboard-lives': pgstat.lives = int(value) if key == 'scoreboard-goals': pgstat.captures = int(value) if key == 'scoreboard-faults': pgstat.drops = int(value) if key == 'scoreboard-laps': pgstat.laps = int(value) if key == 'avglatency': pgstat.avg_latency = float(value) if key == 'scoreboard-captime': pgstat.fastest = datetime.timedelta(seconds=float(value) / 100) if game.game_type_cd == 'ctf': update_fastest_cap(session, player.player_id, game.game_id, gmap.map_id, pgstat.fastest) # there is no "winning team" field, so we have to derive it if wins and pgstat.team is not None and game.winner is None: game.winner = pgstat.team session.add(game) session.add(pgstat) return pgstat
def create_player_game_stat(session=None, player=None, game=None, player_events=None): """ Creates game statistics for a given player in a given game. Parameters: session - SQLAlchemy session factory player - Player record of the player who owns the stats game - Game record for the game to which the stats pertain player_events - dictionary for the actual stats that need to be transformed """ # in here setup default values (e.g. if game type is CTF then # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc # TODO: use game's create date here instead of now() seq = Sequence('player_game_stats_player_game_stat_id_seq') pgstat_id = session.execute(seq) pgstat = PlayerGameStat(player_game_stat_id=pgstat_id, create_dt=datetime.datetime.utcnow()) # set player id from player record pgstat.player_id = player.player_id #set game id from game record pgstat.game_id = game.game_id # all games have a score and every player has an alivetime pgstat.score = 0 pgstat.alivetime = datetime.timedelta(seconds=0) if game.game_type_cd == 'dm' or game.game_type_cd == 'tdm' or game.game_type_cd == 'duel': pgstat.kills = 0 pgstat.deaths = 0 pgstat.suicides = 0 elif game.game_type_cd == 'ctf': pgstat.kills = 0 pgstat.captures = 0 pgstat.pickups = 0 pgstat.drops = 0 pgstat.returns = 0 pgstat.carrier_frags = 0 for (key,value) in player_events.items(): if key == 'n': pgstat.nick = value[:128] pgstat.stripped_nick = strip_colors(qfont_decode(pgstat.nick)) if key == 't': pgstat.team = int(value) if key == 'rank': pgstat.rank = int(value) if key == 'alivetime': pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value)))) if key == 'scoreboard-drops': pgstat.drops = int(value) if key == 'scoreboard-returns': pgstat.returns = int(value) if key == 'scoreboard-fckills': pgstat.carrier_frags = int(value) if key == 'scoreboard-pickups': pgstat.pickups = int(value) if key == 'scoreboard-caps': pgstat.captures = int(value) if key == 'scoreboard-score': pgstat.score = int(value) if key == 'scoreboard-deaths': pgstat.deaths = int(value) if key == 'scoreboard-kills': pgstat.kills = int(value) if key == 'scoreboard-suicides': pgstat.suicides = int(value) # check to see if we had a name, and if # not use an anonymous handle if pgstat.nick == None: pgstat.nick = "Anonymous Player" pgstat.stripped_nick = "Anonymous Player" # otherwise process a nick change elif pgstat.nick != player.nick and player.player_id > 2: register_new_nick(session, player, pgstat.nick) # if the player is ranked #1 and it is a team game, set the game's winner # to be the team of that player # FIXME: this is a hack, should be using the 'W' field (not present) if pgstat.rank == 1 and pgstat.team: game.winner = pgstat.team session.add(game) session.add(pgstat) return pgstat
try: player_nick = session.query(PlayerNick).filter_by( player_id=player.player_id, stripped_nick=stripped_nick).one() except NoResultFound, e: # player_id/stripped_nick not found, create one # but we don't store "Anonymous Player #N" if not re.search('^Anonymous Player #\d+$', player.nick): player_nick = PlayerNick() player_nick.player_id = player.player_id player_nick.stripped_nick = stripped_nick player_nick.nick = player.nick session.add(player_nick) # We change to the new nick regardless player.nick = new_nick player.stripped_nick = strip_colors(qfont_decode(new_nick)) session.add(player) def update_fastest_cap(session, player_id, game_id, map_id, captime): """ Check the fastest cap time for the player and map. If there isn't one, insert one. If there is, check if the passed time is faster. If so, update! """ # we don't record fastest cap times for bots or anonymous players if player_id <= 2: return # see if a cap entry exists already # then check to see if the new captime is faster
def render_image(self, data, output_filename): """Render an image for the given player id.""" # setup variables 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 = [] for gt in data['games_played'].keys(): if gt == 'overall': continue if elos.has_key(gt): game_types.append(gt) # only uses gametypes with elo values (needed later on) ## make sure gametypes list if sorted correctly (number of games, descending) ##game_types = sorted(game_types, key=lambda x: data['games_played'][x].games, reverse=True) # make sure gametypes list if sorted correctly (total playing time per game type, descending) game_types = sorted(game_types, key=lambda x: data['overall_stats'][x].total_playing_time, reverse=True) # build image surf = C.ImageSurface(C.FORMAT_ARGB32, self.width, self.height) ctx = C.Context(surf) self.ctx = ctx ctx.set_antialias(C.ANTIALIAS_GRAY) # set font hinting options fo = C.FontOptions() fo.set_antialias(C.ANTIALIAS_GRAY) fo.set_hint_style(C.HINT_STYLE_FULL) fo.set_hint_metrics(C.HINT_METRICS_ON) ctx.set_font_options(fo) # draw background if self.bg == None: if self.bgcolor != None: # plain fillcolor, full transparency possible with (1,1,1,0) ctx.save() ctx.set_operator(C.OPERATOR_SOURCE) ctx.rectangle(0, 0, self.width, self.height) ctx.set_source_rgba(self.bgcolor[0], self.bgcolor[1], self.bgcolor[2], self.bgcolor[3]) ctx.fill() ctx.restore() else: 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() bg_xoff = 0 while bg_xoff < self.width: bg_yoff = 0 while bg_yoff < self.height: ctx.set_source_surface(bg, bg_xoff, bg_yoff) #ctx.mask_surface(bg) ctx.paint() bg_yoff += bg_h bg_xoff += bg_w except: #print "Error: Can't load background texture: %s" % self.bg pass # draw overlay graphic if self.overlay != None: try: overlay = C.ImageSurface.create_from_png("img/%s.png" % self.overlay) ctx.set_source_surface(overlay, 0, 0) #ctx.mask_surface(overlay) ctx.paint() except: #print "Error: Can't load overlay texture: %s" % self.overlay pass ## draw player's nickname with fancy colors # deocde nick, strip all weird-looking characters qstr = qfont_decode(qstr=player.nick, glyph_translation=True).\ 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) 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 while shrinknick < 0.6 * self.nick_fontsize: ctx.set_font_size(self.nick_fontsize - shrinknick) xoff, yoff, tw, th = ctx.text_extents(stripped_nick)[:4] if tw > self.nick_maxwidth: shrinknick += 1 continue break # determine width of single whitespace for later use 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.2*space_w if sep_w <= 0: sep_w = 1 # split nick into colored segments xoffset = 0 _all_colors = re.compile(r'(\^\d|\^x[\dA-Fa-f]{3})') parts = _all_colors.split(qstr) while len(parts) > 0: tag = None txt = parts[0] if _all_colors.match(txt): tag = txt[1:] # strip leading '^' if len(parts) < 2: break 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 g = int(tag[2] * 2, 16) / 255.0 b = int(tag[3] * 2, 16) / 255.0 hue, light, satur = rgb_to_hls(r, g, b) if light < _contrast_threshold: light = _contrast_threshold r, g, b = hls_to_rgb(hue, light, satur) else: r,g,b = _dec_colors[int(tag[0])] else: r,g,b = _dec_colors[7] 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.encode("utf-8")) tw += (len(txt)-len(txt.strip())) * space_w # account for lost whitespaces xoffset += int(tw + sep_w) ## print elos and ranks xoffset, yoffset = 0, 0 count = 0 for gt in game_types[:self.num_gametypes]: if not elos.has_key(gt): continue count += 1 # re-align segments if less than max. gametypes are shown if count > 0: if count < self.num_gametypes: diff = self.num_gametypes - count if diff % 2 == 0: xoffset += (diff-1) * self.gametype_width yoffset += (diff-1) * self.gametype_height 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 game_types[:self.num_gametypes]: if not elos.has_key(gt): # should not happen continue offset = (xoffset, yoffset) if self.gametype_pos: if self.gametype_upper: txt = self.gametype_text % gt.upper() else: txt = self.gametype_text % gt.lower() self.set_font(self.gametype_fontsize, self.gametype_color, bold=True) self.show_text(txt, self.gametype_pos, self.gametype_align, offset=offset) if self.elo_pos: txt = self.elo_text % round(elos[gt], 0) self.set_font(self.elo_fontsize, self.elo_color) self.show_text(txt, self.elo_pos, self.elo_align, offset=offset) if self.rank_pos: if ranks.has_key(gt): txt = self.rank_text % ranks[gt] else: txt = "(preliminary)" self.set_font(self.rank_fontsize, self.rank_color) self.show_text(txt, self.rank_pos, self.rank_align, offset=offset) xoffset += self.gametype_width yoffset += self.gametype_height else: if self.nostats_pos: xoffset += (self.num_gametypes-2) * self.gametype_width yoffset += (self.num_gametypes-2) * self.gametype_height offset = (xoffset, yoffset) txt = self.nostats_text self.set_font(self.nostats_fontsize, self.nostats_color, bold=True) self.show_text(txt, self.nostats_pos, self.nostats_align, angle=self.nostats_angle, offset=offset) # print win percentage if self.wintext_pos: txt = self.wintext_text self.set_font(self.wintext_fontsize, self.wintext_color) self.show_text(txt, self.wintext_pos, self.wintext_align) txt = "???" try: txt = "%.2f%%" % round(win_pct, 2) except: win_pct = 0. if self.winp_pos: if win_pct >= 50.0: nr = 2*(win_pct/100-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*(win_pct/100) 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] self.set_font(self.winp_fontsize, (r,g,b), bold=True) self.show_text(txt, self.winp_pos, self.winp_align) if self.wins_pos: txt = "%d win" % wins if wins != 1: txt += "s" self.set_font(self.wins_fontsize, self.wins_color) self.show_text(txt, self.wins_pos, self.wins_align) if self.loss_pos: txt = "%d loss" % losses if losses != 1: txt += "es" self.set_font(self.loss_fontsize, self.loss_color) self.show_text(txt, self.loss_pos, self.loss_align) # print kill/death ratio if self.kdtext_pos: 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: txt = "%.3f" % round(kd_ratio, 3) except: kd_ratio = 0 if self.kdr_pos: 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 = 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] self.set_font(self.kdr_fontsize, (r,g,b), bold=True) self.show_text(txt, self.kdr_pos, self.kdr_align) if self.kills_pos: txt = "%d kill" % kills if kills != 1: txt += "s" self.set_font(self.kills_fontsize, self.kills_color) self.show_text(txt, self.kills_pos, self.kills_align) if self.deaths_pos: txt = "" if deaths is not None: txt = "%d death" % deaths if deaths != 1: txt += "s" self.set_font(self.deaths_fontsize, self.deaths_color) self.show_text(txt, self.deaths_pos, self.deaths_align) # print playing time if self.ptime_pos: txt = self.ptime_text % str(alivetime) self.set_font(self.ptime_fontsize, self.ptime_color) self.show_text(txt, self.ptime_pos, self.ptime_align) # save to PNG #surf.write_to_png(output_filename) surf.flush() imgdata = surf.get_data() write_png(output_filename, imgdata, self.width, self.height)
def render_image(self, data, output_filename): """Render an image for the given player id.""" # 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'] # build image surf = C.ImageSurface(C.FORMAT_ARGB32, self.width, self.height) ctx = C.Context(surf) self.ctx = ctx ctx.set_antialias(C.ANTIALIAS_GRAY) # draw background if self.bg == None: if self.bgcolor != None: # plain fillcolor, full transparency possible with (1,1,1,0) ctx.save() ctx.set_operator(C.OPERATOR_SOURCE) ctx.rectangle(0, 0, self.width, self.height) ctx.set_source_rgba(self.bgcolor[0], self.bgcolor[1], self.bgcolor[2], self.bgcolor[3]) ctx.fill() ctx.restore() else: 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() bg_xoff = 0 while bg_xoff < self.width: bg_yoff = 0 while bg_yoff < self.height: ctx.set_source_surface(bg, bg_xoff, bg_yoff) #ctx.mask_surface(bg) ctx.paint() bg_yoff += bg_h bg_xoff += bg_w except: #print "Error: Can't load background texture: %s" % self.bg pass # draw overlay graphic if self.overlay != None: try: overlay = C.ImageSurface.create_from_png("img/%s.png" % self.overlay) ctx.set_source_surface(overlay, 0, 0) #ctx.mask_surface(overlay) ctx.paint() except: #print "Error: Can't load overlay texture: %s" % self.overlay pass ## 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) 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 while shrinknick < 0.6 * self.nick_fontsize: ctx.set_font_size(self.nick_fontsize - shrinknick) xoff, yoff, tw, th = ctx.text_extents(stripped_nick)[:4] if tw > self.nick_maxwidth: shrinknick += 1 continue break # determine width of single whitespace for later use xoff, yoff, tw, th = ctx.text_extents("_")[:4] space_w = tw # split nick into colored segments xoffset = 0 _all_colors = re.compile(r'(\^\d|\^x[\dA-Fa-f]{3})') parts = _all_colors.split(qstr) while len(parts) > 0: tag = None txt = parts[0] if _all_colors.match(txt): tag = txt[1:] # strip leading '^' if len(parts) < 2: break 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 g = int(tag[2] * 2, 16) / 255.0 b = int(tag[3] * 2, 16) / 255.0 hue, light, satur = rgb_to_hls(r, g, b) if light < _contrast_threshold: light = _contrast_threshold r, g, b = hls_to_rgb(hue, light, satur) else: r,g,b = _dec_colors[int(tag[0])] else: r,g,b = _dec_colors[7] 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) tw += (len(txt)-len(txt.strip())) * space_w # account for lost whitespaces xoffset += tw + 2 ## print elos and ranks xoffset, yoffset = 0, 0 count = 0 for gt in data.total_stats['gametypes'][: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: diff = self.num_gametypes - count if diff % 2 == 0: xoffset += (diff-1) * self.gametype_width yoffset += (diff-1) * self.gametype_height 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]: if not elos.has_key(gt) or not ranks.has_key(gt): continue offset = (xoffset, yoffset) if self.gametype_pos: if self.gametype_upper: txt = self.gametype_text % gt.upper() else: txt = self.gametype_text % gt.lower() self.set_font(self.gametype_fontsize, self.gametype_color, bold=True) self.show_text(txt, self.gametype_pos, self.gametype_align, offset=offset) if self.elo_pos: txt = self.elo_text % round(elos[gt], 0) self.set_font(self.elo_fontsize, self.elo_color) self.show_text(txt, self.elo_pos, self.elo_align, offset=offset) if self.rank_pos: txt = self.rank_text % ranks[gt] self.set_font(self.rank_fontsize, self.rank_color) self.show_text(txt, self.rank_pos, self.rank_align, offset=offset) xoffset += self.gametype_width yoffset += self.gametype_height else: if self.nostats_pos: xoffset += (self.num_gametypes-2) * self.gametype_width yoffset += (self.num_gametypes-2) * self.gametype_height offset = (xoffset, yoffset) txt = self.nostats_text self.set_font(self.nostats_fontsize, self.nostats_color, bold=True) self.show_text(txt, self.nostats_pos, self.nostats_align, angle=self.nostats_angle, offset=offset) # print win percentage if self.wintext_pos: txt = self.wintext_text self.set_font(self.wintext_fontsize, self.wintext_color) self.show_text(txt, self.wintext_pos, self.wintext_align) txt = "???" try: ratio = float(wins)/games txt = "%.2f%%" % round(ratio * 100, 2) except: ratio = 0 if self.winp_pos: if ratio >= 0.5: nr = 2*(ratio-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 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] self.set_font(self.winp_fontsize, (r,g,b), bold=True) self.show_text(txt, self.winp_pos, self.winp_align) if self.wins_pos: txt = "%d win" % wins if wins != 1: txt += "s" self.set_font(self.wins_fontsize, self.wins_color) self.show_text(txt, self.wins_pos, self.wins_align) if self.loss_pos: txt = "%d loss" % losses if losses != 1: txt += "es" self.set_font(self.loss_fontsize, self.loss_color) self.show_text(txt, self.loss_pos, self.loss_align) # print kill/death ratio if self.kdtext_pos: 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) except: ratio = 0 if self.kdr_pos: if ratio >= 1.0: nr = 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 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] self.set_font(self.kdr_fontsize, (r,g,b), bold=True) self.show_text(txt, self.kdr_pos, self.kdr_align) if self.kills_pos: txt = "%d kill" % kills if kills != 1: txt += "s" self.set_font(self.kills_fontsize, self.kills_color) self.show_text(txt, self.kills_pos, self.kills_align) if self.deaths_pos: txt = "" if deaths is not None: txt = "%d death" % deaths if deaths != 1: txt += "s" self.set_font(self.deaths_fontsize, self.deaths_color) self.show_text(txt, self.deaths_pos, self.deaths_align) # print playing time if self.ptime_pos: txt = self.ptime_text % str(alivetime) self.set_font(self.ptime_fontsize, self.ptime_color) self.show_text(txt, self.ptime_pos, self.ptime_align) # save to PNG #surf.write_to_png(output_filename) surf.flush() imgdata = surf.get_data() write_png(output_filename, imgdata, self.width, self.height)
def nick_strip_colors(self): if self.nick is None: return "Anonymous Player" else: return strip_colors(self.nick)
def render_image(self, data, output_filename): """Render an image for the given player id.""" # setup variables 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 = [] for gt in data['games_played'].keys(): if gt == 'overall': continue if elos.has_key(gt): game_types.append( gt ) # only uses gametypes with elo values (needed later on) ## make sure gametypes list if sorted correctly (number of games, descending) ##game_types = sorted(game_types, key=lambda x: data['games_played'][x].games, reverse=True) # make sure gametypes list if sorted correctly (total playing time per game type, descending) game_types = sorted( game_types, key=lambda x: data['overall_stats'][x].total_playing_time, reverse=True) # build image surf = C.ImageSurface(C.FORMAT_ARGB32, self.width, self.height) ctx = C.Context(surf) self.ctx = ctx ctx.set_antialias(C.ANTIALIAS_GRAY) # draw background if self.bg == None: if self.bgcolor != None: # plain fillcolor, full transparency possible with (1,1,1,0) ctx.save() ctx.set_operator(C.OPERATOR_SOURCE) ctx.rectangle(0, 0, self.width, self.height) ctx.set_source_rgba(self.bgcolor[0], self.bgcolor[1], self.bgcolor[2], self.bgcolor[3]) ctx.fill() ctx.restore() else: 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() bg_xoff = 0 while bg_xoff < self.width: bg_yoff = 0 while bg_yoff < self.height: ctx.set_source_surface(bg, bg_xoff, bg_yoff) #ctx.mask_surface(bg) ctx.paint() bg_yoff += bg_h bg_xoff += bg_w except: #print "Error: Can't load background texture: %s" % self.bg pass # draw overlay graphic if self.overlay != None: try: overlay = C.ImageSurface.create_from_png("img/%s.png" % self.overlay) ctx.set_source_surface(overlay, 0, 0) #ctx.mask_surface(overlay) ctx.paint() except: #print "Error: Can't load overlay texture: %s" % self.overlay pass ## draw player's nickname with fancy colors # deocde nick, strip all weird-looking characters qstr = qfont_decode(qstr=player.nick, glyph_translation=True).\ 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) 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 while shrinknick < 0.6 * self.nick_fontsize: ctx.set_font_size(self.nick_fontsize - shrinknick) xoff, yoff, tw, th = ctx.text_extents(stripped_nick)[:4] if tw > self.nick_maxwidth: shrinknick += 1 continue break # determine width of single whitespace for later use 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.2 * space_w if sep_w <= 0: sep_w = 1 # split nick into colored segments xoffset = 0 _all_colors = re.compile(r'(\^\d|\^x[\dA-Fa-f]{3})') parts = _all_colors.split(qstr) while len(parts) > 0: tag = None txt = parts[0] if _all_colors.match(txt): tag = txt[1:] # strip leading '^' if len(parts) < 2: break 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 g = int(tag[2] * 2, 16) / 255.0 b = int(tag[3] * 2, 16) / 255.0 hue, light, satur = rgb_to_hls(r, g, b) if light < _contrast_threshold: light = _contrast_threshold r, g, b = hls_to_rgb(hue, light, satur) else: r, g, b = _dec_colors[int(tag[0])] else: r, g, b = _dec_colors[7] 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.encode("utf-8")) tw += (len(txt) - len(txt.strip())) * space_w # account for lost whitespaces xoffset += int(tw + sep_w) ## print elos and ranks xoffset, yoffset = 0, 0 count = 0 for gt in game_types[:self.num_gametypes]: if not elos.has_key(gt): continue count += 1 # re-align segments if less than max. gametypes are shown if count > 0: if count < self.num_gametypes: diff = self.num_gametypes - count if diff % 2 == 0: xoffset += (diff - 1) * self.gametype_width yoffset += (diff - 1) * self.gametype_height 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 game_types[:self.num_gametypes]: if not elos.has_key(gt): # should not happen continue offset = (xoffset, yoffset) if self.gametype_pos: if self.gametype_upper: txt = self.gametype_text % gt.upper() else: txt = self.gametype_text % gt.lower() self.set_font(self.gametype_fontsize, self.gametype_color, bold=True) self.show_text(txt, self.gametype_pos, self.gametype_align, offset=offset) if self.elo_pos: txt = self.elo_text % round(elos[gt], 0) self.set_font(self.elo_fontsize, self.elo_color) self.show_text(txt, self.elo_pos, self.elo_align, offset=offset) if self.rank_pos: if ranks.has_key(gt): txt = self.rank_text % ranks[gt] else: txt = "(preliminary)" self.set_font(self.rank_fontsize, self.rank_color) self.show_text(txt, self.rank_pos, self.rank_align, offset=offset) xoffset += self.gametype_width yoffset += self.gametype_height else: if self.nostats_pos: xoffset += (self.num_gametypes - 2) * self.gametype_width yoffset += (self.num_gametypes - 2) * self.gametype_height offset = (xoffset, yoffset) txt = self.nostats_text self.set_font(self.nostats_fontsize, self.nostats_color, bold=True) self.show_text(txt, self.nostats_pos, self.nostats_align, angle=self.nostats_angle, offset=offset) # print win percentage if self.wintext_pos: txt = self.wintext_text self.set_font(self.wintext_fontsize, self.wintext_color) self.show_text(txt, self.wintext_pos, self.wintext_align) txt = "???" try: txt = "%.2f%%" % round(win_pct, 2) except: win_pct = 0. if self.winp_pos: if win_pct >= 50.0: nr = 2 * (win_pct / 100 - 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 * (win_pct / 100) 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] self.set_font(self.winp_fontsize, (r, g, b), bold=True) self.show_text(txt, self.winp_pos, self.winp_align) if self.wins_pos: txt = "%d win" % wins if wins != 1: txt += "s" self.set_font(self.wins_fontsize, self.wins_color) self.show_text(txt, self.wins_pos, self.wins_align) if self.loss_pos: txt = "%d loss" % losses if losses != 1: txt += "es" self.set_font(self.loss_fontsize, self.loss_color) self.show_text(txt, self.loss_pos, self.loss_align) # print kill/death ratio if self.kdtext_pos: 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: txt = "%.3f" % round(kd_ratio, 3) except: kd_ratio = 0 if self.kdr_pos: 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 = 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] self.set_font(self.kdr_fontsize, (r, g, b), bold=True) self.show_text(txt, self.kdr_pos, self.kdr_align) if self.kills_pos: txt = "%d kill" % kills if kills != 1: txt += "s" self.set_font(self.kills_fontsize, self.kills_color) self.show_text(txt, self.kills_pos, self.kills_align) if self.deaths_pos: txt = "" if deaths is not None: txt = "%d death" % deaths if deaths != 1: txt += "s" self.set_font(self.deaths_fontsize, self.deaths_color) self.show_text(txt, self.deaths_pos, self.deaths_align) # print playing time if self.ptime_pos: txt = self.ptime_text % str(alivetime) self.set_font(self.ptime_fontsize, self.ptime_color) self.show_text(txt, self.ptime_pos, self.ptime_align) # save to PNG #surf.write_to_png(output_filename) surf.flush() imgdata = surf.get_data() write_png(output_filename, imgdata, self.width, self.height)
def nick_stripped(self): return strip_colors(self.nick)