From 23b2e755ca4cce44cf51b4a289d63f3b608f630d Mon Sep 17 00:00:00 2001 From: Skizzerz Date: Sat, 12 Jul 2014 13:10:44 -0500 Subject: [PATCH] Initial commit to add a bunch of new roles --- modules/wolfgame.py | 587 ++++++++++++++++++++----------------------- settings/wolfgame.py | 200 +++++++++------ 2 files changed, 403 insertions(+), 384 deletions(-) diff --git a/modules/wolfgame.py b/modules/wolfgame.py index f36a4cf..bb92eb7 100644 --- a/modules/wolfgame.py +++ b/modules/wolfgame.py @@ -91,11 +91,11 @@ if botconfig.DEBUG_MODE: var.WARN_IDLE_TIME = 0 #180 var.JOIN_TIME_LIMIT = 0 - + def connect_callback(cli): to_be_devoiced = [] cmodes = [] - + @hook("quietlist", hookid=294) def on_quietlist(cli, server, botnick, channel, q, quieted, by, something): if re.match(".+\!\*@\*", quieted): # only unquiet people quieted by bot @@ -113,23 +113,23 @@ def connect_callback(cli): if "+" in status: to_be_devoiced.append(user) var.USERS[user] = dict(cloak=cloak,account=acc) - + @hook("endofwho", hookid=294) def afterwho(*args): for nick in to_be_devoiced: cmodes.append(("-v", nick)) # devoice all on connect - + @hook("mode", hookid=294) def on_give_me_ops(cli, blah, blahh, modeaction, target="", *other): if modeaction == "+o" and target == botconfig.NICK and var.PHASE == "none": - + @hook("quietlistend", 294) def on_quietlist_end(cli, svr, nick, chan, *etc): if chan == botconfig.CHANNEL: decorators.unhook(HOOKS, 294) mass_mode(cli, cmodes) - + cli.mode(botconfig.CHANNEL, "q") # unquiet all cli.mode(botconfig.CHANNEL, "-m") # remove -m mode from channel @@ -154,7 +154,7 @@ def mass_mode(cli, md): arg1 = "".join(z[0]) arg2 = " ".join(z[1]) # + " " + " ".join([x+"!*@*" for x in z[1]]) cli.mode(botconfig.CHANNEL, arg1, arg2) - + def pm(cli, target, message): # message either privmsg or notice, depending on user settings if target in var.USERS and var.USERS[target]["cloak"] in var.SIMPLE_NOTIFY: cli.notice(target, message) @@ -183,7 +183,7 @@ def reset_modes_timers(cli): def reset(cli): var.PHASE = "none" - + var.GAME_ID = 0 var.DEAD = [] @@ -211,7 +211,7 @@ def make_stasis(nick, penalty): @cmd("fdie", "fbye", admin_only=True) def forced_exit(cli, nick, *rest): # Admin Only """Forces the bot to close""" - + if var.PHASE in ("day", "night"): stop_game(cli) else: @@ -246,8 +246,8 @@ def restart_program(cli, nick, *rest): os.execl(python, python, sys.argv[0], "--verbose") else: os.execl(python, python, *sys.argv) - - + + @pmcmd("ping") def pm_ping(cli, nick, rest): @@ -268,7 +268,7 @@ def pinger(cli, nick, chan, rest): cli.notice(nick, ("This command is rate-limited. " + "Please wait a while before using it again.")) return - + var.LAST_PING = datetime.now() if var.PINGING: return @@ -299,10 +299,10 @@ def pinger(cli, nick, chan, rest): if not var.PINGING: return TO_PING.sort(key=lambda x: x.lower()) - + cli.msg(botconfig.CHANNEL, "PING! "+" ".join(TO_PING)) var.PINGING = False - + minimum = datetime.now() + timedelta(seconds=var.PING_MIN_WAIT) if not var.CAN_START_TIME or var.CAN_START_TIME < minimum: var.CAN_START_TIME = minimum @@ -316,19 +316,19 @@ def pinger(cli, nick, chan, rest): @pmcmd("simple", raw_nick = True) def mark_simple_notify(cli, nick, *rest): """If you want the bot to NOTICE you for every interaction""" - + nick, _, __, cloak = parse_nick(nick) - + if cloak in var.SIMPLE_NOTIFY: var.SIMPLE_NOTIFY.remove(cloak) var.remove_simple_rolemsg(cloak) - + cli.notice(nick, "You now no longer receive simple role instructions.") return - + var.SIMPLE_NOTIFY.append(cloak) var.add_simple_rolemsg(cloak) - + cli.notice(nick, "You now receive simple role instructions.") if not var.OPT_IN_PING: @@ -400,9 +400,9 @@ def fpinger(cli, nick, chan, rest): def join(cli, nick, chann_, rest): """Either starts a new game of Werewolf or joins an existing game that has not started yet.""" pl = var.list_players() - + chan = botconfig.CHANNEL - + nick, _, __, cloak = parse_nick(nick) try: @@ -412,10 +412,10 @@ def join(cli, nick, chann_, rest): return except KeyError: cloak = None - + if var.PHASE == "none": - + cli.mode(chan, "+v", nick) var.ROLES["person"].append(nick) var.PHASE = "join" @@ -426,14 +426,14 @@ def join(cli, nick, chann_, rest): cli.msg(chan, ('\u0002{0}\u0002 has started a game of Werewolf. '+ 'Type "{1}join" to join. Type "{1}start" to start the game. '+ 'Type "{1}wait" to increase start wait time.').format(nick, botconfig.CMD_CHAR)) - + # Set join timer if var.JOIN_TIME_LIMIT: t = threading.Timer(var.JOIN_TIME_LIMIT, kill_join, [cli, chan]) var.TIMERS['join'] = t t.daemon = True t.start() - + elif nick in pl: cli.notice(nick, "You're already playing!") elif len(pl) >= var.MAX_PLAYERS: @@ -441,7 +441,7 @@ def join(cli, nick, chann_, rest): elif var.PHASE != "join": cli.notice(nick, "Sorry but the game is already running. Try again next time.") else: - + cli.mode(chan, "+v", nick) var.ROLES["person"].append(nick) cli.msg(chan, '\u0002{0}\u0002 has joined the game and raised the number of players to \u0002{1}\u0002.'.format(nick, len(pl) + 1)) @@ -465,7 +465,7 @@ def join(cli, nick, chann_, rest): var.LAST_PSTATS = None var.LAST_TIME = None - + def kill_join(cli, chan): pl = var.list_players() pl.sort(key=lambda x: x.lower()) @@ -477,7 +477,7 @@ def kill_join(cli, chan): 'has been canceled. If you are still active, ' + 'please join again to start a new game.') var.LOGGER.logMessage('Game canceled.') - + @cmd("fjoin", admin_only=True) def fjoin(cli, nick, chann_, rest): @@ -509,7 +509,7 @@ def fjoin(cli, nick, chann_, rest): @cmd("fleave", "fquit", admin_only=True) def fleave(cli, nick, chann_, rest): chan = botconfig.CHANNEL - + if var.PHASE == "none": cli.notice(nick, "No game is running.") for a in re.split(" +",rest): @@ -525,7 +525,8 @@ def fleave(cli, nick, chann_, rest): return cli.msg(chan, ("\u0002{0}\u0002 is forcing"+ " \u0002{1}\u0002 to leave.").format(nick, a)) - cli.msg(chan, "Say goodbye to the \02{0}\02.".format(var.get_role(a))) + if var.ROLE_REVEAL: + cli.msg(chan, "Say goodbye to the \02{0}\02.".format(var.get_role(a))) if var.PHASE == "join": cli.msg(chan, ("New player count: \u0002{0}\u0002").format(len(var.list_players()) - 1)) if var.PHASE in ("day", "night"): @@ -562,9 +563,9 @@ def stats(cli, nick, chan, rest): if var.PHASE == "none": cli.notice(nick, "No game is currently running.") return - + pl = var.list_players() - + if nick != chan and (nick in pl or var.PHASE == "join"): # only do this rate-limiting stuff if the person is in game if (var.LAST_STATS and @@ -572,16 +573,16 @@ def stats(cli, nick, chan, rest): cli.notice(nick, ("This command is rate-limited. " + "Please wait a while before using it again.")) return - + var.LAST_STATS = datetime.now() - + pl.sort(key=lambda x: x.lower()) if len(pl) > 1: msg = '{0}: \u0002{1}\u0002 players: {2}'.format(nick, len(pl), ", ".join(pl)) else: msg = '{0}: \u00021\u0002 player: {1}'.format(nick, pl[0]) - + if nick == chan: pm(cli, nick, msg) else: @@ -590,8 +591,8 @@ def stats(cli, nick, chan, rest): var.LOGGER.logMessage(msg.replace("\02", "")) else: cli.notice(nick, msg) - - if var.PHASE == "join": + + if var.PHASE == "join" or not var.ROLE_REVEAL: return message = [] @@ -601,7 +602,7 @@ def stats(cli, nick, chan, rest): l2 = [k for k in var.ORIGINAL_ROLES.keys() if var.ORIGINAL_ROLES[k]] rs = list(set(l1+l2)) - + # Due to popular demand, picky ordering if "wolf" in rs: rs.remove("wolf") @@ -609,11 +610,11 @@ def stats(cli, nick, chan, rest): if "seer" in rs: rs.remove("seer") rs.insert(1, "seer") - if "villager" in rs: - rs.remove("villager") - rs.append("villager") - - + if var.DEFAULT_ROLE in rs: + rs.remove(var.DEFAULT_ROLE) + rs.append(var.DEFAULT_ROLE) + + firstcount = len(var.ROLES[rs[0]]) if firstcount > 1 or not firstcount: vb = "are" @@ -625,7 +626,7 @@ def stats(cli, nick, chan, rest): continue elif role == "villager" and var.HIDDEN_TRAITOR: count += len(var.ROLES["traitor"]) - + if count > 1 or count == 0: message.append("\u0002{0}\u0002 {1}".format(count if count else "\u0002no\u0002", var.plural(role))) else: @@ -657,7 +658,7 @@ def hurry_up(cli, gameid, change): return chan = botconfig.CHANNEL - + if not change: cli.msg(chan, ("\02As the sun sinks inexorably toward the horizon, turning the lanky pine " + "trees into fire-edged silhouettes, the villagers are reminded that very little " + @@ -674,10 +675,10 @@ def hurry_up(cli, gameid, change): var.TIMERS["day"] = tmr tmr.start() return - - + + var.DAY_ID = 0 - + pl = var.list_players() avail = len(pl) - len(var.WOUNDED) votesneeded = avail // 2 + 1 @@ -701,7 +702,7 @@ def hurry_up(cli, gameid, change): var.LOGGER.logMessage(("As the sun sets, the villagers agree to "+ "retire to their beds and wait for morning.")) transition_night(cli) - + @@ -741,25 +742,25 @@ def chk_decision(cli): @cmd("votes") def show_votes(cli, nick, chan, rest): """Displays the voting statistics.""" - + if var.PHASE in ("none", "join"): cli.notice(nick, "No game is currently running.") return if var.PHASE != "day": cli.notice(nick, "Voting is only during the day.") return - + if (var.LAST_VOTES and var.LAST_VOTES + timedelta(seconds=var.VOTES_RATE_LIMIT) > datetime.now()): cli.notice(nick, ("This command is rate-limited." + "Please wait a while before using it again.")) - return - + return + pl = var.list_players() - + if nick in pl: - var.LAST_VOTES = datetime.now() - + var.LAST_VOTES = datetime.now() + if not var.VOTES.values(): msg = nick+": No votes yet." if nick in pl: @@ -770,7 +771,7 @@ def show_votes(cli, nick, chan, rest): " ".join(var.VOTES[votee])) for votee in var.VOTES.keys()] msg = "{0}: {1}".format(nick, ", ".join(votelist)) - + if nick in pl: cli.msg(chan, msg) else: @@ -825,16 +826,13 @@ def stop_game(cli, winner = ""): var.LOGGER.logBare("GAME", "TIME", str(total.seconds)) roles_msg = [] - - var.ORIGINAL_ROLES["cursed villager"] = var.CURSED # A hack - var.ORIGINAL_ROLES["gunner"] = list(var.GUNNERS.keys()) lroles = list(var.ORIGINAL_ROLES.keys()) lroles.remove("wolf") lroles.insert(0, "wolf") # picky, howl consistency - + for role in lroles: - if len(var.ORIGINAL_ROLES[role]) == 0 or role == "villager": + if len(var.ORIGINAL_ROLES[role]) == 0 or role == var.DEFAULT_ROLE: continue playersinrole = list(var.ORIGINAL_ROLES[role]) for i,plr in enumerate(playersinrole): @@ -855,17 +853,17 @@ def stop_game(cli, winner = ""): cli.msg(chan, " ".join(roles_msg)) reset_modes_timers(cli) - + # Set temporary phase to deal with disk lag var.PHASE = "writing files" - + plrl = [] for role,ppl in var.ORIGINAL_ROLES.items(): for x in ppl: plrl.append((x, role)) - + var.LOGGER.saveToFile() - + for plr, rol in plrl: #if plr not in var.USERS.keys(): # they died TODO: when a player leaves, count the game as lost for them # if plr in var.DEAD_USERS.keys(): @@ -898,17 +896,17 @@ def stop_game(cli, winner = ""): won = True else: break - + iwon = won and plr in var.list_players() # survived, team won = individual win - + var.update_role_stats(acc, rol, won, iwon) - + size = len(var.list_players()) + len(var.DEAD) if winner != "": # Only update if not an abnormal game stop var.update_game_stats(size, winner) - + reset(cli) - + # This must be after reset(cli) if var.AFTER_FLASTGAME: var.AFTER_FLASTGAME() @@ -916,32 +914,32 @@ def stop_game(cli, winner = ""): if var.ADMIN_TO_PING: # It was an flastgame cli.msg(chan, "PING! " + var.ADMIN_TO_PING) var.ADMIN_TO_PING = None - + return True def chk_win(cli, end_game = True): """ Returns True if someone won """ - + chan = botconfig.CHANNEL lpl = len(var.list_players()) - + if lpl == 0: #cli.msg(chan, "No more players remaining. Game ended.") reset_modes_timers(cli) reset(cli) return True - + if var.PHASE == "join": return False - - + + lwolves = (len(var.ROLES["wolf"])+ len(var.ROLES["traitor"])+ len(var.ROLES["werecrow"])) if var.PHASE == "day": lpl -= len([x for x in var.WOUNDED if x not in var.ROLES["traitor"]]) lwolves -= len([x for x in var.WOUNDED if x in var.ROLES["traitor"]]) - + if lwolves == lpl / 2: message = ("Game over! There are the same number of wolves as " + "uninjured villagers. The wolves overpower the villagers and win.") @@ -956,7 +954,7 @@ def chk_win(cli, end_game = True): message = ("Game over! All the wolves are dead! The villagers " + "chop them up, BBQ them, and have a hearty meal.") village_win = True - elif (not var.ROLES["wolf"] and not + elif (not var.ROLES["wolf"] and not var.ROLES["werecrow"] and var.ROLES["traitor"]): for t in var.ROLES["traitor"]: var.LOGGER.logBare(t, "TRANSFORM") @@ -987,10 +985,10 @@ def del_player(cli, nick, forced_death = False, devoice = True, end_game = True) arg: forced_death = True when lynched or when the seer/wolf both don't act """ t = time.time() # time - + var.LAST_STATS = None # reset var.LAST_VOTES = None - + with var.GRAVEYARD_LOCK: if not var.GAME_ID or var.GAME_ID > t: # either game ended, or a new game has started. @@ -1037,20 +1035,20 @@ def del_player(cli, nick, forced_death = False, devoice = True, end_game = True) if not var.VOTES[k]: # no more votes on that person del var.VOTES[k] break # can only vote once - + if nick in var.WOUNDED: var.WOUNDED.remove(nick) chk_decision(cli) elif var.PHASE == "night" and ret: chk_nightdone(cli) - return ret + return ret def reaper(cli, gameid): # check to see if idlers need to be killed. var.IDLE_WARNED = [] chan = botconfig.CHANNEL - + while gameid == var.GAME_ID: with var.GRAVEYARD_LOCK: # Terminate reaper when experiencing disk lag @@ -1115,7 +1113,7 @@ def reaper(cli, gameid): def update_last_said(cli, nick, chan, rest): if var.PHASE not in ("join", "none"): var.LAST_SAID_TIME[nick] = datetime.now() - + if var.PHASE not in ("none", "join"): var.LOGGER.logChannelMessage(nick, rest) @@ -1197,8 +1195,8 @@ def goat(cli, nick, chan, rest): var.LOGGER.logMessage("{0}'s goat walks by and {1} {2}.".format(nick, goatact, victim)) var.GOATED = True - - + + @hook("nick") def on_nick(cli, prefix, nick): @@ -1207,7 +1205,7 @@ def on_nick(cli, prefix, nick): if prefix in var.USERS: var.USERS[nick] = var.USERS.pop(prefix) - + if prefix == var.ADMIN_TO_PING: var.ADMIN_TO_PING = nick @@ -1279,7 +1277,7 @@ def on_nick(cli, prefix, nick): if cloak == clk: cli.mode(chan, "+v", nick, nick+"!*@*") del var.DISCONNECTED[nick] - + cli.msg(chan, ("\02{0}\02 has returned to "+ "the village.").format(nick)) @@ -1287,7 +1285,7 @@ def leave(cli, what, nick, why=""): nick, _, _, cloak = parse_nick(nick) if what == "part" and why != botconfig.CHANNEL: return - + if why and why == botconfig.CHANGING_HOST_QUIT_MESSAGE: return if var.PHASE == "none": @@ -1302,7 +1300,7 @@ def leave(cli, what, nick, why=""): var.DCED_PLAYERS[nick] = var.PLAYERS.pop(nick) if nick not in var.list_players() or nick in var.DISCONNECTED.keys(): return - + # the player who just quit was in the game killplayer = True if what == "part" and (not var.PART_GRACE_TIME or var.PHASE == "join"): @@ -1355,7 +1353,7 @@ def leave_game(cli, nick, chan, rest): make_stasis(nick, var.LEAVE_STASIS_PENALTY) del_player(cli, nick) - + @@ -1370,7 +1368,7 @@ def begin_day(cli): var.HVISITED = {} var.GUARDED = {} var.STARTED_DAY_PLAYERS = len(var.list_players()) - + msg = ("The villagers must now vote for whom to lynch. "+ 'Use "{0}lynch " to cast your vote. {1} votes '+ 'are required to lynch.').format(botconfig.CMD_CHAR, len(var.list_players()) // 2 + 1) @@ -1391,10 +1389,10 @@ def begin_day(cli): def night_warn(cli, gameid): if gameid != var.NIGHT_ID: return - + if var.PHASE == "day": return - + cli.msg(botconfig.CHANNEL, ("\02A few villagers awake early and notice it " + "is still dark outside. " + "The night is almost over and there are " + @@ -1405,14 +1403,14 @@ def transition_day(cli, gameid=0): if gameid != var.NIGHT_ID: return var.NIGHT_ID = 0 - + if var.PHASE == "day": return - + var.PHASE = "day" var.GOATED = False chan = botconfig.CHANNEL - + # Reset daytime variables var.VOTES = {} var.INVESTIGATED = [] @@ -1425,7 +1423,7 @@ def transition_day(cli, gameid=0): for x in var.ROLES["wolf"]+var.ROLES["werecrow"]+var.ROLES["traitor"]: if not del_player(cli, x, True): return - + var.FIRST_NIGHT = False td = var.DAY_START_TIME - var.NIGHT_START_TIME @@ -1439,7 +1437,7 @@ def transition_day(cli, gameid=0): found[v] += 1 else: found[v] = 1 - + maxc = 0 victim = "" dups = [] @@ -1455,7 +1453,7 @@ def transition_day(cli, gameid=0): if dups: dups.append(victim) victim = random.choice(dups) - + message = [("Night lasted \u0002{0:0>2}:{1:0>2}\u0002. It is now daytime. "+ "The villagers awake, thankful for surviving the night, "+ "and search the village... ").format(min, sec)] @@ -1503,7 +1501,7 @@ def transition_day(cli, gameid=0): "https://i.imgur.com/b8HAvjL.gif", "https://i.imgur.com/PIIfL15.gif"] )) - + if victim in var.GUNNERS.keys() and var.GUNNERS[victim]: # victim had bullets! if random.random() < var.GUNNER_KILLS_WOLF_AT_NIGHT_CHANCE: wc = var.ROLES["werecrow"][:] @@ -1543,21 +1541,21 @@ def transition_day(cli, gameid=0): cli.msg(chan, "\n".join(message)) for msg in message: var.LOGGER.logMessage(msg.replace("\02", "")) - + for deadperson in dead: # kill each player, but don't end the game if one group outnumbers another del_player(cli, deadperson, end_game = False) if chk_win(cli): # if after the last person is killed, one side wins, then actually end the game here return - - if (var.WOLF_STEALS_GUN and victim in dead and + + if (var.WOLF_STEALS_GUN and victim in dead and victim in var.GUNNERS.keys() and var.GUNNERS[victim] > 0): # victim has bullets - guntaker = random.choice(var.ROLES["wolf"] + var.ROLES["werecrow"] + guntaker = random.choice(var.ROLES["wolf"] + var.ROLES["werecrow"] + var.ROLES["traitor"]) # random looter numbullets = var.GUNNERS[victim] var.WOLF_GUNNERS[guntaker] = 1 # transfer bullets a wolf - mmsg = ("While searching {0}'s belongings, You found " + - "a gun loaded with 1 silver bullet! " + + mmsg = ("While searching {0}'s belongings, You found " + + "a gun loaded with 1 silver bullet! " + "You may only use it during the day. " + "If you shoot at a wolf, you will intentionally miss. " + "If you shoot a villager, it is likely that they will be injured.") @@ -1565,7 +1563,7 @@ def transition_day(cli, gameid=0): pm(cli, guntaker, mmsg) var.GUNNERS[victim] = 0 # just in case - + begin_day(cli) @@ -1575,14 +1573,14 @@ def chk_nightdone(cli): len(var.GUARDED.keys()) >= len(var.ROLES["guardian angel"]) and # guardians have guarded len(var.KILLS)+len(var.OBSERVED) >= len(var.ROLES["werecrow"]+var.ROLES["wolf"]) and var.PHASE == "night"): - + # check if wolves are actually agreeing if len(set(var.KILLS.values())) > 1: return - + for x, t in var.TIMERS.items(): t.cancel() - + var.TIMERS = {} if var.PHASE == "night": # Double check transition_day(cli) @@ -1593,7 +1591,7 @@ def chk_nightdone(cli): def vote(cli, nick, chann_, rest): """Use this to vote for a candidate to be lynched""" chan = botconfig.CHANNEL - + rest = re.split(" +",rest)[0].strip().lower() if not rest: @@ -1616,7 +1614,7 @@ def vote(cli, nick, chann_, rest): pl = var.list_players() pl_l = [x.strip().lower() for x in pl] - + matches = 0 for player in pl_l: if rest == player: @@ -1629,7 +1627,7 @@ def vote(cli, nick, chann_, rest): if matches != 1: pm(cli, nick, "\u0002{0}\u0002 is currently not playing.".format(rest)) return - + voted = pl[pl_l.index(target)] if not var.SELF_LYNCH_ALLOWED: @@ -1652,9 +1650,9 @@ def vote(cli, nick, chann_, rest): "\u0002{1}\u0002.").format(nick, voted)) var.LOGGER.logMessage("{0} votes for {1}.".format(nick, voted)) var.LOGGER.logBare(voted, "VOTED", nick) - + var.LAST_VOTES = None # reset - + chk_decision(cli) @@ -1662,16 +1660,16 @@ def vote(cli, nick, chann_, rest): @cmd("retract") def retract(cli, nick, chann_, rest): """Takes back your vote during the day (for whom to lynch)""" - + chan = botconfig.CHANNEL - + if var.PHASE in ("none", "join"): cli.notice(nick, "No game is currently running.") return elif nick not in var.list_players() or nick in var.DISCONNECTED.keys(): cli.notice(nick, "You're not currently playing.") return - + if var.PHASE != "day": cli.notice(nick, ("Lynching is only allowed during the day. "+ "Please wait patiently for morning.")) @@ -1699,7 +1697,7 @@ def wolfretract(cli, nick, rest): elif nick not in var.list_players() or nick in var.DISCONNECTED.keys(): cli.notice(nick, "You're not currently playing.") return - + role = var.get_role(nick) if role not in ('wolf', 'werecrow'): return @@ -1711,7 +1709,7 @@ def wolfretract(cli, nick, rest): pm(cli, nick, ("You have already transformed into a crow, and "+ "cannot turn back until day.")) return - + if nick in var.KILLS.keys(): del var.KILLS[nick] pm(cli, nick, "You have retracted your vote.") @@ -1720,7 +1718,7 @@ def wolfretract(cli, nick, rest): @cmd("shoot") def shoot(cli, nick, chann_, rest): """Use this to fire off a bullet at someone in the day if you have bullets""" - + chan = botconfig.CHANNEL if var.PHASE in ("none", "join"): cli.notice(nick, "No game is currently running.") @@ -1728,7 +1726,7 @@ def shoot(cli, nick, chann_, rest): elif nick not in var.list_players() or nick in var.DISCONNECTED.keys(): cli.notice(nick, "You're not currently playing.") return - + if var.PHASE != "day": cli.notice(nick, ("Shooting is only allowed during the day. "+ "Please wait patiently for morning.")) @@ -1762,24 +1760,24 @@ def shoot(cli, nick, chann_, rest): if victim == nick: cli.notice(nick, "You are holding it the wrong way.") return - + wolfshooter = nick in var.ROLES["wolf"]+var.ROLES["werecrow"]+var.ROLES["traitor"] - + if wolfshooter and nick in var.WOLF_GUNNERS: var.WOLF_GUNNERS[nick] -= 1 else: var.GUNNERS[nick] -= 1 - + rand = random.random() if nick in var.ROLES["village drunk"]: chances = var.DRUNK_GUN_CHANCES else: chances = var.GUN_CHANCES - + wolfvictim = victim in var.ROLES["wolf"]+var.ROLES["werecrow"] if rand <= chances[0] and not (wolfshooter and wolfvictim): # didn't miss or suicide # and it's not a wolf shooting another wolf - + cli.msg(chan, ("\u0002{0}\u0002 shoots \u0002{1}\u0002 with "+ "a silver bullet!").format(nick, victim)) var.LOGGER.logMessage("{0} shoots {1} with a silver bullet!".format(nick, victim)) @@ -1858,7 +1856,7 @@ def kill(cli, nick, rest): return pl = var.list_players() pll = [x.lower() for x in pl] - + matches = 0 for player in pll: if victim == player: @@ -1871,7 +1869,7 @@ def kill(cli, nick, rest): if matches != 1: pm(cli, nick, "\u0002{0}\u0002 is currently not playing.".format(victim)) return - + victim = pl[pll.index(target)] if victim == nick: pm(cli, nick, "Suicide is bad. Don't do it.") @@ -2035,7 +2033,7 @@ def investigate(cli, nick, rest): # The detective's identity is compromised! for badguy in var.ROLES["wolf"] + var.ROLES["werecrow"] + var.ROLES["traitor"]: pm(cli, badguy, ("Someone accidentally drops a paper. The paper reveals "+ - "that \u0002{0}\u0002 is the detective!").format(nick)) + "that \u0002{0}\u0002 is the detective!").format(nick)) var.LOGGER.logBare(nick, "PAPERDROP") @@ -2178,8 +2176,8 @@ def mass_privmsg(cli, targets, msg, notice = False): cli.msg(bgs, msg) else: cli.notice(bgs, msg) - - + + @pmcmd("") def relay(cli, nick, rest): @@ -2191,20 +2189,20 @@ def relay(cli, nick, rest): if len(badguys) > 1: if nick in badguys: badguys.remove(nick) # remove self from list - + if rest.startswith("\01ACTION"): rest = rest[7:-1] - mass_privmsg(cli, [guy for guy in badguys + mass_privmsg(cli, [guy for guy in badguys if (guy in var.PLAYERS and var.PLAYERS[guy]["cloak"] not in var.SIMPLE_NOTIFY)], "\02{0}\02{1}".format(nick, rest)) - mass_privmsg(cli, [guy for guy in badguys + mass_privmsg(cli, [guy for guy in badguys if (guy in var.PLAYERS and var.PLAYERS[guy]["cloak"] in var.SIMPLE_NOTIFY)], nick+rest, True) else: - mass_privmsg(cli, [guy for guy in badguys + mass_privmsg(cli, [guy for guy in badguys if (guy in var.PLAYERS and var.PLAYERS[guy]["cloak"] not in var.SIMPLE_NOTIFY)], "\02{0}\02 says: {1}".format(nick, rest)) - mass_privmsg(cli, [guy for guy in badguys + mass_privmsg(cli, [guy for guy in badguys if (guy in var.PLAYERS and var.PLAYERS[guy]["cloak"] in var.SIMPLE_NOTIFY)], "\02{0}\02 says: {1}".format(nick, rest), True) @@ -2245,7 +2243,7 @@ def transition_night(cli): var.TIMERS["night"] = t var.TIMERS["night"].daemon = True t.start() - + if var.NIGHT_TIME_WARN > 0: t2 = threading.Timer(var.NIGHT_TIME_WARN, night_warn, [cli, var.NIGHT_ID]) var.TIMERS["night_warn"] = t2 @@ -2257,7 +2255,7 @@ def transition_night(cli): wolves = var.ROLES["wolf"]+var.ROLES["traitor"]+var.ROLES["werecrow"] for wolf in wolves: normal_notify = wolf in var.PLAYERS and var.PLAYERS[wolf]["cloak"] not in var.SIMPLE_NOTIFY - + if normal_notify: if wolf in var.ROLES["wolf"]: pm(cli, wolf, ('You are a \u0002wolf\u0002. It is your job to kill all the '+ @@ -2277,8 +2275,8 @@ def transition_night(cli): else: role = var.get_role(wolf) pm(cli, wolf, "You are a \02{0}\02.".format("cursed traitor" if role == "traitor" and wolf in var.CURSED else role)) # !simple - - + + pl = ps[:] random.shuffle(pl) pl.remove(wolf) # remove self from list @@ -2301,7 +2299,7 @@ def transition_night(cli): pl = ps[:] random.shuffle(pl) pl.remove(seer) # remove self from list - + if seer in var.PLAYERS and var.PLAYERS[seer]["cloak"] not in var.SIMPLE_NOTIFY: pm(cli, seer, ('You are a \u0002seer\u0002. '+ 'It is your job to detect the wolves, you '+ @@ -2336,7 +2334,7 @@ def transition_night(cli): else: cli.notice(g_angel, "You are a \02guardian angel\02.") # !simple pm(cli, g_angel, "Players: " + ", ".join(pl)) - + for dttv in var.ROLES["detective"]: pl = ps[:] random.shuffle(pl) @@ -2376,7 +2374,7 @@ def transition_night(cli): gun_msg = gun_msg.format(str(var.GUNNERS[g]) + " bullets") else: continue - + pm(cli, g, gun_msg) dmsg = (daydur_msg + "It is now nighttime. All players "+ @@ -2398,20 +2396,15 @@ def cgamemode(cli, *args): chan = botconfig.CHANNEL if var.ORIGINAL_SETTINGS: # needs reset reset_settings() - + for arg in args: modeargs = arg.split("=", 1) - - if len(modeargs) < 2: # no equal sign in the middle of the arg - cli.msg(botconfig.CHANNEL, "Invalid syntax.") - return False - - modeargs[0] = modeargs[0].strip() + + modeargs = map(strip, modeargs) if modeargs[0] in var.GAME_MODES.keys(): md = modeargs.pop(0) - modeargs[0] = modeargs[0].strip() try: - gm = var.GAME_MODES[md](modeargs[0]) + gm = var.GAME_MODES[md](*modeargs) for attr in dir(gm): val = getattr(gm, attr) if (hasattr(var, attr) and not callable(val) @@ -2429,9 +2422,9 @@ def cgamemode(cli, *args): @cmd("start") def start(cli, nick, chann_, rest): """Starts a game of Werewolf""" - + chan = botconfig.CHANNEL - + villagers = var.list_players() pl = villagers[:] @@ -2444,7 +2437,7 @@ def start(cli, nick, chann_, rest): if nick not in villagers and nick != chan: cli.notice(nick, "You're currently not playing.") return - + now = datetime.now() var.GAME_START_TIME = now # Only used for the idler checker dur = int((var.CAN_START_TIME - now).total_seconds()) @@ -2456,9 +2449,9 @@ def start(cli, nick, chann_, rest): cli.msg(chan, "{0}: \u0002{1}\u0002 or more players are required to play.".format(nick, var.MIN_PLAYERS)) return - for pcount in range(len(villagers), var.MIN_PLAYERS - 1, -1): - addroles = var.ROLES_GUIDE.get(pcount) - if addroles: + for index in range(len(var.ROLES_INDEX), -1, -1): + if var.ROLES_INDEX[index] < len(villagers): + addroles = {k:v[index] for k,v in var.ROLES_GUIDE} break else: cli.msg(chan, "{0}: No game settings are defined for \u0002{1}\u0002 player games.".format(nick, len(villagers))) @@ -2468,13 +2461,11 @@ def start(cli, nick, chann_, rest): if 'join' in var.TIMERS: var.TIMERS['join'].cancel() del var.TIMERS['join'] - + if var.ORIGINAL_SETTINGS: # Custom settings while True: - wvs = (addroles[var.INDEX_OF_ROLE["wolf"]] + - addroles[var.INDEX_OF_ROLE["traitor"]]) - if len(villagers) < (sum(addroles) - addroles[var.INDEX_OF_ROLE["gunner"]] - - addroles[var.INDEX_OF_ROLE["cursed villager"]]): + wvs = sum(addroles[r] for r in var.WOLFCHAT_ROLES) + if len(villagers) < (sum(addroles.values()) - sum([addroles[r] for r in var.TEMPLATE_RESTRICTIONS.keys()])): cli.msg(chan, "There are too few players in the "+ "game to use the custom roles.") elif not wvs: @@ -2488,7 +2479,7 @@ def start(cli, nick, chann_, rest): var.PHASE = "join" return - + if var.ADMIN_TO_PING: if "join" in COMMANDS.keys(): COMMANDS["join"] = [lambda *spam: cli.msg(chan, "This command has been disabled by an admin.")] @@ -2496,7 +2487,6 @@ def start(cli, nick, chann_, rest): COMMANDS["start"] = [lambda *spam: cli.msg(chan, "This command has been disabled by an admin.")] var.ROLES = {} - var.CURSED = [] var.GUNNERS = {} var.WOLF_GUNNERS = {} var.SEEN = [] @@ -2505,10 +2495,8 @@ def start(cli, nick, chann_, rest): var.GUARDED = {} var.HVISITED = {} - villager_roles = ("gunner", "cursed villager") - for i, count in enumerate(addroles): - role = var.ROLE_INDICES[i] - if role in villager_roles: + for role, count in addroles.items(): + if role in var.TEMPLATE_RESTRICTIONS.keys(): var.ROLES[role] = [None] * count continue # We deal with those later, see below selected = random.sample(villagers, count) @@ -2516,61 +2504,50 @@ def start(cli, nick, chann_, rest): for x in selected: villagers.remove(x) - # Now for the villager roles - # Select cursed (just a villager) - if var.ROLES["cursed villager"]: - possiblecursed = pl[:] - for cannotbe in (var.ROLES["wolf"] + var.ROLES["werecrow"] + - var.ROLES["seer"]): - # traitor can be cursed - possiblecursed.remove(cannotbe) - - var.CURSED = random.sample(possiblecursed, len(var.ROLES["cursed villager"])) - del var.ROLES["cursed villager"] - - # Select gunner (also a villager) - if var.ROLES["gunner"]: - + # Now for the templates + for template, restrictions in var.TEMPLATE_RESTRICTIONS.items(): + if template == "sharpshooter": + continue # sharpshooter gets applied specially possible = pl[:] - for cannotbe in (var.ROLES["wolf"] + var.ROLES["werecrow"] + - var.ROLES["traitor"]): - possible.remove(cannotbe) - - for csd in var.CURSED: # cursed cannot be gunner - if csd in possible: - possible.remove(csd) - - for gnr in random.sample(possible, len(var.ROLES["gunner"])): - if gnr in var.ROLES["village drunk"]: - var.GUNNERS[gnr] = (var.DRUNK_SHOTS_MULTIPLIER * - math.ceil(var.SHOTS_MULTIPLIER * len(pl))) - else: - var.GUNNERS[gnr] = math.ceil(var.SHOTS_MULTIPLIER * len(pl)) - del var.ROLES["gunner"] + for cannotbe in [p for r in restrictions for p in var.ROLES[r]]: + possible.removed(cannotbe) + var.ROLES[template] = random.sample(possible, len(var.ROLES[template])) + + # Handle gunner + cannot_be_sharpshooter = [p for r in var.TEMPLATE_RESTRICTIONS["sharpshooter"] for p in var.ROLES[r]] + for gunner in var.GUNNER_LIST: + if gunner in var.ROLES["village drunk"]: + var.GUNNERS[gunner] = (var.DRUNK_SHOTS_MULTIPLIER * math.ceil(var.SHOTS_MULTIPLIER * len(pl))) + elif gunner not in cannot_be_sharpshooter and random.random() <= var.SHARPSHOOTER_CHANCE: + var.GUNNERS[gunner] = math.ceil(var.SHARPSHOOTER_MULTIPLIER * len(pl)) + var.ROLES["gunner"].remove(gunner) + var.ROLES["sharpshooter"].append(gunner) + else: + var.GUNNERS[gunner] = math.ceil(var.SHOTS_MULTIPLIER * len(pl)) var.SPECIAL_ROLES["goat herder"] = [] if var.GOAT_HERDER: var.SPECIAL_ROLES["goat herder"] = [ nick ] - var.ROLES["villager"] = villagers + var.ROLES[var.DEFAULT_ROLE] = villagers cli.msg(chan, ("{0}: Welcome to Werewolf, the popular detective/social party "+ "game (a theme of Mafia).").format(", ".join(pl))) cli.mode(chan, "+m") var.ORIGINAL_ROLES = copy.deepcopy(var.ROLES) # Make a copy - + var.DAY_TIMEDELTA = timedelta(0) var.NIGHT_TIMEDELTA = timedelta(0) var.DAY_START_TIME = datetime.now() var.NIGHT_START_TIME = datetime.now() var.LAST_PING = None - + var.LOGGER.log("Game Start") var.LOGGER.logBare("GAME", "BEGIN", nick) var.LOGGER.logBare(str(len(pl)), "PLAYERCOUNT") - + var.LOGGER.log("***") var.LOGGER.log("ROLES: ") for rol in var.ROLES: @@ -2582,23 +2559,16 @@ def start(cli, nick, chann_, rest): r.append(rwu) r = " ".join(r) var.LOGGER.log("{0}: {1}".format(r, ", ".join(var.ROLES[rol]))) - + for plr in var.ROLES[rol]: var.LOGGER.logBare(plr, "ROLE", rol) - - if var.CURSED: - var.LOGGER.log("Cursed Villagers: "+", ".join(var.CURSED)) - - for plr in var.CURSED: - var.LOGGER.logBare(plr+" ROLE cursed villager") + if var.GUNNERS: var.LOGGER.log("Villagers With Bullets: "+", ".join([x+"("+str(y)+")" for x,y in var.GUNNERS.items()])) - for plr in var.GUNNERS: - var.LOGGER.logBare(plr, "ROLE gunner") - - var.LOGGER.log("***") - - var.PLAYERS = {plr:dict(var.USERS[plr]) for plr in pl if plr in var.USERS} + + var.LOGGER.log("***") + + var.PLAYERS = {plr:dict(var.USERS[plr]) for plr in pl if plr in var.USERS} if not var.START_WITH_DAY: var.FIRST_NIGHT = True @@ -2616,8 +2586,8 @@ def start(cli, nick, chann_, rest): reapertimer.daemon = True reapertimer.start() - - + + @hook("error") def on_error(cli, pfx, msg): if msg.endswith("(Excess Flood)"): @@ -2634,11 +2604,11 @@ def fstasis(cli, nick, chan, rest): if data: lusers = {k.lower(): v for k, v in var.USERS.items()} user = data[0].lower() - + if user not in lusers: pm(cli, nick, "Sorry, {0} cannot be found.".format(data[0])) return - + cloak = lusers[user]['cloak'] if len(data) == 1: @@ -2652,7 +2622,7 @@ def fstasis(cli, nick, chan, rest): except ValueError: pm(cli, nick, "Sorry, invalid integer argument.") return - + if amt > 0: var.STASISED[cloak] = amt msg = "{0} ({1}) is now in stasis for {2} games.".format(data[0], cloak, amt) @@ -2686,10 +2656,10 @@ def fstasis_pm(cli, nick, rest): def wait(cli, nick, chann_, rest): """Increase the wait time (before !start can be used)""" pl = var.list_players() - + chan = botconfig.CHANNEL - - + + if var.PHASE == "none": cli.notice(nick, "No game is currently running.") return @@ -2718,10 +2688,10 @@ def wait(cli, nick, chann_, rest): def fwait(cli, nick, chann_, rest): pl = var.list_players() - + chan = botconfig.CHANNEL - - + + if var.PHASE == "none": cli.notice(nick, "No game is currently running.") return @@ -2738,7 +2708,7 @@ def fwait(cli, nick, chann_, rest): return else: extra = var.EXTRA_WAIT - + now = datetime.now() if now > var.CAN_START_TIME: var.CAN_START_TIME = now + timedelta(seconds=extra) @@ -2815,7 +2785,7 @@ def get_help(cli, rnick, rest): return # if command was not found, or if no command was given: for name, fn in COMMANDS.items(): - if (name and not fn[0].admin_only and + if (name and not fn[0].admin_only and not fn[0].owner_only and name not in fn[0].aliases): fns.append("\u0002"+name+"\u0002") afns = [] @@ -2840,7 +2810,7 @@ def on_invite(cli, nick, something, chan): if chan == botconfig.CHANNEL: cli.join(chan) - + def is_admin(cloak): return bool([ptn for ptn in botconfig.OWNERS+botconfig.ADMINS if fnmatch.fnmatch(cloak.lower(), ptn.lower())]) @@ -2850,16 +2820,16 @@ def show_admins(cli, nick, chan, rest): """Pings the admins that are available.""" admins = [] pl = var.list_players() - + if (var.LAST_ADMINS and var.LAST_ADMINS + timedelta(seconds=var.ADMINS_RATE_LIMIT) > datetime.now()): cli.notice(nick, ("This command is rate-limited. " + "Please wait a while before using it again.")) return - + if not (var.PHASE in ("day", "night") and nick not in pl): var.LAST_ADMINS = datetime.now() - + if var.ADMIN_PINGING: return var.ADMIN_PINGING = True @@ -2878,7 +2848,7 @@ def show_admins(cli, nick, chan, rest): if not var.ADMIN_PINGING: return admins.sort(key=lambda x: x.lower()) - + if chan == nick: pm(cli, nick, "Available admins: "+" ".join(admins)) elif var.PHASE in ("day", "night") and nick not in pl: @@ -2900,11 +2870,11 @@ def show_admins_pm(cli, nick, rest): @cmd("coin") def coin(cli, nick, chan, rest): """It's a bad idea to base any decisions on this command.""" - + if var.PHASE in ("day", "night") and nick not in var.list_players(): cli.notice(nick, "You may not use this command right now.") return - + cli.msg(chan, "\2{0}\2 tosses a coin into the air...".format(nick)) var.LOGGER.logMessage("{0} tosses a coin into the air...".format(nick)) coin = random.choice(["heads", "tails"]) @@ -2935,7 +2905,7 @@ def pony(cli, nick, chan, rest): @cmd("time") def timeleft(cli, nick, chan, rest): """Returns the time left until the next day/night transition.""" - + if var.PHASE not in ("day", "night"): cli.notice(nick, "No game is currently running.") return @@ -2961,9 +2931,9 @@ def timeleft(cli, nick, chan, rest): else: remaining = int(var.NIGHT_TIME_LIMIT - (datetime.now() - var.NIGHT_START_TIME).total_seconds()) - + #Check if timers are actually enabled - if (var.PHASE == "day") and ((var.STARTED_DAY_PLAYERS <= var.SHORT_DAY_PLAYERS and + if (var.PHASE == "day") and ((var.STARTED_DAY_PLAYERS <= var.SHORT_DAY_PLAYERS and var.SHORT_DAY_LIMIT_WARN == 0) or (var.DAY_TIME_LIMIT_WARN == 0 and var.STARTED_DAY_PLAYERS > var.SHORT_DAY_PLAYERS)): msg = "Day timers are currently disabled." @@ -2971,7 +2941,7 @@ def timeleft(cli, nick, chan, rest): msg = "Night timers are currently disabled." else: msg = "There is \x02{0[0]:0>2}:{0[1]:0>2}\x02 remaining until {1}.".format( - divmod(remaining, 60), "sunrise" if var.PHASE == "night" else "sunset") + divmod(remaining, 60), "sunrise" if var.PHASE == "night" else "sunset") if nick == chan: pm(cli, nick, msg) @@ -3023,20 +2993,23 @@ def myrole(cli, nick, chan, rest): if var.PHASE in ("none", "join"): cli.notice(nick, "No game is currently running.") return - + ps = var.list_players() if nick not in ps: cli.notice(nick, "You're currently not playing.") return - + pm(cli, nick, "You are a \02{0}\02.".format(var.get_role(nick))) - + # Check for gun/bullets if nick in var.GUNNERS and var.GUNNERS[nick]: + role = "gunner" + if nick in var.ROLES["sharpshooter"]: + role = "sharpshooter" if var.GUNNERS[nick] == 1: - pm(cli, nick, "You have a \02gun\02 with {0} {1}.".format(var.GUNNERS[nick], "bullet")) + pm(cli, nick, "You are a {0} and have a \02gun\02 with {1} {2}.".format(role, var.GUNNERS[nick], "bullet")) else: - pm(cli, nick, "You have a \02gun\02 with {0} {1}.".format(var.GUNNERS[nick], "bullets")) + pm(cli, nick, "You are a {0} and have a \02gun\02 with {1} {2}.".format(role, var.GUNNERS[nick], "bullets")) elif nick in var.WOLF_GUNNERS and var.WOLF_GUNNERS[nick]: if var.WOLF_GUNNERS[nick] == 1: pm(cli, nick, "You have a \02gun\02 with {0} {1}.".format(var.WOLF_GUNNERS[nick], "bullet")) @@ -3046,12 +3019,12 @@ def myrole(cli, nick, chan, rest): @pmcmd("myrole") def myrole_pm(cli, nick, rest): myrole(cli, nick, "", rest) - + def aftergame(cli, rawnick, rest): """Schedule a command to be run after the game by someone.""" chan = botconfig.CHANNEL nick = parse_nick(rawnick)[0] - + rst = re.split(" +", rest) cmd = rst.pop(0).lower().replace(botconfig.CMD_CHAR, "", 1).strip() @@ -3066,16 +3039,16 @@ def aftergame(cli, rawnick, rest): else: cli.notice(nick, "That command was not found.") return - + if var.PHASE == "none": do_action() return - + cli.msg(chan, ("The command \02{0}\02 has been scheduled to run "+ "after this game by \02{1}\02.").format(cmd, nick)) var.AFTER_FLASTGAME = do_action - + @cmd("faftergame", admin_only=True, raw_nick=True) def _faftergame(cli, nick, chan, rest): @@ -3083,9 +3056,9 @@ def _faftergame(cli, nick, chan, rest): cli.notice(parse_nick(nick)[0], "Incorrect syntax for this command.") return aftergame(cli, nick, rest) - - - + + + @pmcmd("faftergame", admin_only=True, raw_nick=True) def faftergame(cli, nick, rest): _faftergame(cli, nick, botconfig.CHANNEL, rest) @@ -3100,13 +3073,13 @@ def fghost(cli, nick, *rest): @cmd("funghost", owner_only=True) def funghost(cli, nick, *rest): cli.mode(botconfig.CHANNEL, "-v", nick) - + @pmcmd("flastgame", admin_only=True, raw_nick=True) def flastgame(cli, nick, rest): """This command may be used in the channel or in a PM, and it disables starting or joining a game. !flastgame """ rawnick = nick nick, _, __, cloak = parse_nick(rawnick) - + chan = botconfig.CHANNEL if var.PHASE != "join": if "join" in COMMANDS.keys(): @@ -3116,18 +3089,18 @@ def flastgame(cli, nick, rest): if "start" in COMMANDS.keys(): del COMMANDS["start"] cmd("join")(lambda *spam: cli.msg(chan, "This command has been disabled by an admin.")) - + cli.msg(chan, "Starting a new game has now been disabled by \02{0}\02.".format(nick)) var.ADMIN_TO_PING = nick - + if rest.strip(): aftergame(cli, rawnick, rest) - + @cmd("flastgame", admin_only=True, raw_nick=True) def _flastgame(cli, nick, chan, rest): flastgame(cli, nick, rest) - - + + @cmd("gamestats", "gstats") def game_stats(cli, nick, chan, rest): """Gets the game stats for a given game size or lists game totals for all game sizes if no game size is given.""" @@ -3143,7 +3116,7 @@ def game_stats(cli, nick, chan, rest): if var.PHASE not in ("none", "join"): cli.notice(nick, "Wait until the game is over to view stats.") return - + # List all games sizes and totals if no size is given. if rest == "": if chan == nick: @@ -3151,13 +3124,13 @@ def game_stats(cli, nick, chan, rest): else: cli.msg(chan, var.get_game_totals()) return - + # Check for invalid input rest = rest.strip() if not rest.isdigit() or int(rest) > var.MAX_PLAYERS or int(rest) < var.MIN_PLAYERS: cli.notice(nick, "Please enter an integer between {0} and {1}.".format(var.MIN_PLAYERS, var.MAX_PLAYERS)) return - + # Attempt to find game stats for the given game size. if chan == nick: pm(cli, nick, var.get_game_stats(int(rest))) @@ -3167,7 +3140,7 @@ def game_stats(cli, nick, chan, rest): @pmcmd("gamestats", "gstats") def game_stats_pm(cli, nick, rest): game_stats(cli, nick, nick, rest) - + @cmd("playerstats", "pstats", "player", "p") def player_stats(cli, nick, chan, rest): """Gets the stats for the given player and role or a list of role totals if no role is given.""" @@ -3183,7 +3156,7 @@ def player_stats(cli, nick, chan, rest): if var.PHASE not in ("none", "join"): cli.notice(nick, "Wait until the game is over to view stats.") return - + # Check if we have enough parameters. params = rest.split() if len(params) < 1: @@ -3199,7 +3172,7 @@ def player_stats(cli, nick, chan, rest): return else: acc = user - + # List the player's total games for all roles if no role is given if len(params) < 2: if chan == nick: @@ -3207,17 +3180,17 @@ def player_stats(cli, nick, chan, rest): else: cli.msg(chan, var.get_player_totals(acc)) else: - role = " ".join(params[1:]) + role = " ".join(params[1:]) # Attempt to find the player's stats. if chan == nick: pm(cli, nick, var.get_player_stats(acc, role)) else: cli.msg(chan, var.get_player_stats(acc, role)) - + @pmcmd("playerstats", "pstats", "player", "p") def player_stats_pm(cli, nick, rest): player_stats(cli, nick, nick, rest) - + @cmd("fpull", admin_only=True) def fpull(cli, nick, chan, rest): output = None @@ -3242,7 +3215,7 @@ def fsend(cli, nick, rest): print('{0} - {1} fsend - {2}'.format(time.strftime('%Y-%m-%dT%H:%M:%S%z'), nick, rest)) cli.send(rest) - + before_debug_mode_commands = list(COMMANDS.keys()) before_debug_mode_pmcommands = list(PM_COMMANDS.keys()) @@ -3264,9 +3237,9 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: cli.msg(chan, a[0:500]) except Exception as e: cli.msg(chan, str(type(e))+":"+str(e)) - - - + + + @cmd("exec", owner_only = True) @pmcmd("exec", owner_only = True) def py(cli, nick, *rest): @@ -3280,17 +3253,14 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: except Exception as e: cli.msg(chan, str(type(e))+":"+str(e)) - + @cmd("revealroles", admin_only=True) def revroles(cli, nick, chan, rest): if var.PHASE != "none": cli.msg(chan, str(var.ROLES)) - if var.PHASE in ('night','day'): - cli.msg(chan, "Cursed: "+str(var.CURSED)) - cli.msg(chan, "Gunners: "+str(list(var.GUNNERS.keys()))) - - + + @cmd("fgame", admin_only=True) def game(cli, nick, chan, rest): pl = var.list_players() @@ -3308,7 +3278,7 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: if cgamemode(cli, *re.split(" +",rest)): cli.msg(chan, ("\u0002{0}\u0002 has changed the "+ "game settings successfully.").format(nick)) - + def fgame_help(args = ""): args = args.strip() if not args: @@ -3343,12 +3313,12 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: cmd = rst.pop(0).lower().replace(botconfig.CMD_CHAR, "", 1) did = False if PM_COMMANDS.get(cmd) and not PM_COMMANDS[cmd][0].owner_only: - if (PM_COMMANDS[cmd][0].admin_only and nick in var.USERS and + if (PM_COMMANDS[cmd][0].admin_only and nick in var.USERS and not is_admin(var.USERS[nick]["cloak"])): # Not a full admin cli.notice(nick, "Only full admins can force an admin-only command.") return - + for fn in PM_COMMANDS[cmd]: if fn.raw_nick: continue @@ -3361,12 +3331,12 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: #if var.PHASE == "night": <- Causes problems with night starting twice. # chk_nightdone(cli) elif COMMANDS.get(cmd) and not COMMANDS[cmd][0].owner_only: - if (COMMANDS[cmd][0].admin_only and nick in var.USERS and + if (COMMANDS[cmd][0].admin_only and nick in var.USERS and not is_admin(var.USERS[nick]["cloak"])): # Not a full admin cli.notice(nick, "Only full admins can force an admin-only command.") return - + for fn in COMMANDS[cmd]: if fn.raw_nick: continue @@ -3378,8 +3348,8 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: cli.msg(chan, "Not possible with this command.") else: cli.msg(chan, "That command was not found.") - - + + @cmd("rforce", admin_only=True) def rforcepm(cli, nick, chan, rest): rst = re.split(" +",rest) @@ -3388,7 +3358,7 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: return who = rst.pop(0).strip().lower() who = who.replace("_", " ") - + if (who not in var.ROLES or not var.ROLES[who]) and (who != "gunner" or var.PHASE in ("none", "join")): cli.msg(chan, nick+": invalid role") @@ -3400,12 +3370,12 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: cmd = rst.pop(0).lower().replace(botconfig.CMD_CHAR, "", 1) if PM_COMMANDS.get(cmd) and not PM_COMMANDS[cmd][0].owner_only: - if (PM_COMMANDS[cmd][0].admin_only and nick in var.USERS and + if (PM_COMMANDS[cmd][0].admin_only and nick in var.USERS and not is_admin(var.USERS[nick]["cloak"])): # Not a full admin cli.notice(nick, "Only full admins can force an admin-only command.") return - + for fn in PM_COMMANDS[cmd]: for guy in tgt[:]: fn(cli, guy, " ".join(rst)) @@ -3413,12 +3383,12 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: #if var.PHASE == "night": <- Causes problems with night starting twice. # chk_nightdone(cli) elif cmd.lower() in COMMANDS.keys() and not COMMANDS[cmd][0].owner_only: - if (COMMANDS[cmd][0].admin_only and nick in var.USERS and + if (COMMANDS[cmd][0].admin_only and nick in var.USERS and not is_admin(var.USERS[nick]["cloak"])): # Not a full admin cli.notice(nick, "Only full admins can force an admin-only command.") return - + for fn in COMMANDS[cmd]: for guy in tgt[:]: fn(cli, guy, chan, " ".join(rst)) @@ -3448,12 +3418,8 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: if who == botconfig.NICK or not who: cli.msg(chan, "No.") return - if rol not in var.ROLES.keys(): - pl = var.list_players() - if var.PHASE not in ("night", "day"): - cli.msg(chan, "This is only allowed in game.") - return - if rol.startswith("gunner"): + if rol in var.ROLES.keys() or rol.startswith("gunner") or rol.startswith("sharpshooter"): + if rol.startswith("gunner") or rol.startswith("sharpshooter"): rolargs = re.split(" +",rol, 1) if len(rolargs) == 2 and rolargs[1].isdigit(): if len(rolargs[1]) < 7: @@ -3462,19 +3428,12 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: else: var.GUNNERS[who] = 999 var.WOLF_GUNNERS[who] = 999 - else: + elif rol.startswith("gunner"): var.GUNNERS[who] = math.ceil(var.SHOTS_MULTIPLIER * len(pl)) - if who not in pl: - var.ROLES["villager"].append(who) - elif rol == "cursed villager": - if who not in var.CURSED: - var.CURSED.append(who) - if who not in pl: - var.ROLES["villager"].append(who) - else: - cli.msg(chan, "Not a valid role.") - return - cli.msg(chan, "Operation successful.") + else: + var.GUNNERS[who] = math.ceil(var.SHARPSHOOTER_MULTIPLIER * len(pl)) + else: + cli.msg(chan, "Not a valid role.") return if who in var.list_players(): var.del_player(who) @@ -3483,10 +3442,10 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: if var.PHASE not in ('none','join'): chk_win(cli) - + if botconfig.ALLOWED_NORMAL_MODE_COMMANDS and not botconfig.DEBUG_MODE: for comd in list(COMMANDS.keys()): - if (comd not in before_debug_mode_commands and + if (comd not in before_debug_mode_commands and comd not in botconfig.ALLOWED_NORMAL_MODE_COMMANDS): del COMMANDS[comd] for pmcomd in list(PM_COMMANDS.keys()): diff --git a/settings/wolfgame.py b/settings/wolfgame.py index 49cc4ac..fb2c8a9 100644 --- a/settings/wolfgame.py +++ b/settings/wolfgame.py @@ -12,6 +12,7 @@ GSTATS_RATE_LIMIT = 15 PSTATS_RATE_LIMIT = 15 TIME_RATE_LIMIT = 60 SHOTS_MULTIPLIER = .12 # ceil(shots_multiplier * len_players) = bullets given +SHARPSHOOTER_MULTIPLIER = 0.06 MIN_PLAYERS = 4 MAX_PLAYERS = 21 DRUNK_SHOTS_MULTIPLIER = 3 @@ -24,6 +25,9 @@ JOIN_TIME_LIMIT = 3600 SHORT_DAY_PLAYERS = 6 # Number of players left to have a short day SHORT_DAY_LIMIT_WARN = 400 SHORT_DAY_LIMIT_CHANGE = 120 +# If time lord is lynched, the day timer gets set to this instead +TIME_LORD_WARN = 60 +TIME_LORD_CHANGE = 30 KILL_IDLE_TIME = 300 WARN_IDLE_TIME = 180 PART_GRACE_TIME = 30 @@ -38,6 +42,12 @@ GOAT_HERDER = True SELF_LYNCH_ALLOWED = True HIDDEN_TRAITOR = True +VENGEFUL_GHOST_KNOWS_ROLES = True +WOLF_MAYOR = True +BODYGUARD_CAN_GUARD_SELF = True +START_WITH_DAY = False +WOLF_STEALS_GUN = True # at night, the wolf can steal steal the victim's bullets +ROLE_REVEAL = True CARE_BOLD = False CARE_COLOR = False @@ -47,48 +57,97 @@ KILL_BOLD = False LOG_FILENAME = "" BARE_LOG_FILENAME = "" - # HIT MISS SUICIDE -GUN_CHANCES = ( 5/7 , 1/7 , 1/7 ) -DRUNK_GUN_CHANCES = ( 2/7 , 3/7 , 2/7 ) -MANSLAUGHTER_CHANCE = 2/5 # ACCIDENTAL HEADSHOT (FATAL) + # HIT MISS SUICIDE HEADSHOT +GUN_CHANCES = ( 5/7 , 1/7 , 1/7 , 2/5 ) +WOLF_GUN_CHANCES = ( 5/7 , 1/7 , 1/7 , 2/5 ) +DRUNK_GUN_CHANCES = ( 2/7 , 3/7 , 2/7 , 2/5 ) +SHARPSHOOTER_GUN_CHANCES = ( 1 , 0 , 0 , 1 ) GUNNER_KILLS_WOLF_AT_NIGHT_CHANCE = 1/4 -GUARDIAN_ANGEL_DIES_CHANCE = 1/2 +GUARDIAN_ANGEL_DIES_CHANCE = 0 +BODYGUARD_DIES_CHANCE = 0 DETECTIVE_REVEALED_CHANCE = 2/5 +SHARPSHOOTER_CHANCE = 1/5 # if sharpshooter is enabled, chance that a gunner will become a sharpshooter instead -################################################################################################################# -# 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 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), ## - 8 : ( 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 ), ## - 10 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 ), ## - 12 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 ), ## - 15 : ( 1 , 3 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 ), ## - 17 : ( 1 , 3 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), ## - 18 : ( 1 , 3 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), ## - 20 : ( 1 , 4 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), ## - None : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )} ## -################################################################################################################# -# Notes: ## -################################################################################################################# +AMNESIAC_NIGHTS = 3 # amnesiac gets to know their actual role on this night +BUREAUCRAT_VOTES = 2 # bureaucrat votes count for this many normal votes + + # DEATH PROTECTION REVEALING NARCOLEPSY SILENCE DESPERATION +TOTEM_CHANCES = ( 1/6 , 1/6 , 1/6 , 1/6 , 1/6 , 1/6 ) GAME_MODES = {} AWAY = ['services.', 'services.int'] # cloaks of people who are away. SIMPLE_NOTIFY = [] # cloaks of people who !simple, who want everything /notice'd -ROLE_INDICES = {0 : "seer", - 1 : "wolf", - 2 : "cursed villager", - 3 : "village drunk", - 4 : "harlot", - 5 : "traitor", - 6 : "gunner", - 7 : "werecrow", - 8 : "guardian angel", - 9 : "detective"} +# TODO: move this to a game mode called "fixed" once we implement a way to randomize roles (and have that game mode be called "random") +DEFAULT_ROLE = "villager" +ROLES_INDEX = ( 4 , 6 , 8 , 10 , 12 , 15 , 17 , 18 , 20 ) +ROLES_GUIDE = {# village roles + "villager" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "seer" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), + "oracle" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "village drunk" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), + "harlot" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), + "guardian angel" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ), + "bodyguard" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "detective" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ), + "village elder" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "time lord" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "matchmaker" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "mad scientist" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "hunter" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "shaman" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + # wolf roles + "wolf" : ( 1 , 1 , 1 , 2 , 2 , 3 , 3 , 3 , 4 ), + "traitor" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), + "werecrow" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ), + "cultist" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "minion" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "hag" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "wolf cub" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "sorcerer" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + # neutral roles + "lycan" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "vengeful ghost" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "clone" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "crazed shaman" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "fool" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "monster" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + # templates + "cursed villager" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 ), + "gunner" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ), + # NB: for sharpshooter, numbers can't be higher than gunner, since gunners get converted to sharpshooters. This is the MAX number of gunners that can be converted. + "sharpshooter" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "mayor" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "assassin" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "amnesiac" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + "bureaucrat" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), + } -INDEX_OF_ROLE = dict((v,k) for k,v in ROLE_INDICES.items()) +# Harlot dies when visiting, gunner kills when shooting, GA and bodyguard have a chance at dying when guarding +WOLF_ROLES = ["wolf", "werecrow", "wolf cub"] +# Access to wolfchat, and counted towards the # of wolves vs villagers when determining if a side has won +WOLFCHAT_ROLES = ["wolf", "traitor", "werecrow", "hag", "wolf cub", "sorceror"] +# Wins with the wolves, even if the roles are not necessarily wolves themselves +WOLFTEAM_ROLES = ["wolf", "traitor", "werecrow", "hag", "wolf cub", "sorceror", "minion", "cultist"] + +# The roles in here are considered templates and will be applied on TOP of other roles. The restrictions are a list of roles that they CANNOT be applied to +# NB: if you want a template to apply to everyone, list it here but make the restrictions an empty list. Templates not listed here are considered full roles instead +TEMPLATE_RESTRICTIONS = {"cursed villager" : ["wolf", "wolf cub", "werecrow", "seer", "fool"], + "gunner" : ["wolf", "traitor", "werecrow", "hag", "wolf cub", "sorcerer", "minion", "cultist", "fool", "cursed villager"], + "sharpshooter" : ["wolf", "traitor", "werecrow", "hag", "wolf cub", "sorcerer", "minion", "cultist", "fool", "cursed villager"], + "mayor" : ["fool"], + "assassin" : ["seer", "harlot", "detective", "bodyguard", "guardian angel", "village drunk", "hunter", "shaman", "crazed shaman", "fool", "mayor"], + "amnesiac" : ["villager", "cultist"], + "bureaucrat" : [], + } +TEMPLATE_KEYS = {"cursed villager" : "CURSED", + "gunner" : "GUNNER_LIST", + "mayor" : "MAYORS", + "assassin" : "ASSASSINS", + "amnesiac" : "AMNESIACS", + "bureaucrat" : "BUREAUCRATS", + } NO_VICTIMS_MESSAGES = ("The body of a young penguin pet is found.", "A pool of blood and wolf paw prints are found.", @@ -98,14 +157,17 @@ LYNCH_MESSAGES = ("The villagers, after much debate, finally decide on lynching "Despite protests, the mob drags their victim to the hanging tree. \u0002{0}\u0002 succumbs to the will of the horde, and is hanged. The villagers have killed a \u0002{1}\u0002.", "Resigned to the inevitable, \u0002{0}\u0002 is led to the gallows. Once the twitching stops, it is discovered that the village lynched a \u0002{1}\u0002.", "Before the rope is pulled, \u0002{0}\u0002, the \u0002{1}\u0002, throws a grenade at the mob. The grenade explodes early.") +LYNCH_MESSAGES_NO_REVEAL = ("The villagers, after much debate, finally decide on lynching \u0002{0}\u0002.", + "Under a lot of noise, the pitchfork-bearing villagers lynch \u0002{0}\u0002.", + "Despite protests, the mob drags their victim to the hanging tree. \u0002{0}\u0002 succumbs to the will of the horde, and is hanged.", + "Resigned to the inevitable, \u0002{0}\u0002 is led to the gallows.", + "Before the rope is pulled, \u0002{0}\u0002 throws a grenade at the mob. The grenade explodes early.") import botconfig RULES = (botconfig.CHANNEL + " channel rules: http://wolf.xnrand.com/rules") # Other settings: -START_WITH_DAY = False -WOLF_STEALS_GUN = True # at night, the wolf can steal steal the victim's bullets OPT_IN_PING = False # instead of !away/!back, users can opt-in to be pinged PING_IN = [] # cloaks of users who have opted in for ping @@ -152,28 +214,13 @@ def game_mode(name): return decor -CHANGEABLE_ROLES = { "seers" : INDEX_OF_ROLE["seer"], - "wolves" : INDEX_OF_ROLE["wolf"], - "cursed" : INDEX_OF_ROLE["cursed villager"], - "drunks" : INDEX_OF_ROLE["village drunk"], - "harlots" : INDEX_OF_ROLE["harlot"], - "traitors" : INDEX_OF_ROLE["traitor"], - "gunners" : INDEX_OF_ROLE["gunner"], - "werecrows" : INDEX_OF_ROLE["werecrow"], - "angels" : INDEX_OF_ROLE["guardian angel"], - "detectives" : INDEX_OF_ROLE["detective"]} - - - - -# TODO: implement game modes +# TODO: implement more game modes @game_mode("roles") class ChangedRolesMode(object): - """Example: !fgame roles=wolves:1,seers:0,angels:1""" + """Example: !fgame roles=wolf:1,seer:0,guardian angel:1""" - def __init__(self, arg): + def __init__(self, arg = ""): self.ROLES_GUIDE = ROLES_GUIDE.copy() - lx = list(ROLES_GUIDE[None]) pairs = arg.split(",") if not pairs: raise InvalidModeException("Invalid syntax for mode roles.") @@ -184,16 +231,29 @@ class ChangedRolesMode(object): role, num = change try: num = int(num) - try: - lx[CHANGEABLE_ROLES[role.lower()]] = num - except KeyError: + if role.lower() in self.ROLES_GUIDE: + self.ROLES_GUIDE[role.lower()] = tuple([num] * len(ROLES_INDEX)) + else: raise InvalidModeException(("The role \u0002{0}\u0002 "+ "is not valid.").format(role)) except ValueError: raise InvalidModeException("A bad value was used in mode roles.") - for k in ROLES_GUIDE.keys(): - self.ROLES_GUIDE[k] = tuple(lx) +@game_mode("evilvillage") +class EvilVillageMode(object): + def __init__(self): + self.MIN_PLAYERS = 6 + self.MAX_PLAYERS = 12 + self.DEFAULT_ROLE = "cultist" + self.ROLES_INDEX = ( 6 , 10 ) + self.ROLES_GUIDE = {# village roles + "oracle" : ( 1 , 1 ), + "shaman" : ( 1 , 1 ), + "bodyguard" : ( 0 , 1 ), + # wolf roles + "wolf" : ( 1 , 1 ), + "minion" : ( 0 , 1 ), + } # Persistence @@ -221,7 +281,7 @@ with conn: c.execute('DROP TABLE IF EXISTS roles') c.execute('CREATE TABLE roles (id INTEGER PRIMARY KEY AUTOINCREMENT, role TEXT)') - for x in ["villager"]+list(ROLE_INDICES.values()): + for x in list(ROLES_GUIDE.keys()): c.execute("INSERT OR REPLACE INTO roles (role) VALUES (?)", (x,)) @@ -229,11 +289,11 @@ with conn: 'teamwins SMALLINT, individualwins SMALLINT, totalgames SMALLINT, '+ 'UNIQUE(player, role))')) - + c.execute(('CREATE TABLE IF NOT EXISTS gamestats (size SMALLINT, villagewins SMALLINT, ' + 'wolfwins SMALLINT, totalgames SMALLINT, UNIQUE(size))')) - - + + if OPT_IN_PING: c.execute('CREATE TABLE IF NOT EXISTS ping (cloak text)') @@ -261,7 +321,7 @@ def add_simple_rolemsg(clk): def remove_ping(clk): with conn: c.execute('DELETE from ping where cloak=?', (clk,)) - + def add_ping(clk): with conn: c.execute('INSERT into ping VALUES (?)', (clk,)) @@ -289,24 +349,24 @@ def update_role_stats(acc, role, won, iwon): def update_game_stats(size, winner): with conn: vwins, wwins, total = 0, 0, 0 - + c.execute("SELECT villagewins, wolfwins, totalgames FROM gamestats "+ "WHERE size=?", (size,)) row = c.fetchone() if row: vwins, wwins, total = row - + if winner == "wolves": wwins += 1 elif winner == "villagers": vwins += 1 total += 1 - + c.execute("INSERT OR REPLACE INTO gamestats VALUES (?,?,?,?)", (size, vwins, wwins, total)) - + def get_player_stats(acc, role): - if role.lower() not in ["villager"] + [v.lower() for k, v in ROLE_INDICES.items()]: + if role.lower() not in [k.lower() for k in ROLES_GUIDE.keys()]: return "No such role: {0}".format(role) with conn: c.execute("SELECT player FROM rolestats WHERE player=? COLLATE NOCASE", (acc,)) @@ -325,7 +385,7 @@ def get_player_totals(acc): c.execute("SELECT player FROM rolestats WHERE player=? COLLATE NOCASE", (acc,)) player = c.fetchone() if player: - for role in ["villager"] + [v for k, v in ROLE_INDICES.items()]: + for role in [k.lower() for k in ROLES_GUIDE.keys()]: c.execute("SELECT totalgames FROM rolestats WHERE player=? COLLATE NOCASE AND role=? COLLATE NOCASE", (acc, role)) row = c.fetchone() if row: @@ -333,7 +393,7 @@ def get_player_totals(acc): return "\u0002{0}\u0002's totals | {1}".format(player[0], ", ".join(role_totals)) else: return "{0} has not played any games.".format(acc) - + def get_game_stats(size): with conn: for row in c.execute("SELECT * FROM gamestats WHERE size=?", (size,)): @@ -352,7 +412,7 @@ def get_game_totals(): if row: size_totals.append("\u0002{0}p\u0002: {1}".format(*row)) total += row[1] - + if len(size_totals) == 0: return "No games have been played." else: