Esempio n. 1
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
Esempio n. 2
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"])