Add !rolestats command (#344)

This might me a little confusing, because it calculates the number of players that won (similar to !playerstats) instead of the number of times a team won (like !gamestats). This way it gives the proper winrate of a player playing that role, but gives an inflated number of total victories and games in cases where there are multiples of a role in the same game. Maybe it would be better to hide the actual numbers and show only the winrates in order to avoid confusion.
This commit is contained in:
random-nick 2018-07-17 18:18:37 +02:00 committed by Ryan Schmidt
parent 43471dcf03
commit 34da084a94
4 changed files with 132 additions and 1 deletions

View File

@ -463,6 +463,74 @@ def get_game_totals(mode):
else: else:
return "Total games (\u0002{0}\u0002): {1} | {2}".format(mode, total_games, ", ".join(totals)) return "Total games (\u0002{0}\u0002): {1} | {2}".format(mode, total_games, ", ".join(totals))
def get_role_stats(role, mode=None):
conn = _conn()
c = conn.cursor()
if mode is None:
c.execute("""SELECT
gpr.role AS role,
SUM(gp.team_win) AS team,
SUM(gp.indiv_win) AS indiv,
SUM(gp.team_win OR gp.indiv_win) AS overall,
COUNT(1) AS total
FROM game g
JOIN game_player gp
ON gp.game = g.id
JOIN game_player_role gpr
ON gpr.game_player = gp.id
WHERE role = ?
GROUP BY role""", (role,))
else:
c.execute("""SELECT
gpr.role AS role,
g.gamemode AS gamemode,
SUM(gp.team_win) AS team,
SUM(gp.indiv_win) AS indiv,
SUM(gp.team_win OR gp.indiv_win) AS overall,
COUNT(1) AS total
FROM game g
JOIN game_player gp
ON gp.game = g.id
JOIN game_player_role gpr
ON gpr.game_player = gp.id
WHERE role = ?
AND gamemode = ?
GROUP BY role, gamemode""", (role, mode))
row = c.fetchone()
if row and row[2] is not None:
if mode is None:
return ("\u0002{0[0]}\u0002 | Team winners: {0[1]} ({1:.0%}), "
"Individual winners: {0[2]} ({2:.0%}), Overall winners: {0[3]} ({3:.0%}), Total games: {0[4]}.").format(row, row[1]/row[4], row[2]/row[4], row[3]/row[4])
else:
return ("\u0002{0[0]}\u0002 in \u0002{0[1]}\u0002 | Team winners: {0[2]} ({1:.0%}), "
"Individual winners: {0[3]} ({2:.0%}), Overall winners: {0[4]} ({3:.0%}), Total games: {0[5]}.").format(row, row[2]/row[5], row[3]/row[5], row[4]/row[5])
else:
if mode is None:
return "No stats for \u0002{0}\u0002.".format(role)
else:
return "No stats for \u0002{0}\u0002 in \u0002{1}\u0002.".format(role, mode)
def get_role_totals():
conn = _conn()
c = conn.cursor()
c.execute("SELECT COUNT(1) FROM game")
total_games = c.fetchone()[0]
if not total_games:
return "No games played."
c.execute("""SELECT
gpr.role AS role,
COUNT(1) AS count
FROM game_player_role gpr
GROUP BY role
ORDER BY count DESC""")
totals = []
for row in c:
totals.append("\u0002{0}\u0002: {1}".format(*row))
return "Total games: {0} | {1}".format(total_games, ", ".join(totals))
def get_warning_points(acc, hostmask): def get_warning_points(acc, hostmask):
peid, plid = _get_ids(acc, hostmask) peid, plid = _get_ids(acc, hostmask)
conn = _conn() conn = _conn()

View File

@ -23,6 +23,7 @@ VOTES_RATE_LIMIT = 60
ADMINS_RATE_LIMIT = 300 ADMINS_RATE_LIMIT = 300
GSTATS_RATE_LIMIT = 0 GSTATS_RATE_LIMIT = 0
PSTATS_RATE_LIMIT = 0 PSTATS_RATE_LIMIT = 0
RSTATS_RATE_LIMIT = 0
TIME_RATE_LIMIT = 10 TIME_RATE_LIMIT = 10
START_RATE_LIMIT = 10 # (per-user) START_RATE_LIMIT = 10 # (per-user)
WAIT_RATE_LIMIT = 10 # (per-user) WAIT_RATE_LIMIT = 10 # (per-user)

View File

@ -1,4 +1,5 @@
import itertools import itertools
import functools
import fnmatch import fnmatch
import re import re
@ -9,7 +10,7 @@ from src.events import Event
from src.messages import messages from src.messages import messages
__all__ = ["pm", "is_fake_nick", "mass_mode", "mass_privmsg", "reply", __all__ = ["pm", "is_fake_nick", "mass_mode", "mass_privmsg", "reply",
"is_user_simple", "is_user_notice", "in_wolflist", "is_user_simple", "is_user_notice", "in_wolflist", "complete_role",
"relay_wolfchat_command", "irc_lower", "irc_equals", "match_hostmask", "relay_wolfchat_command", "irc_lower", "irc_equals", "match_hostmask",
"is_owner", "is_admin", "plural", "singular", "list_players", "is_owner", "is_admin", "plural", "singular", "list_players",
"get_role", "get_roles", "role_order", "break_long_message", "get_role", "get_roles", "role_order", "break_long_message",
@ -375,4 +376,23 @@ def get_victim(cli, nick, victim, in_chan, self_in_list=False, bot_in_list=False
class InvalidModeException(Exception): pass class InvalidModeException(Exception): pass
def complete_role(var, wrapper, role):
if role not in var.ROLE_GUIDE.keys():
special_keys = {"lover"}
evt = Event("get_role_metadata", {})
evt.dispatch(var, "special_keys")
special_keys = functools.reduce(lambda x, y: x | y, evt.data.values(), special_keys)
if role.lower() in var.ROLE_ALIASES:
matches = (var.ROLE_ALIASES[role.lower()],)
else:
matches = complete_match(role, var.ROLE_GUIDE.keys() | special_keys)
if not matches:
wrapper.reply(messages["no_such_role"].format(role))
return False
if len(matches) > 1:
wrapper.reply(messages["ambiguous_role"].format(", ".join(matches)))
return False
return matches[0]
return role
# vim: set sw=4 expandtab: # vim: set sw=4 expandtab:

View File

@ -72,6 +72,7 @@ var.LAST_VOTES = None
var.LAST_ADMINS = None var.LAST_ADMINS = None
var.LAST_GSTATS = None var.LAST_GSTATS = None
var.LAST_PSTATS = None var.LAST_PSTATS = None
var.LAST_RSTATS = None
var.LAST_TIME = None var.LAST_TIME = None
var.LAST_START = {} var.LAST_START = {}
var.LAST_WAIT = {} var.LAST_WAIT = {}
@ -977,6 +978,7 @@ def join_player(var, wrapper, who=None, forced=False, *, sanity=True):
var.LAST_STATS = None # reset var.LAST_STATS = None # reset
var.LAST_GSTATS = None var.LAST_GSTATS = None
var.LAST_PSTATS = None var.LAST_PSTATS = None
var.LAST_RSTATS = None
var.LAST_TIME = None var.LAST_TIME = None
with var.WARNING_LOCK: with var.WARNING_LOCK:
@ -5574,6 +5576,46 @@ def my_stats(cli, nick, chan, rest):
rest = rest.split() rest = rest.split()
player_stats.func(cli, nick, chan, " ".join([nick] + rest)) player_stats.func(cli, nick, chan, " ".join([nick] + rest))
@command("rolestats", "rstats", pm=True)
def role_stats(var, wrapper, rest):
"""Gets the stats for a given role in a given gamemode or lists role totals across all games if no role is given."""
if (wrapper.target != users.Bot and var.LAST_RSTATS and var.RSTATS_RATE_LIMIT and
var.LAST_RSTATS + timedelta(seconds=var.RSTATS_RATE_LIMIT) > datetime.now()):
wrapper.pm(messages["command_ratelimited"])
return
if wrapper.target != users.Bot:
var.LAST_RSTATS = datetime.now()
if var.PHASE not in ("none", "join") and wrapper.target is not channel.Main:
wrapper.pm(messages["stats_wait_for_game_end"])
return
rest = rest.split()
if len(rest) == 0:
# this is a long message
wrapper.pm(db.get_role_totals())
elif len(rest) == 1 or (rest[-1] == "all" and rest.pop()):
role = complete_role(var, wrapper, " ".join(rest))
if role:
wrapper.reply(db.get_role_stats(role))
else:
role = complete_role(var, wrapper, " ".join(rest[:-1]))
if not role:
return
gamemode = rest[-1]
if gamemode not in var.GAME_MODES.keys():
matches = complete_match(gamemode, var.GAME_MODES.keys())
if len(matches) == 1:
gamemode = matches[0]
if not matches:
wrapper.pm(messages["invalid_mode"].format(rest[1]))
return
if len(matches) > 1:
wrapper.pm(messages["ambiguous_mode"].format(rest[1], ", ".join(matches)))
return
wrapper.reply(db.get_role_stats(role, gamemode))
# Called from !game and !join, used to vote for a game mode # Called from !game and !join, used to vote for a game mode
def vote_gamemode(var, wrapper, gamemode, doreply): def vote_gamemode(var, wrapper, gamemode, doreply):
if var.FGAMED: if var.FGAMED: