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 end_who(cli, bot_server, bot_nick, target, rest): """Handle the end of WHO/WHOX responses from the server. Ordering and meaning of arguments for the end of a WHO/WHOX request: 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 target the request was made against 4 - A string containing some information; traditionally "End of /WHO list." This fires off the "who_end" event, and dispatches it with two arguments: The game state namespace and the channel or user the request was made to, or None if it could not be resolved. """ try: target = channels.get(target) except KeyError: try: target = users._get(target) # FIXME except KeyError: target = None else: if target._pending is not None: for name, params, args in target._pending: Event(name, params).dispatch(*args) target._pending = None Event("who_end", {}).dispatch(var, target)
def hvisit(cli, nick, chan, rest): """Visit a player. You will die if you visit a wolf or a target of the wolves.""" if VISITED.get(nick): pm(cli, nick, messages["harlot_already_visited"].format(VISITED[nick])) return victim = get_victim(cli, nick, re.split(" +", rest)[0], False, True) if not victim: return if nick == victim: pm(cli, nick, messages["harlot_not_self"]) return evt = Event("targeted_command", { "target": victim, "misdirection": True, "exchange": True }) evt.dispatch(cli, var, "visit", nick, victim, frozenset({"immediate"})) if evt.prevent_default: return victim = evt.data["target"] vrole = get_role(victim) VISITED[nick] = victim pm(cli, nick, messages["harlot_success"].format(victim)) if nick != victim: pm(cli, victim, messages["harlot_success"].format(nick)) revt = Event("harlot_visit", {}) revt.dispatch(cli, var, nick, victim) debuglog("{0} ({1}) VISIT: {2} ({3})".format(nick, get_role(nick), victim, vrole)) chk_nightdone(cli)
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 evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) if not evt.dispatch(var, wrapper.source, target): return target = evt.data["target"] 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 status.remove_lycanthropy(var, target) status.remove_disease(var, target) debuglog("{0} (doctor) IMMUNIZE: {1} ({2})".format(wrapper.source, target, get_main_role(target)))
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 evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) if not evt.dispatch(var, wrapper.source, target): return target = evt.data["target"] 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 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) # Don't track players that quit before the game started if var.PHASE != "join": 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) # give roles/modes an opportunity to adjust !stats now that all deaths have resolved evt = Event("reconfigure_stats", {"new": []}) newstats = set() for rs in var.ROLE_STATS: d = Counter(dict(rs)) evt.data["new"] = [d] evt.dispatch(var, d, "del_player") for v in evt.data["new"]: if min(v.values()) >= 0: newstats.add(frozenset(v.items())) var.ROLE_STATS = frozenset(newstats) # 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 wolf_kill(var, wrapper, message): """Kills one or more players as a wolf.""" role = get_main_role(wrapper.source) # eventually cub will listen on targeted_command and block kills that way if var.DISEASED_WOLVES: wrapper.pm(messages["ill_wolves"]) return pieces = re.split(" +", message) targets = [] orig = [] nevt = Event("wolf_numkills", {"numkills": 1}) nevt.dispatch(var) num_kills = nevt.data["numkills"] if len(pieces) < num_kills: wrapper.pm(messages["wolf_must_target_multiple"]) return targs = iter(pieces) # allow random words to follow the initial line without issue for i in range(num_kills): target = get_target(var, wrapper, next(targs, None), 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) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) if not evt.dispatch(var, wrapper.source, target): return target = evt.data["target"] 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, role, *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, role, targets[0], get_main_role(targets[0]))) send_wolfchat_message(var, wrapper.source, msg, var.WOLF_ROLES, role=role, command="kill")
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 evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": False}) evt.dispatch(var, "visit", wrapper.source, target, frozenset({"detrimental", "immediate"})) if evt.prevent_default: return target = evt.data["target"] 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) # TODO: split these into assassin, hag, and alpha wolf when they are split off if users._get(var.TARGETED.get(target.nick), allow_none=True) in get_all_players(("succubus",)): # FIXME msg = messages["no_target_succubus"].format(var.TARGETED[target.nick]) del var.TARGETED[target.nick] if target in get_all_players(("village drunk",)): victim = random.choice(list(get_all_players() - get_all_players(("succubus",)) - {target})) msg += messages["drunk_target"].format(victim) var.TARGETED[target.nick] = victim.nick target.send(msg) if target.nick in var.HEXED and users._get(var.LASTHEXED[target.nick]) in get_all_players(("succubus",)): # FIXME target.send(messages["retract_hex_succubus"].format(var.LASTHEXED[target.nick])) var.TOBESILENCED.remove(wrapper.source.nick) var.HEXED.remove(target.nick) del var.LASTHEXED[target.nick] if users._get(var.BITE_PREFERENCES.get(target.nick), allow_none=True) in get_all_players(("succubus",)): # FIXME target.send(messages["no_kill_succubus"].format(var.BITE_PREFERENCES[target.nick])) del var.BITE_PREFERENCES[target.nick] debuglog("{0} (succubus) VISIT: {1} ({2})".format(wrapper.source, target, get_main_role(target)))
def hvisit(cli, nick, chan, rest): """Entrance a player, converting them to your team.""" if VISITED.get(nick): pm(cli, nick, messages["succubus_already_visited"].format(VISITED[nick])) return victim = get_victim(cli, nick, re.split(" +",rest)[0], False, True) if not victim: return if nick == victim: pm(cli, nick, messages["succubus_not_self"]) return evt = Event("targeted_command", {"target": victim, "misdirection": True, "exchange": False}) evt.dispatch(cli, var, "visit", nick, victim, frozenset({"detrimental", "immediate"})) if evt.prevent_default: return victim = evt.data["target"] vrole = get_role(victim) VISITED[nick] = victim if vrole != "succubus": ENTRANCED.add(victim) pm(cli, nick, messages["succubus_target_success"].format(victim)) else: pm(cli, nick, messages["harlot_success"].format(victim)) if nick != victim: if vrole != "succubus": pm(cli, victim, messages["notify_succubus_target"].format(nick)) else: pm(cli, victim, messages["harlot_success"].format(nick)) revt = Event("succubus_visit", {}) revt.dispatch(cli, var, nick, victim) # TODO: split these into assassin, hag, and alpha wolf when they are split off if var.TARGETED.get(victim) in var.ROLES["succubus"]: msg = messages["no_target_succubus"].format(var.TARGETED[victim]) del var.TARGETED[victim] if victim in var.ROLES["village drunk"]: target = random.choice(list(set(list_players()) - var.ROLES["succubus"] - {victim})) msg += messages["drunk_target"].format(target) var.TARGETED[victim] = target pm(cli, victim, nick) if victim in var.HEXED and var.LASTHEXED[victim] in var.ROLES["succubus"]: pm(cli, victim, messages["retract_hex_succubus"].format(var.LASTHEXED[victim])) var.TOBESILENCED.remove(nick) var.HEXED.remove(victim) del var.LASTHEXED[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] debuglog("{0} ({1}) VISIT: {2} ({3})".format(nick, get_role(nick), victim, vrole)) chk_nightdone(cli)
def on_transition_day_resolve6(evt, cli, var, victim): # TODO: remove these checks once everything is split # right now they're needed because otherwise retribution may fire off when the target isn't actually dying # that will not be an issue once everything is using the event if victim in var.ROLES["harlot"] and var.HVISITED.get( victim) and victim not in evt.data["dead"] and victim in evt.data[ "onlybywolves"]: return if evt.data["protected"].get(victim): return if victim in var.ROLES["lycan"] and victim in evt.data[ "onlybywolves"] and victim not in var.IMMUNIZED: return # END checks to remove 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 == loser: killers.remove(loser) continue break if loser in evt.data["dead"] or victim == loser: loser = None ret_evt = Event("retribution_kill", {"target": loser, "message": []}) ret_evt.dispatch(cli, var, victim, loser) loser = ret_evt.data["target"] evt.data["message"].extend(ret_evt.data["message"]) if loser in evt.data["dead"] or victim == loser: loser = None if loser is not None: prots = deque(var.ACTIVE_PROTECTIONS[loser]) 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) ret_evt = Event("retribution_totem", {"message": []}) if not ret_evt.dispatch(cli, var, victim, loser, prots[0]): evt.data["message"].extend(ret_evt.data["message"]) return prots.popleft() evt.data["dead"].append(loser) if var.ROLE_REVEAL in ("on", "team"): role = get_reveal_role(loser) an = "n" if role.startswith(("a", "e", "i", "o", "u")) else "" evt.data["message"].append(messages["totem_death"].format( victim, loser, an, role)) else: evt.data["message"].append( messages["totem_death_no_reveal"].format(victim, loser))
def investigate(cli, nick, chan, rest): """Investigate a player to determine their exact role.""" if nick in INVESTIGATED: pm(cli, nick, messages["already_investigated"]) return victim = get_victim(cli, nick, re.split(" +", rest)[0], False) if not victim: return if victim == nick: pm(cli, nick, messages["no_investigate_self"]) return det = users._get(nick) # FIXME target = users._get(victim) # FIXME evt = Event("targeted_command", { "target": target, "misdirection": True, "exchange": True }) evt.dispatch(var, "identify", det, target, frozenset({"info", "immediate"})) if evt.prevent_default: return victim = evt.data["target"].nick vrole = get_role(victim) if vrole == "amnesiac": vrole = var.AMNESIAC_ROLES[victim] evt = Event("investigate", {"role": vrole}) evt.dispatch(cli, var, nick, victim) vrole = evt.data["role"] INVESTIGATED.add(nick) pm(cli, nick, (messages["investigate_success"]).format(victim, vrole)) debuglog("{0} ({1}) ID: {2} ({3})".format(nick, get_role(nick), victim, vrole)) if random.random( ) < var.DETECTIVE_REVEALED_CHANCE: # a 2/5 chance (should be changeable in settings) # The detective's identity is compromised! wcroles = var.WOLFCHAT_ROLES if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES: if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF: wcroles = var.WOLF_ROLES else: wcroles = var.WOLF_ROLES | {"traitor"} mass_privmsg(cli, list_players(wcroles), messages["investigator_reveal"].format(nick)) debuglog("{0} ({1}) PAPER DROP".format(nick, get_role(nick)))
def on_transition_night_end(evt, var): chances = var.CURRENT_GAMEMODE.TOTEM_CHANCES max_totems = sum(x["shaman"] for x in chances.values()) ps = get_players() shamans = get_all_players(("shaman", )) for s in list(LASTGIVEN): if s not in shamans: del LASTGIVEN[s] shamans = list(shamans) random.shuffle(shamans) for shaman in shamans: pl = ps[:] random.shuffle(pl) for given in itertools.chain.from_iterable(LASTGIVEN[shaman].values()): if given in pl: pl.remove(given) event = Event("num_totems", {"num": var.CURRENT_GAMEMODE.NUM_TOTEMS["shaman"]}) event.dispatch(var, shaman, "shaman") num_totems = event.data["num"] totems = {} for i in range(num_totems): target = 0 rand = random.random() * max_totems for t in chances: target += chances[t]["shaman"] if rand <= target: if t in totems: totems[t] += 1 else: totems[t] = 1 break event = Event("totem_assignment", {"totems": totems}) event.dispatch(var, shaman, "shaman") TOTEMS[shaman] = event.data["totems"] num_totems = sum(TOTEMS[shaman].values()) if num_totems > 1: shaman.send( messages["shaman_notify_multiple_known"].format("shaman")) else: shaman.send(messages["shaman_notify"].format("shaman")) tmsg = totem_message(TOTEMS[shaman]) for totem in TOTEMS[shaman]: tmsg += " " + messages[totem + "_totem"] shaman.send(tmsg) shaman.send(messages["players_list"].format(pl))
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 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 guard(cli, nick, chan, rest): """Guard a player, preventing them from being killed that night.""" if nick in GUARDED: pm(cli, nick, messages["already_protecting"]) return role = get_role(nick) self_in_list = role == "guardian angel" and var.GUARDIAN_ANGEL_CAN_GUARD_SELF victim = get_victim(cli, nick, re.split(" +",rest)[0], False, self_in_list) if not victim: return if (role == "bodyguard" or not var.GUARDIAN_ANGEL_CAN_GUARD_SELF) and victim == nick: pm(cli, nick, messages["cannot_guard_self"]) return if role == "guardian angel" and LASTGUARDED.get(nick) == victim: pm(cli, nick, messages["guardian_target_another"].format(victim)) return # self-guard ignores luck/misdirection/exchange totem evt = Event("targeted_command", {"target": victim, "misdirection": victim != nick, "exchange": victim != nick}) if not evt.dispatch(cli, var, "guard", nick, victim, frozenset({"beneficial"})): return victim = evt.data["target"] GUARDED[nick] = victim LASTGUARDED[nick] = victim if victim == nick: pm(cli, nick, messages["guardian_guard_self"]) else: pm(cli, nick, messages["protecting_target"].format(GUARDED[nick])) pm(cli, victim, messages["target_protected"]) debuglog("{0} ({1}) GUARD: {2} ({3})".format(nick, role, 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 add_lycanthropy(var, target, prefix="lycan"): """Effect the target with lycanthropy. Fire the add_lycanthropy event.""" if target in LYCANTHROPES: return if target in get_players() and Event("add_lycanthropy", {}).dispatch(var, target): LYCANTHROPES[target] = prefix
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 vg_kill(cli, nick, chan, rest): """Take revenge on someone each night after you die.""" if GHOSTS[nick][0] == "!": 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)
def on_transition_night_end(evt, cli, var): # init with all roles that haven't been split yet special = set( list_players(("harlot", "priest", "prophet", "matchmaker", "doctor", "hag", "sorcerer", "turncoat", "clone", "piper"))) 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"] for wolf in var.ROLES["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(special & (pl - wolves - neutral)) pm( cli, wolf, messages["wolf_mystic_info"].format( "are" if numvills != 1 else "is", numvills, "s" if numvills != 1 else "")) for mystic in var.ROLES["mystic"]: if mystic in var.PLAYERS and not is_user_simple(mystic): pm(cli, mystic, messages["mystic_notify"]) else: pm(cli, mystic, messages["mystic_simple"]) # 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 numevil = len(wolves) pm( cli, mystic, messages["mystic_info"].format("are" if numevil != 1 else "is", numevil, "s" if numevil != 1 else ""))
def on_chk_decision_lynch5(evt, cli, var, voters): votee = evt.data["votee"] if votee in DESPERATION: # Also kill the very last person to vote them, unless they voted themselves last in which case nobody else dies target = voters[-1] if target != votee: prots = deque(var.ACTIVE_PROTECTIONS[target]) 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) desp_evt = Event("desperation_totem", {}) if not desp_evt.dispatch(cli, var, votee, target, prots[0]): return prots.popleft() if var.ROLE_REVEAL in ("on", "team"): r1 = get_reveal_role(target) an1 = "n" if r1.startswith(("a", "e", "i", "o", "u")) else "" tmsg = messages["totem_desperation"].format( votee, target, an1, r1) else: tmsg = messages["totem_desperation_no_reveal"].format( votee, target) cli.msg(botconfig.CHANNEL, tmsg) # we lie to this function so it doesn't devoice the player yet. instead, we'll let the call further down do it evt.data["deadlist"].append(target) better_deadlist = [users._get(p) for p in evt.data["deadlist"]] # FIXME target_user = users._get(target) # FIXME evt.params.del_player(target_user, end_game=False, killer_role="shaman", deadlist=better_deadlist, ismain=False)
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 on_reconfigure_stats(evt, var, roleset, reason): from src.roles.helper.wolves import get_wolfchat_roles if reason != "howl" or not LYCANTHROPES 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 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 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 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_exchange(evt, cli, var, actor, nick, actor_role, nick_role): special = set( list_players(("harlot", "priest", "prophet", "matchmaker", "doctor", "hag", "sorcerer", "turncoat", "clone", "piper"))) 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 end_who(cli, bot_server, bot_nick, target, rest): """Handle the end of WHO/WHOX responses from the server. Ordering and meaning of arguments for the end of a WHO/WHOX request: 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 target the request was made against 4 - A string containing some information; traditionally "End of /WHO list." This fires off the "who_end" event, and dispatches it with one argument: The channel or user the request was made to, or None if it could not be resolved. """ try: target = channels.get(target) except KeyError: try: target = users.get(target) except KeyError: target = None else: target.dispatch_queue() old = _who_old.get(target.name, target) _who_old.clear() Event("who_end", {}, old=old).dispatch(target)
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, wrapper.source, target) 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 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