diff --git a/src/gamemodes.py b/src/gamemodes.py index c951a0f..20faa26 100644 --- a/src/gamemodes.py +++ b/src/gamemodes.py @@ -229,7 +229,8 @@ class VillagergameMode(GameMode): tgt = hlt if not tgt: tgt = random.choice(pl) - var.KILLS[botconfig.NICK] = [tgt] + from src.roles import wolf + wolf.KILLS[botconfig.NICK] = [tgt] @game_mode("foolish", minp = 8, maxp = 24, likelihood = 8) class FoolishMode(GameMode): diff --git a/src/roles/seer.py b/src/roles/seer.py index 7f10516..aed1e92 100644 --- a/src/roles/seer.py +++ b/src/roles/seer.py @@ -82,6 +82,11 @@ 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/wolf.py b/src/roles/wolf.py new file mode 100644 index 0000000..b84d937 --- /dev/null +++ b/src/roles/wolf.py @@ -0,0 +1,270 @@ +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/wolfgame.py b/src/wolfgame.py index 11f0cd5..7d1532e 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -3171,12 +3171,6 @@ 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]): @@ -3558,21 +3552,6 @@ 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 = [] @@ -3827,7 +3806,6 @@ def begin_day(cli): # Reset nighttime variables var.GAMEPHASE = "day" - 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 @@ -4083,10 +4061,12 @@ 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.KILLS or target in var.HEXED or target in var.CURSED): + target in var.OBSERVED or target in var.HEXED or target in var.CURSED or evt.data["acted"]): pm(cli, crow, messages["werecrow_success"].format(target)) else: pm(cli, crow, messages["werecrow_failure"].format(target)) @@ -4102,63 +4082,29 @@ def transition_day(cli, gameid=0): var.NIGHT_TIMEDELTA += td min, sec = td.seconds // 60, td.seconds % 60 - 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) + # 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"] wolfghostvictims = [] for k, d in var.OTHER_KILLS.items(): @@ -4701,16 +4647,14 @@ 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, var.KILLS, + actedcount = sum(map(len, (var.SEEN, var.HVISITED, var.GUARDED, var.OTHER_KILLS, var.PASSED, var.OBSERVED, var.HEXED, var.SHAMANS, var.CURSED, var.CHARMERS))) - nightroles = get_roles("harlot", "succubus", "bodyguard", - "guardian angel", "wolf", "werecrow", "alpha wolf", + nightroles = get_roles("harlot", "succubus", "bodyguard", "guardian angel", "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 + "warlock", "piper", "vigilante", "doomsayer", + "prophet", "wolf shaman") for ghost, against in var.VENGEFUL_GHOSTS.items(): if not against.startswith("!"): @@ -4734,12 +4678,7 @@ def chk_nightdone(cli): actedcount += len(var.MATCHMAKERS | var.CLONED.keys()) nightroles.extend(get_roles("matchmaker", "clone")) - 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: + if 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"]]) @@ -4777,21 +4716,6 @@ 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() @@ -4980,9 +4904,6 @@ 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] @@ -4995,8 +4916,6 @@ 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: @@ -5015,12 +4934,8 @@ 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": @@ -5046,9 +4961,6 @@ 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] @@ -5061,8 +4973,6 @@ 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: @@ -5080,12 +4990,8 @@ 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": @@ -5275,11 +5181,7 @@ def retract(cli, nick, chan, rest): elif role == "hunter" and nick in var.HUNTERS and nick not in var.OTHER_KILLS.keys(): return - 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(): + if 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) @@ -5291,8 +5193,6 @@ 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": @@ -5413,10 +5313,7 @@ def kill(cli, nick, chan, rest): role = get_role(nick) except KeyError: role = None - 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(): + if role not in {"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 @@ -5432,24 +5329,8 @@ def kill(cli, nick, chan, rest): 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: @@ -5457,15 +5338,8 @@ 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 or victim2 == nick: + if victim == nick: if nick in var.VENGEFUL_GHOSTS.keys(): pm(cli, nick, messages["player_dead"]) else: @@ -5481,53 +5355,19 @@ def kill(cli, nick, chan, rest): pm(cli, nick, messages["vengeful_ghost_villager"]) return - 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"]) + rv = choose_target(nick, victim) + if nick not in var.VENGEFUL_GHOSTS.keys(): + if check_exchange(cli, nick, rv): return - 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) + var.OTHER_KILLS[nick] = rv + if role == "hunter": + var.HUNTERS.add(nick) + var.PASSED.discard(nick) - 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"]) + msg = messages["wolf_target"].format(victim) + pm(cli, nick, messages["player"].format(msg)) - 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))) + debuglog("{0} ({1}) KILL: {2} ({3})".format(nick, role, victim, get_role(victim))) chk_nightdone(cli) @@ -5652,8 +5492,10 @@ def observe(cli, nick, chan, rest): if check_exchange(cli, nick, victim): return var.OBSERVED[nick] = victim - if nick in var.KILLS.keys(): - del var.KILLS[nick] + # 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 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) @@ -5863,13 +5705,15 @@ def hvisit(cli, nick, chan, rest): var.TOBESILENCED.remove(nick) var.HEXED.remove(victim) del var.LASTHEXED[victim] - if set(var.KILLS.get(victim, ())) & var.ROLES["succubus"]: + # temp hack, will do something better once succubus is split off + from src.roles import wolf + if set(wolf.KILLS.get(victim, ())) & var.ROLES["succubus"]: for s in var.ROLES["succubus"]: - if s in var.KILLS[victim]: + if s in wolf.KILLS[victim]: pm(cli, victim, messages["no_kill_succubus"].format(nick)) - var.KILLS[victim].remove(s) - if not var.KILLS[victim]: - del var.KILLS[victim] + wolf.KILLS[victim].remove(s) + if not wolf.KILLS[victim]: + del wolf.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] @@ -6563,7 +6407,6 @@ 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 @@ -6665,78 +6508,6 @@ 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[:] @@ -7341,7 +7112,6 @@ def start(cli, nick, chan, forced = False, restart = ""): var.GUNNERS = {} var.SEEN = set() var.OBSERVED = {} - var.KILLS = {} var.GUARDED = {} var.HVISITED = {} var.HUNTERS = set()