def on_transition_day_resolve_end(evt, var, victims): evt2 = Event("get_role_metadata", {}) evt2.dispatch(var, "lycanthropy_role") for victim in victims: if victim in LYCANTHROPES and evt.data["killers"][victim] == ["@wolves"] and victim in evt.data["dead"]: vrole = get_main_role(victim) if vrole not in Wolf: new_role = "wolf" prefix = LYCANTHROPES[victim] if vrole in evt2.data: if "role" in evt2.data[vrole]: new_role = evt2.data[vrole]["role"] if "prefix" in evt2.data[vrole]: prefix = evt2.data[vrole]["prefix"] for sec_role in evt2.data[vrole].get("secondary_roles", ()): var.ROLES[sec_role].add(victim) to_send = "{0}_{1}".format(sec_role.replace(" ", "_"), "simple" if victim.prefers_simple() else "notify") victim.send(messages[to_send]) # FIXME: Not every role has proper message keys, such as shamans change_role(var, victim, vrole, new_role, message=prefix + "_turn") evt.data["howl"] += 1 evt.data["novictmsg"] = False evt.data["dead"].remove(victim) evt.data["killers"][victim].remove("@wolves") del evt.data["message"][victim] debuglog("{0} ({1}) TURN {2}".format(victim, vrole, new_role))
def immunize(var, wrapper, message): """Immunize a player, preventing them from turning into a wolf.""" if not DOCTORS[wrapper.source]: wrapper.pm(messages["doctor_fail"]) return target = get_target(var, wrapper, re.split(" +", message)[0], allow_self=True) if not target: return target = try_misdirection(var, wrapper.source, target) if try_exchange(var, wrapper.source, target): return doctor_evt = Event("doctor_immunize", {"message": "villager_immunized"}) doctor_evt.dispatch(var, wrapper.source, target) wrapper.pm(messages["doctor_success"].format(target)) target.send(messages["immunization_success"].format(messages[doctor_evt.data["message"]])) IMMUNIZED.add(target) DOCTORS[wrapper.source] -= 1 remove_lycanthropy(var, target) remove_disease(var, target) debuglog("{0} (doctor) IMMUNIZE: {1} ({2})".format(wrapper.source, target, get_main_role(target)))
def main(): evt = Event("init", {}) evt.dispatch() src.plog("Connecting to {0}:{1}{2}".format(botconfig.HOST, "+" if botconfig.USE_SSL else "", botconfig.PORT)) cli = IRCClient( {"privmsg": lambda *s: None, "notice": lambda *s: None, "": handler.unhandled}, host=botconfig.HOST, port=botconfig.PORT, authname=botconfig.USERNAME, password=botconfig.PASS, nickname=botconfig.NICK, ident=botconfig.IDENT, real_name=botconfig.REALNAME, sasl_auth=botconfig.SASL_AUTHENTICATION, server_pass=botconfig.SERVER_PASS, use_ssl=botconfig.USE_SSL, cert_verify=var.SSL_VERIFY, cert_fp=var.SSL_CERTFP, client_certfile=var.SSL_CERTFILE, client_keyfile=var.SSL_KEYFILE, cipher_list=var.SSL_CIPHERS, tokenbucket=TokenBucket(var.IRC_TB_BURST, var.IRC_TB_DELAY, init=var.IRC_TB_INIT), connect_cb=handler.connect_callback, stream_handler=src.stream, ) cli.mainLoop()
def vigilante_kill(cli, nick, chan, rest): """Kill someone at night, but you die too if they aren't a wolf or win stealer!""" victim = get_victim(cli, nick, re.split(" +",rest)[0], False) if not victim: return if victim == nick: pm(cli, nick, messages["no_suicide"]) return 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 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)
def dullahan_kill(cli, nick, chan, rest): """Kill someone at night as a dullahan until everyone on your list is dead.""" if not TARGETS[nick] & set(list_players()): pm(cli, nick, messages["dullahan_targets_dead"]) return victim = get_victim(cli, nick, re.split(" +",rest)[0], False) if not victim: return if victim == nick: pm(cli, nick, messages["no_suicide"]) return 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 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)
def mode_change(cli, rawnick, chan, mode, *targets): """Update the channel and user modes whenever a mode change occurs. Ordering and meaning of arguments for a MODE change: 0 - The IRCClient instance (like everywhere else) 1 - The raw nick of the mode setter/actor 2 - The channel (target) of the mode change 3 - The mode changes * - The targets of the modes (if any) This takes care of properly updating all relevant users and the channel modes to make sure we remain internally consistent. """ if chan == users.Bot.nick: # we only see user modes set to ourselves users.Bot.modes.update(mode) return if "!" not in rawnick: # Only sync modes if a server changed modes because # 1) human ops probably know better # 2) other bots might start a fight over modes # 3) recursion; we see our own mode changes. evt = Event("sync_modes", {}) evt.dispatch(var) return actor = users._get(rawnick, allow_none=True) # FIXME target = channels.add(chan, cli) target.queue("mode_change", {"mode": mode, "targets": targets}, (var, actor, target))
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 victim = get_victim(cli, nick, re.split(" +",rest)[0], False) if not victim: return if victim == nick: pm(cli, nick, messages["no_suicide"]) return 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)
def consecrate(var, wrapper, message): """Consecrates a corpse, putting its spirit to rest and preventing other unpleasant things from happening.""" alive = get_players() targ = re.split(" +", message)[0] if not targ: wrapper.pm(messages["not_enough_parameters"]) return dead = set(var.ALL_PLAYERS) - set(alive) target, _ = users.complete_match(targ, dead) if target is None: wrapper.pm(messages["consecrate_fail"].format(targ)) return # we have a target, so mark them as consecrated, right now all this does is silence a VG for a night # 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 evt = Event("consecrate", {}) evt.dispatch(var, wrapper.source, target) wrapper.pm(messages["consecrate_success"].format(target)) debuglog("{0} (priest) CONSECRATE: {1}".format(wrapper.source, target)) add_absent(var, wrapper.source, "consecrating") from src.votes import chk_decision from src.wolfgame import chk_win if not chk_win(): # game didn't immediately end due to marking as absent, see if we should force through a lynch chk_decision(var)
def hvisit(var, wrapper, message): """Entrance a player, converting them to your team.""" if VISITED.get(wrapper.source): wrapper.send(messages["succubus_already_visited"].format(VISITED[wrapper.source])) return target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="succubus_not_self") if not target: return target = try_misdirection(var, wrapper.source, target) if try_exchange(var, wrapper.source, target): return VISITED[wrapper.source] = target PASSED.discard(wrapper.source) if target not in get_all_players(("succubus",)): ENTRANCED.add(target) wrapper.send(messages["succubus_target_success"].format(target)) else: wrapper.send(messages["harlot_success"].format(target)) if wrapper.source is not target: if target not in get_all_players(("succubus",)): target.send(messages["notify_succubus_target"].format(wrapper.source)) else: target.send(messages["harlot_success"].format(wrapper.source)) revt = Event("succubus_visit", {}) revt.dispatch(var, wrapper.source, target) debuglog("{0} (succubus) VISIT: {1} ({2})".format(wrapper.source, target, get_main_role(target)))
def on_reconfigure_stats(evt, var, roleset, reason): from src.roles.helper.wolves import get_wolfchat_roles if reason != "howl" or not SCOPE: return evt2 = Event("get_role_metadata", {}) evt2.dispatch(var, "lycanthropy_role") roles = {} wolfchat = get_wolfchat_roles(var) for role, count in roleset.items(): if role in wolfchat or count == 0 or role not in SCOPE: continue if role in evt2.data and "role" in evt2.data[role]: roles[role] = evt2.data[role]["role"] else: roles[role] = "wolf" if roles and roleset in evt.data["new"]: evt.data["new"].remove(roleset) for role, new_role in roles.items(): rs = roleset.copy() rs[role] -= 1 rs[new_role] = rs.get(new_role, 0) + 1 evt.data["new"].append(rs)
def see(var, wrapper, message): """Use your paranormal powers to determine the role or alignment of a player.""" if wrapper.source in SEEN: wrapper.send(messages["seer_fail"]) return target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="no_see_self") if target is None: return target = try_misdirection(var, wrapper.source, target) if try_exchange(var, wrapper.source, target): return targrole = get_main_role(target) trole = targrole # keep a copy for logging evt = Event("investigate", {"role": targrole}) evt.dispatch(var, wrapper.source, target) targrole = evt.data["role"] aura = "blue" if targrole in Wolfteam: aura = "red" elif targrole in Neutral: aura = "grey" wrapper.send(messages["augur_success"].format(target, aura)) debuglog("{0} (augur) SEE: {1} ({2}) as {3} ({4} aura)".format(wrapper.source, target, trole, targrole, aura)) SEEN.add(wrapper.source)
def hvisit(var, wrapper, message): """Visit a player. You will die if you visit a wolf or a target of the wolves.""" if VISITED.get(wrapper.source): wrapper.pm(messages["harlot_already_visited"].format(VISITED[wrapper.source])) return target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="harlot_not_self") if not target: return target = try_misdirection(var, wrapper.source, target) if try_exchange(var, wrapper.source, target): return vrole = get_main_role(target) VISITED[wrapper.source] = target PASSED.discard(wrapper.source) wrapper.pm(messages["harlot_success"].format(target)) if target is not wrapper.source: target.send(messages["harlot_success"].format(wrapper.source)) revt = Event("harlot_visit", {}) revt.dispatch(var, wrapper.source, target) debuglog("{0} (harlot) VISIT: {1} ({2})".format(wrapper.source, target, vrole))
def on_update_stats3(evt, var, player, mainrole, revealrole, allroles): # if this is a night death and we know for sure that wolves (and only wolves) # killed, then that kill cannot be traitor as long as they're in wolfchat. # check if the reason == "night_death", otherwise it's probably a chained death which # can be traitor even if only wolves killed, so we short-circuit there as well # TODO: luck/misdirection totem can leak info due to our short-circuit below this comment. # If traitor dies due to one of these totems, both traitor count and villager count is reduced by # 1. If traitor does not die, and no other roles can kill (no death totems), then traitor count is unchanged # and villager count is reduced by 1. We should make it so that both counts are reduced when # luck/misdirection are potentially in play. # FIXME: this doesn't check RESTRICT_WOLFCHAT to see if traitor was removed from wolfchat. If # they've been removed, they can be killed like normal so all this logic is meaningless. if "traitor" not in evt.data["possible"] or evt.params.reason != "night_death" or mainrole == "traitor": return if var.PHASE == "day" and var.GAMEPHASE == "night": mevt = Event("get_role_metadata", {}) mevt.dispatch(var, "night_kills") nonwolf = 0 total = 0 for role, num in mevt.data.items(): if role != "wolf": nonwolf += num total += num if nonwolf == 0: evt.data["possible"].discard("traitor") return
def try_lynch_immunity(var, user) -> bool: if user in IMMUNITY: reason = IMMUNITY[user].pop() # get a random reason evt = Event("lynch_immunity", {"immune": False}) evt.dispatch(var, user, reason) return evt.data["immune"] return False
def on_new_role(evt, var, player, old_role): wcroles = get_wolfchat_roles(var) if old_role is None: # initial role assignment; don't do all the logic below about notifying other wolves and such if evt.data["role"] in wcroles: evt.data["in_wolfchat"] = True return sayrole = evt.data["role"] if sayrole in Hidden: sayrole = var.HIDDEN_ROLE an = "n" if sayrole.startswith(("a", "e", "i", "o", "u")) else "" if player in KILLS: del KILLS[player] if old_role not in wcroles and evt.data["role"] in wcroles: # a new wofl has joined the party, give them tummy rubs and the wolf list # and let the other wolves know to break out the confetti and villager steaks wofls = get_players(wcroles) evt.data["in_wolfchat"] = True if wofls: new_wolves = [] for wofl in wofls: wofl.queue_message(messages["wolfchat_new_member"].format(player, an, sayrole)) wofl.send_messages() else: return # no other wolves, nothing else to do pl = get_players() if player in pl: pl.remove(player) random.shuffle(pl) pt = [] wevt = Event("wolflist", {"tags": set()}) for p in pl: prole = get_main_role(p) wevt.data["tags"].clear() wevt.dispatch(var, p, player) tags = " ".join(wevt.data["tags"]) if prole in wcroles: if tags: tags += " " pt.append("\u0002{0}\u0002 ({1}{2})".format(p, tags, prole)) elif tags: pt.append("{0} ({1})".format(p, tags)) else: pt.append(p.nick) evt.data["messages"].append(messages["players_list"].format(", ".join(pt))) if var.PHASE == "night" and evt.data["role"] in Wolf & Killer: # inform the new wolf that they can kill and stuff nevt = Event("wolf_numkills", {"numkills": 1, "message": ""}) nevt.dispatch(var) if not nevt.data["numkills"] and nevt.data["message"]: evt.data["messages"].append(messages[nevt.data["message"]])
def wolf_can_kill(var, wolf): # a wolf can kill if wolves in general can kill, and the wolf is a Killer # this is a utility function meant to be used by other wolf role modules nevt = Event("wolf_numkills", {"numkills": 1, "message": ""}) nevt.dispatch(var) num_kills = nevt.data["numkills"] if num_kills == 0: return False wolfroles = get_all_roles(wolf) return bool(Wolf & Killer & wolfroles)
def remove_user(self, user): self.users.remove(user) for mode in Features["PREFIX"].values(): if mode in self.modes: self.modes[mode].discard(user) if not self.modes[mode]: del self.modes[mode] del user.channels[self] if not user.channels: # Only fire if the user left all channels event = Event("cleanup_user", {}) event.dispatch(var, user)
def remove_all_protections(var, target, attacker, attacker_role, reason, scope=All): """Remove all protections from a player.""" if target not in PROTECTIONS: return for protector, entries in list(PROTECTIONS[target].items()): for cat, protector_role in entries: if scope & cat: evt = Event("remove_protection", {"remove": False}) evt.dispatch(var, target, attacker, attacker_role, protector, protector_role, reason) if evt.data["remove"]: PROTECTIONS[target][protector].remove((cat, protector_role))
def wolf_kill(var, wrapper, message): """Kill one or more players as a wolf.""" pieces = re.split(" +", message) targets = [] orig = [] nevt = Event("wolf_numkills", {"numkills": 1, "message": ""}) nevt.dispatch(var) num_kills = nevt.data["numkills"] if not num_kills: if nevt.data["message"]: wrapper.pm(messages[nevt.data["message"]]) return if len(pieces) < num_kills: wrapper.pm(messages["wolf_must_target_multiple"]) return for targ in pieces[:num_kills]: target = get_target(var, wrapper, targ, not_self_message="no_suicide") if target is None: return if is_known_wolf_ally(var, wrapper.source, target): wrapper.pm(messages["wolf_no_target_wolf"]) return if target in orig: wrapper.pm(messages["wolf_must_target_multiple"]) return orig.append(target) target = try_misdirection(var, wrapper.source, target) if try_exchange(var, wrapper.source, target): return targets.append(target) KILLS[wrapper.source] = UserList(targets) if len(orig) > 1: # TODO: Expand this so we can support arbitrarily many kills (instead of just one or two) wrapper.pm(messages["player_kill_multiple"].format(*orig)) msg = messages["wolfchat_kill_multiple"].format(wrapper.source, *orig) debuglog("{0} ({1}) KILL: {2} ({4}) and {3} ({5})".format(wrapper.source, rolename, *targets, get_main_role(targets[0]), get_main_role(targets[1]))) else: wrapper.pm(messages["player_kill"].format(orig[0])) msg = messages["wolfchat_kill"].format(wrapper.source, orig[0]) debuglog("{0} ({1}) KILL: {2} ({3})".format(wrapper.source, rolename, targets[0], get_main_role(targets[0]))) send_wolfchat_message(var, wrapper.source, msg, Wolf, role=rolename, command="kill")
def complete_role(var, role): from src.cats import ROLES if role not in ROLES: special_keys = {"lover"} evt = Event("get_role_metadata", {}) evt.dispatch(var, "special_keys") special_keys = functools.reduce(lambda x, y: x | y, evt.data.values(), special_keys) if role.lower() in var.ROLE_ALIASES: matches = (var.ROLE_ALIASES[role.lower()],) else: matches = complete_match(role, ROLES.keys() | special_keys) if not matches: return [] return matches return [role]
def who_reply(cli, bot_server, bot_nick, chan, ident, host, server, nick, status, hopcount_gecos): """Handle WHO replies for servers without WHOX support. Ordering and meaning of arguments for a bare WHO response: 0 - The IRCClient instance (like everywhere else) 1 - The server the requester (i.e. the bot) is on 2 - The nickname of the requester (i.e. the bot) 3 - The channel the request was made on 4 - The ident of the user in this reply 5 - The hostname of the user in this reply 6 - The server the user in this reply is on 7 - The nickname of the user in this reply 8 - The status (H = Not away, G = Away, * = IRC operator, @ = Opped in the channel in 4, + = Voiced in the channel in 4) 9 - The hop count and realname (gecos) This fires off the "who_result" event, and dispatches it with three arguments, the game state namespace, a Channel, and a User. Less important attributes can be accessed via the event.params namespace. """ hop, realname = hopcount_gecos.split(" ", 1) hop = int(hop) # We throw away the information about the operness of the user, but we probably don't need to care about that # We also don't directly pass which modes they have, since that's already on the channel/user is_away = ("G" in status) modes = {Features["PREFIX"].get(s) for s in status} - {None} user = users._add(cli, nick=nick, ident=ident, host=host, realname=realname) # FIXME ch = None if not channels.predicate(chan): # returns True if it's not a channel ch = channels.add(chan, cli) if ch not in user.channels: user.channels[ch] = modes ch.users.add(user) for mode in modes: if mode not in ch.modes: ch.modes[mode] = set() ch.modes[mode].add(user) event = Event("who_result", {}, away=is_away, data=0, ip_address=None, server=server, hop_count=hop, idle_time=None, extended_who=False) event.dispatch(var, ch, user) if ch is channels.Main and not users.exists(nick): # FIXME users.add(nick, ident=ident, host=host, account="*", inchan=True, modes=modes, moded=set())
def on_role_assignment(evt, cli, var, gamemode, pl, restart): # assign random targets to dullahan to kill if var.ROLES["dullahan"]: max_targets = math.ceil(8.1 * math.log(len(pl), 10) - 5) for dull in var.ROLES["dullahan"]: TARGETS[dull] = set() dull_targets = Event("dullahan_targets", {"targets": TARGETS}) # support sleepy dull_targets.dispatch(cli, var, var.ROLES["dullahan"], max_targets) for dull, ts in TARGETS.items(): ps = pl[:] ps.remove(dull) while len(ts) < max_targets: target = random.choice(ps) ps.remove(target) ts.add(target)
def kill_players(var, *, end_game: bool = True) -> bool: """ Kill all players marked as dying. This function is not re-entrant; do not call it inside of a del_player or kill_players event listener. This function does not print anything to the channel; code which calls add_dying should print things as appropriate. :param var: The game state :param end_game: Whether or not to check for win conditions and perform state transitions (temporary) :returns: True if the game is ending (temporary) """ t = time.time() with var.GRAVEYARD_LOCK: # FIXME if not var.GAME_ID or var.GAME_ID > t: # either game ended, or a new game has started return dead = set() while DYING: player, (killer_role, reason, death_triggers) = DYING.popitem() main_role = get_main_role(player) reveal_role = get_reveal_role(player) all_roles = get_all_roles(player) # kill them off del var.MAIN_ROLES[player] for role in all_roles: var.ROLES[role].remove(player) dead.add(player) var.DEAD.add(player) # notify listeners that the player died for possibility of chained deaths evt = Event("del_player", {}, killer_role=killer_role, main_role=main_role, reveal_role=reveal_role, reason=reason) evt_death_triggers = death_triggers and var.PHASE in var.GAME_PHASES evt.dispatch(var, player, all_roles, evt_death_triggers) # notify listeners that all deaths have resolved # FIXME: end_game is a temporary hack until we move state transitions into the event loop # (priority 10 listener sets prevent_default if end_game=True and game is ending; that's another temporary hack) # Once hacks are removed, this function will not have any return value and the end_game kwarg will go away evt = Event("kill_players", {}, end_game=end_game) return not evt.dispatch(var, dead)
def on_new_role(evt, var, player, old_role): if player in TARGETS and old_role == "dullahan" and evt.data["role"] != "dullahan": del KILLS[:player:] del TARGETS[player] if player not in TARGETS and evt.data["role"] == "dullahan": ps = get_players() max_targets = math.ceil(8.1 * math.log(len(ps), 10) - 5) TARGETS[player] = UserSet() dull_targets = Event("dullahan_targets", {"targets": TARGETS[player]}) # support sleepy dull_targets.dispatch(var, player, max_targets) ps.remove(player) while len(TARGETS[player]) < max_targets: target = random.choice(ps) ps.remove(target) TARGETS[player].add(target)
def on_chk_nightdone(evt, var): nevt = Event("wolf_numkills", {"numkills": 1, "message": ""}) nevt.dispatch(var) num_kills = nevt.data["numkills"] wofls = [x for x in get_players(Wolf & Killer) if not is_silent(var, x)] if not num_kills or not wofls: return evt.data["nightroles"].extend(wofls) evt.data["actedcount"] += len(KILLS) evt.data["nightroles"].append(users.FakeUser.from_nick("@WolvesAgree@")) # check if wolves are actually agreeing or not; # only add to count if they actually agree # (this is *slighty* less hacky than deducting 1 from actedcount as we did previously) kills = set() for ls in KILLS.values(): kills.update(ls) # check if wolves are actually agreeing if len(kills) == num_kills: evt.data["actedcount"] += 1
def see(var, wrapper, message): """Use your paranormal powers to determine the role or alignment of a player.""" if wrapper.source in SEEN: wrapper.send(messages["seer_fail"]) return target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="no_see_self") if target is None: return target = try_misdirection(var, wrapper.source, target) if try_exchange(var, wrapper.source, target): return targrole = get_main_role(target) trole = targrole # keep a copy for logging for i in range(2): # need to go through loop twice iswolf = False if targrole in Cursed: targrole = "wolf" iswolf = True elif targrole in Safe | Innocent: targrole = var.HIDDEN_ROLE elif targrole in Wolf: targrole = "wolf" iswolf = True else: targrole = var.HIDDEN_ROLE if i: break evt = Event("see", {"role": targrole}) evt.dispatch(var, wrapper.source, target) targrole = evt.data["role"] wrapper.send(messages["oracle_success"].format(target, "" if iswolf else "\u0002not\u0002 ", "\u0002" if iswolf else "")) debuglog("{0} (oracle) SEE: {1} ({2}) (Wolf: {3})".format(wrapper.source, target, trole, str(iswolf))) SEEN.add(wrapper.source)
def try_protection(var, target, attacker, attacker_role, reason): """Attempt to protect the player, and return a list of messages or None.""" prots = [] for protector, entries in PROTECTIONS.get(target, {}).items(): for scope, protector_role in entries: if attacker_role in scope: entry = (protector, protector_role, scope) prots.append(entry) try_evt = Event("try_protection", {"protections": prots, "messages": []}) if not try_evt.dispatch(var, target, attacker, attacker_role, reason) or not try_evt.data["protections"]: return None protector, protector_role, scope = try_evt.data["protections"].pop(0) PROTECTIONS[target][protector].remove((scope, protector_role)) prot_evt = Event("player_protected", {"messages": try_evt.data["messages"]}) prot_evt.dispatch(var, target, attacker, attacker_role, protector, protector_role, reason) return prot_evt.data["messages"]
def try_exchange(var, actor, target): """Check if an exchange is happening. Return True if the exchange occurs.""" if actor is target or target not in EXCHANGE: return False EXCHANGE.remove(target) role = get_main_role(actor) target_role = get_main_role(target) actor_role = change_role(var, actor, role, target_role, inherit_from=target) target_role = change_role(var, target, target_role, role, inherit_from=actor) if actor_role == target_role: # swap state of two players with the same role evt = Event("swap_role_state", {"actor_messages": [], "target_messages": []}) evt.dispatch(var, actor, target, actor_role) actor.send(*evt.data["actor_messages"]) target.send(*evt.data["target_messages"]) return True
def investigate(var, wrapper, message): """Investigate a player to determine their exact role.""" if wrapper.source in INVESTIGATED: wrapper.send(messages["already_investigated"]) return target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="no_investigate_self") if target is None: return target = try_misdirection(var, wrapper.source, target) if try_exchange(var, wrapper.source, target): return targrole = get_main_role(target) evt = Event("investigate", {"role": targrole}) evt.dispatch(var, wrapper.source, target) targrole = evt.data["role"] INVESTIGATED.add(wrapper.source) wrapper.send(messages["investigate_success"].format(target, targrole)) debuglog("{0} (detective) ID: {1} ({2})".format(wrapper.source, target, targrole)) if random.random() < var.DETECTIVE_REVEALED_CHANCE: # a 2/5 chance (should be changeable in settings) # The detective's identity is compromised! wcroles = Wolfchat if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES: if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF: wcroles = Wolf else: wcroles = Wolf | {"traitor"} wolves = get_all_players(wcroles) if wolves: for wolf in wolves: wolf.queue_message(messages["detective_reveal"].format(wrapper.source)) wolf.send_messages() debuglog("{0} (detective) PAPER DROP".format(wrapper.source))
def see(var, wrapper, message): """Use your paranormal powers to determine the role or alignment of a player.""" if wrapper.source in SEEN: wrapper.send(messages["seer_fail"]) return target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="no_see_self") if target is None: return target = try_misdirection(var, wrapper.source, target) if try_exchange(var, wrapper.source, target): return targrole = get_main_role(target) trole = targrole # keep a copy for logging if targrole in Cursed: targrole = "wolf" elif targrole in Safe: pass # Keep the same role elif targrole in Innocent: targrole = var.HIDDEN_ROLE elif targrole in (Neutral - Win_Stealer - Team_Switcher): pass # Keep the same role elif targrole in Wolf: targrole = "wolf" else: targrole = var.HIDDEN_ROLE evt = Event("see", {"role": targrole}) evt.dispatch(var, wrapper.source, target) targrole = evt.data["role"] wrapper.send(messages["seer_success"].format(target, targrole)) debuglog("{0} (seer) SEE: {1} ({2}) as {3}".format(wrapper.source, target, trole, targrole)) SEEN.add(wrapper.source)
def on_exchange(evt, var, actor, target, actor_role, target_role): if actor_role not in ("mystic", "wolf mystic") and target_role not in ( "mystic", "wolf mystic"): return special = set( get_players(("harlot", "priest", "prophet", "matchmaker", "doctor", "hag", "sorcerer", "turncoat", "clone"))) evt2 = Event("get_special", {"special": special}) evt2.dispatch(var) pl = set(get_players()) wolves = set(get_players(var.WOLFTEAM_ROLES)) neutral = set(get_players(var.TRUE_NEUTRAL_ROLES)) special = evt2.data["special"] if target_role == "wolf mystic" and actor_role != "wolf mystic": # # of special villagers = # of players - # of villagers - # of wolves - # of neutrals numvills = len(special & (pl - wolves - neutral)) evt.data["actor_messages"].append(messages["wolf_mystic_info"].format( "are" if numvills != 1 else "is", numvills, "s" if numvills != 1 else "")) elif target_role == "mystic" and actor_role != "mystic": numevil = len(wolves) evt.data["actor_messages"].append(messages["mystic_info"].format( "are" if numevil != 1 else "is", numevil, "s" if numevil != 1 else "")) if actor_role == "wolf mystic" and target_role != "wolf mystic": # # of special villagers = # of players - # of villagers - # of wolves - # of neutrals numvills = len(special & (pl - wolves - neutral)) evt.data["target_messages"].append(messages["wolf_mystic_info"].format( "are" if numvills != 1 else "is", numvills, "s" if numvills != 1 else "")) elif actor_role == "mystic" and target_role != "mystic": numevil = len(wolves) evt.data["target_messages"].append(messages["mystic_info"].format( "are" if numevil != 1 else "is", numevil, "s" if numevil != 1 else ""))
def on_new_role(evt, var, player, old_role): wcroles = get_wolfchat_roles(var) if old_role is None: # initial role assignment; don't do all the logic below about notifying other wolves and such if evt.data["role"] in wcroles: evt.data["in_wolfchat"] = True return sayrole = evt.data["role"] if sayrole in Hidden: sayrole = var.HIDDEN_ROLE if player in KILLS: del KILLS[player] if old_role not in wcroles and evt.data["role"] in wcroles: # a new wofl has joined the party, give them tummy rubs and the wolf list # and let the other wolves know to break out the confetti and villager steaks wofls = get_players(wcroles) evt.data["in_wolfchat"] = True if wofls: for wofl in wofls: wofl.queue_message(messages["wolfchat_new_member"].format( player, sayrole)) wofl.send_messages() else: return # no other wolves, nothing else to do evt.data["messages"].append(messages["players_list"].format( get_wolflist(var, player))) if var.PHASE == "night" and evt.data["role"] in Wolf & Killer: # inform the new wolf that they can kill and stuff nevt = Event("wolf_numkills", {"numkills": 1, "message": ""}) nevt.dispatch(var) if not nevt.data["numkills"] and nevt.data["message"]: evt.data["messages"].append(messages[nevt.data["message"]])
def vg_kill(var, wrapper, message): """Take revenge on someone each night after you die.""" if GHOSTS[wrapper.source][0] == "!": return target = get_target(var, wrapper, re.split(" +", message)[0]) if not target: return if target is wrapper.source: wrapper.pm(messages["player_dead"]) return wolves = get_players(var.WOLFTEAM_ROLES) if GHOSTS[wrapper.source] == "wolves" and target not in wolves: wrapper.pm(messages["vengeful_ghost_wolf"]) return elif GHOSTS[wrapper.source] == "villagers" and target in wolves: wrapper.pm(messages["vengeful_ghost_villager"]) return orig = target evt = Event("targeted_command", { "target": target, "misdirection": True, "exchange": False }) evt.dispatch(var, wrapper.source, target) if evt.prevent_default: return target = evt.data["target"] KILLS[wrapper.source] = target wrapper.pm(messages["player_kill"].format(orig)) debuglog("{0} (vengeful ghost) KILL: {1} ({2})".format( wrapper.source, target, get_main_role(target)))
def on_del_player(evt, var, player, mainrole, allroles, death_triggers): if player in TARGETED.values(): for x, y in list(TARGETED.items()): if y is player: del TARGETED[x] PREV_ACTED.discard(x) if death_triggers and "assassin" in allroles and player in TARGETED: target = TARGETED[player] del TARGETED[player] PREV_ACTED.discard(player) if target in evt.data["pl"]: prots = deque(var.ACTIVE_PROTECTIONS[target.nick]) aevt = Event("assassinate", {"pl": evt.data["pl"], "target": target}, del_player=evt.params.del_player, deadlist=evt.params.deadlist, original=evt.params.original, refresh_pl=evt.params.refresh_pl, message_prefix="assassin_fail_", source="assassin", killer=player, killer_mainrole=mainrole, killer_allroles=allroles, prots=prots) while len(prots) > 0: # an event can read the current active protection and cancel the assassination # if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS # so that it cannot be used again (if the protection is meant to be usable once-only) if not aevt.dispatch(var, player, target, prots[0]): pl = aevt.data["pl"] if target is not aevt.data["target"]: target = aevt.data["target"] prots = deque(var.ACTIVE_PROTECTIONS[target.nick]) aevt.params.prots = prots continue break prots.popleft() if not prots: if var.ROLE_REVEAL in ("on", "team"): role = get_reveal_role(target) an = "n" if role.startswith(("a", "e", "i", "o", "u")) else "" message = messages["assassin_success"].format(player, target, an, role) else: message = messages["assassin_success_no_reveal"].format(player, target) channels.Main.send(message) debuglog("{0} (assassin) ASSASSINATE: {1} ({2})".format(player, target, get_main_role(target))) evt.params.del_player(target, end_game=False, killer_role=mainrole, deadlist=evt.params.deadlist, original=evt.params.original, ismain=False) evt.data["pl"] = evt.params.refresh_pl(aevt.data["pl"])
def get_reveal_role(nick): if var.HIDDEN_TRAITOR and get_role(nick) == "traitor": role = var.DEFAULT_ROLE elif var.HIDDEN_AMNESIAC and nick in var.ORIGINAL_ROLES["amnesiac"]: role = "amnesiac" elif var.HIDDEN_CLONE and nick in var.ORIGINAL_ROLES["clone"]: role = "clone" else: role = get_role(nick) evt = Event("get_reveal_role", {"role": role}) evt.dispatch(var, nick) role = evt.data["role"] if var.ROLE_REVEAL != "team": return role if role in var.WOLFTEAM_ROLES: return "wolf" elif role in var.TRUE_NEUTRAL_ROLES: return "neutral player" else: return "villager"
def get_reveal_role(nick): if var.HIDDEN_TRAITOR and get_role(nick) == "traitor": role = var.DEFAULT_ROLE elif var.HIDDEN_AMNESIAC and nick in var.ORIGINAL_ROLES["amnesiac"]: role = "amnesiac" elif var.HIDDEN_CLONE and nick in var.ORIGINAL_ROLES["clone"]: role = "clone" else: role = get_role(nick) evt = Event("get_reveal_role", {"role": role}) evt.dispatch(var, nick) role = evt.data["role"] if var.ROLE_REVEAL != "team": return role if role in var.WOLFTEAM_ROLES: return "wolf" elif role in var.TRUE_NEUTRAL_ROLES: return "neutral player" else: return "villager"
def on_transition_day_begin(evt, var): # Select random totem recipients if shamans didn't act pl = get_players() for shaman in get_players(("crazed shaman",)): if shaman not in SHAMANS and shaman.nick not in var.SILENCED: ps = pl[:] if shaman in LASTGIVEN: if LASTGIVEN[shaman] in ps: ps.remove(LASTGIVEN[shaman]) levt = Event("get_random_totem_targets", {"targets": ps}) levt.dispatch(var, shaman) ps = levt.data["targets"] if ps: target = random.choice(ps) dispatcher = MessageDispatcher(shaman, shaman) tags = get_tags(var, TOTEMS[shaman]) SHAMANS[shaman] = give_totem(var, dispatcher, target, prefix=messages["random_totem_prefix"], tags=tags, role="crazed shaman", msg="") else: LASTGIVEN[shaman] = None elif shaman not in SHAMANS: LASTGIVEN[shaman] = None
def observe(var, wrapper, message): """Observe a player to obtain various information.""" target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="no_observe_self") if not target: return if wrapper.source in OBSERVED: wrapper.pm(messages["already_observed"]) return if is_known_wolf_ally(var, wrapper.source, target): wrapper.pm(messages["no_observe_wolf"]) return evt = Event("targeted_command", { "target": target, "exchange": True, "misdirection": True }) if not evt.dispatch(var, wrapper.source, target): return target = evt.data["target"] OBSERVED.add(wrapper.source) targrole = get_main_role(target) if targrole == "amnesiac": from src.roles.amnesiac import ROLES as amn_roles targrole = amn_roles[target] an = "" key = "sorcerer_fail" if targrole in Spy: if targrole.startswith(("a", "e", "i", "o", "u")): an = "n" key = "sorcerer_success" wrapper.pm(messages[key].format(target, an, targrole)) send_wolfchat_message(var, wrapper.source, messages["sorcerer_success_wolfchat"].format( wrapper.source, target), {"sorcerer"}, role="sorcerer", command="observe") debuglog("{0} (sorcerer) OBSERVE: {1} ({2})".format( wrapper.source, target, targrole))
def observe(var, wrapper, message): """Observe a player to see whether they are able to act at night.""" if wrapper.source in OBSERVED: wrapper.pm(messages["werecrow_already_observing"].format(OBSERVED[wrapper.source])) return target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="werecrow_no_observe_self") if not target: return if is_known_wolf_ally(var, wrapper.source, target): wrapper.pm(messages["werecrow_no_target_wolf"]) return orig = target evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt.dispatch(var, wrapper.source, target) if evt.prevent_default: return target = evt.data["target"] OBSERVED[wrapper.source] = target wrapper.pm(messages["werecrow_observe_success"].format(orig)) send_wolfchat_message(var, wrapper.source, messages["wolfchat_observe"].format(wrapper.source, target), {"werecrow"}, role="werecrow", command="observe") debuglog("{0} (werecrow) OBSERVE: {1} ({2})".format(wrapper.source, target, get_main_role(target)))
def get_reveal_role(nick): # FIXME: make the arg a user instead of a nick from src import users if var.HIDDEN_AMNESIAC and nick in var.ORIGINAL_ROLES["amnesiac"]: role = "amnesiac" elif var.HIDDEN_CLONE and nick in var.ORIGINAL_ROLES["clone"]: role = "clone" else: role = get_role(nick) evt = Event("get_reveal_role", {"role": role}) evt.dispatch(var, users._get(nick)) role = evt.data["role"] if var.ROLE_REVEAL != "team": return role if role in var.WOLFTEAM_ROLES: return "wolfteam player" elif role in var.TRUE_NEUTRAL_ROLES: return "neutral player" else: return "village member"
def on_transition_day_resolve_end(evt, var, victims): evt2 = Event("get_role_metadata", {}) evt2.dispatch(var, "lycanthropy_role") for victim in victims: if victim in LYCANTHROPES and evt.data["killers"][victim] == [ "@wolves" ] and victim in evt.data["dead"]: vrole = get_main_role(victim) if vrole not in Wolf: new_role = "wolf" prefix = LYCANTHROPES[victim] if vrole in evt2.data: if "role" in evt2.data[vrole]: new_role = evt2.data[vrole]["role"] if "prefix" in evt2.data[vrole]: prefix = evt2.data[vrole]["prefix"] for sec_role in evt2.data[vrole].get( "secondary_roles", ()): var.ROLES[sec_role].add(victim) to_send = "{0}_{1}".format( sec_role.replace(" ", "_"), "simple" if victim.prefers_simple() else "notify") victim.send(messages[to_send]) # FIXME: Not every role has proper message keys, such as shamans change_role(var, victim, vrole, new_role, message=prefix + "_turn") evt.data["howl"] += 1 evt.data["novictmsg"] = False evt.data["dead"].remove(victim) evt.data["killers"][victim].remove("@wolves") del evt.data["message"][victim] debuglog("{0} ({1}) TURN {2}".format(victim, vrole, new_role))
def change_role(var, player, oldrole, newrole, *, inherit_from=None, message="new_role"): # in_wolfchat is filled as part of priority 4 # if you wish to modify evt.data["role"], do so in priority 3 or sooner evt = Event("new_role", { "role": newrole, "messages": [], "in_wolfchat": False }, inherit_from=inherit_from) evt.dispatch(var, player, oldrole) newrole = evt.data["role"] var.ROLES[oldrole].remove(player) var.ROLES[newrole].add(player) # only adjust MAIN_ROLES/FINAL_ROLES if we're changing the player's actual role if var.MAIN_ROLES[player] == oldrole: var.MAIN_ROLES[player] = newrole var.FINAL_ROLES[player] = newrole # if giving the player a new role during night, don't warn them for not acting var.NIGHT_IDLE_EXEMPT.add(player) sayrole = newrole if sayrole in Hidden: sayrole = var.HIDDEN_ROLE if message: player.send(messages[message].format(sayrole)) player.send(*evt.data["messages"]) return newrole
def hvisit(var, wrapper, message): """Visit a player. You will die if you visit a wolf or a target of the wolves.""" if VISITED.get(wrapper.source): wrapper.pm(messages["harlot_already_visited"].format( VISITED[wrapper.source])) return target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="harlot_not_self") if not target: return evt = Event("targeted_command", { "target": target, "misdirection": True, "exchange": True }) evt.dispatch(var, "visit", wrapper.source, target, frozenset({"immediate"})) if evt.prevent_default: return target = evt.data["target"] vrole = get_main_role(target) VISITED[wrapper.source] = target PASSED.discard(wrapper.source) wrapper.pm(messages["harlot_success"].format(target)) if target is not wrapper.source: target.send(messages["harlot_success"].format(wrapper.source)) revt = Event("harlot_visit", {}) revt.dispatch(var, wrapper.source, target) debuglog("{0} (harlot) VISIT: {1} ({2})".format(wrapper.source, target, vrole))
def hunter_kill(var, wrapper, message): """Kill someone once per game.""" if wrapper.source in HUNTERS and wrapper.source not in KILLS: wrapper.pm(messages["hunter_already_killed"]) return target = get_target(var, wrapper, re.split(" +", message)[0]) if not target: return if wrapper.source is target: wrapper.pm(messages["no_suicide"]) return orig = target evt = Event("targeted_command", { "target": target.nick, "misdirection": True, "exchange": True }) evt.dispatch(wrapper.client, var, "kill", wrapper.source.nick, target.nick, frozenset({"detrimental"})) if evt.prevent_default: return target = users._get( evt.data["target"] ) # FIXME: Need to fix once targeted_command uses the new API KILLS[wrapper.source] = target HUNTERS.add(wrapper.source) PASSED.discard(wrapper.source) wrapper.pm(messages["player_kill"].format(orig)) debuglog("{0} (hunter) KILL: {1} ({2})".format(wrapper.source, target, get_role(target.nick))) chk_nightdone(wrapper.client)
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] elif h == nick: del KILLS[h] if death_triggers and nickrole == "dullahan": pl = evt.data["pl"] targets = TARGETS[nick] & set(pl) if targets: target = random.choice(list(targets)) prots = deque(var.ACTIVE_PROTECTIONS[target]) aevt = Event("assassinate", {"pl": evt.data["pl"]}, del_player=evt.params.del_player, deadlist=evt.params.deadlist, original=evt.params.original, refresh_pl=evt.params.refresh_pl, message_prefix="dullahan_die_") while len(prots) > 0: # an event can read the current active protection and cancel the totem # if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS # so that it cannot be used again (if the protection is meant to be usable once-only) if not aevt.dispatch(cli, var, nick, target, prots[0]): evt.data["pl"] = aevt.data["pl"] return prots.popleft() if var.ROLE_REVEAL in ("on", "team"): role = get_reveal_role(target) an = "n" if role.startswith(("a", "e", "i", "o", "u")) else "" cli.msg( botconfig.CHANNEL, messages["dullahan_die_success"].format( nick, target, an, role)) else: cli.msg( botconfig.CHANNEL, messages["dullahan_die_success_noreveal"].format( nick, target)) debuglog("{0} ({1}) DULLAHAN ASSASSINATE: {2} ({3})".format( nick, nickrole, target, get_role(target))) evt.params.del_player(cli, target, True, end_game=False, killer_role=nickrole, deadlist=evt.params.deadlist, original=evt.params.original, ismain=False) evt.data["pl"] = evt.params.refresh_pl(pl)
def try_protection(var, target, attacker, attacker_role, reason): """Attempt to protect the player, and return a list of messages or None.""" prots = [] for protector, entries in PROTECTIONS.get(target, {}).items(): for scope, protector_role in entries: if attacker_role in scope: entry = (protector, protector_role, scope) prots.append(entry) try_evt = Event("try_protection", {"protections": prots, "messages": []}) if not try_evt.dispatch(var, target, attacker, attacker_role, reason) or not try_evt.data["protections"]: return None protector, protector_role, scope = try_evt.data["protections"].pop(0) PROTECTIONS[target][protector].remove((scope, protector_role)) prot_evt = Event("player_protected", {"messages": try_evt.data["messages"]}) prot_evt.dispatch(var, target, attacker, attacker_role, protector, protector_role, reason) return prot_evt.data["messages"]
def on_whois_end(cli, bot_server, bot_nick, nick, message): """Handle the end of WHOIS and fire events. Ordering and meaning of arguments for an end of WHOIS reply: 0 - The IRCClient instance (like everywhere else) 1 - The server the requester (i.e. the bot) is on 2 - The nickname of the requester (i.e. the bot) 3 - The nickname of the target 4 - A human-friendly message, usually "End of /WHOIS list." This uses data accumulated from the above WHOIS listeners, and fires the "who_result" event (once per shared channel with the bot) and the "who_end" event with the relevant User instance as the arg. """ values = _whois_pending.pop(nick) # check for account change new_user = user = values["user"] if {user.account, values["account"]} != {None} and not context.equals( user.account, values["account"]): # first check tests if both are None, and skips over this if so old_account = user.account user.account = values["account"] new_user = users.get(user.nick, user.ident, user.host, values["account"], allow_bot=True) Event("account_change", {}, old=user).dispatch(new_user, old_account) event = Event("who_result", {}, away=values["away"], data=0, old=user) for chan in values["channels"]: event.dispatch(chan, new_user) Event("who_end", {}, old=user).dispatch(new_user)
def dullahan_kill(var, wrapper, message): """Kill someone at night as a dullahan until everyone on your list is dead.""" if not TARGETS[wrapper.source] & set(get_players()): wrapper.pm(messages["dullahan_targets_dead"]) return target = get_target(var, wrapper, re.split(" +", message)[0]) if not target: return if target is wrapper.source: wrapper.pm(messages["no_suicide"]) return orig = target evt = Event("targeted_command", { "target": target.nick, "misdirection": True, "exchange": True }) evt.dispatch(wrapper.client, var, "kill", wrapper.source.nick, target.nick, frozenset({"detrimental"})) if evt.prevent_default: return target = users._get( evt.data["target"] ) # FIXME: Need to fix once targeted_command uses the new API KILLS[wrapper.source] = target wrapper.pm(messages["player_kill"].format(orig)) debuglog("{0} (dullahan) KILL: {1} ({2})".format(wrapper.source, target, get_main_role(target))) chk_nightdone(wrapper.client)
def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role): special = set( list_players( ("harlot", "guardian angel", "bodyguard", "priest", "prophet", "matchmaker", "shaman", "doctor", "hag", "sorcerer", "turncoat", "clone", "crazed shaman", "piper", "succubus"))) evt2 = Event("get_special", {"special": special}) evt2.dispatch(cli, var) pl = set(list_players()) wolves = set(list_players(var.WOLFTEAM_ROLES)) neutral = set(list_players(var.TRUE_NEUTRAL_ROLES)) special = evt2.data["special"] if nick_role == "wolf mystic" and actor_role != "wolf mystic": # # of special villagers = # of players - # of villagers - # of wolves - # of neutrals numvills = len(special & (pl - wolves - neutral)) evt.data["actor_messages"].append(messages["wolf_mystic_info"].format( "are" if numvills != 1 else "is", numvills, "s" if numvills != 1 else "")) elif nick_role == "mystic" and actor_role != "mystic": numevil = len(wolves) evt.data["actor_messages"].append(messages["mystic_info"].format( "are" if numevil != 1 else "is", numevil, "s" if numevil != 1 else "")) if actor_role == "wolf mystic" and nick_role != "wolf mystic": # # of special villagers = # of players - # of villagers - # of wolves - # of neutrals numvills = len(special & (pl - wolves - neutral)) evt.data["nick_messages"].append(messages["wolf_mystic_info"].format( "are" if numvills != 1 else "is", numvills, "s" if numvills != 1 else "")) elif actor_role == "mystic" and nick_role != "mystic": numevil = len(wolves) evt.data["nick_messages"].append(messages["mystic_info"].format( "are" if numevil != 1 else "is", numevil, "s" if numevil != 1 else ""))
def see(var, wrapper, message): """Use your paranormal powers to determine the role or alignment of a player.""" if wrapper.source in SEEN: wrapper.send(messages["seer_fail"]) return target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="no_see_self") if target is None: return target = try_misdirection(var, wrapper.source, target) if try_exchange(var, wrapper.source, target): return targrole = get_main_role(target) trole = targrole # keep a copy for logging if targrole in Cursed: targrole = "wolf" elif targrole in Safe | Innocent: targrole = var.HIDDEN_ROLE elif targrole in Wolf: targrole = "wolf" evt = Event("see", {"role": targrole}) evt.dispatch(var, wrapper.source, target) targrole = evt.data["role"] to_send = "oracle_success_not_wolf" if targrole == "wolf": to_send = "oracle_success_wolf" wrapper.send(messages[to_send].format(target)) debuglog("{0} (oracle) SEE: {1} ({2}) (Wolf: {3})".format(wrapper.source, target, trole, "True" if targrole == "wolf" else "False")) SEEN.add(wrapper.source)
def see(var, wrapper, message): """Use your paranormal powers to determine the role or alignment of a player.""" if wrapper.source in SEEN: wrapper.send(messages["seer_fail"]) return target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="no_see_self") if target is None: return target = try_misdirection(var, wrapper.source, target) if try_exchange(var, wrapper.source, target): return targrole = get_main_role(target) trole = targrole # keep a copy for logging evt = Event("investigate", {"role": targrole}) evt.dispatch(var, wrapper.source, target) targrole = evt.data["role"] aura = "blue" if targrole in Wolfteam: aura = "red" elif targrole in Neutral: aura = "grey" # used message keys (for grep): augur_success_blue, augur_success_red, augur_success_grey wrapper.send(messages["augur_success_" + aura].format(target)) debuglog("{0} (augur) SEE: {1} ({2}) as {3} ({4} aura)".format( wrapper.source, target, trole, targrole, aura)) SEEN.add(wrapper.source)
def on_transition_day(evt, var): # figure out wolf target found = defaultdict(int) nevt = Event("wolf_numkills", {"numkills": 1}) nevt.dispatch(var) num_kills = nevt.data["numkills"] for v in KILLS.values(): for p in v: 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: target = random.choice(dups) evt.data["victims"].append(target) evt.data["bywolves"].add(target) evt.data["onlybywolves"].add(target) # special key to let us know to randomly select a wolf in case of retribution totem evt.data["killers"][target].append("@wolves") del found[target] # when monster is split, add protection to them if in onlybywolves # fallen angel will then remove that protection # TODO: when monster is split off if var.ROLES["fallen angel"]: for monster in get_all_players(("monster",)): if monster in evt.data["victims"]: evt.data["victims"].remove(monster) evt.data["bywolves"].discard(monster) evt.data["onlybywolves"].discard(monster)
def on_chk_decision_lynch3(evt, cli, var, voters): votee = evt.data["votee"] if votee in REVEALING: role = get_role(votee) rev_evt = Event("revealing_totem", {"role": role}) rev_evt.dispatch(cli, var, votee) role = rev_evt.data["role"] # TODO: once amnesiac is split, roll this into the revealing_totem event if role == "amnesiac": role = var.AMNESIAC_ROLES[votee] change_role(users._get(votee), "amnesiac", role) # FIXME var.AMNESIACS.add(votee) pm(cli, votee, messages["totem_amnesia_clear"]) # If wolfteam, don't bother giving list of wolves since night is about to start anyway # Existing wolves also know that someone just joined their team because revealing totem says what they are # If turncoat, set their initial starting side to "none" just in case game ends before they can set it themselves if role == "turncoat": var.TURNCOATS[votee] = ("none", -1) an = "n" if role.startswith(("a", "e", "i", "o", "u")) else "" cli.msg(botconfig.CHANNEL, messages["totem_reveal"].format(votee, an, role)) evt.data["votee"] = None evt.prevent_default = True evt.stop_processing = True
def hunter_kill(var, wrapper, message): """Kill someone once per game.""" if wrapper.source in HUNTERS and wrapper.source not in KILLS: wrapper.pm(messages["hunter_already_killed"]) return target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="no_suicide") if not target: return orig = target evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt.dispatch(var, "kill", wrapper.source, target, frozenset({"detrimental"})) if evt.prevent_default: return target = evt.data["target"] KILLS[wrapper.source] = target HUNTERS.add(wrapper.source) PASSED.discard(wrapper.source) wrapper.pm(messages["player_kill"].format(orig)) debuglog("{0} (hunter) KILL: {1} ({2})".format(wrapper.source, target, get_main_role(target)))
def on_transition_day(evt, var): # figure out wolf target found = defaultdict(int) nevt = Event("wolf_numkills", {"numkills": 1, "message": ""}) nevt.dispatch(var) num_kills = nevt.data["numkills"] for v in KILLS.values(): for p in v: 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: target = random.choice(dups) evt.data["victims"].append(target) # special key to let us know to randomly select a wolf in case of retribution totem evt.data["killers"][target].append("@wolves") del found[target]
def change_role(var, player, oldrole, newrole, *, inherit_from=None, message="new_role"): # in_wolfchat is filled as part of priority 4 # if you wish to modify evt.data["role"], do so in priority 3 or sooner evt = Event("new_role", { "role": newrole, "messages": [], "in_wolfchat": False }, inherit_from=inherit_from) evt.dispatch(var, player, oldrole) newrole = evt.data["role"] var.ROLES[oldrole].remove(player) var.ROLES[newrole].add(player) # only adjust MAIN_ROLES/FINAL_ROLES if we're changing the player's actual role if var.MAIN_ROLES[player] == oldrole: var.MAIN_ROLES[player] = newrole var.FINAL_ROLES[player.nick] = newrole sayrole = newrole if sayrole in var.HIDDEN_VILLAGERS: sayrole = "villager" elif sayrole in var.HIDDEN_ROLES: sayrole = var.DEFAULT_ROLE an = "n" if sayrole.startswith(("a", "e", "i", "o", "u")) else "" player.send(messages[message].format(an, sayrole)) player.send(*evt.data["messages"]) return newrole
def observe(var, wrapper, message): """Turn a player into a wolf!""" if not ENABLED: wrapper.pm(messages["alpha_no_bite"]) return if wrapper.source in ALPHAS: wrapper.pm(messages["alpha_already_bit"]) return target = get_target(var, wrapper, re.split(" +", message)[0]) if not target: return if is_known_wolf_ally(var, wrapper.source, target): wrapper.pm(messages["alpha_no_bite_wolf"]) return orig = target evt = Event("targeted_command", { "target": target, "misdirection": True, "exchange": True }) evt.dispatch(var, wrapper.source, target) if evt.prevent_default: return target = evt.data["target"] BITTEN[wrapper.source] = target wrapper.pm(messages["alpha_bite_target"].format(orig)) send_wolfchat_message(var, wrapper.source, messages["alpha_bite_wolfchat"].format( wrapper.source, target), {"alpha wolf"}, role="alpha wolf", command="bite") debuglog("{0} (alpha wolf) BITE: {1} ({2})".format(wrapper.source, target, get_main_role(target)))
def on_update_stats3(evt, var, player, mainrole, revealrole, allroles): # if this is a night death and we know for sure that wolves (and only wolves) # killed, then that kill cannot be traitor as long as they're in wolfchat. wolfchat = get_wolfchat_roles(var) if evt.params.reason != "night_death": # a chained death, someone dying during day, or someone idling out # either way, traitor can die here return if "traitor" not in wolfchat: # wolves can kill traitor normally in this configuration return if "traitor" not in evt.data["possible"]: # not under consideration return if mainrole == "traitor": # definitely dying, so we shouldn't remove them from consideration # this may lead to info leaks, but info leaks are better than !stats just entirely breaking return if in_misdirection_scope(var, Wolf, as_actor=True) or in_misdirection_scope( var, All - wolfchat, as_target=True): # luck/misdirection totems are in play, a wolf kill could have bounced to traitor anyway return if var.PHASE == "day" and var.GAMEPHASE == "night": mevt = Event("get_role_metadata", {}) mevt.dispatch(var, "night_kills") nonwolf = 0 total = 0 for role, num in mevt.data.items(): if role != "wolf": nonwolf += num total += num if nonwolf == 0: evt.data["possible"].discard("traitor") return
def on_transition_day_resolve6(evt, var, victims): for victim in victims: if victim in RETRIBUTION: killers = list(evt.data["killers"].get(victim, [])) loser = None while killers: loser = random.choice(killers) if loser in evt.data["dead"] or victim is loser: killers.remove(loser) continue break if loser in evt.data["dead"] or victim is loser: loser = None ret_evt = Event("retribution_kill", { "target": loser, "message": [] }) ret_evt.dispatch(var, victim, loser) loser = ret_evt.data["target"] evt.data["message"][loser].extend(ret_evt.data["message"]) if loser in evt.data["dead"] or victim is loser: loser = None if loser is not None: protected = try_protection(var, loser, victim, get_main_role(victim), "retribution_totem") if protected is not None: channels.Main.send(*protected) return evt.data["dead"].append(loser) to_send = "totem_death_no_reveal" if var.ROLE_REVEAL in ("on", "team"): to_send = "totem_death" evt.data["message"][loser].append(messages[to_send].format( victim, loser, get_reveal_role(loser)))
def on_del_player(evt, var, user, mainrole, allroles, death_triggers): for h, v in list(KILLS.items()): if v is user: h.send(messages["hunter_discard"]) del KILLS[h] elif h is user: del KILLS[h] if death_triggers and "dullahan" in allroles: pl = evt.data["pl"] targets = TARGETS[user].intersection(users._get(x) for x in pl) # FIXME if targets: target = random.choice(list(targets)) prots = deque(var.ACTIVE_PROTECTIONS[target.nick]) aevt = Event("assassinate", {"pl": evt.data["pl"], "target": target}, del_player=evt.params.del_player, deadlist=evt.params.deadlist, original=evt.params.original, refresh_pl=evt.params.refresh_pl, message_prefix="dullahan_die_", source="dullahan", killer=user.nick, killer_mainrole=mainrole, killer_allroles=allroles, prots=prots) while len(prots) > 0: # an event can read the current active protection and cancel or redirect the assassination # if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS # so that it cannot be used again (if the protection is meant to be usable once-only) if not aevt.dispatch(user.client, var, user.nick, target.nick, prots[0]): evt.data["pl"] = aevt.data["pl"] if target is not aevt.data["target"]: target = aevt.data["target"] prots = deque(var.ACTIVE_PROTECTIONS[target.nick]) aevt.params.prots = prots continue return prots.popleft() target = target.nick # FIXME if var.ROLE_REVEAL in ("on", "team"): role = get_reveal_role(target) an = "n" if role.startswith(("a", "e", "i", "o", "u")) else "" channels.Main.send(messages["dullahan_die_success"].format(user, target, an, role)) else: channels.Main.send(messages["dullahan_die_success_noreveal"].format(user, target)) debuglog("{0} (dullahan) DULLAHAN ASSASSINATE: {1} ({2})".format(user, target, get_role(target))) evt.params.del_player(user.client, target, True, end_game=False, killer_role="dullahan", deadlist=evt.params.deadlist, original=evt.params.original, ismain=False) evt.data["pl"] = evt.params.refresh_pl(pl)