diff --git a/src/gamemodes.py b/src/gamemodes.py index 20faa26..c951a0f 100644 --- a/src/gamemodes.py +++ b/src/gamemodes.py @@ -229,8 +229,7 @@ class VillagergameMode(GameMode): tgt = hlt if not tgt: tgt = random.choice(pl) - from src.roles import wolf - wolf.KILLS[botconfig.NICK] = [tgt] + var.KILLS[botconfig.NICK] = [tgt] @game_mode("foolish", minp = 8, maxp = 24, likelihood = 8) class FoolishMode(GameMode): diff --git a/src/roles/hunter.py b/src/roles/hunter.py deleted file mode 100644 index 3e4f539..0000000 --- a/src/roles/hunter.py +++ /dev/null @@ -1,151 +0,0 @@ -import re -import random -from collections import defaultdict - -import src.settings as var -from src.utilities import * -from src import debuglog, errlog, plog -from src.decorators import cmd, event_listener -from src.messages import messages -from src.events import Event - -KILLS = {} # type: Dict[str, str] -HUNTERS = set() -PASSED = set() - -@cmd("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("hunter",)) -def hunter_kill(cli, nick, chan, rest): - """Kill someone once per game.""" - if nick in HUNTERS and nick not in KILLS: - pm(cli, nick, messages["hunter_already_killed"]) - return - pieces = re.split(" +", rest) - victim = pieces[0] - orig = victim - evt = Event("targeted_command", {"target": victim, "misdirection": True, "exchange": True}) - evt.dispatch(cli, var, "kill", nick, victim, frozenset({"detrimental"})) - if evt.prevent_default: - return - victim = evt.data["target"] - - KILLS[nick] = victim - HUNTERS.add(nick) - PASSED.discard(nick) - - msg = messages["wolf_target"].format(orig) - pm(cli, nick, messages["player"].format(msg)) - - debuglog("{0} ({1}) KILL: {2} ({3})".format(nick, get_role(nick), victim, get_role(victim))) - - chk_nightdone(cli) - -@cmd("retract", "r", chan=False, pm=True, playing=True, phases=("night",), roles=("hunter",)) -def hunter_retract(cli, nick, chan, rest): - """Removes a hunter's kill selection.""" - if nick not in KILLS and nick not in PASSED: - return - if nick in KILLS: - del KILLS[nick] - HUNTERS.discard(nick) - PASSED.discard(nick) - pm(cli, nick, messages["retracted_kill"]) - -@cmd("pass", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("hunter",)) -def hunter_pass(cli, nick, chan, rest): - """Do not use hunter's once-per-game kill tonight.""" - if nick in HUNTERS and nick not in KILLS: - pm(cli, nick, messages["hunter_already_killed"]) - return - if nick in KILLS: - del KILLS[nick] - HUNTERS.discard(nick) - PASSED.add(nick) - pm(cli, nick, messages["hunter_pass"]) - -@event_listener("del_player") -def on_del_player(evt, cli, var, nick, nickrole, nicktpls, lynched, end_game, death_triggers, killer_role, deadlist, original, ismain, refresh_pl): - for h,v in list(KILLS.items()): - if v == nick: - HUNTERS.discard(h) - PASSED.discard(h) - pm(cli, h, messages["hunter_discard"]) - del KILLS[h] - elif h == nick: - del KILLS[h] - -@event_listener("rename_player") -def on_rename(evt, cli, var, prefix, nick): - kvp = [] - for a,b in KILLS.items(): - if a == prefix: - a = nick - if b == prefix: - b = nick - kvp.append((a,b)) - KILLS.update(kvp) - if prefix in KILLS: - del KILLS[prefix] - if prefix in HUNTERS: - HUNTERS.discard(prefix) - HUNTERS.add(nick) - if prefix in PASSED: - PASSED.discard(prefix) - PASSED.add(nick) - -@event_listener("acted") -def on_acted(evt, cli, var, nick, sender): - if nick in KILLS: - evt.data["acted"] = True - -@event_listener("transition_day", priority=2) -def on_transition_day(evt, cli, var): - for k, d in list(KILLS.items()): - evt.data["victims"].append(d) - evt.data["onlybywolves"].discard(d) - evt.data["killers"][d] = k - # important, otherwise our del_player listener lets hunter kill again - del KILLS[k] - -@event_listener("exchange_roles") -def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role): - if actor in KILLS: - del KILLS[actor] - if nick in KILLS: - del KILLS[nick] - HUNTERS.discard(actor) - HUNTERS.discard(nick) - PASSED.discard(actor) - PASSED.discard(nick) - -@event_listener("chk_nightdone") -def on_chk_nightdone(evt, cli, var): - evt.data["actedcount"] += len(KILLS) + len(PASSED) - evt.data["nightroles"].extend([p for p in var.ROLES["hunter"] if p not in HUNTERS or p in KILLS]) - -@event_listener("transition_night_end", priority=2) -def on_transition_night_end(evt, cli, var): - ps = list_players() - for hunter in var.ROLES["hunter"]: - if hunter in HUNTERS: - continue #already killed - pl = ps[:] - random.shuffle(pl) - pl.remove(hunter) - if hunter in var.PLAYERS and not is_user_simple(hunter): - pm(cli, hunter, messages["hunter_notify"]) - else: - pm(cli, hunter, messages["hunter_simple"]) - pm(cli, hunter, "Players: " + ", ".join(pl)) - -@event_listener("begin_day") -def on_begin_day(evt, cli, var): - KILLS.clear() - PASSED.clear() - -@event_listener("reset") -def on_reset(evt, var): - KILLS.clear() - PASSED.clear() - HUNTERS.clear() - -# vim: set sw=4 expandtab: diff --git a/src/roles/seer.py b/src/roles/seer.py index aed1e92..7f10516 100644 --- a/src/roles/seer.py +++ b/src/roles/seer.py @@ -82,11 +82,6 @@ def on_rename(evt, cli, var, prefix, nick): SEEN.remove(prefix) SEEN.add(nick) -@event_listener("acted") -def on_acted(evt, cli, var, nick, sender): - if nick in SEEN: - evt.data["acted"] = True - @event_listener("exchange_roles") def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role): if actor_role in ("seer", "oracle", "augur"): diff --git a/src/roles/villager.py b/src/roles/villager.py deleted file mode 100644 index 927fd85b..0000000 --- a/src/roles/villager.py +++ /dev/null @@ -1,52 +0,0 @@ -import src.settings as var -from src.utilities import * -from src import debuglog, errlog, plog -from src.decorators import cmd, event_listener -from src.messages import messages -from src.events import Event - -# handles villager and cultist - -@event_listener("transition_night_end", priority=2) -def on_transition_night_end(evt, cli, var): - if var.FIRST_NIGHT or var.ALWAYS_PM_ROLE: - villroles = var.HIDDEN_VILLAGERS | {"villager"} - if var.DEFAULT_ROLE == "villager": - villroles |= var.HIDDEN_ROLES - villagers = list_players(villroles) - for villager in villagers: - if villager in var.PLAYERS and not is_user_simple(villager): - pm(cli, villager, messages["villager_notify"]) - else: - pm(cli, villager, messages["villager_simple"]) - - cultroles = {"cultist"} - if var.DEFAULT_ROLE == "cultist": - cultroles |= var.HIDDEN_ROLES - cultists = list_players(cultroles) - for cultist in cultists: - if cultist in var.PLAYERS and not is_user_simple(cultist): - pm(cli, cultist, messages["cultist_notify"]) - else: - pm(cli, cultist, messages["cultist_simple"]) - -# No listeners should register before this one -# This sets up the initial state, based on village/wolfteam/neutral affiliation -@event_listener("player_win", priority=0) -def on_player_win(evt, cli, var, nick, role, winner, survived): - # init won/iwon to False - evt.data["won"] = False - evt.data["iwon"] = False - - if role in var.WOLFTEAM_ROLES or (var.DEFAULT_ROLE == "cultist" and role in var.HIDDEN_ROLES): - if winner == "wolves": - evt.data["won"] = True - evt.data["iwon"] = survived - elif role in var.TRUE_NEUTRAL_ROLES: - # handled in their individual files - pass - elif winner == "villagers": - evt.data["won"] = True - evt.data["iwon"] = survived - -# vim: set sw=4 expandtab: diff --git a/src/roles/wolf.py b/src/roles/wolf.py deleted file mode 100644 index b84d937..0000000 --- a/src/roles/wolf.py +++ /dev/null @@ -1,270 +0,0 @@ -import re -import random -from collections import defaultdict - -import src.settings as var -from src.utilities import * -from src import debuglog, errlog, plog -from src.decorators import cmd, event_listener -from src.messages import messages -from src.events import Event - -KILLS = {} # type: Dict[str, List[str]] - -@cmd("kill", chan=False, pm=True, playing=True, phases=("night",)) -def wolf_kill(cli, nick, chan, rest): - """Kills one or more players as a wolf.""" - role = get_role(nick) - # eventually cub will listen on targeted_command and block kills that way - if role not in var.WOLF_ROLES - {"wolf cub"}: - return - if nick in var.SILENCED: - pm(cli, nick, messages["silenced"]) - return - if var.DISEASED_WOLVES: - pm(cli, nick, messages["ill_wolves"]) - return - # eventually crow will listen on targeted_command and block kills that way - # (or more likely, that restriction will be lifted and crow can do both) - if role == "werecrow" and var.OBSERVED.get(nick): - pm(cli, nick, messages["werecrow_transformed_nokill"]) - return - - pieces = re.split(" +", rest) - victims = [] - orig = [] - num_kills = 1 - if var.ANGRY_WOLVES: - num_kills = 2 - - i = 0 - extra = 0 - while i < num_kills + extra: - try: - victim = pieces[i] - except IndexError: - break - if victim.lower() == "and": - extra += 1 - i += 1 - victim = pieces[i] - victim = get_victim(cli, nick, victim, False) - if not victim: - return - - if victim == nick: - pm(cli, nick, messages["no_suicide"]) - return - if in_wolflist(nick, victim): - pm(cli, nick, messages["wolf_no_target_wolf"]) - return - orig.append(victim) - evt = Event("targeted_command", {"target": victim, "misdirection": True, "exchange": True}) - evt.dispatch(cli, var, "kill", nick, victim, frozenset({"detrimental"})) - if evt.prevent_default: - return - victim = evt.data["target"] - victims.append(victim) - i += 1 - - if len(set(victims)) < len(victims): - pm(cli, nick, messages["wolf_must_target_multiple"]) - return - KILLS[nick] = victims - if len(orig) > 1: - # need to expand this eventually - msg = messages["wolf_target_multiple"].format(orig[0], orig[1]) - pm(cli, nick, messages["player"].format(msg)) - debuglog("{0} ({1}) KILL: {2} ({3}) and {4} ({5})".format(nick, role, victims[0], get_role(victims[0]), victims[1], get_role(victims[1]))) - else: - msg = messages["wolf_target"].format(orig[0]) - pm(cli, nick, messages["player"].format(msg)) - if num_kills > 1: - pm(cli, nick, messages["wolf_target_second"]) - debuglog("{0} ({1}) KILL: {2} ({3})".format(nick, role, victims[0], get_role(victims[0]))) - - if in_wolflist(nick, nick): - relay_wolfchat_command(cli, nick, messages["wolfchat"].format(nick, msg), var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True) - - chk_nightdone(cli) - -@cmd("retract", "r", chan=False, pm=True, playing=True, phases=("night",)) -def wolf_retract(cli, nick, chan, rest): - """Removes a wolf's kill selection.""" - if nick not in KILLS: - return - del KILLS[nick] - pm(cli, nick, messages["retracted_kill"]) - relay_wolfchat_command(cli, nick, messages["wolfchat_retracted_kill"].format(nick), var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True) - -@event_listener("del_player") -def on_del_player(evt, cli, var, nick, nickrole, nicktpls, lynched, end_game, death_triggers, killer_role, deadlist, original, ismain, refresh_pl): - for a,b in list(KILLS.items()): - for n in b: - if n == nick: - KILLS[a].remove(nick) - if a == nick or len(KILLS[a]) == 0: - del KILLS[a] - -@event_listener("rename_player") -def on_rename(evt, cli, var, prefix, nick): - kvp = [] - for a,b in KILLS.items(): - nl = [] - for n in b: - if n == prefix: - n = nick - nl.append(n) - if a == prefix: - a = nick - kvp.append((a,nl)) - KILLS.update(kvp) - if prefix in KILLS: - del KILLS[prefix] - -@event_listener("acted") -def on_acted(evt, cli, var, nick, sender): - if nick in KILLS: - evt.data["acted"] = True - -@event_listener("transition_day", priority=1) -def on_transition_day(evt, cli, var): - # figure out wolf target - found = defaultdict(int) - # split off into event + wolfcub.py - num_kills = 1 - if var.ANGRY_WOLVES: - num_kills = 2 - for v in KILLS.values(): - for p in v: - if p: - # kill target starting with ! is invalid - # right now nothing does this, but monster eventually will - if p[0] == "!": - continue - found[p] += 1 - for i in range(num_kills): - maxc = 0 - dups = [] - for v, c in found.items(): - if c > maxc: - maxc = c - dups = [v] - elif c == maxc: - dups.append(v) - if maxc and dups: - victim = random.choice(dups) - evt.data["victims"].append(victim) - evt.data["bywolves"].add(victim) - evt.data["onlybywolves"].add(victim) - # special key to let us know to randomly select a wolf in case of retribution totem - evt.data["killers"][victim].append("@wolves") - del found[victim] - - # this should be moved to an event in kill, where monster prefixes their nick with ! - # and fallen angel subsequently removes the ! prefix - if len(var.ROLES["fallen angel"]) == 0: - for monster in var.ROLES["monster"]: - if monster in victims: - evt.data["victims"].remove(monster) - evt.data["bywolves"].discard(monster) - evt.data["onlybywolves"].discard(monster) - -@event_listener("exchange_roles") -def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role): - if actor in KILLS: - del KILLS[actor] - if nick in KILLS: - del KILLS[nick] - -@event_listener("chk_nightdone", priority=3) -def on_chk_nightdone(evt, cli, var): - if not var.DISEASED_WOLVES: - evt.data["actedcount"] += len(KILLS) - # eventually wolf cub will remove itself from nightroles in wolfcub.py - evt.data["nightroles"].extend(list_players(var.WOLF_ROLES - {"wolf cub"})) - -@event_listener("chk_nightdone", priority=20) -def on_chk_nightdone2(evt, cli, var): - if not evt.prevent_default and not var.DISEASED_WOLVES: - # flatten KILLS - kills = set() - for ls in KILLS.values(): - kills.update(ls) - # check if wolves are actually agreeing - # allow len(kills) == 0 through as that means that crow was dumb and observed instead - if not var.ANGRY_WOLVES and len(kills) > 1: - evt.data["actedcount"] -= 1 - elif var.ANGRY_WOLVES and (len(kills) == 1 or len(kills) > 2): - evt.data["actedcount"] -= 1 - -@event_listener("transition_night_end", priority=2) -def on_transition_night_end(evt, cli, var): - ps = list_players() - wolves = list_players(var.WOLFCHAT_ROLES) - for wolf in wolves: - # should make the cursed information an event that cursedvillager can then add to - # (e.g. an event to change what prefixes are sent with the role message, and a - # 2nd event to change information in parens in player list) - normal_notify = wolf in var.PLAYERS and not is_user_simple(wolf) - role = get_role(wolf) - cursed = "cursed " if wolf in var.ROLES["cursed villager"] else "" - - if normal_notify: - msg = "{0}_notify".format(role.replace(" ", "_")) - cmsg = "cursed_" + msg - try: - if cursed: - try: - pm(cli, wolf, messages[cmsg]) - except KeyError: - pm(cli, wolf, messages[msg].format(cursed)) - else: - pm(cli, wolf, messages[msg].format(cursed)) - except KeyError: - # catchall in case we forgot something above - an = 'n' if role.startswith(("a", "e", "i", "o", "u")) else "" - pm(cli, wolf, messages["undefined_role_notify"].format(an, role)) - - if len(wolves) > 1: - pm(cli, wolf, messages["wolfchat_notify"]) - else: - an = "n" if cursed == "" and role.startswith(("a", "e", "i", "o", "u")) else "" - pm(cli, wolf, messages["wolf_simple"].format(an, cursed, role)) # !simple - - pl = ps[:] - random.shuffle(pl) - pl.remove(wolf) # remove self from list - for i, player in enumerate(pl): - prole = get_role(player) - if prole in var.WOLFCHAT_ROLES: - cursed = "" - if player in var.ROLES["cursed villager"]: - cursed = "cursed " - pl[i] = "\u0002{0}\u0002 ({1}{2})".format(player, cursed, prole) - elif player in var.ROLES["cursed villager"]: - pl[i] = player + " (cursed)" - - pm(cli, wolf, "Players: " + ", ".join(pl)) - if var.DISEASED_WOLVES: - pm(cli, wolf, messages["ill_wolves"]) - # TODO: split the following out into their own files (mystic, cub and alpha) - if role == "wolf mystic": - # if adding this info to !myrole, you will need to save off this count so that they can't get updated info until the next night - # # of special villagers = # of players - # of villagers - # of wolves - # of neutrals - numvills = len(ps) - len(list_players(var.WOLFTEAM_ROLES)) - len(list_players(("villager", "vengeful ghost", "time lord", "amnesiac", "lycan"))) - len(list_players(var.TRUE_NEUTRAL_ROLES)) - pm(cli, wolf, messages["wolf_mystic_info"].format("are" if numvills != 1 else "is", numvills, "s" if numvills != 1 else "")) - if not var.DISEASED_WOLVES and var.ANGRY_WOLVES and role in var.WOLF_ROLES and role != "wolf cub": - pm(cli, wolf, messages["angry_wolves"]) - if var.ALPHA_ENABLED and role == "alpha wolf" and wolf not in var.ALPHA_WOLVES: - pm(cli, wolf, messages["wolf_bite"]) - -@event_listener("begin_day") -def on_begin_day(evt, cli, var): - KILLS.clear() - -@event_listener("reset") -def on_reset(evt, var): - KILLS.clear() - -# vim: set sw=4 expandtab: diff --git a/src/settings.py b/src/settings.py index 7811d18..66b4910 100644 --- a/src/settings.py +++ b/src/settings.py @@ -283,10 +283,6 @@ SEEN_WOLF = WOLF_ROLES | {"monster", "mad scientist", "succubus"} # These are seen as the default role (or villager) when seen by seer (this overrides SEEN_WOLF) SEEN_DEFAULT = frozenset({"traitor", "hag", "sorcerer", "time lord", "villager", "cultist", "minion", "turncoat", "amnesiac", "vengeful ghost", "lycan", "clone", "fool", "jester", "werekitten", "warlock", "piper", "demoniac"}) -# These roles are notified that they are villager -HIDDEN_VILLAGERS = frozenset({"time lord"}) -# These roles are notified that they are the default role. They also win alongside the default role barring other role-specific win conds. -HIDDEN_ROLES = frozenset({"vengeful ghost", "amnesiac"}) # these totems are beneficial for the *receiving* person, but can be detrimental to someone else acting on the receiver! BENEFICIAL_TOTEMS = frozenset({"protection", "revealing", "desperation", "influence", "luck", "pestilence", "retribution"}) diff --git a/src/wolfgame.py b/src/wolfgame.py index dd5dd85..67d3a1d 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -2486,19 +2486,6 @@ def stop_game(cli, winner="", abort=False, additional_winners=None, log=True): won = False iwon = False - survived = list_players() - if not pentry["dced"]: - evt = Event("player_win", {"won": won, "iwon": iwon, "special": pentry["special"]}) - evt.dispatch(cli, var, splr, rol, winner, splr in survived) - won = evt.data["won"] - iwon = evt.data["iwon"] - # ensure that it is a) a list, and b) a copy (so it can't be mutated out from under us later) - pentry["special"] = list(evt.data["special"]) - - # special-case everyone for after the event - if winner == "everyone": - iwon = True - # determine if this player's team won if rol in var.TRUE_NEUTRAL_ROLES: # most true neutral roles never have a team win, only individual wins. Exceptions to that are here @@ -2512,8 +2499,19 @@ def stop_game(cli, winner="", abort=False, additional_winners=None, log=True): elif winner != "succubi" and splr in var.ENTRANCED: # entranced players can't win with villager or wolf teams won = False + elif rol in ("amnesiac", "vengeful ghost") and splr not in var.VENGEFUL_GHOSTS: + if var.DEFAULT_ROLE == "villager" and winner == "villagers": + won = True + elif var.DEFAULT_ROLE == "cultist" and winner == "wolves": + won = True + elif rol in var.WOLFTEAM_ROLES: + if winner == "wolves": + won = True + elif winner == "villagers": + won = True - if pentry["dced"]: + survived = list_players() + if plr.startswith("(dced)"): # You get NOTHING! You LOSE! Good DAY, sir! won = False iwon = False @@ -2583,6 +2581,8 @@ def stop_game(cli, winner="", abort=False, additional_winners=None, log=True): iwon = True elif winner == "succubi" and splr in var.ENTRANCED | var.ROLES["succubus"]: iwon = True + elif winner == "everyone" and not survived: + iwon = True # nobody survived but an event made everyone win elif not iwon: iwon = won and splr in survived # survived, team won = individual win @@ -3171,6 +3171,12 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death if var.PHASE in var.GAME_PHASES: # remove the player from variables if they're in there if ret: + for a,b in list(var.KILLS.items()): + for n in b: #var.KILLS can have 2 kills in a list + if n == nick: + var.KILLS[a].remove(nick) + if a == nick or len(var.KILLS[a]) == 0: + del var.KILLS[a] for x in (var.OBSERVED, var.HVISITED, var.GUARDED, var.TARGETED, var.LASTGUARDED, var.LASTGIVEN, var.LASTHEXED): for k in list(x): if nick in (k, x[k]): @@ -3181,6 +3187,7 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death del x[k] for k in list(var.OTHER_KILLS): if var.OTHER_KILLS[k] == nick: + var.HUNTERS.discard(k) pm(cli, k, messages["hunter_discard"]) del var.OTHER_KILLS[k] elif nick == k: @@ -3196,7 +3203,7 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death # remove players from night variables # the dicts are handled above, these are the lists of who has acted which is used to determine whether night should end # if these aren't cleared properly night may end prematurely - for x in (var.SEEN, var.PASSED, var.HEXED, var.MATCHMAKERS, var.CURSED, var.CHARMERS): + for x in (var.SEEN, var.PASSED, var.HUNTERS, var.HEXED, var.MATCHMAKERS, var.CURSED, var.CHARMERS): x.discard(nick) if var.PHASE == "day" and not forced_death and ret: # didn't die from lynching var.VOTES.pop(nick, None) # Delete other people's votes on the player @@ -3551,6 +3558,21 @@ def rename_player(cli, prefix, nick): var.DOCTORS, var.BITTEN_ROLES, var.LYCAN_ROLES, var.AMNESIAC_ROLES): if prefix in dictvar.keys(): dictvar[nick] = dictvar.pop(prefix) + # Looks like {'jacob2': ['5'], '7': ['3']} + for dictvar in (var.KILLS,): + kvp = [] + for a,b in dictvar.items(): + nl = [] + for n in b: + if n == prefix: + n = nick + nl.append(n) + if a == prefix: + a = nick + kvp.append((a,nl)) + dictvar.update(kvp) + if prefix in dictvar.keys(): + del dictvar[prefix] # Looks like {'6': {'jacob3'}, 'jacob3': {'6'}} for dictvar in (var.LOVERS, var.ORIGINAL_LOVERS, var.DULLAHAN_TARGETS): kvp = [] @@ -3574,7 +3596,7 @@ def rename_player(cli, prefix, nick): b = nick var.EXCHANGED_ROLES[idx] = (a, b) for setvar in (var.SEEN, var.HEXED, var.ASLEEP, var.DESPERATE, var.REVEALED, var.SILENCED, var.TOBESILENCED, - var.REVEALED_MAYORS, var.MATCHMAKERS, var.PASSED, var.JESTERS, var.AMNESIACS, + var.REVEALED_MAYORS, var.MATCHMAKERS, var.HUNTERS, var.PASSED, var.JESTERS, var.AMNESIACS, var.INFLUENTIAL, var.LYCANTHROPES, var.TOBELYCANTHROPES, var.LUCKY, var.TOBELUCKY, var.SICK, var.DISEASED, var.TOBEDISEASED, var.RETRIBUTION, var.MISDIRECTED, var.TOBEMISDIRECTED, var.EXCHANGED, var.IMMUNIZED, var.CURED_LYCANS, var.ALPHA_WOLVES, var.CURSED, var.CHARMERS, @@ -3805,7 +3827,8 @@ def begin_day(cli): # Reset nighttime variables var.GAMEPHASE = "day" - var.OTHER_KILLS = {} # other kill victims (vigilante/vengeful ghost) + var.KILLS = {} # nicknames of kill victims (wolves only) + var.OTHER_KILLS = {} # other kill victims (hunter/vengeful ghost) var.KILLER = "" # nickname of who chose the victim var.SEEN = set() # set of doomsayers that have had visions var.HEXED = set() # set of hags that have silenced others @@ -4060,12 +4083,10 @@ def transition_day(cli, gameid=0): for crow, target in iter(var.OBSERVED.items()): if crow not in var.ROLES["werecrow"]: continue - evt = Event("night_acted", {"acted": False}) - evt.dispatch(cli, var, target, crow) if ((target in var.HVISITED and var.HVISITED[target]) or target in var.SEEN or target in var.SHAMANS or (target in var.GUARDED and var.GUARDED[target]) or target in var.OTHER_KILLS or (target in var.PRAYED and var.PRAYED[target][0] > 0) or target in var.CHARMERS or - target in var.OBSERVED or target in var.HEXED or target in var.CURSED or evt.data["acted"]): + target in var.OBSERVED or target in var.KILLS or target in var.HEXED or target in var.CURSED): pm(cli, crow, messages["werecrow_success"].format(target)) else: pm(cli, crow, messages["werecrow_failure"].format(target)) @@ -4081,29 +4102,63 @@ def transition_day(cli, gameid=0): var.NIGHT_TIMEDELTA += td min, sec = td.seconds // 60, td.seconds % 60 - # built-in logic runs at the following priorities: - # 1 = wolf kills - # 2 = non-wolf kills - # 3 = alpha wolf bite - # 4 = protections/fallen angel - # 5 = rearranging victim list (ensure bodyguard/harlot messages plays), - # fixing killers dict to have correct priority (wolf-side VG kills -> non-wolf kills -> wolf kills) - # Actually killing off the victims happens in transition_day_resolve - evt = Event("transition_day", { - "victims": [], - "killers": defaultdict(list), - "bywolves": set(), - "onlybywolves": set(), - "protected": {}, - "bitten": [] - }) - evt.dispatch(cli, var) - victims = evt.data["victims"] - killers = evt.data["killers"] - bywolves = evt.data["bywolves"] - onlybywolves = evt.data["onlybywolves"] - protected = evt.data["protected"] - bitten = evt.data["bitten"] + found = defaultdict(int) + for v in var.KILLS.values(): + for p in v: + found[p] += 1 + + maxc = 0 + victims = [] + bitten = [] + # dict of victim: list of killers (for retribution totem) + killers = defaultdict(list) + # wolves targeted, others may have as well (needed for harlot visit and maybe other things) + bywolves = set() + # wolves and nobody else targeted (needed for lycan) + onlybywolves = set() + + dups = [] + for v, c in found.items(): + if c > maxc: + maxc = c + dups = [v] + elif c == maxc: + dups.append(v) + + if maxc and dups: + victim = random.choice(dups) + victims.append(victim) + bywolves.add(victim) + onlybywolves.add(victim) + # special key to let us know to randomly select a wolf + killers[victim].append("@wolves") + + + if victims and var.ANGRY_WOLVES: + # they got a 2nd kill + del found[victims[0]] + maxc = 0 + dups = [] + for v, c in found.items(): + if c > maxc: + maxc = c + dups = [v] + elif c == maxc: + dups.append(v) + if maxc and dups: + victim = random.choice(dups) + victims.append(victim) + bywolves.add(victim) + onlybywolves.add(victim) + # special key to let us know to randomly select a wolf + killers[victim].append("@wolves") + + if len(var.ROLES["fallen angel"]) == 0: + for monster in var.ROLES["monster"]: + if monster in victims: + victims.remove(monster) + bywolves.discard(monster) + onlybywolves.discard(monster) wolfghostvictims = [] for k, d in var.OTHER_KILLS.items(): @@ -4119,6 +4174,7 @@ def transition_day(cli, gameid=0): for v in var.ENTRANCED_DYING: var.DYING.add(v) + # clear list so that it doesn't pm hunter / ghost about being able to kill again var.OTHER_KILLS = {} for k, d in var.DEATH_TOTEM: @@ -4645,14 +4701,16 @@ def chk_nightdone(cli): # TODO: alphabetize and/or arrange sensibly pl = list_players() spl = set(pl) - actedcount = sum(map(len, (var.SEEN, var.HVISITED, var.GUARDED, + actedcount = sum(map(len, (var.SEEN, var.HVISITED, var.GUARDED, var.KILLS, var.OTHER_KILLS, var.PASSED, var.OBSERVED, var.HEXED, var.SHAMANS, var.CURSED, var.CHARMERS))) - nightroles = get_roles("harlot", "succubus", "bodyguard", "guardian angel", - "sorcerer", "hag", "shaman", "crazed shaman", - "warlock", "piper", "vigilante", "doomsayer", - "prophet", "wolf shaman") + nightroles = get_roles("harlot", "succubus", "bodyguard", + "guardian angel", "wolf", "werecrow", "alpha wolf", + "sorcerer", "hunter", "hag", "shaman", "crazed shaman", + "werekitten", "warlock", "piper", "wolf mystic", + "fallen angel", "vigilante", "doomsayer", "doomsayer", # NOT a mistake, doomsayer MUST be listed twice + "prophet", "wolf shaman", "wolf shaman") # wolf shaman also must be listed twice for ghost, against in var.VENGEFUL_GHOSTS.items(): if not against.startswith("!"): @@ -4676,11 +4734,22 @@ def chk_nightdone(cli): actedcount += len(var.MATCHMAKERS | var.CLONED.keys()) nightroles.extend(get_roles("matchmaker", "clone")) - if var.ALPHA_ENABLED: + if var.DISEASED_WOLVES: + nightroles = [p for p in nightroles if p not in list_players(var.WOLF_ROLES - {"wolf cub", "werecrow", "doomsayer"})] + # only remove 1 doomsayer instance to indicate they cannot kill but can still see + for p in var.ROLES["doomsayer"]: + nightroles.remove(p) + elif var.ALPHA_ENABLED: # alphas both kill and bite if they're activated at night, so add them into the counts nightroles.extend(get_roles("alpha wolf")) actedcount += len([p for p in var.ALPHA_WOLVES if p in var.ROLES["alpha wolf"]]) + for p in var.HUNTERS: + # only remove one instance of their name if they have used hunter ability, in case they have templates + # the OTHER_KILLS check ensures we only remove them if they acted in a *previous* night + if p in var.ROLES["hunter"] and p not in var.OTHER_KILLS: + nightroles.remove(p) + # but remove all instances of their name if they are silenced nightroles = [p for p in nightroles if p not in var.SILENCED] @@ -4708,6 +4777,21 @@ def chk_nightdone(cli): for ass in var.ROLES["assassin"]: if ass not in var.TARGETED.keys() | var.SILENCED: return + if not var.DISEASED_WOLVES: + # flatten var.KILLS + kills = set() + for ls in var.KILLS.values(): + if not isinstance(ls, str): + kills.update(ls) + else: + kills.add(ls) + # check if wolves are actually agreeing + # allow len(kills) == 0 through as that means that crow was dumb and observed instead + # of killingor alpha wolf was alone and chose to bite instead of kill + if not var.ANGRY_WOLVES and len(kills) > 1: + return + elif var.ANGRY_WOLVES and (len(kills) == 1 or len(kills) > 2): + return for x, t in var.TIMERS.items(): t[0].cancel() @@ -4896,6 +4980,13 @@ def check_exchange(cli, actor, nick): del var.SHAMANS[actor] if actor in var.LASTGIVEN: del var.LASTGIVEN[actor] + elif actor_role in var.WOLF_ROLES - {"werecrow", "wolf cub", "alpha wolf", "doomsayer"}: + if actor in var.KILLS: + del var.KILLS[actor] + elif actor_role == "hunter": + if actor in var.OTHER_KILLS: + del var.OTHER_KILLS[actor] + var.HUNTERS.discard(actor) elif actor_role in ("bodyguard", "guardian angel"): if actor in var.GUARDED: pm(cli, var.GUARDED.pop(actor), messages["protector_disappeared"]) @@ -4904,6 +4995,8 @@ def check_exchange(cli, actor, nick): elif actor_role in ("werecrow", "sorcerer"): if actor in var.OBSERVED: del var.OBSERVED[actor] + if actor in var.KILLS: + del var.KILLS[actor] elif actor_role == "harlot": if actor in var.HVISITED: if var.HVISITED[actor] is not None: @@ -4922,8 +5015,12 @@ def check_exchange(cli, actor, nick): var.DOCTORS[nick] = var.DOCTORS.pop(actor) elif actor_role == "alpha wolf": var.ALPHA_WOLVES.discard(actor) + if actor in var.KILLS: + del var.KILLS[actor] elif actor_role == "doomsayer": var.SEEN.discard(actor) + if actor in var.KILLS: + del var.KILLS[actor] elif actor_role == "warlock": var.CURSED.discard(actor) elif actor_role == "turncoat": @@ -4949,6 +5046,13 @@ def check_exchange(cli, actor, nick): del var.SHAMANS[nick] if nick in var.LASTGIVEN: del var.LASTGIVEN[nick] + elif nick_role in var.WOLF_ROLES - {"werecrow", "wolf cub", "alpha wolf", "doomsayer"}: + if nick in var.KILLS: + del var.KILLS[nick] + elif nick_role == "hunter": + if nick in var.OTHER_KILLS: + del var.OTHER_KILLS[nick] + var.HUNTERS.discard(nick) elif nick_role in ("bodyguard", "guardian angel"): if nick in var.GUARDED: pm(cli, var.GUARDED.pop(nick), messages["protector_disappeared"]) @@ -4957,6 +5061,8 @@ def check_exchange(cli, actor, nick): elif nick_role in ("werecrow", "sorcerer"): if nick in var.OBSERVED: del var.OBSERVED[nick] + if nick in var.KILLS: + del var.KILLS[nick] elif nick_role == "harlot": if nick in var.HVISITED: if var.HVISITED[nick] is not None: @@ -4974,8 +5080,12 @@ def check_exchange(cli, actor, nick): var.DOCTORS[actor] = var.DOCTORS.pop(nick) elif nick_role == "alpha wolf": var.ALPHA_WOLVES.discard(nick) + if nick in var.KILLS: + del var.KILLS[nick] elif nick_role == "doomsayer": var.SEEN.discard(nick) + if nick in var.KILLS: + del var.KILLS[nick] elif nick_role == "warlock": var.CURSED.discard(nick) elif nick_role == "turncoat": @@ -5011,15 +5121,15 @@ def check_exchange(cli, actor, nick): del var.LYCAN_ROLES[nick] actor_rev_role = actor_role - if actor_role in var.HIDDEN_ROLES: + if actor_role == "vengeful ghost": actor_rev_role = var.DEFAULT_ROLE - elif actor_role in var.HIDDEN_VILLAGERS: + elif actor_role == "time lord": actor_rev_role = "villager" nick_rev_role = nick_role - if nick_role in var.HIDDEN_ROLES: + if nick_role == "vengeful ghost": nick_rev_role = var.DEFAULT_ROLE - elif actor_role in var.HIDDEN_VILLAGERS: + elif actor_role == "time lord": nick_rev_role = "villager" # don't say who, since misdirection/luck totem may have switched it @@ -5154,7 +5264,7 @@ def retract(cli, nick, chan, rest): if chan == nick: # PM, use different code role = get_role(nick) - if role not in var.WOLF_ROLES - {"wolf cub"} and nick not in var.VENGEFUL_GHOSTS.keys(): + if role not in var.WOLF_ROLES - {"wolf cub"} and role != "hunter" and nick not in var.VENGEFUL_GHOSTS.keys(): return if var.PHASE != "night": return @@ -5162,9 +5272,17 @@ def retract(cli, nick, chan, rest): if var.OBSERVED.get(nick): pm(cli, nick, (messages["werecrow_transformed"])) return + elif role == "hunter" and nick in var.HUNTERS and nick not in var.OTHER_KILLS.keys(): + return - if role not in var.WOLF_ROLES and nick in var.OTHER_KILLS.keys(): + if role in var.WOLF_ROLES and nick in var.KILLS.keys(): + del var.KILLS[nick] + pm(cli, nick, messages["retracted_kill"]) + relay_wolfchat_command(cli, nick, messages["wolfchat_retracted_kill"].format(nick), var.WOLF_ROLES - {"wolf cub"}, is_wolf_command=True, is_kill_command=True) + elif role not in var.WOLF_ROLES and nick in var.OTHER_KILLS.keys(): del var.OTHER_KILLS[nick] + if role == "hunter": + var.HUNTERS.remove(nick) pm(cli, nick, messages["retracted_kill"]) elif role == "alpha wolf" and nick in var.BITE_PREFERENCES.keys(): del var.BITE_PREFERENCES[nick] @@ -5173,6 +5291,8 @@ def retract(cli, nick, chan, rest): relay_wolfchat_command(cli, nick, messages["wolfchat_no_bite"].format(nick), ("alpha wolf",), is_wolf_command=True) elif role == "alpha wolf" and var.ALPHA_ENABLED: pm(cli, nick, messages["kill_bite_pending"]) + else: + pm(cli, nick, messages["kill_pending"]) return if var.PHASE != "day": @@ -5293,7 +5413,10 @@ def kill(cli, nick, chan, rest): role = get_role(nick) except KeyError: role = None - if role not in {"dullahan", "vigilante"} and nick not in var.VENGEFUL_GHOSTS.keys(): + wolfroles = var.WOLF_ROLES - {"wolf cub"} + if role in var.WOLFCHAT_ROLES and role not in wolfroles: + return # they do this a lot. + if role not in wolfroles | {"hunter", "dullahan", "vigilante"} and nick not in var.VENGEFUL_GHOSTS.keys(): return if nick in var.VENGEFUL_GHOSTS.keys() and var.VENGEFUL_GHOSTS[nick][0] == "!": # ghost was driven away by retribution @@ -5302,11 +5425,31 @@ def kill(cli, nick, chan, rest): # all their targets are dead pm(cli, nick, messages["dullahan_targets_dead"]) return + if role == "hunter" and nick in var.HUNTERS and nick not in var.OTHER_KILLS: + # they are a hunter and did not kill this night (if they killed this night, this allows them to switch) + pm(cli, nick, messages["hunter_already_killed"]) + return if nick in var.SILENCED: pm(cli, nick, messages["silenced"]) return + if role in wolfroles and var.DISEASED_WOLVES: + pm(cli, nick, messages["ill_wolves"]) + return pieces = re.split(" +",rest) victim = pieces[0] + victim2 = None + if role in wolfroles and var.ANGRY_WOLVES: + if len(pieces) > 1: + if len(pieces) > 2 and pieces[1].lower() == "and": + victim2 = pieces[2] + else: + victim2 = pieces[1] + else: + victim2 = None + if role == "werecrow": # Check if flying to observe + if var.OBSERVED.get(nick): + pm(cli, nick, (messages["werecrow_transformed_nokill"])) + return victim = get_victim(cli, nick, victim, False) if not victim: @@ -5314,8 +5457,15 @@ def kill(cli, nick, chan, rest): if is_safe(nick, victim): pm(cli, nick, messages["no_acting_on_succubus"].format("kill")) return + if victim2 != None: + victim2 = get_victim(cli, nick, victim2, False) + if not victim2: + return + if is_safe(nick, victim2): + pm(cli, nick, messages["no_acting_on_succubus"].format("kill")) + return - if victim == nick: + if victim == nick or victim2 == nick: if nick in var.VENGEFUL_GHOSTS.keys(): pm(cli, nick, messages["player_dead"]) else: @@ -5331,16 +5481,53 @@ def kill(cli, nick, chan, rest): pm(cli, nick, messages["vengeful_ghost_villager"]) return - rv = choose_target(nick, victim) - if nick not in var.VENGEFUL_GHOSTS.keys(): - if check_exchange(cli, nick, rv): + if role in wolfroles: + if in_wolflist(nick, victim) or (victim2 is not None and in_wolflist(nick, victim2)): + pm(cli, nick, messages["wolf_no_target_wolf"]) return - var.OTHER_KILLS[nick] = rv + if var.ANGRY_WOLVES and victim2 is not None: + if victim == victim2: + pm(cli, nick, messages["wolf_must_target_multiple"]) + return + else: + rv = choose_target(nick, victim) + rv2 = choose_target(nick, victim2) + if check_exchange(cli, nick, rv): + return + if check_exchange(cli, nick, rv2): + return + var.KILLS[nick] = [rv, rv2] + else: + rv = choose_target(nick, victim) + if check_exchange(cli, nick, rv): + return + var.KILLS[nick] = [rv] + else: + rv = choose_target(nick, victim) + if nick not in var.VENGEFUL_GHOSTS.keys(): + if check_exchange(cli, nick, rv): + return + var.OTHER_KILLS[nick] = rv + if role == "hunter": + var.HUNTERS.add(nick) + var.PASSED.discard(nick) - msg = messages["wolf_target"].format(victim) - pm(cli, nick, messages["player"].format(msg)) + if victim2 != None: + msg = messages["wolf_target_multiple"].format(victim, victim2) + pm(cli, nick, messages["player"].format(msg)) + else: + msg = messages["wolf_target"].format(victim) + pm(cli, nick, messages["player"].format(msg)) + if var.ANGRY_WOLVES and role in wolfroles: + pm(cli, nick, messages["wolf_target_second"]) - debuglog("{0} ({1}) KILL: {2} ({3})".format(nick, role, victim, get_role(victim))) + if in_wolflist(nick, nick): + relay_wolfchat_command(cli, nick, messages["wolfchat"].format(nick, msg), var.WOLF_ROLES - {"wolf cub"}, is_wolf_command=True, is_kill_command=True) + + if victim2: + debuglog("{0} ({1}) KILL: {2} and {3} ({4})".format(nick, role, victim, victim2, get_role(victim2))) + else: + debuglog("{0} ({1}) KILL: {2} ({3})".format(nick, role, victim, get_role(victim))) chk_nightdone(cli) @@ -5465,10 +5652,8 @@ def observe(cli, nick, chan, rest): if check_exchange(cli, nick, victim): return var.OBSERVED[nick] = victim - # temp hack, will do something better once crow is split off - from src.roles import wolf - if nick in wolf.KILLS: - del wolf.KILLS[nick] + if nick in var.KILLS.keys(): + del var.KILLS[nick] if role == "werecrow": pm(cli, nick, messages["werecrow_observe_success"].format(victim)) relay_wolfchat_command(cli, nick, messages["wolfchat_observe"].format(nick, victim), ("werecrow",), is_wolf_command=True) @@ -5660,6 +5845,7 @@ def hvisit(cli, nick, chan, rest): if var.OTHER_KILLS.get(victim) in var.ROLES["succubus"]: pm(cli, victim, messages["no_kill_succubus"].format(var.OTHER_KILLS[victim])) del var.OTHER_KILLS[victim] + var.HUNTERS.discard(victim) if var.TARGETED.get(victim) in var.ROLES["succubus"]: msg = messages["no_target_succubus"].format(var.TARGETED[victim]) del var.TARGETED[victim] @@ -5677,19 +5863,13 @@ def hvisit(cli, nick, chan, rest): var.TOBESILENCED.remove(nick) var.HEXED.remove(victim) del var.LASTHEXED[victim] - # temp hack, will do something better once succubus is split off - from src.roles import wolf, hunter - if set(wolf.KILLS.get(victim, ())) & var.ROLES["succubus"]: + if set(var.KILLS.get(victim, ())) & var.ROLES["succubus"]: for s in var.ROLES["succubus"]: - if s in wolf.KILLS[victim]: + if s in var.KILLS[victim]: pm(cli, victim, messages["no_kill_succubus"].format(nick)) - wolf.KILLS[victim].remove(s) - if not wolf.KILLS[victim]: - del wolf.KILLS[victim] - if hunter.KILLS.get(victim) in var.ROLES["succubus"]: - pm(cli, victim, messages["no_kill_succubus"].format(hunter.KILLS[victim])) - del hunter.KILLS[victim] - hunter.HUNTERS.discard(victim) + var.KILLS[victim].remove(s) + if not var.KILLS[victim]: + del var.KILLS[victim] if var.BITE_PREFERENCES.get(victim) in var.ROLES["succubus"]: pm(cli, victim, messages["no_kill_succubus"].format(var.BITE_PREFERENCES[victim])) del var.BITE_PREFERENCES[victim] @@ -5847,7 +6027,7 @@ def bite_cmd(cli, nick, chan, rest): chk_nightdone(cli) @cmd("pass", chan=False, pm=True, playing=True, phases=("night",), - roles=("harlot", "bodyguard", "guardian angel", "turncoat", "warlock", "piper", "succubus", "vigilante")) + roles=("hunter", "harlot", "bodyguard", "guardian angel", "turncoat", "warlock", "piper", "succubus", "vigilante")) def pass_cmd(cli, nick, chan, rest): """Decline to use your special power for that night.""" nickrole = get_role(nick) @@ -5860,7 +6040,17 @@ def pass_cmd(cli, nick, chan, rest): cli.notice(nick, messages["silenced"]) return - if nickrole == "harlot": + if nickrole == "hunter": + if nick in var.OTHER_KILLS.keys(): + del var.OTHER_KILLS[nick] + var.HUNTERS.remove(nick) + if nick in var.HUNTERS: + pm(cli, nick, messages["hunter_already_killed"]) + return + + pm(cli, nick, messages["hunter_pass"]) + var.PASSED.add(nick) + elif nickrole == "harlot": if var.HVISITED.get(nick): pm(cli, nick, (messages["harlot_already_visited"]).format(var.HVISITED[nick])) return @@ -6373,13 +6563,14 @@ def transition_night(cli): var.TIMERS = {} # Reset nighttime variables + var.KILLS = {} var.GUARDED = {} # key = by whom, value = the person that is visited var.KILLER = "" # nickname of who chose the victim var.SEEN = set() # set of seers that have had visions var.HEXED = set() # set of hags that have hexed var.CURSED = set() # set of warlocks that have cursed var.SHAMANS = {} - var.PASSED = set() + var.PASSED = set() # set of hunters that have chosen not to kill var.OBSERVED = {} # those whom werecrows have observed var.CHARMERS = set() # pipers who have charmed var.HVISITED = {} @@ -6447,9 +6638,9 @@ def transition_night(cli): if var.FIRST_NIGHT: # we don't need to tell them twice if they remember right away continue showrole = amnrole - if showrole in var.HIDDEN_VILLAGERS: + if showrole == "time lord": showrole = "villager" - elif showrole in var.HIDDEN_ROLES: + elif showrole == "vengeful ghost": showrole = var.DEFAULT_ROLE n = "" if showrole.startswith(("a", "e", "i", "o", "u")): @@ -6474,6 +6665,78 @@ def transition_night(cli): # send PMs ps = list_players() + wolves = list_players(var.WOLFCHAT_ROLES) + for wolf in wolves: + normal_notify = wolf in var.PLAYERS and not is_user_simple(wolf) + role = get_role(wolf) + cursed = "cursed " if wolf in var.ROLES["cursed villager"] else "" + + if normal_notify: + if role == "wolf": + pm(cli, wolf, messages["wolf_notify"]) + elif role == "traitor": + if cursed: + pm(cli, wolf, messages["cursed_traitor_notify"]) + else: + pm(cli, wolf, messages["traitor_notify"]) + elif role == "werecrow": + pm(cli, wolf, messages["werecrow_notify"]) + elif role == "hag": + pm(cli, wolf, messages["hag_notify"].format(cursed)) + elif role == "sorcerer": + pm(cli, wolf, messages["sorcerer_notify"].format(cursed)) + elif role == "wolf cub": + pm(cli, wolf, messages["wolf_cub_notify"]) + elif role == "alpha wolf": + pm(cli, wolf, messages["alpha_wolf_notify"]) + elif role == "werekitten": + pm(cli, wolf, messages["werekitten_notify"]) + elif role == "warlock": + pm(cli, wolf, messages["warlock_notify"].format(cursed)) + elif role == "wolf mystic": + pm(cli, wolf, messages["wolf_mystic_notify"]) + elif role == "wolf shaman": + pm(cli, wolf, messages["wolf_shaman_notify"]) + elif role == "fallen angel": + pm(cli, wolf, messages["fallen_angel_notify"]) + elif role == "doomsayer": + pm(cli, wolf, messages["doomsayer_notify"]) + else: + # catchall in case we forgot something above + an = 'n' if role.startswith(("a", "e", "i", "o", "u")) else "" + pm(cli, wolf, messages["undefined_role_notify"].format(an, role)) + + if len(wolves) > 1: + pm(cli, wolf, messages["wolfchat_notify"]) + else: + an = "n" if cursed == "" and role.startswith(("a", "e", "i", "o", "u")) else "" + pm(cli, wolf, messages["wolf_simple"].format(an, cursed, role)) # !simple + + pl = ps[:] + random.shuffle(pl) + pl.remove(wolf) # remove self from list + for i, player in enumerate(pl): + prole = get_role(player) + if prole in var.WOLFCHAT_ROLES: + cursed = "" + if player in var.ROLES["cursed villager"]: + cursed = "cursed " + pl[i] = "\u0002{0}\u0002 ({1}{2})".format(player, cursed, prole) + elif player in var.ROLES["cursed villager"]: + pl[i] = player + " (cursed)" + + pm(cli, wolf, "Players: " + ", ".join(pl)) + if role == "wolf mystic": + # if adding this info to !myrole, you will need to save off this count so that they can't get updated info until the next night + # # of special villagers = # of players - # of villagers - # of wolves - # of neutrals + numvills = len(ps) - len(list_players(var.WOLFTEAM_ROLES)) - len(list_players(("villager", "vengeful ghost", "time lord", "amnesiac", "lycan"))) - len(list_players(var.TRUE_NEUTRAL_ROLES)) + pm(cli, wolf, messages["wolf_mystic_info"].format("are" if numvills != 1 else "is", numvills, "s" if numvills != 1 else "")) + if var.DISEASED_WOLVES: + pm(cli, wolf, messages["ill_wolves"]) + elif var.ANGRY_WOLVES and role in var.WOLF_ROLES and role != "wolf cub": + pm(cli, wolf, messages["angry_wolves"]) + if var.ALPHA_ENABLED and role == "alpha wolf" and wolf not in var.ALPHA_WOLVES: + pm(cli, wolf, messages["wolf_bite"]) for harlot in var.ROLES["harlot"]: pl = ps[:] @@ -6633,6 +6896,18 @@ def transition_night(cli): if role not in var.WOLFCHAT_ROLES: pm(cli, shaman, "Players: " + ", ".join(pl)) + for hunter in var.ROLES["hunter"]: + if hunter in var.HUNTERS: + continue #already killed + pl = ps[:] + random.shuffle(pl) + pl.remove(hunter) + if hunter in var.PLAYERS and not is_user_simple(hunter): + pm(cli, hunter, messages["hunter_notify"]) + else: + pm(cli, hunter, messages["hunter_simple"]) + pm(cli, hunter, "Players: " + ", ".join(pl)) + for dullahan in var.ROLES["dullahan"]: targets = list(var.DULLAHAN_TARGETS[dullahan]) for target in var.DEAD: @@ -6843,6 +7118,25 @@ def transition_night(cli): pm(cli, minion, messages["minion_simple"]) pm(cli, minion, "Wolves: " + ", ".join(wolves)) + villagers = copy.copy(var.ROLES["villager"]) + villagers |= var.ROLES["time lord"] + if var.DEFAULT_ROLE == "villager": + villagers |= var.ROLES["vengeful ghost"] | var.ROLES["amnesiac"] + for villager in villagers: + if villager in var.PLAYERS and not is_user_simple(villager): + pm(cli, villager, messages["villager_notify"]) + else: + pm(cli, villager, messages["villager_simple"]) + + cultists = copy.copy(var.ROLES["cultist"]) + if var.DEFAULT_ROLE == "cultist": + cultists |= var.ROLES["vengeful ghost"] | var.ROLES["amnesiac"] + for cultist in cultists: + if cultist in var.PLAYERS and not is_user_simple(cultist): + pm(cli, cultist, messages["cultist_notify"]) + else: + pm(cli, cultist, messages["cultist_simple"]) + for blessed in var.ROLES["blessed villager"]: if blessed in var.PLAYERS and not is_user_simple(blessed): pm(cli, blessed, messages["blessed_notify"]) @@ -7066,8 +7360,10 @@ def start(cli, nick, chan, forced = False, restart = ""): var.GUNNERS = {} var.SEEN = set() var.OBSERVED = {} + var.KILLS = {} var.GUARDED = {} var.HVISITED = {} + var.HUNTERS = set() var.VENGEFUL_GHOSTS = {} var.CLONED = {} var.TARGETED = {} @@ -7905,9 +8201,9 @@ def myrole(cli, nick, chan, rest): return role = get_role(nick) - if role in var.HIDDEN_VILLAGERS: + if role == "time lord": role = "villager" - elif role in var.HIDDEN_ROLES: + elif role in ("amnesiac", "vengeful ghost"): role = var.DEFAULT_ROLE evt = Event("myrole", {"role": role, "messages": []})