diff --git a/src/roles/dullahan.py b/src/roles/dullahan.py index a0c7ff1..3c28bc5 100644 --- a/src/roles/dullahan.py +++ b/src/roles/dullahan.py @@ -142,7 +142,7 @@ 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 + evt.data["killers"][d].append(k) del KILLS[k] @event_listener("exchange_roles") diff --git a/src/roles/hunter.py b/src/roles/hunter.py index 322a95c..cf19b75 100644 --- a/src/roles/hunter.py +++ b/src/roles/hunter.py @@ -110,7 +110,7 @@ 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 + evt.data["killers"][d].append(k) # important, otherwise our del_player listener lets hunter kill again del KILLS[k] diff --git a/src/roles/vengefulghost.py b/src/roles/vengefulghost.py new file mode 100644 index 0000000..d0d843d --- /dev/null +++ b/src/roles/vengefulghost.py @@ -0,0 +1,237 @@ +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] +GHOSTS = {} # type: Dict[str, str] + +@cmd("kill", chan=False, pm=True, playing=False, phases=("night",)) +def vg_kill(cli, nick, chan, rest): + """Take revenge on someone each night after you die.""" + if nick not in GHOSTS or GHOSTS[nick][0] == "!": + return + if nick in var.SILENCED: + pm(cli, nick, messages["silenced"]) + return + + victim = get_victim(cli, nick, re.split(" +",rest)[0], False) + if not victim: + return + + if victim == nick: + pm(cli, nick, messages["player_dead"]) + return + + wolves = list_players(var.WOLFTEAM_ROLES) + if GHOSTS[nick] == "wolves" and victim not in wolves: + pm(cli, nick, messages["vengeful_ghost_wolf"]) + return + elif GHOSTS[nick] == "villagers" and victim in wolves: + pm(cli, nick, messages["vengeful_ghost_villager"]) + return + + orig = victim + evt = Event("targeted_command", {"target": victim, "misdirection": True, "exchange": False}) + evt.dispatch(cli, var, "kill", nick, victim, frozenset({"detrimental"})) + if evt.prevent_default: + return + victim = evt.data["target"] + + KILLS[nick] = victim + + 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=False, phases=("night",)) +def vg_retract(cli, nick, chan, rest): + """Removes a vengeful ghost's kill selection.""" + if nick not in GHOSTS: + return + if nick in KILLS: + del KILLS[nick] + pm(cli, nick, messages["retracted_kill"]) + +@event_listener("list_participants") +def on_list_participants(evt, var): + evt.data["pl"].extend([p for p in GHOSTS if GHOSTS[p][0] != "!"]) + +@event_listener("player_win", priority=1) +def on_player_win(evt, cli, var, nick, role, winner, survived): + # alive VG winning is handled in villager.py + # extending VG to work with new teams can be done by registering + # a listener at priority > 1, importing src.roles.vengefulghost, + # and checking if the nick is in GHOSTS. + if nick in GHOSTS: + evt.data["special"].append("vg activated") + against = GHOSTS[nick] + if GHOSTS[nick][0] == "!": + evt.data["special"].append("vg driven off") + against = against[1:] + if against == "villagers" and winner == "wolves": + won = True + iwon = True + elif against == "wolves" and winner == "villagers": + won = True + iwon = True + else: + won = False + iwon = False + +@event_listener("del_player", priority=6) +def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers): + for h,v in list(KILLS.items()): + if v == nick: + pm(cli, h, messages["hunter_discard"]) + del KILLS[h] + # extending VG to work with new teams can be done by registering a listener + # at priority < 6, importing src.roles.vengefulghost, and setting + # GHOSTS[nick] to something; if that is done then this logic is not run. + if death_triggers and nickrole == "vengeful ghost" and nick not in GHOSTS: + if evt.params.killer_role in var.WOLFTEAM_ROLES: + GHOSTS[nick] = "wolves" + else: + GHOSTS[nick] = "villagers" + pm(cli, nick, messages["vengeful_turn"].format(GHOSTS[nick])) + debuglog(nick, "(vengeful ghost) TRIGGER", GHOSTS[nick]) + +@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 GHOSTS: + GHOSTS[nick] = GHOSTS.pop(prefix) + +@event_listener("night_acted") +def on_acted(evt, cli, var, nick, sender): + if nick in KILLS: + evt.data["acted"] = True + +@event_listener("transition_day_begin", priority=6) +def on_transition_day_begin(evt, cli, var): + # select a random target for VG if they didn't kill + wolves = set(list_players(var.WOLFTEAM_ROLES)) + villagers = set(list_players()) - wolves + for ghost, target in GHOSTS.items(): + if target[0] == "!" or ghost in var.SILENCED: + continue + if ghost not in KILLS: + choice = set() + if target == "wolves": + choice = wolves.copy() + elif target == "villagers": + choice = villagers.copy() + evt = Event("vg_kill", {"pl": choice}) + evt.dispatch(cli, var, ghost, target) + choice = evt.data["pl"] + # roll this into the above event once succubus is split off + if ghost in var.ENTRANCED: + choice -= var.ROLES["succubus"] + if choice: + KILLS[ghost] = random.choice(list(choice)) + +@event_listener("transition_day", priority=2) +def on_transition_day(evt, cli, var): + for k, d in KILLS.items(): + evt.data["victims"].append(d) + evt.data["onlybywolves"].discard(d) + evt.data["killers"][d].append(k) + +@event_listener("transition_day", priority=5.01) +def on_transition_day2(evt, cli, var): + for k, d in list(KILLS.items()): + if GHOSTS[k] == "villagers": + evt.data["killers"][d].remove(k) + evt.data["killers"][d].insert(0, k) + # important, otherwise our del_player listener messages the vg + del KILLS[k] + +@event_listener("retribution_kill") +def on_retribution_kill(evt, cli, var, victim, orig_target): + t = evt.data["target"] + if t in GHOSTS: + GHOSTS[t] = "!" + GHOSTS[t] + evt.data["message"].append(messages["totem_banish"].format(victim, t)) + evt.data["target"] = None + +@event_listener("get_participant_role") +def on_get_participant_role(evt, var, nick): + if nick in GHOSTS: + against = GHOSTS[nick] + if against == "villagers": + evt.data["role"] = "wolf" + elif against == "wolves": + evt.data["role"] = "villager" + +@event_listener("chk_nightdone") +def on_chk_nightdone(evt, cli, var): + evt.data["actedcount"] += len(KILLS) + evt.data["nightroles"].extend([p for p in GHOSTS if GHOSTS[p][0] != "!"]) + +@event_listener("transition_night_end", priority=2) +def on_transition_night_end(evt, cli, var): + # alive VGs are messaged as part of villager.py, this handles dead ones + ps = list_players() + wolves = list_players(var.WOLFTEAM_ROLES) + for v_ghost, who in GHOSTS.items(): + if who[0] == "!": + continue + if who == "wolves": + pl = wolves[:] + else: + pl = ps[:] + for wolf in wolves: + pl.remove(wolf) + + random.shuffle(pl) + + if v_ghost in var.PLAYERS and not is_user_simple(v_ghost): + pm(cli, v_ghost, messages["vengeful_ghost_notify"].format(who)) + else: + pm(cli, v_ghost, messages["vengeful_ghost_simple"]) + pm(cli, v_ghost, who.capitalize() + ": " + ", ".join(pl)) + debuglog("GHOST: {0} (target: {1}) - players: {2}".format(v_ghost, who, ", ".join(pl))) + +@event_listener("myrole") +def on_myrole(evt, cli, var, nick): + if nick in GHOSTS: + evt.prevent_default = True + if GHOSTS[nick][0] != "!": + pm(cli, nick, messages["vengeful_role"].format(GHOSTS[nick])) + +@event_listener("revealroles") +def on_revealroles(evt, cli, var): + if GHOSTS: + glist = [] + for ghost, team in GHOSTS.items(): + dead = "driven away, " if team[0] == "!" else "" + glist.append("{0} ({1}against {2})".format(ghost, dead, team.lstrip("!"))) + evt.data["output"].append("\u0002dead vengeful ghost\u0002: {0}".format(", ".join(glist))) + +@event_listener("begin_day") +def on_begin_day(evt, cli, var): + KILLS.clear() + +@event_listener("reset") +def on_reset(evt, var): + KILLS.clear() + GHOSTS.clear() + +# vim: set sw=4 expandtab: diff --git a/src/roles/vigilante.py b/src/roles/vigilante.py index 5cfba85..b62a707 100644 --- a/src/roles/vigilante.py +++ b/src/roles/vigilante.py @@ -97,7 +97,7 @@ 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 + evt.data["killers"][d].append(k) # important, otherwise our del_player listener lets hunter kill again del KILLS[k] diff --git a/src/roles/wolf.py b/src/roles/wolf.py index 31256e5..1b52817 100644 --- a/src/roles/wolf.py +++ b/src/roles/wolf.py @@ -91,11 +91,15 @@ def wolf_kill(cli, nick, chan, rest): @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) + if nick in KILLS: + 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) + if get_role(nick) == "alpha wolf" and nick in var.BITE_PREFERENCES: + del var.BITE_PREFERENCES[nick] + var.ALPHA_WOLVES.remove(nick) + pm(cli, nick, messages["no_bite"]) + relay_wolfchat_command(cli, nick, messages["wolfchat_no_bite"].format(nick), ("alpha wolf",), is_wolf_command=True) @event_listener("del_player") def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers): @@ -170,6 +174,35 @@ def on_transition_day(evt, cli, var): evt.data["bywolves"].discard(monster) evt.data["onlybywolves"].discard(monster) +@event_listener("transition_day", priority=5) +def on_transition_day2(evt, cli, var): + wolfteam = list_players(var.WOLFTEAM_ROLES) + for victim, killers in list(evt.data["killers"].items()): + k2 = [] + kappend = [] + wolves = False + for k in killers: + if k in wolfteam: + kappend.append(k) + elif k == "@wolves": + wolves = True + else: + k2.append(k) + k2.extend(kappend) + if wolves: + k2.append("@wolves") + evt.data["killers"][victim] = k2 + +@event_listener("retribution_kill") +def on_retribution_kill(evt, cli, var, victim, orig_target): + t = evt.data["target"] + if t == "@wolves": + wolves = list_players(var.WOLF_ROLES) + for crow in var.ROLES["werecrow"]: + if crow in var.OBSERVED: + wolves.remove(crow) + evt.data["target"] = random.choice(wolves) + @event_listener("exchange_roles") def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role): if actor in KILLS: diff --git a/src/utilities.py b/src/utilities.py index 88fd05d..eb5eacc 100644 --- a/src/utilities.py +++ b/src/utilities.py @@ -18,8 +18,8 @@ __all__ = ["pm", "is_fake_nick", "mass_mode", "mass_privmsg", "reply", "relay_wolfchat_command", "chk_nightdone", "chk_decision", "chk_win", "irc_lower", "irc_equals", "is_role", "match_hostmask", "is_owner", "is_admin", "plural", "singular", "list_players", - "list_players_and_roles", "get_role", "get_reveal_role", - "get_templates", "role_order", "break_long_message", + "list_players_and_roles", "list_participants", "get_role", "get_roles", + "get_reveal_role", "get_templates", "role_order", "break_long_message", "complete_match", "get_victim", "get_nick", "pastebin_tb", "InvalidModeException"] # message either privmsg or notice, depending on user settings @@ -309,7 +309,7 @@ def singular(plural): # otherwise we just added an s on the end return plural[:-1] -def list_players(roles = None): +def list_players(roles=None): if roles is None: roles = var.ROLES.keys() pl = set() @@ -329,12 +329,28 @@ def list_players_and_roles(): plr[p] = x return plr +def list_participants(): + """List all people who are still able to participate in the game in some fashion.""" + pl = list_players() + evt = Event("list_participants", {"pl": pl}) + evt.dispatch(var) + return evt.data["pl"][:] + def get_role(p): for role, pl in var.ROLES.items(): if role in var.TEMPLATE_RESTRICTIONS.keys(): continue # only get actual roles if p in pl: return role + # not found in player list, see if they're a special participant + role = None + if p in list_participants(): + evt = Event("get_participant_role", {"role": None}) + evt.dispatch(var, p) + role = evt.data["role"] + if role is None: + raise ValueError("Nick {0} isn't playing and has no defined participant role".format(p)) + return role def get_roles(*roles): all_roles = [] @@ -342,7 +358,6 @@ def get_roles(*roles): all_roles.append(var.ROLES[role]) return list(itertools.chain(*all_roles)) - def get_reveal_role(nick): if var.HIDDEN_TRAITOR and get_role(nick) == "traitor": role = var.DEFAULT_ROLE diff --git a/src/wolfgame.py b/src/wolfgame.py index 82fcd7d..39d9137 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -98,7 +98,6 @@ var.OPPED = False # Keeps track of whether the bot is opped var.BITTEN_ROLES = {} var.LYCAN_ROLES = {} -var.VENGEFUL_GHOSTS = {} var.CHARMED = set() if botconfig.DEBUG_MODE and var.DISABLE_DEBUG_MODE_TIMERS: @@ -677,7 +676,7 @@ def replace(cli, nick, chan, rest): for user in var.USERS: if irc_lower(var.USERS[user]["account"]) == account: - if user == nick or (user not in list_players() and user not in var.VENGEFUL_GHOSTS): + if user == nick or user not in list_participants(): pass elif target is None: target = user @@ -690,7 +689,7 @@ def replace(cli, nick, chan, rest): reply(cli, nick, chan, msg, private=True) return else: - pl = list_players() + list(var.VENGEFUL_GHOSTS.keys()) + pl = list_participants() pll = [irc_lower(i) for i in pl] target, _ = complete_match(irc_lower(rest[0]), pll) @@ -698,7 +697,7 @@ def replace(cli, nick, chan, rest): if target is not None: target = pl[pll.index(target)] - if (target not in list_players() and target not in var.VENGEFUL_GHOSTS) or target not in var.USERS: + if target not in pl or target not in var.USERS: msg = messages["target_not_playing"].format(" longer" if target in var.DEAD else "t") reply(cli, nick, chan, msg, private=True) return @@ -956,9 +955,9 @@ def join_deadchat(cli, *all_nicks): return nicks = [] - pl = list_players() + pl = list_participants() for nick in all_nicks: - if is_user_stasised(nick) or nick in pl or nick in var.DEADCHAT_PLAYERS or nick in var.VENGEFUL_GHOSTS: + if is_user_stasised(nick) or nick in pl or nick in var.DEADCHAT_PLAYERS: continue nicks.append(nick) @@ -2486,10 +2485,6 @@ def stop_game(cli, winner="", abort=False, additional_winners=None, log=True): pentry["special"].append("lover") if splr in var.ENTRANCED: pentry["special"].append("entranced") - if splr in var.VENGEFUL_GHOSTS: - pentry["special"].append("vg activated") - if var.VENGEFUL_GHOSTS[splr][0] == "!": - pentry["special"].append("vg driven off") won = False iwon = False @@ -2561,29 +2556,6 @@ def stop_game(cli, winner="", abort=False, additional_winners=None, log=True): # For clone, this means they ended game while being clone and not some other role if splr in survived and not winner.startswith("@") and winner not in ("monsters", "demoniacs", "pipers"): iwon = True - elif rol == "vengeful ghost": - if not winner.startswith("@") and winner not in ("monsters", "demoniacs", "pipers"): - if winner != "succubi" and splr in var.ENTRANCED: - won = False - iwon = False - elif won and splr in survived: - iwon = True - elif splr in var.VENGEFUL_GHOSTS and var.VENGEFUL_GHOSTS[splr] == "villagers" and winner == "wolves": - won = True - iwon = True - elif splr in var.VENGEFUL_GHOSTS and var.VENGEFUL_GHOSTS[splr] == "!villagers" and winner == "wolves": - # Starts with ! if they were driven off by retribution totem - won = True - iwon = False - elif splr in var.VENGEFUL_GHOSTS and var.VENGEFUL_GHOSTS[splr] == "wolves" and winner == "villagers": - won = True - iwon = True - elif splr in var.VENGEFUL_GHOSTS and var.VENGEFUL_GHOSTS[splr] == "!wolves" and winner == "villagers": - won = True - iwon = False - else: - won = False - iwon = False elif rol == "jester" and splr in var.JESTERS: iwon = True elif winner == "succubi" and splr in var.ENTRANCED | var.ROLES["succubus"]: @@ -2874,10 +2846,9 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death var.FINAL_ROLES[clone] = nickrole sayrole = nickrole debuglog("{0} (clone) CLONE DEAD PLAYER: {1} ({2})".format(clone, target, sayrole)) - # if cloning time lord or vengeful ghost, say they are villager instead - if sayrole == "time lord": + if sayrole in var.HIDDEN_VILLAGERS: sayrole = "villager" - elif sayrole == "vengeful ghost": + elif sayrole in var.HIDDEN_ROLES: sayrole = var.DEFAULT_ROLE an = "n" if sayrole.startswith(("a", "e", "i", "o", "u")) else "" pm(cli, clone, messages["clone_turn"].format(an, sayrole)) @@ -3021,13 +2992,6 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death t.start() debuglog(nick, "(time lord) TRIGGER") - if nickrole == "vengeful ghost": - if killer_role in var.WOLFTEAM_ROLES: - var.VENGEFUL_GHOSTS[nick] = "wolves" - else: - var.VENGEFUL_GHOSTS[nick] = "villagers" - pm(cli, nick, messages["vengeful_turn"].format(var.VENGEFUL_GHOSTS[nick])) - debuglog(nick, "(vengeful ghost) TRIGGER", var.VENGEFUL_GHOSTS[nick]) if nickrole == "wolf cub": var.ANGRY_WOLVES = True if nickrole in var.WOLF_ROLES: @@ -3538,7 +3502,7 @@ def rename_player(cli, prefix, nick): dictvar.update(kvp) if prefix in dictvar.keys(): del dictvar[prefix] - for dictvar in (var.VENGEFUL_GHOSTS, var.TOTEMS, var.FINAL_ROLES, var.GUNNERS, var.TURNCOATS, + for dictvar in (var.TOTEMS, var.FINAL_ROLES, var.GUNNERS, var.TURNCOATS, var.DOCTORS, var.BITTEN_ROLES, var.LYCAN_ROLES, var.AMNESIAC_ROLES): if prefix in dictvar.keys(): dictvar[nick] = dictvar.pop(prefix) @@ -3796,7 +3760,7 @@ def begin_day(cli): # Reset nighttime variables var.GAMEPHASE = "day" - var.OTHER_KILLS = {} # other kill victims (vengeful ghost) + var.OTHER_KILLS = {} 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 @@ -3901,21 +3865,6 @@ def transition_day(cli, gameid=0): # NOTE: Random assassin selection is further down, since if we're choosing at random we pick someone # that isn't going to be dying today, meaning we need to know who is dying first :) - # Select a random target for vengeful ghost if they didn't kill - wolves = set(list_players(var.WOLFTEAM_ROLES)) - villagers = set(list_players()) - wolves - for ghost, target in var.VENGEFUL_GHOSTS.items(): - if target[0] == "!" or ghost in var.SILENCED: - continue - if ghost not in var.OTHER_KILLS: - if target == "wolves": - choice = wolves.copy() - else: - choice = villagers.copy() - if ghost in var.ENTRANCED: - choice -= var.ROLES["succubus"] - var.OTHER_KILLS[ghost] = random.choice(list(choice)) - # Select random totem recipients if shamans didn't act shamans = list_players(var.TOTEM_ORDER) for shaman in shamans: @@ -4096,7 +4045,6 @@ def transition_day(cli, gameid=0): protected = evt.data["protected"] bitten = evt.data["bitten"] - wolfghostvictims = [] for k, d in var.OTHER_KILLS.items(): victims.append(d) onlybywolves.discard(d) @@ -4425,17 +4373,13 @@ def transition_day(cli, gameid=0): break if loser in dead or victim == loser: loser = None - if loser == "@wolves": - wolves = list_players(var.WOLF_ROLES) - for crow in var.ROLES["werecrow"]: - if crow in var.OBSERVED: - wolves.remove(crow) - loser = random.choice(wolves) - if loser in var.VENGEFUL_GHOSTS.keys(): - # mark ghost as being unable to kill any more - var.VENGEFUL_GHOSTS[loser] = "!" + var.VENGEFUL_GHOSTS[loser] - message.append(messages["totem_banish"].format(victim, loser)) - elif loser is not None and loser not in var.ROLES["blessed villager"]: + evt = Event("retribution_kill", {"target": loser, "message": []}) + evt.dispatch(cli, var, victim, loser) + loser = evt.data["target"] + message.extend(evt.data["message"]) + if loser in dead or victim == loser: + loser = None + if loser is not None and loser not in var.ROLES["blessed villager"]: dead.append(loser) if var.ROLE_REVEAL in ("on", "team"): role = get_reveal_role(loser) @@ -4598,13 +4542,20 @@ def transition_day(cli, gameid=0): var.FINAL_ROLES[chump] = newrole relay_wolfchat_command(cli, chump, messages["wolfchat_new_member"].format(chump, newrole), var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True) - for deadperson in dead: # kill each player, but don't end the game if one group outnumbers another - # take a shortcut for killer_role here since vengeful ghost only cares about team and not particular roles - # this will have to be modified to track the actual killer if that behavior changes - # we check if they have already been killed as well since del_player could do chain reactions and we want + for deadperson in dead: + # check if they have already been killed since del_player could do chain reactions and we want # to avoid sending duplicate messages. if deadperson in list_players(): - del_player(cli, deadperson, end_game=False, killer_role="wolf" if deadperson in onlybywolves or deadperson in wolfghostvictims else "villager", deadlist=dead, original=deadperson) + if deadperson in killers: + killer = killers[deadperson][0] + if killer == "@wolves": + killer_role = "wolf" + else: + killer_role = get_role(killer) + else: + # no killers, so assume suicide + killer_role = get_role(deadperson) + del_player(cli, deadperson, end_game=False, killer_role=killer_role, deadlist=dead, original=deadperson) message = [] @@ -4642,10 +4593,6 @@ def chk_nightdone(cli): "warlock", "piper", "doomsayer", "prophet", "wolf shaman") - for ghost, against in var.VENGEFUL_GHOSTS.items(): - if not against.startswith("!"): - nightroles.append(ghost) - for nick, info in var.PRAYED.items(): if info[0] > 0: actedcount += 1 @@ -4664,9 +4611,6 @@ def chk_nightdone(cli): nightroles.extend(get_roles("alpha wolf")) actedcount += len([p for p in var.ALPHA_WOLVES if p in var.ROLES["alpha wolf"]]) - # but remove all instances of their name if they are silenced - nightroles = [p for p in nightroles if p not in var.SILENCED] - # add in turncoats who should be able to act -- if they passed they're already in var.PASSED # but if they can act they're in var.TURNCOATS where the second tuple item is the current night # (if said tuple item is the previous night, then they are not allowed to act tonight) @@ -4683,6 +4627,9 @@ def chk_nightdone(cli): event.dispatch(cli, var) actedcount = event.data["actedcount"] + # remove all instances of their name if they are silenced (makes implementing the event easier) + nightroles = [p for p in nightroles if p not in var.SILENCED] + if var.PHASE == "night" and actedcount >= len(nightroles): if not event.prevent_default: # check for assassins that have not yet targeted @@ -5128,13 +5075,13 @@ def check_exchange(cli, actor, nick): return True return False -@cmd("retract", "r", pm=True, phases=("day", "night", "join")) +@cmd("retract", "r", pm=True, phases=("day", "join")) def retract(cli, nick, chan, rest): """Takes back your vote during the day (for whom to lynch).""" - if chan not in (botconfig.CHANNEL, nick): + if chan != botconfig.CHANNEL: return - if (nick not in var.VENGEFUL_GHOSTS.keys() and nick not in list_players()) or nick in var.DISCONNECTED.keys(): + if nick not in list_players() or nick in var.DISCONNECTED.keys(): return with var.GRAVEYARD_LOCK, var.WARNING_LOCK: @@ -5151,29 +5098,6 @@ def retract(cli, nick, chan, rest): del var.TIMERS['start_votes'] return - 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(): - return - if var.PHASE != "night": - return - if role == "werecrow": # Check if already observed - if var.OBSERVED.get(nick): - pm(cli, nick, (messages["werecrow_transformed"])) - return - - if role not in var.WOLF_ROLES and nick in var.OTHER_KILLS.keys(): - del var.OTHER_KILLS[nick] - pm(cli, nick, messages["retracted_kill"]) - elif role == "alpha wolf" and nick in var.BITE_PREFERENCES.keys(): - del var.BITE_PREFERENCES[nick] - var.ALPHA_WOLVES.remove(nick) - pm(cli, nick, messages["no_bite"]) - 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"]) - return - if var.PHASE != "day": return if nick in var.NO_LYNCH: @@ -5282,62 +5206,6 @@ def shoot(cli, nick, chan, rest): def is_safe(nick, victim): # helper function return nick in var.ENTRANCED and victim in var.ROLES["succubus"] -@cmd("kill", chan=False, pm=True, phases=("night",)) -def kill(cli, nick, chan, rest): - """Kill a player. Behaviour varies depending on your role.""" - if (nick not in var.VENGEFUL_GHOSTS.keys() and nick not in list_players()) or nick in var.DISCONNECTED.keys(): - return - try: - role = get_role(nick) - except KeyError: - role = None - if 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 - return - if nick in var.SILENCED: - pm(cli, nick, messages["silenced"]) - return - pieces = re.split(" +",rest) - victim = pieces[0] - - victim = get_victim(cli, nick, victim, False) - if not victim: - return - if is_safe(nick, victim): - pm(cli, nick, messages["no_acting_on_succubus"].format("kill")) - return - - if victim == nick: - if nick in var.VENGEFUL_GHOSTS.keys(): - pm(cli, nick, messages["player_dead"]) - else: - pm(cli, nick, messages["no_suicide"]) - return - - if nick in var.VENGEFUL_GHOSTS.keys(): - allwolves = list_players(var.WOLFTEAM_ROLES) - if var.VENGEFUL_GHOSTS[nick] == "wolves" and victim not in allwolves: - pm(cli, nick, messages["vengeful_ghost_wolf"]) - return - elif var.VENGEFUL_GHOSTS[nick] == "villagers" and victim in allwolves: - 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): - return - var.OTHER_KILLS[nick] = rv - - msg = messages["wolf_target"].format(victim) - pm(cli, nick, messages["player"].format(msg)) - - debuglog("{0} ({1}) KILL: {2} ({3})".format(nick, role, victim, get_role(victim))) - - chk_nightdone(cli) - @cmd("guard", "protect", "save", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("bodyguard", "guardian angel")) def guard(cli, nick, chan, rest): """Guard a player, preventing them from being killed that night.""" @@ -5417,7 +5285,8 @@ def consecrate(cli, nick, chan, rest): # but other roles that do stuff after death or impact dead players should have functionality here as well # (for example, if there was a role that could raise corpses as undead somethings, this would prevent that from working) # regardless if this has any actual effect or not, it still removes the priest from being able to vote - if victim in var.VENGEFUL_GHOSTS.keys(): + from src.roles import vengefulghost + if victim in vengefulghost.GHOSTS: var.SILENCED.add(victim) var.CONSECRATING.add(nick) @@ -6713,26 +6582,6 @@ def transition_night(cli): else: pm(cli, lycan, messages["lycan_simple"]) - for v_ghost, who in var.VENGEFUL_GHOSTS.items(): - if who[0] == "!": - continue - wolves = list_players(var.WOLFTEAM_ROLES) - if who == "wolves": - pl = wolves - else: - pl = ps[:] - for wolf in wolves: - pl.remove(wolf) - - random.shuffle(pl) - - if v_ghost in var.PLAYERS and not is_user_simple(v_ghost): - pm(cli, v_ghost, messages["vengeful_ghost_notify"].format(who)) - else: - pm(cli, v_ghost, messages["vengeful_ghost_simple"]) - pm(cli, v_ghost, who.capitalize() + ": " + ", ".join(pl)) - debuglog("GHOST: {0} (target: {1}) - players: {2}".format(v_ghost, who, ", ".join(pl))) - for ass in var.ROLES["assassin"]: if ass in var.TARGETED and var.TARGETED[ass] != None: continue # someone already targeted @@ -7040,7 +6889,6 @@ def start(cli, nick, chan, forced = False, restart = ""): var.OBSERVED = {} var.GUARDED = {} var.HVISITED = {} - var.VENGEFUL_GHOSTS = {} var.CLONED = {} var.TARGETED = {} var.LASTGUARDED = {} @@ -7853,12 +7701,7 @@ def listroles(cli, nick, chan, rest): def myrole(cli, nick, chan, rest): """Reminds you of your current role.""" - #special case vengeful ghost (that hasn't been driven away) - if nick in var.VENGEFUL_GHOSTS.keys() and var.VENGEFUL_GHOSTS[nick][0] != "!": - pm(cli, nick, messages["vengeful_role"].format(var.VENGEFUL_GHOSTS[nick])) - return - - ps = list_players() + ps = list_participants() if nick not in ps: return @@ -7869,7 +7712,8 @@ def myrole(cli, nick, chan, rest): role = var.DEFAULT_ROLE evt = Event("myrole", {"role": role, "messages": []}) - evt.dispatch(cli, var, nick) + if not evt.dispatch(cli, var, nick): + return role = evt.data["role"] an = "n" if role.startswith(("a", "e", "i", "o", "u")) else "" @@ -8234,7 +8078,7 @@ def can_run_restricted_cmd(nick): if botconfig.DEBUG_MODE: return True - pl = list_players() + [vg for (vg, against) in var.VENGEFUL_GHOSTS.items() if not against.startswith("!")] + pl = list_participants() if nick in pl: return False @@ -8382,12 +8226,6 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: elif len(lovers) > 2: output.append("\u0002lovers\u0002: {0}, and {1}".format(", ".join(lovers[0:-1]), lovers[-1])) - # print out vengeful ghosts, also vengeful ghosts that were driven away by 'retribution' totem - if var.VENGEFUL_GHOSTS: - output.append("\u0002dead vengeful ghost\u0002: {0}".format(", ".join("{0} ({1}against {2})".format( - ghost, team.startswith("!") and "driven away, " or "", team.lstrip("!")) - for (ghost, team) in var.VENGEFUL_GHOSTS.items()))) - #show who got immunized if var.IMMUNIZED: output.append("\u0002immunized\u0002: {0}".format(", ".join(var.IMMUNIZED)))