diff --git a/oyoyo/client.py b/oyoyo/client.py index c0825d6..dde27b6 100644 --- a/oyoyo/client.py +++ b/oyoyo/client.py @@ -17,12 +17,49 @@ import logging import socket +import time from oyoyo.parse import parse_raw_irc_command class IRCClientError(Exception): pass + +# Adapted from http://code.activestate.com/recipes/511490-implementation-of-the-token-bucket-algorithm/ +class TokenBucket(object): + """An implementation of the token bucket algorithm. + + >>> bucket = TokenBucket(80, 0.5) + >>> bucket.consume(1) + """ + def __init__(self, tokens, fill_rate): + """tokens is the total tokens in the bucket. fill_rate is the + rate in tokens/second that the bucket will be refilled.""" + self.capacity = float(tokens) + self._tokens = float(tokens) + self.fill_rate = float(fill_rate) + self.timestamp = time.time() + + def consume(self, tokens): + """Consume tokens from the bucket. Returns True if there were + sufficient tokens otherwise False.""" + if tokens <= self.tokens: + self._tokens -= tokens + else: + return False + return True + + @property + def tokens(self): + if self._tokens < self.capacity: + now = time.time() + delta = self.fill_rate * (now - self.timestamp) + self._tokens = min(self.capacity, self._tokens + delta) + self.timestamp = now + return self._tokens + + + def add_commands(d): def dec(cls): for c in d: @@ -70,6 +107,7 @@ class IRCClient(object): self.port = None self.connect_cb = None self.blocking = True + self.tokenbucket = TokenBucket(3, 7.3) self.__dict__.update(kwargs) self.command_handler = cmd_handler @@ -181,6 +219,8 @@ class IRCClient(object): self.socket.close() def msg(self, user, msg): for line in msg.split('\n'): + while not self.tokenbucket.consume(1): + time.sleep(1) self.send("PRIVMSG", user, ":{0}".format(line)) privmsg = msg # Same thing def notice(self, user, msg): diff --git a/var.py b/var.py index 0951ebd..ab3fa58 100644 --- a/var.py +++ b/var.py @@ -18,18 +18,19 @@ MANSLAUGHTER_CHANCE = 1/5 GAME_MODES = {} -###################################################################################################### -# ROLE INDEX: PLAYERS SEER WOLF CURSED DRUNK HARLOT TRAITOR GUNNER CROW ANGEL ## -###################################################################################################### -ROLES_GUIDE = { 4 : ( 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0), ## - 6 : ( 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0), ## - 8 : ( 1 , 2 , 1 , 1 , 1 , 0 , 0 , 0 , 0), ## - 10 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 0 , 0), ## - 11 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 0 , 1), ## - None : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0)} ## -###################################################################################################### -# Notes: ## -###################################################################################################### +################################################################################################################# +# ROLE INDEX: PLAYERS SEER WOLF CURSED DRUNK HARLOT TRAITOR GUNNER CROW ANGEL DETECTIVE ## +################################################################################################################# +ROLES_GUIDE = { 4 : ( 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), ## + 6 : ( 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 ), ## + 8 : ( 1 , 2 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 ), ## + 10 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 ), ## + 11 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 ), ## + 15 : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 1 ), ## + None : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )} ## +################################################################################################################# +# Notes: ## +################################################################################################################# ROLE_INDICES = {0 : "seer", @@ -40,7 +41,8 @@ ROLE_INDICES = {0 : "seer", 5 : "traitor", 6 : "gunner", 7 : "werecrow", - 8 : "guardian angel"} + 8 : "guardian angel", + 9 : "detective"} INDEX_OF_ROLE = dict((v,k) for k,v in ROLE_INDICES.items()) @@ -98,7 +100,8 @@ CHANGEABLE_ROLES = { "seers" : INDEX_OF_ROLE["seer"], "traitors" : INDEX_OF_ROLE["traitor"], "gunners" : INDEX_OF_ROLE["gunner"], "werecrows" : INDEX_OF_ROLE["werecrow"], - "angels" : INDEX_OF_ROLE["guardian angel"]} + "angels" : INDEX_OF_ROLE["guardian angel"], + "detectives" : INDEX_OF_ROLE["detective"]} diff --git a/wolfbot.py b/wolfbot.py index 6a15ff5..fb0fbe4 100644 --- a/wolfbot.py +++ b/wolfbot.py @@ -39,7 +39,11 @@ def main(): nickname=botconfig.NICK, connect_cb=wolfgame.connect_callback ) - cli.mainLoop() + try: + cli.mainLoop() + except Exception as e: + cli.msg(botconfig.CHANNEL, "An error has occured: "+str(e)) + raise e if __name__ == "__main__": diff --git a/wolfgame.py b/wolfgame.py index 7472693..0f31a47 100644 --- a/wolfgame.py +++ b/wolfgame.py @@ -183,7 +183,7 @@ def take_op(cli, nick, chan, rest): @cmd("!sudo revoke", owner_only=True) def revoke(cli, nick, chan, rest): r = rest.strip() - if r in botconfig.ADMINS: + if var.CLOAKS[var.USERS.index(r)] in botconfig.ADMINS: ladmins = list(botconfig.ADMINS) ladmins.remove(r) botconfig.ADMINS = tuple(ladmins) @@ -729,6 +729,9 @@ def on_nick(cli, prefix, nick): if prefix in var.WOUNDED: var.WOUNDED.remove(prefix) var.WOUNDED.append(nick) + if prefix in var.INVESTIGATED: + var.INVESTIGATED.remove(prefix) + var.INVESTIGATED.append(prefix) if prefix in var.VOTES: var.VOTES[nick] = var.VOTES.pop(prefix) for v in var.VOTES.values(): @@ -816,6 +819,7 @@ def transition_day(cli, gameid=0): # Reset daytime variables var.VOTES = {} + var.INVESTIGATED = [] var.WOUNDED = [] var.DAY_START_TIME = datetime.now() @@ -1173,6 +1177,45 @@ def observe(cli, nick, rest): +@pmcmd("id", "!id") +def investigate(cli, nick, rest): + if var.PHASE in ("none", "join"): + cli.notice(nick, "No game is currently running.") + return + elif nick not in var.list_players(): + cli.notice(nick, "You're not currently playing.") + return + if not var.is_role(nick, "detective"): + cli.msg(nick, "Only a detective may use this command.") + return + if var.PHASE != "day": + cli.msg(nick, "You may only investigate people during the day.") + return + if nick in var.INVESTIGATED: + cli.msg(nick, "You may only investigate one person per round.") + return + victim = re.split("\s+", rest)[0].strip().lower() + if not victim: + cli.msg(nick, "Not enough parameters") + return + pl = var.list_players() + pll = [x.lower() for x in pl] + if victim not in pll: + cli.msg(nick, "\u0002{0}\u0002 is currently not playing.".format(victim)) + return + victim = pl[pll.index(victim)] + + var.INVESTIGATED.append(nick) + cli.msg(nick, ("The results of your investigation have returned. \u0002{0}\u0002"+ + " is a... \u0002{1}\u0002").format(victim, var.get_role(victim))) + if random.random < 0.4: # a 2/5 chance (should be changeable in settings) + # Reveal his role! + for badguy in var.ROLES["wolf"] + var.ROLES["werecrow"] + var.ROLES["traitor"]: + cli.msg(badguy, ("\0002{0}\0002 accidentally drops a paper. The paper reveals "+ + "that (s)he is the detective!").format(nick)) + + + @pmcmd("visit", "!visit") def hvisit(cli, nick, rest): if var.PHASE in ("none", "join"): @@ -1330,7 +1373,7 @@ def relay(cli, nick, rest): return badguys = var.ROLES["wolf"] + var.ROLES["traitor"] + var.ROLES["werecrow"] if len(badguys) > 1: - if var.get_role(nick) in ("wolf","traitor","werecrow"): + if nick in badguys: badguys.remove(nick) # remove self from list for badguy in badguys: cli.msg(badguy, "{0} says: {1}".format(nick, rest)) @@ -1425,7 +1468,11 @@ def transition_night(cli): ' wolf, there is a 50/50 chance of you dying, if you guard '+ 'a victim, they will live. Use !guard to guard a player.')); cli.msg(g_angel, "Players: " + ", ".join(pl)) - + for dttv in var.ROLES["detective"]: + cli.msg(dttv, ("You are a \u0002detective\u0002.\n"+ + "It is your job to determine all the wolves and traitors. "+ + "Your job is during the day, and you can see the true "+ + "identity of all users, even traitors.")) for d in var.ROLES["village drunk"]: cli.msg(d, 'You have been drinking too much! You are the \u0002village drunk\u0002.') @@ -1698,7 +1745,6 @@ def fwait(cli, nick, chan, rest): "{1} seconds.").format(nick, var.EXTRA_WAIT)) -@cmd("!reset") +@cmd("!reset",admin_only=True) def reset_game(cli, nick, chan, rest): - if nick in ("nyuszika7h", "jcao219"): - reset(cli) \ No newline at end of file + reset(cli) \ No newline at end of file