class MapStats(db.Model): id = db.Column(db.Integer, primary_key=True) match_id = db.Column(db.Integer, db.ForeignKey('match.id')) map_number = db.Column(db.Integer) map_name = db.Column(db.String(64)) start_time = db.Column(db.DateTime) end_time = db.Column(db.DateTime) winner = db.Column(db.Integer, db.ForeignKey('team.id')) team1_score = db.Column(db.Integer, default=0) team2_score = db.Column(db.Integer, default=0) player_stats = db.relationship('PlayerStats', backref='mapstats', lazy='dynamic') @staticmethod def get_or_create(match_id, map_number, map_name=''): match = Match.query.get(match_id) if match is None or map_number >= match.max_maps: return None rv = MapStats.query.filter_by(match_id=match_id, map_number=map_number).first() if rv is None: rv = MapStats() rv.match_id = match_id rv.map_number = map_number rv.map_name = map_name rv.start_time = datetime.datetime.utcnow() rv.team1_score = 0 rv.team2_score = 0 db.session.add(rv) return rv def __repr__(self): return 'MapStats(' + str(self.id) + ',' + str(self.map_name) + ')'
class GameServer(db.Model): id = db.Column(db.Integer, primary_key=True) display_name = db.Column(db.String(32), default='') ip_string = db.Column(db.String(32)) port = db.Column(db.Integer) rcon_password = db.Column(db.String(32)) in_use = db.Column(db.Boolean, default=False) public_server = db.Column(db.Boolean, default=False, index=True) @staticmethod def create(display_name, ip_string, port, rcon_password, public_server): rv = GameServer() rv.display_name = display_name rv.ip_string = ip_string rv.port = port rv.rcon_password = rcon_password rv.public_server = public_server db.session.add(rv) return rv def send_rcon_command(self, command, raise_errors=False, num_retries=3, timeout=3.0): return util.send_rcon_command(self.ip_string, self.port, self.rcon_password, command, raise_errors, num_retries, timeout) def get_hostport(self): return '{}:{}'.format(self.ip_string, self.port) def get_display(self): if self.display_name: return '{} ({})'.format(self.display_name, self.get_hostport()) else: return self.get_hostport() def __repr__(self): return 'GameServer({})'.format(self.get_hostport())
class Team(db.Model): MAXPLAYERS = 7 id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(40)) tag = db.Column(db.String(40), default='') flag = db.Column(db.String(4), default='') logo = db.Column(db.String(10), default='') auths = db.Column(db.PickleType) public_team = db.Column(db.Boolean, index=True) @staticmethod def create(name, tag, flag, logo, auths, public_team=True): rv = Team() rv.set_data(name, tag, flag, logo, auths, public_team) db.session.add(rv) return rv def set_data(self, name, tag, flag, logo, auths, public_team): self.name = name self.tag = tag self.flag = flag.lower() if flag else '' self.logo = logo self.auths = auths self.public_team = public_team def get_players(self): results = [] for steam64 in self.auths: if steam64: name = get_steam_name(steam64) if not name: name = '' results.append((steam64, name)) return results def get_recent_matches(self, limit=10): matches = Match.query.order_by(-Match.id).limit(100).from_self() recent_matches = matches.filter( ((Match.team1_id == self.id) | (Match.team2_id == self.id)) & (Match.cancelled == False) & (Match.start_time != None) # noqa: E712 ).order_by(-Match.id).limit(5) if recent_matches is None: return [] else: return recent_matches def get_vs_match_result(self, match_id): other_team = None my_score = 0 other_team_score = 0 match = Match.query.get(match_id) if match.team1_id == self.id: my_score = match.team1_score other_team_score = match.team2_score other_team = Team.query.get(match.team2_id) else: my_score = match.team2_score other_team_score = match.team1_score other_team = Team.query.get(match.team1_id) # for a bo1 replace series score with the map score if match.max_maps == 1: mapstat = match.map_stats.first() if mapstat: if match.team1_id == self.id: my_score = mapstat.team1_score other_team_score = mapstat.team2_score else: my_score = mapstat.team2_score other_team_score = mapstat.team1_score if match.live(): return 'Live, {}:{} vs {}'.format(my_score, other_team_score, other_team.name) if my_score < other_team_score: return 'Lost {}:{} vs {}'.format(my_score, other_team_score, other_team.name) elif my_score > other_team_score: return 'Won {}:{} vs {}'.format(my_score, other_team_score, other_team.name) else: return 'Tied {}:{} vs {}'.format(other_team_score, my_score, other_team.name) def get_flag_html(self, scale=1.0): # flags are expected to be 32x21 width = int(round(32.0 * scale)) height = int(round(21.0 * scale)) html = '<img src="{}" width="{}" height="{}">' output = html.format(countries.get_flag_img_path(self.flag), width, height) return Markup(output) def get_logo_html(self, scale=1.0): if logos.has_logo(self.logo): width = int(round(32.0 * scale)) height = int(round(32.0 * scale)) html = ('<img src="{}" width="{}" height="{}">') return Markup( html.format(logos.get_logo_img(self.logo), width, height)) else: return '' def get_url(self): return url_for('team.team', teamid=self.id) def get_name_url_html(self): return Markup('<a href="{}">{}</a>'.format(self.get_url(), self.name)) def get_logo_or_flag_html(self, scale=1.0, other_team=None): if logos.has_logo(self.logo) and (other_team is None or logos.has_logo(other_team.logo)): return self.get_logo_html(scale) else: return self.get_flag_html(scale) def __repr__(self): return 'Team(id={}, name={}, flag={}, logo={}, public={})'.format( self.id, self.name, self.flag, self.logo, self.public_team)
class PlayerStats(db.Model): id = db.Column(db.Integer, primary_key=True) match_id = db.Column(db.Integer, db.ForeignKey('match.id')) map_id = db.Column(db.Integer, db.ForeignKey('map_stats.id')) team_id = db.Column(db.Integer, db.ForeignKey('team.id')) steam_id = db.Column(db.String(40)) name = db.Column(db.String(40)) kills = db.Column(db.Integer, default=0) deaths = db.Column(db.Integer, default=0) roundsplayed = db.Column(db.Integer, default=0) assists = db.Column(db.Integer, default=0) flashbang_assists = db.Column(db.Integer, default=0) teamkills = db.Column(db.Integer, default=0) suicides = db.Column(db.Integer, default=0) headshot_kills = db.Column(db.Integer, default=0) damage = db.Column(db.Integer, default=0) bomb_plants = db.Column(db.Integer, default=0) bomb_defuses = db.Column(db.Integer, default=0) v1 = db.Column(db.Integer, default=0) v2 = db.Column(db.Integer, default=0) v3 = db.Column(db.Integer, default=0) v4 = db.Column(db.Integer, default=0) v5 = db.Column(db.Integer, default=0) k1 = db.Column(db.Integer, default=0) k2 = db.Column(db.Integer, default=0) k3 = db.Column(db.Integer, default=0) k4 = db.Column(db.Integer, default=0) k5 = db.Column(db.Integer, default=0) firstkill_t = db.Column(db.Integer, default=0) firstkill_ct = db.Column(db.Integer, default=0) firstdeath_t = db.Column(db.Integer, default=0) firstdeath_Ct = db.Column(db.Integer, default=0) def get_steam_url(self): return 'http://steamcommunity.com/profiles/{}'.format(self.steam_id) def get_rating(self): AverageKPR = 0.679 AverageSPR = 0.317 AverageRMK = 1.277 KillRating = float(self.kills) / float(self.roundsplayed) / AverageKPR SurvivalRating = float(self.roundsplayed - self.deaths) / self.roundsplayed / AverageSPR killcount = float(self.k1 + 4 * self.k2 + 9 * self.k3 + 16 * self.k4 + 25 * self.k5) RoundsWithMultipleKillsRating = killcount / \ self.roundsplayed / AverageRMK rating = (KillRating + 0.7 * SurvivalRating + RoundsWithMultipleKillsRating) / 2.7 return rating def get_kdr(self): if self.deaths == 0: return float(self.kills) else: return float(self.kills) / self.deaths def get_hsp(self): if self.kills == 0: return 0.0 else: return float(self.headshot_kills) / self.kills def get_adr(self): if self.roundsplayed == 0: return 0.0 else: return float(self.damage) / self.roundsplayed def get_fpr(self): if self.roundsplayed == 0: return 0.0 else: return float(self.kills) / self.roundsplayed @staticmethod def get_or_create(matchid, mapnumber, steam_id): mapstats = MapStats.get_or_create(matchid, mapnumber) if len(mapstats.player_stats.all()) >= 40: # Cap on players per map return None rv = mapstats.player_stats.filter_by(steam_id=steam_id).first() if rv is None: rv = PlayerStats() rv.match_id = matchid rv.map_number = mapstats.id rv.steam_id = steam_id rv.map_id = mapstats.id db.session.add(rv) return rv
class Match(db.Model): id = db.Column(db.Integer, primary_key=True) server_id = db.Column(db.Integer, db.ForeignKey('game_server.id'), index=True) team1_id = db.Column(db.Integer, db.ForeignKey('team.id')) team2_id = db.Column(db.Integer, db.ForeignKey('team.id')) team1_string = db.Column(db.String(32), default='') team2_string = db.Column(db.String(32), default='') winner = db.Column(db.Integer, db.ForeignKey('team.id')) plugin_version = db.Column(db.String(32), default='unknown') forfeit = db.Column(db.Boolean, default=False) cancelled = db.Column(db.Boolean, default=False) start_time = db.Column(db.DateTime) end_time = db.Column(db.DateTime) max_maps = db.Column(db.Integer) title = db.Column(db.String(60), default='') skip_veto = db.Column(db.Boolean) api_key = db.Column(db.String(32)) veto_mappool = db.Column(db.String(500)) map_stats = db.relationship('MapStats', backref='match', lazy='dynamic') team1_score = db.Column(db.Integer, default=0) team2_score = db.Column(db.Integer, default=0) @staticmethod def create(team1_id, team2_id, team1_string, team2_string, max_maps, skip_veto, title, veto_mappool, server_id=None): rv = Match() rv.team1_id = team1_id rv.team2_id = team2_id rv.skip_veto = skip_veto rv.title = title rv.veto_mappool = ' '.join(veto_mappool) rv.server_id = server_id rv.max_maps = max_maps rv.api_key = ''.join( random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(24)) db.session.add(rv) return rv def get_status_string(self, show_winner=True): if self.pending(): return 'Pending' elif self.live(): team1_score, team2_score = self.get_current_score() return 'Live, {}:{}'.format(team1_score, team2_score) elif self.finished(): t1score, t2score = self.get_current_score() min_score = min(t1score, t2score) max_score = max(t1score, t2score) score_string = '{}:{}'.format(max_score, min_score) if not show_winner: return 'Finished' elif self.winner == self.team1_id: return 'Won {} by {}'.format(score_string, self.get_team1().name) elif self.winner == self.team2_id: return 'Won {} by {}'.format(score_string, self.get_team2().name) else: return 'Tied {}'.format(score_string) else: return 'Cancelled' def get_vs_string(self): team1 = self.get_team1() team2 = self.get_team2() scores = self.get_current_score() str = '{} vs {} ({}:{})'.format(team1.get_name_url_html(), team2.get_name_url_html(), scores[0], scores[1]) return Markup(str) def finalized(self): return self.cancelled or self.finished() def pending(self): return self.start_time is None and not self.cancelled def finished(self): return self.end_time is not None and not self.cancelled def live(self): return self.start_time is not None and self.end_time is None and not self.cancelled def get_server(self): return GameServer.query.filter_by(id=self.server_id).first() def get_current_score(self): if self.max_maps == 1: mapstat = self.map_stats.first() if not mapstat: return (0, 0) else: return (mapstat.team1_score, mapstat.team2_score) else: return (self.team1_score, self.team2_score) def send_to_server(self): server = GameServer.query.get(self.server_id) if not server: return False url = url_for('match.match_config', matchid=self.id, _external=True, _scheme='http') # Remove http protocal since the get5 plugin can't parse args with the # : in them. url = url.replace("http://", "") url = url.replace("https://", "") loadmatch_response = server.send_rcon_command('get5_loadmatch_url ' + url) server.send_rcon_command('get5_web_api_key ' + self.api_key) if loadmatch_response: # There should be no response return False return True def get_team1(self): return Team.query.get(self.team1_id) def get_team2(self): return Team.query.get(self.team2_id) def get_winner(self): if self.team1_score > self.team2_score: return self.get_team1() elif self.team2_score > self.team1_score: return self.get_team2() else: return None def get_loser(self): if self.team1_score > self.team2_score: return self.get_team2() elif self.team2_score > self.team1_score: return self.get_team1() else: return None def build_match_dict(self): d = {} d['matchid'] = str(self.id) d['match_title'] = self.title d['side_type'] = 'always_knife' d['skip_veto'] = self.skip_veto d['num_maps'] = self.max_maps def add_team_data(teamkey, teamid, matchtext): team = Team.query.get(teamid) if not team: return d[teamkey] = {} # Add entries if they have values. def add_if(key, value): if value: d[teamkey][key] = value add_if('name', team.name) add_if('name', team.name) add_if('tag', team.tag) add_if('flag', team.flag.upper()) add_if('logo', team.logo) add_if('matchtext', matchtext) d[teamkey]['players'] = filter(lambda x: x != '', team.auths) add_team_data('team1', self.team1_id, self.team1_string) add_team_data('team2', self.team2_id, self.team2_string) d['cvars'] = {} d['cvars']['get5_web_api_url'] = url_for('home', _external=True, _scheme='http') if self.veto_mappool: d['maplist'] = [] for map in self.veto_mappool.split(): d['maplist'].append(map) return d def __repr__(self): return 'Match(id={})'.format(self.id)