import re import random import itertools import math from collections import defaultdict import botconfig import src.settings as var from src.utilities import * from src import debuglog, errlog, plog from src.decorators import cmd, event_listener from src.messages import messages from src.events import Event GUARDED = {} # type: Dict[str, str] LASTGUARDED = {} # type: Dict[str, str] PASSED = set() # type: Set[str] @cmd("guard", "protect", "save", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("bodyguard", "guardian angel")) def guard(cli, nick, chan, rest): """Guard a player, preventing them from being killed that night.""" 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) @cmd("pass", chan=False, pm=True, playing=True, phases=("night",), roles=("bodyguard", "guardian angel")) def pass_cmd(cli, nick, chan, rest): """Decline to use your special power for that night.""" if nick in GUARDED: pm(cli, nick, messages["already_protecting"]) return PASSED.add(nick) pm(cli, nick, messages["guardian_no_protect"]) debuglog("{0} ({1}) PASS".format(nick, get_role(nick))) chk_nightdone(cli) @event_listener("rename_player") def on_rename(evt, cli, var, prefix, nick): for dictvar in (GUARDED, LASTGUARDED): kvp = {} for a,b in dictvar.items(): if a == prefix: if b == prefix: kvp[nick] = nick else: kvp[nick] = b elif b == prefix: kvp[a] = nick dictvar.update(kvp) if prefix in dictvar: del dictvar[prefix] if prefix in PASSED: PASSED.discard(prefix) PASSED.add(nick) @event_listener("del_player") def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers): if var.PHASE == "night" and nick in GUARDED: pm(cli, GUARDED[nick], messages["protector_disappeared"]) for dictvar in (GUARDED, LASTGUARDED): for k,v in list(dictvar.items()): if nick in (k, v): del dictvar[k] if nick in PASSED: PASSED.discard(nick) @event_listener("night_acted") def on_acted(evt, cli, var, nick, sender): if nick in GUARDED: evt.data["acted"] = True @event_listener("get_special") def on_get_special(evt, cli, var): evt.data["special"].update(list_players(("guardian angel", "bodyguard"))) @event_listener("exchange_roles") def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role): if actor_role in ("bodyguard", "guardian angel"): if actor in GUARDED: pm(cli, GUARDED.pop(actor), messages["protector disappeared"]) if actor in LASTGUARDED: del LASTGUARDED[actor] if nick_role in ("bodyguard", "guardian angel"): if nick in GUARDED: pm(cli, GUARDED.pop(nick), messages["protector disappeared"]) if nick in LASTGUARDED: del LASTGUARDED[nick] @event_listener("chk_nightdone") def on_chk_nightdone(evt, cli, var): evt.data["actedcount"] += len(GUARDED) + len(PASSED) evt.data["nightroles"].extend(list_players(("guardian angel", "bodyguard"))) @event_listener("transition_day", priority=4.2) def on_transition_day(evt, cli, var): pl = list_players() vs = set(evt.data["victims"]) for v in pl: if v in vs: if v in var.DYING: continue for g in var.ROLES["guardian angel"]: if GUARDED.get(g) == v: evt.data["numkills"][v] -= 1 if evt.data["numkills"][v] >= 0: evt.data["killers"][v].pop(0) if evt.data["numkills"][v] <= 0 and v not in evt.data["protected"]: evt.data["protected"][v] = "angel" elif evt.data["numkills"][v] <= 0: var.ACTIVE_PROTECTIONS[v].append("angel") for g in var.ROLES["bodyguard"]: if GUARDED.get(g) == v: evt.data["numkills"][v] -= 1 if evt.data["numkills"][v] >= 0: evt.data["killers"][v].pop(0) if evt.data["numkills"][v] <= 0 and v not in evt.data["protected"]: evt.data["protected"][v] = "bodyguard" elif evt.data["numkills"][v] <= 0: var.ACTIVE_PROTECTIONS[v].append("bodyguard") else: for g in var.ROLES["guardian angel"]: if GUARDED.get(g) == v: var.ACTIVE_PROTECTIONS[v].append("angel") for g in var.ROLES["bodyguard"]: if GUARDED.get(g) == v: var.ACTIVE_PROTECTIONS[v].append("bodyguard") @event_listener("fallen_angel_guard_break") def on_fagb(evt, cli, var, victim, killer): for g in var.ROLES["guardian angel"]: if GUARDED.get(g) == victim: if random.random() < var.FALLEN_ANGEL_KILLS_GUARDIAN_ANGEL_CHANCE: if g in evt.data["protected"]: del evt.data["protected"][g] evt.data["bywolves"].add(g) if g not in evt.data["victims"]: evt.data["onlybywolves"].add(g) evt.data["victims"].append(g) evt.data["killers"][g].append(killer) if g != victim: pm(cli, g, messages["fallen_angel_success"].format(victim)) for g in var.ROLES["bodyguard"]: if GUARDED.get(g) == victim: if g in evt.data["protected"]: del evt.data["protected"][g] evt.data["bywolves"].add(g) if g not in evt.data["victims"]: evt.data["onlybywolves"].add(g) evt.data["victims"].append(g) evt.data["killers"][g].append(killer) if g != victim: pm(cli, g, messages["fallen_angel_success"].format(victim)) @event_listener("transition_day_resolve", priority=2) def on_transition_day_resolve(evt, cli, var, victim): # TODO: remove these checks once everything is split # right now they're needed because otherwise protection may fire off even if the person isn't home # that will not be an issue once everything is using the event if victim in var.ROLES["harlot"] | var.ROLES["succubus"] and var.HVISITED.get(victim) and victim not in evt.data["dead"] and victim in evt.data["onlybywolves"]: return # END checks to remove if evt.data["protected"].get(victim) == "angel": evt.data["message"].append(messages["angel_protection"].format(victim)) evt.data["novictmsg"] = False evt.stop_processing = True evt.prevent_default = True elif evt.data["protected"].get(victim) == "bodyguard": for bodyguard in var.ROLES["bodyguard"]: if GUARDED.get(bodyguard) == victim: evt.data["dead"].append(bodyguard) evt.data["message"].append(messages["bodyguard_protection"].format(bodyguard)) evt.data["novictmsg"] = False evt.stop_processing = True evt.prevent_default = True break @event_listener("transition_day_resolve_end") def on_transition_day_resolve_end(evt, cli, var, victims): for bodyguard in var.ROLES["bodyguard"]: if GUARDED.get(bodyguard) in list_players(var.WOLF_ROLES) and bodyguard not in evt.data["dead"] and bodyguard not in evt.data["bitten"]: r = random.random() if r < var.BODYGUARD_DIES_CHANCE: evt.data["bywolves"].add(bodyguard) evt.data["onlybywolves"].add(bodyguard) if var.ROLE_REVEAL == "on": evt.data["message"].append(messages["bodyguard_protected_wolf"].format(bodyguard)) else: # off and team evt.data["message"].append(messages["bodyguard_protection"].format(bodyguard)) evt.data["dead"].append(bodyguard) for gangel in var.ROLES["guardian angel"]: if GUARDED.get(gangel) in list_players(var.WOLF_ROLES) and gangel not in evt.data["dead"] and gangel not in evt.data["bitten"]: r = random.random() if r < var.GUARDIAN_ANGEL_DIES_CHANCE: evt.data["bywolves"].add(gangel) evt.data["onlybywolves"].add(gangel) if var.ROLE_REVEAL == "on": evt.data["message"].append(messages["guardian_angel_protected_wolf"].format(gangel)) else: # off and team evt.data["message"].append(messages["guardian_angel_protected_wolf_no_reveal"].format(gangel)) evt.data["dead"].append(gangel) @event_listener("transition_night_begin") def on_transition_night_begin(evt, cli, var): # needs to be here in order to allow bodyguard protections to work during the daytime # (right now they don't due to other reasons, but that may change) GUARDED.clear() @event_listener("transition_night_end", priority=2) def on_transition_night_end(evt, cli, var): # the messages for angel and guardian angel are different enough to merit individual loops ps = list_players() for bg in var.ROLES["bodyguard"]: pl = ps[:] random.shuffle(pl) pl.remove(bg) chance = math.floor(var.BODYGUARD_DIES_CHANCE * 100) warning = "" if chance > 0: warning = messages["bodyguard_death_chance"].format(chance) if bg in var.PLAYERS and not is_user_simple(bg): pm(cli, bg, messages["bodyguard_notify"].format(warning)) else: pm(cli, bg, messages["bodyguard_simple"]) # !simple pm(cli, bg, "Players: " + ", ".join(pl)) for gangel in var.ROLES["guardian angel"]: pl = ps[:] random.shuffle(pl) gself = messages["guardian_self_notification"] if not var.GUARDIAN_ANGEL_CAN_GUARD_SELF: pl.remove(gangel) gself = "" if LASTGUARDED.get(gangel) in pl: pl.remove(LASTGUARDED[gangel]) chance = math.floor(var.GUARDIAN_ANGEL_DIES_CHANCE * 100) warning = "" if chance > 0: warning = messages["bodyguard_death_chance"].format(chance) if gangel in var.PLAYERS and not is_user_simple(gangel): pm(cli, gangel, messages["guardian_notify"].format(warning, gself)) else: pm(cli, gangel, messages["guardian_simple"]) # !simple pm(cli, gangel, "Players: " + ", ".join(pl)) @event_listener("assassinate") def on_assassinate(evt, cli, var, nick, target, prot): if prot == "angel" and var.GAMEPHASE == "night": var.ACTIVE_PROTECTIONS[target].remove("angel") evt.prevent_default = True evt.stop_processing = True cli.msg(botconfig.CHANNEL, messages[evt.params.message_prefix + "angel"].format(nick, target)) elif prot == "bodyguard": var.ACTIVE_PROTECTIONS[target].remove("bodyguard") evt.prevent_default = True evt.stop_processing = True for bg in var.ROLES["bodyguard"]: if GUARDED.get(bg) == target: cli.msg(botconfig.CHANNEL, messages[evt.params.message_prefix + "bodyguard"].format(nick, target, bg)) evt.params.del_player(cli, bg, 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) break @event_listener("begin_day") def on_begin_day(evt, cli, var): PASSED.clear() # clear out LASTGUARDED for people that didn't guard last night for g in list(LASTGUARDED.keys()): if g not in GUARDED: del LASTGUARDED[g] @event_listener("reset") def on_reset(evt, var): GUARDED.clear() LASTGUARDED.clear() PASSED.clear() # vim: set sw=4 expandtab: