Example #1
0
    def __init__(self, arg=""):
        super().__init__(arg)
        self.LIMIT_ABSTAIN = False
        self.DEFAULT_ROLE = "shaman"
        # If you add non-wolfteam, non-shaman roles, be sure to update update_stats to account for it!
        # otherwise !stats will break if they turn into VG.
        self.ROLE_GUIDE = {
            6: ["wolf shaman"],
            10: ["wolf shaman(2)"],
            15: ["wolf shaman(3)"],
            20: ["wolf shaman(4)"]
            }
        self.EVENTS = {
            "transition_night_end": EventListener(self.on_transition_night_end, priority=1),
            "wolf_numkills": EventListener(self.on_wolf_numkills),
            "totem_assignment": EventListener(self.on_totem_assignment),
            "transition_day_begin": EventListener(self.on_transition_day_begin, priority=8),
            "transition_day_resolve_end": EventListener(self.on_transition_day_resolve_end, priority=2),
            "del_player": EventListener(self.on_del_player),
            "apply_totem": EventListener(self.on_apply_totem),
            "lynch": EventListener(self.on_lynch),
            "chk_win": EventListener(self.on_chk_win),
            "revealroles_role": EventListener(self.on_revealroles_role),
            "update_stats": EventListener(self.on_update_stats),
            "begin_night": EventListener(self.on_begin_night)
        }

        self.TOTEM_CHANCES = {totem: {} for totem in self.DEFAULT_TOTEM_CHANCES}
        self.set_default_totem_chances()
        for totem, roles in self.TOTEM_CHANCES.items():
            for role in roles:
                self.TOTEM_CHANCES[totem][role] = 0
        # custom totems
        self.TOTEM_CHANCES["sustenance"] = {"shaman": 12, "wolf shaman": 0, "crazed shaman": 0}
        self.TOTEM_CHANCES["hunger"] = {"shaman": 0, "wolf shaman": 6, "crazed shaman": 0}
        # extra shaman totems
        self.TOTEM_CHANCES["revealing"]["shaman"] = 4
        self.TOTEM_CHANCES["death"]["shaman"] = 1
        self.TOTEM_CHANCES["pacifism"]["shaman"] = 2
        self.TOTEM_CHANCES["silence"]["shaman"] = 1
        # extra WS totems: note that each WS automatically gets a hunger totem in addition to this in phase 1
        self.TOTEM_CHANCES["death"]["wolf shaman"] = 2
        self.TOTEM_CHANCES["revealing"]["wolf shaman"] = 2
        self.TOTEM_CHANCES["luck"]["wolf shaman"] = 4
        self.TOTEM_CHANCES["silence"]["wolf shaman"] = 1
        self.TOTEM_CHANCES["pacifism"]["wolf shaman"] = 2
        self.TOTEM_CHANCES["impatience"]["wolf shaman"] = 3

        self.hunger_levels = DefaultUserDict(int)
        self.totem_tracking = defaultdict(int) # no need to make a user container, this is only non-empty a very short time
        self.phase = 1
        self.max_nights = 7
        self.village_hunger = 0
        self.village_hunger_percent_base = 0.3
        self.village_hunger_percent_adj = 0.03
        self.village_starve = 0
        self.max_village_starve = 3
        self.num_retribution = 0
        self.saved_messages = {} # type: Dict[str, str]
        self.feed_command = None
Example #2
0
def add_protection(var, target, protector, protector_role, scope=All):
    """Add a protection to the target affecting the relevant scope."""
    if target not in get_players():
        return

    if target not in PROTECTIONS:
        PROTECTIONS[target] = DefaultUserDict(list)

    prot_entry = (scope, protector_role)
    PROTECTIONS[target][protector].append(prot_entry)
Example #3
0
from src.containers import DefaultUserDict
from src.decorators import event_listener
from src.functions import get_players
from src.events import Event

__all__ = ["add_lynch_immunity", "try_lynch_immunity"]

IMMUNITY = DefaultUserDict(set)  # type: UserDict[User, set]


def add_lynch_immunity(var, user, reason):
    """Make user immune to lynching for one day."""
    if user not in get_players():
        return
    IMMUNITY[user].add(reason)


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


