def ingame_mods(): red = _red() mods = red.hgetall("moderators") or {} if not mods: return [] rcon = RecordedRcon(SERVER_INFO) players = rcon.get_players() mods_ids = set(v.decode() for v in mods.values()) ig_mods = [] for player in players: if player['steam_id_64'] in mods_ids: ig_mods.append({ "username": player["name"], 'steam_id_64': player['steam_id_64'] }) return ig_mods
("unban", unban), ("get_hooks", get_hooks), ("set_hooks", set_hooks), ("do_unwatch_player", do_unwatch_player), ("do_watch_player", do_watch_player), ("public_info", public_info), ("set_camera_config", set_camera_config), ("get_camera_config", get_camera_config), ("set_votekick_autotoggle_config", set_votekick_autotoggle_config), ("get_votekick_autotoggle_config", get_votekick_autotoggle_config), ] logger.info("Initializing endpoint") # Dynamically register all the methods from ServerCtl for name, func in inspect.getmembers(ctl): if not any(name.startswith(prefix) for prefix in PREFIXES_TO_EXPOSE): continue commands.append( (name, wrap_method(func, inspect.signature(func).parameters, name))) # Warm the cache as fetching steam profile 1 by 1 takes a while if not os.getenv("DJANGO_DEBUG", None): try: logger.info("Warming up the cache this may take minutes") ctl.get_players() logger.info("Cache warm up done") except: logger.exception("Failed to warm the cache")
class LiveStats: def __init__(self): self.rcon = RecordedRcon(SERVER_INFO) self.voted_yes_regex = re.compile(".*PV_Favour.*") self.voted_no_regex = re.compile(".*PV_Against.*") self.red = get_redis_client() def _get_player_session_time(self, player): if not player or not player.get("profile"): #logger.warning("Can't use player profile") return -1 player_time_sec = player.get("profile", {}).get("current_playtime_seconds", 0) if player["name"] == "Dr.WeeD": logger.debug("Player %s - playtime sec: %s", player["name"], player_time_sec) return player_time_sec def _is_log_from_current_session(self, now, player, log): if player["name"] == "Dr.WeeD": logger.debug( "%s %s %s %s", log["timestamp_ms"], (now.timestamp() - self._get_player_session_time(player)) * 1000, log["timestamp_ms"] >= (now.timestamp() - self._get_player_session_time(player)) * 1000, log['raw']) return ( log["timestamp_ms"] >= (now.timestamp() - self._get_player_session_time(player)) * 1000) def _get_indexed_logs_by_player_for_session(self, now, indexed_players, logs): logs_indexed = {} for l in logs: player = indexed_players.get(l["player"]) player2 = indexed_players.get(l["player2"]) try: # Only consider stats for a player from his last connection (so a disconnect reconnect should reset stats) otherwise multiple sessions could be blended into one, even if they are far apart if player and self._is_log_from_current_session( now, player, l): logs_indexed.setdefault(l["player"], []).append(l) if player2 and self._is_log_from_current_session( now, player2, l): logs_indexed.setdefault(l["player2"], []).append(l) except KeyError: logger.exception("Invalid log line %s", l) return logs_indexed def _is_player_death(self, player, log): return player["name"] == log["player2"] def _is_player_kill(self, player, log): return player["name"] == log["player"] def _add_kd(self, attacker_key, victim_key, stats, player, log): if self._is_player_kill(player, log): stats[attacker_key] += 1 elif self._is_player_death(player, log): stats[victim_key] += 1 else: logger.warning( "Log line does not belong to player '%s' line: '%s'", player["name"], log["raw"], ) def _add_kill(self, stats, player, log): self._add_kd("kills", "deaths", stats, player, log) def _add_tk(self, stats, player, log): self._add_kd("teamkills", "deaths_by_tk", stats, player, log) def _add_vote(self, stats, player, log): if self.voted_no_regex.match(log["raw"]): stats["nb_voted_no"] += 1 elif self.voted_yes_regex.match(log["raw"]): stats["nb_voted_yes"] += 1 else: logger.warning( "VOTE log line does not match either vote yes or no regex: %s", log["raw"], ) def _add_vote_started(self, stats, player, log): stats["nb_vote_started"] += 1 def _streaks_accumulator(self, player, log, stats, streaks): action = log["action"] if action == "KILL": if self._is_player_kill(player, log): streaks.kill += 1 streaks.death = 0 streaks.teamkills = 0 elif self._is_player_death(player, log): streaks.kill = 0 streaks.deaths_by_tk = 0 streaks.death += 1 if action == "TEAM KILL": if self._is_player_kill(player, log): streaks.teamkills += 1 if self._is_player_death(player, log): streaks.deaths_by_tk += 1 stats["kills_streak"] = max(streaks.kill, stats["kills_streak"]) stats["deaths_without_kill_streak"] = max( streaks.death, stats["deaths_without_kill_streak"]) stats["teamkills_streak"] = max(streaks.teamkills, stats["teamkills_streak"]) stats["deaths_by_tk_streak"] = max(streaks.deaths_by_tk, stats["deaths_by_tk_streak"]) def get_current_players_stats(self): players = self.rcon.get_players() if not players: logger.debug("No players") return {} profiles_by_id = { profile["steam_id_64"]: profile for profile in get_profiles([p["steam_id_64"] for p in players], nb_sessions=0) } logger.info("%s players, %s profiles loaded", len(players), len(profiles_by_id)) oldest_session_seconds = self._get_player_session_time( max(players, key=self._get_player_session_time)) logger.debug("Oldest session: %s", oldest_session_seconds) now = datetime.datetime.now() min_timestamp = ( now - datetime.timedelta(seconds=oldest_session_seconds)).timestamp() logger.debug("Min timestamp: %s", min_timestamp) logs = get_recent_logs(min_timestamp=min_timestamp) logger.info("%s log lines to process", len(logs["logs"])) indexed_players = {p["name"]: p for p in players} indexed_logs = self._get_indexed_logs_by_player_for_session( now, indexed_players, logs["logs"]) stats_by_player = {} actions_processors = { "KILL": self._add_kill, "TEAM KILL": self._add_tk, "VOTE STARTED": self._add_vote_started, "VOTE": self._add_vote, } for p in players: logger.debug("Crunching stats for %s", p) player_logs = indexed_logs.get(p["name"], []) stats = { "player": p["name"], "steam_id_64": p.get("steam_id_64"), "steaminfo": profiles_by_id.get(p.get("steam_id_64"), {}).get("steaminfo"), "kills": 0, "kills_streak": 0, "deaths": 0, "deaths_without_kill_streak": 0, "teamkills": 0, "teamkills_streak": 0, "deaths_by_tk": 0, "deaths_by_tk_streak": 0, "nb_vote_started": 0, "nb_voted_yes": 0, "nb_voted_no": 0, "time_seconds": self._get_player_session_time(p), } streaks = Streaks() for l in player_logs: action = l["action"] processor = actions_processors.get(action, lambda **kargs: None) processor(stats=stats, player=p, log=l) self._streaks_accumulator(p, l, stats, streaks) # stats = self._compute_stats(stats) stats_by_player[p["name"]] = self._compute_stats(stats) return stats_by_player def set_live_stats(self): snapshot_ts = datetime.datetime.now().timestamp() stats = self.get_current_players_stats() self.red.set( "LIVE_STATS", pickle.dumps(dict(snapshot_timestamp=snapshot_ts, stats=stats)), ) def get_cached_stats(self): stats = self.red.get("LIVE_STATS") if stats: stats = pickle.loads(stats) return stats def _compute_stats(self, stats): new_stats = dict(**stats) new_stats["kills_per_minute"] = round( stats["kills"] / max(stats["time_seconds"] / 60, 1), 2) new_stats["deaths_per_minute"] = round( stats["deaths"] / max(stats["time_seconds"] / 60, 1), 2) new_stats["kill_death_ratio"] = round( stats["kills"] / max(stats["deaths"], 1), 2) return new_stats