@event_listener("revealroles")
def on_revealroles(evt, var, wrapper):
    if IMMUNITY:
        evt.data["output"].append("\u0002lynch immunity\u0002: {0}".format(
Example #4
0
class BorealMode(GameMode):
    """Some shamans are working against you. Exile them before you starve!"""
    def __init__(self, arg=""):
        super().__init__(arg)
        self.LIMIT_ABSTAIN = False
        self.SELF_LYNCH_ALLOWED = False
        self.DEFAULT_ROLE = "shaman"
        # If you add non-wolfteam, non-shaman roles, be sure to update update_stats to account for it!
        # otherwise !stats will break if they turn into VG.
        self.ROLE_GUIDE = {
            6: ["wolf shaman", "wolf shaman(2)"],
            10: ["wolf shaman(3)"],
            15: ["wolf shaman(4)"],
            20: ["wolf shaman(5)"]
        }
        self.EVENTS = {
            "transition_night_begin":
            EventListener(self.on_transition_night_begin),
            "transition_night_end":
            EventListener(self.on_transition_night_end, priority=1),
            "wolf_numkills":
            EventListener(self.on_wolf_numkills),
            "totem_assignment":
            EventListener(self.on_totem_assignment),
            "transition_day_begin":
            EventListener(self.on_transition_day_begin, priority=8),
            "transition_day_resolve_end":
            EventListener(self.on_transition_day_resolve_end, priority=2),
            "del_player":
            EventListener(self.on_del_player),
            "apply_totem":
            EventListener(self.on_apply_totem),
            "lynch":
            EventListener(self.on_lynch),
            "chk_win":
            EventListener(self.on_chk_win),
            "revealroles_role":
            EventListener(self.on_revealroles_role),
            "update_stats":
            EventListener(self.on_update_stats),
            "begin_night":
            EventListener(self.on_begin_night),
            "num_totems":
            EventListener(self.on_num_totems)
        }

        self.TOTEM_CHANCES = {
            totem: {}
            for totem in self.DEFAULT_TOTEM_CHANCES
        }
        self.set_default_totem_chances()
        for totem, roles in self.TOTEM_CHANCES.items():
            for role in roles:
                self.TOTEM_CHANCES[totem][role] = 0
        # custom totems
        self.TOTEM_CHANCES["sustenance"] = {
            "shaman": 60,
            "wolf shaman": 10,
            "crazed shaman": 0
        }
        self.TOTEM_CHANCES["hunger"] = {
            "shaman": 0,
            "wolf shaman": 40,
            "crazed shaman": 0
        }
        # extra shaman totems
        self.TOTEM_CHANCES["revealing"]["shaman"] = 10
        self.TOTEM_CHANCES["death"]["shaman"] = 10
        self.TOTEM_CHANCES["pacifism"]["shaman"] = 10
        self.TOTEM_CHANCES["silence"]["shaman"] = 10
        # extra WS totems
        self.TOTEM_CHANCES["death"]["wolf shaman"] = 10
        self.TOTEM_CHANCES["revealing"]["wolf shaman"] = 10
        self.TOTEM_CHANCES["luck"]["wolf shaman"] = 10
        self.TOTEM_CHANCES["silence"]["wolf shaman"] = 10
        self.TOTEM_CHANCES["pacifism"]["wolf shaman"] = 10

        self.hunger_levels = DefaultUserDict(int)
        self.totem_tracking = defaultdict(
            int
        )  # no need to make a user container, this is only non-empty a very short time
        self.phase = 1
        self.max_nights = 7
        self.village_hunger = 0
        self.village_hunger_percent_base = 0.4
        self.village_hunger_percent_adj = 0.03
        self.ws_num_totem_percent = 0.5
        self.ws_extra_totem = 0
        self.village_starve = 0
        self.max_village_starve = 3
        self.num_retribution = 0
        self.saved_messages = {}  # type: Dict[str, str]
        self.feed_command = None

    def startup(self):
        super().startup()
        self.phase = 1
        self.village_starve = 0
        self.hunger_levels.clear()
        self.saved_messages = {
            "wolf_shaman_notify": messages.messages["wolf_shaman_notify"],
            "vengeful_turn": messages.messages["vengeful_turn"],
            "lynch_reveal": messages.messages["lynch_reveal"]
        }

        messages.messages[
            "wolf_shaman_notify"] = ""  # don't tell WS they can kill
        messages.messages["vengeful_turn"] = messages.messages["boreal_turn"]
        messages.messages["lynch_reveal"] = messages.messages["boreal_exile"]

        kwargs = dict(chan=False,
                      pm=True,
                      playing=True,
                      silenced=True,
                      phases=("night", ),
                      roles=("shaman", "wolf shaman"))
        self.feed_command = command("feed", **kwargs)(self.feed)

    def teardown(self):
        from src import decorators
        super().teardown()

        def remove_command(name, command):
            if len(decorators.COMMANDS[name]) > 1:
                decorators.COMMANDS[name].remove(command)
            else:
                del decorators.COMMANDS[name]

        self.hunger_levels.clear()
        for key, value in self.saved_messages.items():
            messages.messages[key] = value
        remove_command("feed", self.feed_command)

    def on_totem_assignment(self, evt, var, player, role):
        if role == "shaman":
            # In phase 2, we want to hand out as many retribution totems as there are active VGs (if possible)
            if self.num_retribution > 0:
                self.num_retribution -= 1
                evt.data["totems"] = {"retribution": 1}

    def on_transition_night_begin(self, evt, var):
        num_s = len(
            get_players(("shaman", ), mainroles=var.ORIGINAL_MAIN_ROLES))
        num_ws = len(get_players(("wolf shaman", )))
        # as wolf shamans die, we want to pass some extras onto the remaining ones; each ws caps at 2 totems though
        self.ws_extra_totem = int(num_s * self.ws_num_totem_percent) - num_ws

    def on_transition_night_end(self, evt, var):
        from src.roles import vengefulghost
        # determine how many retribution totems we need to hand out tonight
        self.num_retribution = sum(1 for p in vengefulghost.GHOSTS
                                   if vengefulghost.GHOSTS[p][0] != "!")
        if self.num_retribution > 0:
            self.phase = 2
        # determine how many tribe members need to be fed. It's a percentage of remaining shamans
        # Each alive WS reduces the percentage needed; the number is rounded off (.5 rounding to even)
        percent = self.village_hunger_percent_base - self.village_hunger_percent_adj * len(
            get_players(("wolf shaman", )))
        self.village_hunger = round(len(get_players(("shaman", ))) * percent)

    def on_wolf_numkills(self, evt, var):
        evt.data["numkills"] = 0

    def on_num_totems(self, evt, var, player, role):
        if role == "wolf shaman" and self.ws_extra_totem > 0:
            self.ws_extra_totem -= 1
            evt.data["num"] = 2

    def on_transition_day_begin(self, evt, var):
        from src.roles import vengefulghost
        num_wendigos = len(vengefulghost.GHOSTS)
        num_wolf_shamans = len(get_players(("wolf shaman", )))
        ps = get_players()
        for p in ps:
            if get_main_role(p) in Wolfteam:
                continue  # wolf shamans can't starve

            if self.totem_tracking[p] > 0:
                # if sustenance totem made it through, fully feed player
                self.hunger_levels[p] = 0
            elif self.totem_tracking[p] < 0:
                # if hunger totem made it through, fast-track player to starvation
                if self.hunger_levels[p] < 3:
                    self.hunger_levels[p] = 3

            # apply natural hunger
            self.hunger_levels[p] += 1

            if self.hunger_levels[p] >= 5:
                # if they hit 5, they die of starvation
                # if there are less VGs than alive wolf shamans, they become a wendigo as well
                if num_wendigos < num_wolf_shamans:
                    num_wendigos += 1
                    change_role(var,
                                p,
                                get_main_role(p),
                                "vengeful ghost",
                                message=None)
                add_dying(var,
                          p,
                          killer_role="villager",
                          reason="boreal_starvation")
            elif self.hunger_levels[p] >= 3:
                # if they are at 3 or 4, alert them that they are hungry
                p.send(messages["boreal_hungry"])

        self.totem_tracking.clear()

    def on_transition_day_resolve_end(self, evt, var, victims):
        if len(evt.data["dead"]) == 0:
            evt.data["novictmsg"] = False
        # say if the village went hungry last night (and apply those effects if it did)
        if self.village_hunger > 0:
            self.village_starve += 1
            evt.data["message"]["*"].append(messages["boreal_village_hungry"])
        # say how many days remaining
        remain = self.max_nights - var.NIGHT_COUNT
        if remain > 0:
            evt.data["message"]["*"].append(
                messages["boreal_day_count"].format(remain))

    def on_lynch(self, evt, var, votee, voters):
        if get_main_role(votee) not in Wolfteam:
            # if there are less VGs than alive wolf shamans, they become a wendigo as well
            from src.roles import vengefulghost
            num_wendigos = len(vengefulghost.GHOSTS)
            num_wolf_shamans = len(get_players(("wolf shaman", )))
            if num_wendigos < num_wolf_shamans:
                change_role(var,
                            votee,
                            get_main_role(votee),
                            "vengeful ghost",
                            message=None)

    def on_del_player(self, evt, var, player, all_roles, death_triggers):
        for a, b in list(self.hunger_levels.items()):
            if player in (a, b):
                del self.hunger_levels[a]

    def on_apply_totem(self, evt, var, role, totem, shaman, target):
        if totem == "sustenance":
            if target is users.Bot:
                # fed the village
                self.village_hunger -= 1
            else:
                # gave to a player
                self.totem_tracking[target] += 1
        elif totem == "hunger":
            if target is users.Bot:
                # tried to starve the village
                self.village_hunger += 1
            else:
                # gave to a player
                self.totem_tracking[target] -= 1

    def on_chk_win(self, evt, var, rolemap, mainroles, lpl, lwolves,
                   lrealwolves):
        if self.village_starve == self.max_village_starve and var.PHASE == "day":
            # if village didn't feed the NPCs enough nights, the starving tribe members destroy themselves from within
            # this overrides built-in win conds (such as all wolves being dead)
            evt.data["winner"] = "wolves"
            evt.data["message"] = messages["boreal_village_starve"]
        elif var.NIGHT_COUNT == self.max_nights and var.PHASE == "day":
            # if village survived for N nights without losing, they outlast the storm and win
            # this overrides built-in win conds (such as same number of wolves as villagers)
            evt.data["winner"] = "villagers"
            evt.data["message"] = messages["boreal_time_up"]
        elif evt.data["winner"] == "villagers":
            evt.data["message"] = messages["boreal_village_win"]
        elif evt.data["winner"] == "wolves":
            evt.data["message"] = messages["boreal_wolf_win"]

    def on_revealroles_role(self, evt, var, player, role):
        if player in self.hunger_levels:
            evt.data["special_case"].append(
                messages["boreal_revealroles"].format(
                    self.hunger_levels[player]))

    def on_update_stats(self, evt, var, player, main_role, reveal_role,
                        all_roles):
        if main_role == "vengeful ghost":
            evt.data["possible"].add("shaman")

    def on_begin_night(self, evt, var):
        evt.data["messages"].append(messages["boreal_night_reminder"].format(
            self.village_hunger, self.village_starve))

    def feed(self, var, wrapper, message):
        """Give your totem to the tribe members."""
        from src.roles.shaman import TOTEMS as s_totems, SHAMANS as s_shamans
        from src.roles.wolfshaman import TOTEMS as ws_totems, SHAMANS as ws_shamans

        pieces = re.split(" +", message)
        valid = ("sustenance", "hunger")
        state_vars = ((s_totems, s_shamans), (ws_totems, ws_shamans))
        for TOTEMS, SHAMANS in state_vars:
            if wrapper.source not in TOTEMS:
                continue

            totem_types = list(TOTEMS[wrapper.source].keys())
            given = complete_one_match(pieces[0], totem_types)
            if not given and TOTEMS[wrapper.source].get(
                    "sustenance", 0) + TOTEMS[wrapper.source].get("hunger",
                                                                  0) > 1:
                wrapper.send(messages["boreal_ambiguous_feed"])
                return

            for totem in valid:
                if (given and totem != given) or TOTEMS[wrapper.source].get(
                        totem, 0) == 0:
                    continue  # doesn't have a totem that can be used to feed tribe

                SHAMANS[wrapper.source][totem].append(users.Bot)
                if len(SHAMANS[wrapper.source][totem]) > TOTEMS[
                        wrapper.source][totem]:
                    SHAMANS[wrapper.source][totem].pop(0)

                wrapper.pm(messages["boreal_feed_success"].format(totem))
                # send_wolfchat_message already takes care of checking whether the player has access to wolfchat, so this will only be sent for wolf shamans
                send_wolfchat_message(var,
                                      wrapper.source,
                                      messages["boreal_wolfchat_feed"].format(
                                          wrapper.source), {"wolf shaman"},
                                      role="wolf shaman",
                                      command="feed")
                return
Example #5
0
 def ulf():
     # Factory method to create a DefaultUserDict[*, UserList]
     # this can be passed into a DefaultUserDict constructor so we can make nested defaultdicts easily
     return DefaultUserDict(UserList)
Example #6
0
def setup_variables(rolename, *, knows_totem):
    """Setup role variables and shared events."""
    def ulf():
        # Factory method to create a DefaultUserDict[*, UserList]
        # this can be passed into a DefaultUserDict constructor so we can make nested defaultdicts easily
        return DefaultUserDict(UserList)

    TOTEMS = DefaultUserDict(
        dict)  # type: DefaultUserDict[users.User, Dict[str, int]]
    LASTGIVEN = DefaultUserDict(
        ulf
    )  # type: DefaultUserDict[users.User, DefaultUserDict[str, UserList]]
    SHAMANS = DefaultUserDict(
        ulf
    )  # type: DefaultUserDict[users.User, DefaultUserDict[str, UserList]]
    RETARGET = DefaultUserDict(
        UserDict
    )  # type: DefaultUserDict[users.User, UserDict[users.User, users.User]]
    _rolestate[rolename] = {
        "TOTEMS": TOTEMS,
        "LASTGIVEN": LASTGIVEN,
        "SHAMANS": SHAMANS,
        "RETARGET": RETARGET
    }

    @event_listener("reset",
                    listener_id="shamans.<{}>.on_reset".format(rolename))
    def on_reset(evt, var):
        TOTEMS.clear()
        LASTGIVEN.clear()
        SHAMANS.clear()
        RETARGET.clear()

    @event_listener("begin_day",
                    listener_id="shamans.<{}>.on_begin_day".format(rolename))
    def on_begin_day(evt, var):
        SHAMANS.clear()
        RETARGET.clear()

    @event_listener(
        "revealroles_role",
        listener_id="shamans.<{}>.revealroles_role".format(rolename))
    def on_revealroles(evt, var, user, role):
        if role == rolename and user in TOTEMS:
            if var.PHASE == "night":
                evt.data["special_case"].append(
                    messages["shaman_revealroles_night"].format(
                        (messages["shaman_revealroles_night_totem"].format(
                            num, totem)
                         for num, totem in TOTEMS[user].items()),
                        sum(TOTEMS[user].values())))
            elif user in LASTGIVEN and LASTGIVEN[user]:
                given = []
                for totem, recips in LASTGIVEN[user].items():
                    for recip in recips:
                        given.append(
                            messages["shaman_revealroles_day_totem"].format(
                                totem, recip))
                evt.data["special_case"].append(
                    messages["shaman_revealroles_day"].format(given))

    @event_listener(
        "transition_day_begin",
        priority=7,
        listener_id="shamans.<{}>.transition_day_begin".format(rolename))
    def on_transition_day_begin2(evt, var):
        LASTGIVEN.clear()
        for shaman, given in SHAMANS.items():
            for totem, targets in given.items():
                for target in targets:
                    victim = RETARGET[shaman].get(target, target)
                    if not victim:
                        continue
                    if totem == "death":  # this totem stacks
                        if shaman not in DEATH:
                            DEATH[shaman] = UserList()
                        DEATH[shaman].append(victim)
                    elif totem == "protection":  # this totem stacks
                        PROTECTION.append(victim)
                    elif totem == "revealing":
                        REVEALING.add(victim)
                    elif totem == "narcolepsy":
                        NARCOLEPSY.add(victim)
                    elif totem == "silence":
                        SILENCE.add(victim)
                    elif totem == "desperation":
                        DESPERATION.add(victim)
                    elif totem == "impatience":  # this totem stacks
                        IMPATIENCE.append(victim)
                    elif totem == "pacifism":  # this totem stacks
                        PACIFISM.append(victim)
                    elif totem == "influence":
                        INFLUENCE.add(victim)
                    elif totem == "exchange":
                        EXCHANGE.add(victim)
                    elif totem == "lycanthropy":
                        LYCANTHROPY.add(victim)
                    elif totem == "luck":
                        LUCK.add(victim)
                    elif totem == "pestilence":
                        PESTILENCE.add(victim)
                    elif totem == "retribution":
                        RETRIBUTION.add(victim)
                    elif totem == "misdirection":
                        MISDIRECTION.add(victim)
                    elif totem == "deceit":
                        DECEIT.add(victim)
                    else:
                        event = Event("apply_totem", {})
                        event.dispatch(var, rolename, totem, shaman, victim)

                    if target is not victim:
                        shaman.send(messages["totem_retarget"].format(
                            victim, target))
                    LASTGIVEN[shaman][totem].append(victim)
                    havetotem.append(victim)

    @event_listener("del_player",
                    listener_id="shamans.<{}>.del_player".format(rolename))
    def on_del_player(evt, var, player, all_roles, death_triggers):
        for a, b in list(SHAMANS.items()):
            if player is a:
                del SHAMANS[a]
            else:
                for totem, c in b.items():
                    if player in c:
                        SHAMANS[a][totem].remove(player)
        del RETARGET[:player:]
        for a, b in list(RETARGET.items()):
            for c, d in list(b.items()):
                if player in (c, d):
                    del RETARGET[a][c]

    @event_listener("chk_nightdone",
                    listener_id="shamans.<{}>.chk_nightdone".format(rolename))
    def on_chk_nightdone(evt, var):
        # only count shaman as acted if they've given out all of their totems
        for shaman in SHAMANS:
            totemcount = sum(TOTEMS[shaman].values())
            given = len(
                list(itertools.chain.from_iterable(SHAMANS[shaman].values())))
            if given == totemcount:
                evt.data["acted"].append(shaman)
        evt.data["nightroles"].extend(get_all_players((rolename, )))

    @event_listener(
        "get_role_metadata",
        listener_id="shamans.<{}>.get_role_metadata".format(rolename))
    def on_get_role_metadata(evt, var, kind):
        if kind == "night_kills":
            # only add shamans here if they were given a death totem
            # even though retribution kills, it is given a special kill message
            evt.data[rolename] = list(
                itertools.chain.from_iterable(TOTEMS.values())).count("death")

    @event_listener("new_role",
                    listener_id="shamans.<{}>.new_role".format(rolename))
    def on_new_role(evt, var, player, old_role):
        if evt.params.inherit_from in TOTEMS and old_role != rolename and evt.data[
                "role"] == rolename:
            totems = TOTEMS.pop(evt.params.inherit_from)
            del SHAMANS[:evt.params.inherit_from:]
            del LASTGIVEN[:evt.params.inherit_from:]

            if knows_totem:
                evt.data["messages"].append(totem_message(totems))
            TOTEMS[player] = totems

    @event_listener("swap_role_state",
                    listener_id="shamans.<{}>.swap_role_state".format(rolename)
                    )
    def on_swap_role_state(evt, var, actor, target, role):
        if role == rolename and actor in TOTEMS and target in TOTEMS:
            TOTEMS[actor], TOTEMS[target] = TOTEMS[target], TOTEMS[actor]
            del SHAMANS[:actor:]
            del SHAMANS[:target:]
            del LASTGIVEN[:actor:]
            del LASTGIVEN[:target:]

            if knows_totem:
                evt.data["actor_messages"].append(totem_message(TOTEMS[actor]))
                evt.data["target_messages"].append(
                    totem_message(TOTEMS[target]))

    @event_listener("default_totems",
                    priority=3,
                    listener_id="shamans.<{}>.default_totems".format(rolename))
    def add_shaman(evt, chances):
        evt.data["shaman_roles"].add(rolename)

    @event_listener(
        "transition_night_end",
        listener_id="shamans.<{}>.on_transition_night_end".format(rolename))
    def on_transition_night_end(evt, var):
        if var.NIGHT_COUNT == 0 or not get_all_players((rolename, )):
            return
        if var.CURRENT_GAMEMODE.TOTEM_CHANCES["lycanthropy"][rolename] > 0:
            status.add_lycanthropy_scope(var, All)
        if var.CURRENT_GAMEMODE.TOTEM_CHANCES["luck"][rolename] > 0:
            status.add_misdirection_scope(var, All, as_target=True)
        if var.CURRENT_GAMEMODE.TOTEM_CHANCES["misdirection"][rolename] > 0:
            status.add_misdirection_scope(var, All, as_actor=True)

    if knows_totem:

        @event_listener("myrole",
                        listener_id="shamans.<{}>.on_myrole".format(rolename))
        def on_myrole(evt, var, user):
            if evt.data[
                    "role"] == rolename and var.PHASE == "night" and user not in SHAMANS:
                evt.data["messages"].append(totem_message(TOTEMS[user]))

    return (TOTEMS, LASTGIVEN, SHAMANS, RETARGET)
Example #7
0
class BorealMode(GameMode):
    """Some shamans are working against you. Exile them before you starve!"""
    def __init__(self, arg=""):
        super().__init__(arg)
        self.LIMIT_ABSTAIN = False
        self.DEFAULT_ROLE = "shaman"
        # If you add non-wolfteam, non-shaman roles, be sure to update update_stats to account for it!
        # otherwise !stats will break if they turn into VG.
        self.ROLE_GUIDE = {
            6: ["wolf shaman"],
            10: ["wolf shaman(2)"],
            15: ["wolf shaman(3)"],
            20: ["wolf shaman(4)"]
            }
        self.EVENTS = {
            "transition_night_end": EventListener(self.on_transition_night_end, priority=1),
            "wolf_numkills": EventListener(self.on_wolf_numkills),
            "totem_assignment": EventListener(self.on_totem_assignment),
            "transition_day_begin": EventListener(self.on_transition_day_begin, priority=8),
            "transition_day_resolve_end": EventListener(self.on_transition_day_resolve_end, priority=2),
            "del_player": EventListener(self.on_del_player),
            "apply_totem": EventListener(self.on_apply_totem),
            "lynch": EventListener(self.on_lynch),
            "chk_win": EventListener(self.on_chk_win),
            "revealroles_role": EventListener(self.on_revealroles_role),
            "update_stats": EventListener(self.on_update_stats),
            "begin_night": EventListener(self.on_begin_night)
        }

        self.TOTEM_CHANCES = {totem: {} for totem in self.DEFAULT_TOTEM_CHANCES}
        self.set_default_totem_chances()
        for totem, roles in self.TOTEM_CHANCES.items():
            for role in roles:
                self.TOTEM_CHANCES[totem][role] = 0
        # custom totems
        self.TOTEM_CHANCES["sustenance"] = {"shaman": 12, "wolf shaman": 0, "crazed shaman": 0}
        self.TOTEM_CHANCES["hunger"] = {"shaman": 0, "wolf shaman": 6, "crazed shaman": 0}
        # extra shaman totems
        self.TOTEM_CHANCES["revealing"]["shaman"] = 4
        self.TOTEM_CHANCES["death"]["shaman"] = 1
        self.TOTEM_CHANCES["pacifism"]["shaman"] = 2
        self.TOTEM_CHANCES["silence"]["shaman"] = 1
        # extra WS totems: note that each WS automatically gets a hunger totem in addition to this in phase 1
        self.TOTEM_CHANCES["death"]["wolf shaman"] = 2
        self.TOTEM_CHANCES["revealing"]["wolf shaman"] = 2
        self.TOTEM_CHANCES["luck"]["wolf shaman"] = 4
        self.TOTEM_CHANCES["silence"]["wolf shaman"] = 1
        self.TOTEM_CHANCES["pacifism"]["wolf shaman"] = 2
        self.TOTEM_CHANCES["impatience"]["wolf shaman"] = 3

        self.hunger_levels = DefaultUserDict(int)
        self.totem_tracking = defaultdict(int) # no need to make a user container, this is only non-empty a very short time
        self.phase = 1
        self.max_nights = 7
        self.village_hunger = 0
        self.village_hunger_percent_base = 0.3
        self.village_hunger_percent_adj = 0.03
        self.village_starve = 0
        self.max_village_starve = 3
        self.num_retribution = 0
        self.saved_messages = {} # type: Dict[str, str]
        self.feed_command = None

    def startup(self):
        super().startup()
        self.phase = 1
        self.village_starve = 0
        self.hunger_levels.clear()
        self.saved_messages = {
            "wolf_shaman_notify": messages.messages["wolf_shaman_notify"],
            "vengeful_turn": messages.messages["vengeful_turn"],
            "lynch_reveal": messages.messages["lynch_reveal"]
        }

        messages.messages["wolf_shaman_notify"] = "" # don't tell WS they can kill
        messages.messages["vengeful_turn"] = messages.messages["boreal_turn"]
        messages.messages["lynch_reveal"] = messages.messages["boreal_exile"]

        kwargs = dict(chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("shaman",))
        self.feed_command = command("feed", **kwargs)(self.feed)

    def teardown(self):
        from src import decorators
        super().teardown()
        def remove_command(name, command):
            if len(decorators.COMMANDS[name]) > 1:
                decorators.COMMANDS[name].remove(command)
            else:
                del decorators.COMMANDS[name]

        self.hunger_levels.clear()
        for key, value in self.saved_messages.items():
            messages.messages[key] = value
        remove_command("feed", self.feed_command)

    def on_totem_assignment(self, evt, var, role):
        if role == "shaman":
            # In phase 2, we want to hand out as many retribution totems as there are active VGs (if possible)
            if self.num_retribution > 0:
                self.num_retribution -= 1
                evt.data["totems"] = {"retribution": 1}
        elif role == "wolf shaman":
            # In phase 1, wolf shamans get a bonus hunger totem
            if self.phase == 1:
                if "hunger" in evt.data["totems"]:
                    evt.data["totems"]["hunger"] += 1
                else:
                    evt.data["totems"]["hunger"] = 1

    def on_transition_night_end(self, evt, var):
        from src.roles import vengefulghost
        # determine how many retribution totems we need to hand out tonight
        self.num_retribution = sum(1 for p in vengefulghost.GHOSTS if vengefulghost.GHOSTS[p][0] != "!")
        if self.num_retribution > 0:
            self.phase = 2
        # determine how many tribe members need to be fed. It's a percentage of remaining shamans
        # Each alive WS reduces the percentage needed; the number is rounded off (.5 rounding to even)
        percent = self.village_hunger_percent_base - self.village_hunger_percent_adj * len(get_players(("wolf shaman",)))
        self.village_hunger = round(len(get_players(("shaman",))) * percent)

    def on_wolf_numkills(self, evt, var):
        evt.data["numkills"] = 0

    def on_transition_day_begin(self, evt, var):
        ps = get_players()
        for p in ps:
            if get_main_role(p) in Wolfteam:
                continue # wolf shamans can't starve

            if self.totem_tracking[p] > 0:
                # if sustenance totem made it through, fully feed player
                self.hunger_levels[p] = 0
            elif self.totem_tracking[p] < 0:
                # if hunger totem made it through, fast-track player to starvation
                if self.hunger_levels[p] < 3:
                    self.hunger_levels[p] = 3

            # apply natural hunger
            self.hunger_levels[p] += 1

            if self.hunger_levels[p] >= 5:
                # if they hit 5, they die of starvation
                # if there are less VGs than alive wolf shamans, they become a wendigo as well
                self.maybe_make_wendigo(var, p)
                add_dying(var, p, killer_role="villager", reason="boreal_starvation")
            elif self.hunger_levels[p] >= 3:
                # if they are at 3 or 4, alert them that they are hungry
                p.send(messages["boreal_hungry"])

        self.totem_tracking.clear()

    def on_transition_day_resolve_end(self, evt, var, victims):
        if len(evt.data["dead"]) == 0:
            evt.data["novictmsg"] = False
        # say if the village went hungry last night (and apply those effects if it did)
        if self.village_hunger > 0:
            self.village_starve += 1
            evt.data["message"]["*"].append(messages["boreal_village_hungry"])
        # say how many days remaining
        remain = self.max_nights - var.NIGHT_COUNT
        if remain > 0:
            evt.data["message"]["*"].append(messages["boreal_day_count"].format(remain, "s" if remain != 1 else ""))

    def maybe_make_wendigo(self, var, player):
        from src.roles import vengefulghost
        num_wendigos = len(vengefulghost.GHOSTS)
        num_wolf_shamans = len(get_players(("wolf shaman",)))
        if num_wendigos < num_wolf_shamans:
            change_role(var, player, get_main_role(player), "vengeful ghost", message=None)

    def on_lynch(self, evt, var, votee, voters):
        if get_main_role(votee) not in Wolfteam:
            # if there are less VGs than alive wolf shamans, they become a wendigo as well
            self.maybe_make_wendigo(var, votee)

    def on_del_player(self, evt, var, player, all_roles, death_triggers):
        for a, b in list(self.hunger_levels.items()):
            if player in (a, b):
                del self.hunger_levels[a]

    def on_apply_totem(self, evt, var, role, totem, shaman, target):
        if totem == "sustenance":
            if target is users.Bot:
                # fed the village
                self.village_hunger = max(0, self.village_hunger - 1)
            else:
                # gave to a player
                self.totem_tracking[target] += 1
        elif totem == "hunger":
            self.totem_tracking[target] -= 1

    def on_chk_win(self, evt, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
        if self.village_starve == self.max_village_starve and var.PHASE == "day":
            # if village didn't feed the NPCs enough nights, the starving tribe members destroy themselves from within
            if evt.data["winner"] is None:
                evt.data["winner"] = "wolves"
                evt.data["message"] = messages["boreal_village_starve"]
        elif var.NIGHT_COUNT == self.max_nights and var.PHASE == "day":
            # if village survived for N nights without losing, they outlast the storm and win
            if evt.data["winner"] is None:
                evt.data["winner"] = "villagers"
                evt.data["message"] = messages["boreal_time_up"]
        elif evt.data["winner"] == "villagers":
            evt.data["message"] = messages["boreal_village_win"]
        elif evt.data["winner"] == "wolves":
            evt.data["message"] = messages["boreal_wolf_win"]

    def on_revealroles_role(self, evt, var, player, role):
        if player in self.hunger_levels:
            evt.data["special_case"].append(messages["boreal_revealroles"].format(self.hunger_levels[player]))

    def on_update_stats(self, evt, var, player, main_role, reveal_role, all_roles):
        if main_role == "vengeful ghost":
            evt.data["possible"].add("shaman")

    def on_begin_night(self, evt, var):
        evt.data["messages"].append(messages["boreal_night_reminder"].format(
            self.village_hunger,
            "s" if self.village_hunger != 1 else "",
            self.village_starve,
            "s" if self.village_starve != 1 else ""
        ))

    def feed(self, var, wrapper, message):
        """Give your sustenance totem to the tribe members so they don't starve."""
        from src.roles.shaman import TOTEMS, SHAMANS
        if TOTEMS[wrapper.source].get("sustenance", 0) == 0:
            return # doesn't have a sustenance totem

        SHAMANS[wrapper.source]["sustenance"].append(users.Bot)
        if len(SHAMANS[wrapper.source]["sustenance"]) > TOTEMS[wrapper.source]["sustenance"]:
            SHAMANS[wrapper.source]["sustenance"].pop(0)

        wrapper.pm(messages["boreal_feed_success"])