From 28f26e181bb588214fe581978d8f1aca8704d114 Mon Sep 17 00:00:00 2001 From: Em Barry Date: Fri, 13 Apr 2018 16:37:04 -0400 Subject: [PATCH] Add the new User containers (#316) - Add the new User containers for easier handling of users throughout the codebase - Remove the swap_player event (replaced by User.swap, made possible thanks to the containers) - Remove the cli argument from several events - Remove !frole (a replacement will follow) - Remove the ALLOWED_NORMAL_MODE_COMMANDS config option Plus a couple of small fixes here and there. --- botconfig.py.example | 1 - messages/en.json | 5 - src/containers.py | 341 +++++++++++++++++++++++++++++++++++++ src/gamemodes.py | 85 +++++---- src/roles/angel.py | 5 +- src/roles/blessed.py | 3 +- src/roles/cursed.py | 3 +- src/roles/detective.py | 5 +- src/roles/doomsayer.py | 7 +- src/roles/dullahan.py | 105 +++++------- src/roles/fallenangel.py | 1 + src/roles/harlot.py | 17 +- src/roles/hunter.py | 21 +-- src/roles/investigator.py | 13 +- src/roles/madscientist.py | 1 + src/roles/mayor.py | 3 +- src/roles/mystic.py | 1 + src/roles/piper.py | 31 ++-- src/roles/seer.py | 5 +- src/roles/shaman.py | 29 +--- src/roles/skel.py | 9 +- src/roles/succubus.py | 28 +-- src/roles/traitor.py | 1 + src/roles/vengefulghost.py | 7 +- src/roles/vigilante.py | 17 +- src/roles/villager.py | 1 + src/roles/wildchild.py | 5 +- src/roles/wolf.py | 3 +- src/roles/wolfcub.py | 1 + src/settings.py | 2 - src/users.py | 42 ++++- src/wolfgame.py | 274 +++++++++-------------------- 32 files changed, 615 insertions(+), 457 deletions(-) create mode 100644 src/containers.py diff --git a/botconfig.py.example b/botconfig.py.example index a9cda63..75d17a3 100644 --- a/botconfig.py.example +++ b/botconfig.py.example @@ -41,7 +41,6 @@ OWNERS_ACCOUNTS = ("1owner_acc",) #RULES = "https://werewolf.chat/Freenode:Rules" -ALLOWED_NORMAL_MODE_COMMANDS = [] # Debug mode commands to be allowed in normal mode OWNERS_ONLY_COMMANDS = [] # Commands that should only be allowed for owners, regardless of their original permissions DISABLE_DEBUG_MODE_REAPER = True diff --git a/messages/en.json b/messages/en.json index 780615e..0bc3924 100644 --- a/messages/en.json +++ b/messages/en.json @@ -621,11 +621,6 @@ "invalid_target": "This can only be done on players in the channel or fake nicks.", "admin_only_force": "Only full admins can force an admin-only command.", "operation_successful": "Operation successful.", - "template_default_role": "Added default role ({0}) because only a template was specified for a new player.", - "improper_template_mod": "Improper template modification.", - "template_mod_syntax": "Please specify \u0002+{0}\u0002 or \u0002-{0}\u0002 to add/remove this template.", - "invalid_role": "Not a valid role.", - "stats_accurate": "{0}stats type changed to accurate due to use of {0}frole.", "not_owner": "You are not the owner.", "invalid_permissions": "You do not have permission to use that command.", "player_joined_deadchat": "\u0002{0}\u0002 has joined the deadchat.", diff --git a/src/containers.py b/src/containers.py new file mode 100644 index 0000000..476458f --- /dev/null +++ b/src/containers.py @@ -0,0 +1,341 @@ +from src.users import User + +__all__ = ["UserList", "UserSet", "UserDict"] + +""" * Important * + +The containers present here should always follow these rules: + +- Once a global variable has been set to one of the containers, it *must not* be overwritten; + +- The proper way to empty a container is with the 'container.clear()' method. The 'UserDict.clear' method + also takes care of calling the '.clear()' method of nested containers (if any), so you needn't do that; + +- If any local variable points to a container, the 'container.clear()' method + *must* be called before the variable goes out of scope; + +- Copying a container for mutation purpose in a local context should make use of context managers, + e.g. 'with copy.deepcopy(var.ROLES) as rolelist:' instead of 'rolelist = copy.deepcopy(var.ROLES)', + with all operations on 'rolelist' being done inside the block. Once the 'with' block is exited (be it + through exceptions or normal execution), the copied contained ('rolelist' in this case) is automatically cleared. + +- If fetching a container from a 'UserDict' with the intent to keep it around separate from the dictionary, + a copy is advised, as 'UserDict.clear' is recursive and will clear all nested containers, even if they + are being used outside (as the function has no way to know). + +Role files should use User containers as their global variables without ever overwriting them. It is advised to +pay close attention to where the variables get touched, to keep the above rules enforced. Refer to existing role +files to get an idea of how those containers should be used. + +""" + +class UserList(list): + def __init__(self, iterable=()): + super().__init__() + try: + for item in iterable: + self.append(item) + except: + self.clear() + raise + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + self.clear() + + def __add__(self, other): + if not isinstance(other, list): + return NotImplemented + + self.extend(other) + + def __setitem__(self, index, value): + if not isinstance(value, User): + raise TypeError("UserList may only contain User instances") + + item = self[index] + super().__setitem__(index, value) + if item not in self: + item.lists.remove(self) + + if self not in value.lists: + value.lists.append(self) + + def __delitem__(self, index): + item = self[index] + + super().__delitem__(index) + + if item not in self: # there may have been multiple instances + item.lists.remove(self) + + def append(self, item): + if not isinstance(item, User): + raise TypeError("UserList may only contain User instances") + + if self not in item.lists: + item.lists.append(self) + + super().append(item) + + def clear(self): + for item in self: + if self in item.lists: + item.lists.remove(self) + + super().clear() + + def copy(self): + return type(self)(self) + + def extend(self, iterable): + for item in iterable: + self.append(item) + + def insert(self, index, item): + if not isinstance(item, User): + raise TypeError("UserList may only contain User instances") + + super().insert(index, item) + + # If it didn't work, we don't get here + + if self not in item.lists: + item.lists.append(self) + + def pop(self, index=-1): + item = super().pop(index) + + if item not in self: + item.lists.remove(self) + + return item + + def remove(self, item): + super().remove(item) + + if item not in self: + item.lists.remove(self) + +class UserSet(set): + def __init__(self, iterable=()): + super().__init__() + try: + for item in iterable: + self.add(item) + except: + self.clear() + raise + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + self.clear() + + # Comparing UserSet instances for equality doesn't make much sense in our context + # However, if there are identical instances in a list, we only want to remove ourselves + + def __eq__(self, other): + return self is other + + # Operators are not overloaded - 'user_set & other_set' will return a regular set + # This is a deliberate design decision. To get a UserSet out of them, use the named ones + + # Augmented assignment method overrides + + def __iand__(self, other): + res = super().__iand__(other) + if not isinstance(other, set): + return NotImplemented + + self.intersection_update(other) + return self + + def __ior__(self, other): + if not isinstance(other, set): + return NotImplemented + + self.update(other) + return self + + def __isub__(self, other): + if not isinstance(other, set): + return NotImplemented + + self.difference_update(other) + return + + def __ixor__(self, other): + if not isinstance(other, set): + return NotImplemented + + self.symmetric_difference_update(other) + return self + + def add(self, item): + if item not in self: + if not isinstance(item, User): + raise TypeError("UserSet may only contain User instances") + + item.sets.append(self) + super().add(item) + + def clear(self): + for item in self: + item.sets.remove(self) + + super().clear() + + def copy(self): + return type(self)(self) + + def difference(self, iterable): + return type(self)(super().difference(iterable)) + + def difference_update(self, iterable): + for item in iterable: + if item in self: + self.remove(item) + + def discard(self, item): + if item in self: + item.sets.remove(self) + + super().discard(item) + + def intersection(self, iterable): + return type(self)(super().intersection(iterable)) + + def intersection_update(self, iterable): + for item in set(self): + if item not in iterable: + self.remove(item) + + def pop(self): + item = super().pop() + item.sets.remove(self) + return item + + def remove(self, item): + super().remove(item) + + item.sets.remove(self) + + def symmetric_difference(self, iterable): + return type(self)(super().symmetric_difference(iterable)) + + def symmetric_difference_update(self, iterable): + for item in iterable: + if item in self: + self.remove(item) + else: + self.add(item) + + def union(self, iterable): + return type(self)(super().union(iterable)) + + def update(self, iterable): + for item in iterable: + if item not in self: + self.add(item) + +class UserDict(dict): + def __init__(_self, _it=(), **kwargs): + super().__init__() + if hasattr(_it, "items"): + _it = _it.items() + try: + for key, value in _it: + self[key] = value + for key, value in kwargs.items(): + self[key] = value + except: + while self: + self.popitem() # don't clear, as it's recursive (we might not want that) + raise + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + self.clear() + + def __setitem__(self, item, value): + old = self.get(item) + super().__setitem__(item, value) + if isinstance(old, User): + if old not in self.values(): + old.dict_values.remove(self) + + if isinstance(item, User): + if self not in item.dict_keys: + item.dict_keys.append(self) + + if isinstance(value, User): + if self not in value.dict_values: + value.dict_values.append(self) + + def __delitem__(self, item): + value = self[item] + super().__delitem__(item) + if isinstance(item, User): + item.dict_keys.remove(self) + + if isinstance(value, User): + if value not in self.values(): + value.dict_values.remove(self) + + def clear(self): + for key, value in self.items(): + if isinstance(key, User): + key.dict_keys.remove(self) + if isinstance(value, User): + if self in value.dict_values: + value.dict_values.remove(self) + + if isinstance(key, (UserList, UserSet, UserDict)): + key.clear() + if isinstance(value, (UserList, UserSet, UserDict)): + value.clear() + + super().clear() + + def copy(self): + return type(self)(self.items()) + + @classmethod + def fromkeys(cls, iterable, value=None): + return cls(dict.fromkeys(iterable, value)) + + def pop(self, key, *default): + value = super().pop(key, *default) + if isinstance(key, User): + if self in key.dict_keys: + key.dict_keys.remove(self) + if isinstance(value, User): + if value not in self.values(): + value.dict_values.remove(self) + return value + + def popitem(self): + key, value = super().popitem() + if isinstance(key, User): + key.dict_keys.remove(self) + if isinstance(value, User): + if value not in self.values(): + value.dict_values.remove(self) + return key, value + + def setdefault(self, key, default=None): + if key not in self: + self[key] = default + return self[key] + + def update(self, iterable): + if hasattr(iterable, "items"): + iterable = iterable.items() + for key, value in iterable: + self[key] = value diff --git a/src/gamemodes.py b/src/gamemodes.py index 4c67ba8..ce2a437 100644 --- a/src/gamemodes.py +++ b/src/gamemodes.py @@ -12,6 +12,7 @@ from src.utilities import * from src.messages import messages from src.functions import get_players, get_all_players, get_main_role from src.decorators import handle_error, command +from src.containers import UserList, UserSet, UserDict from src import events, channels, users def game_mode(name, minp, maxp, likelihood = 0): @@ -601,7 +602,7 @@ class RandomMode(GameMode): events.remove_listener("role_attribution", self.role_attribution) events.remove_listener("chk_win", self.lovers_chk_win) - def role_attribution(self, evt, cli, var, chk_win_conditions, villagers): + def role_attribution(self, evt, var, chk_win_conditions, villagers): lpl = len(villagers) - 1 addroles = evt.data["addroles"] for role in var.ROLE_GUIDE: @@ -629,8 +630,8 @@ class RandomMode(GameMode): mainroles[u] = role i += count - if chk_win_conditions(cli, rolemap, mainroles, end_game=False): - return self.role_attribution(evt, cli, var, chk_win_conditions, villagers) + if chk_win_conditions(rolemap, mainroles, end_game=False): + return self.role_attribution(evt, var, chk_win_conditions, villagers) evt.prevent_default = True @@ -875,20 +876,19 @@ class SleepyMode(GameMode): # disable wolfchat #self.RESTRICT_WOLFCHAT = 0x0f - self.having_nightmare = None - def startup(self): events.add_listener("dullahan_targets", self.dullahan_targets) events.add_listener("transition_night_begin", self.setup_nightmares) events.add_listener("chk_nightdone", self.prolong_night) events.add_listener("transition_day_begin", self.nightmare_kill) events.add_listener("del_player", self.happy_fun_times) - events.add_listener("rename_player", self.rename_player) self.north_cmd = command("north", "n", chan=False, pm=True, playing=True, phases=("night",))(functools.partial(self.move, "n")) self.east_cmd = command("east", "e", chan=False, pm=True, playing=True, phases=("night",))(functools.partial(self.move, "e")) self.south_cmd = command("south", "s", chan=False, pm=True, playing=True, phases=("night",))(functools.partial(self.move, "s")) self.west_cmd = command("west", "w", chan=False, pm=True, playing=True, phases=("night",))(functools.partial(self.move, "w")) + self.having_nightmare = UserList() + def teardown(self): from src import decorators events.remove_listener("dullahan_targets", self.dullahan_targets) @@ -896,7 +896,6 @@ class SleepyMode(GameMode): events.remove_listener("chk_nightdone", self.prolong_night) events.remove_listener("transition_day_begin", self.nightmare_kill) events.remove_listener("del_player", self.happy_fun_times) - events.remove_listener("rename_player", self.rename_player) def remove_command(name, command): if len(decorators.COMMANDS[name]) > 1: decorators.COMMANDS[name].remove(command) @@ -911,23 +910,18 @@ class SleepyMode(GameMode): remove_command("west", self.west_cmd) remove_command("w", self.west_cmd) - def dullahan_targets(self, evt, cli, var, dullahans, max_targets): + self.having_nightmare.clear() + + def dullahan_targets(self, evt, var, dullahans, max_targets): for dull in dullahans: - evt.data["targets"][dull] = set(var.ROLES["priest"]) + evt.data["targets"][dull] = UserSet(var.ROLES["priest"]) def setup_nightmares(self, evt, cli, var): if random.random() < 1/5: - self.having_nightmare = True with var.WARNING_LOCK: t = threading.Timer(60, self.do_nightmare, (var, random.choice(get_players()), var.NIGHT_COUNT)) t.daemon = True t.start() - else: - self.having_nightmare = None - - def rename_player(self, evt, cli, var, prefix, nick): - if self.having_nightmare == prefix: - self.having_nightmare = nick @handle_error def do_nightmare(self, var, target, night): @@ -935,7 +929,8 @@ class SleepyMode(GameMode): return if target not in get_players(): return - self.having_nightmare = target + self.having_nightmare.clear() + self.having_nightmare.append(target) target.send(messages["sleepy_nightmare_begin"]) target.send(messages["sleepy_nightmare_navigate"]) self.correct = [None, None, None] @@ -976,30 +971,30 @@ class SleepyMode(GameMode): directions = "north, south, and west" if self.step == 0: - self.having_nightmare.send(messages["sleepy_nightmare_0"].format(directions)) + self.having_nightmare[0].send(messages["sleepy_nightmare_0"].format(directions)) elif self.step == 1: - self.having_nightmare.send(messages["sleepy_nightmare_1"].format(directions)) + self.having_nightmare[0].send(messages["sleepy_nightmare_1"].format(directions)) elif self.step == 2: - self.having_nightmare.send(messages["sleepy_nightmare_2"].format(directions)) + self.having_nightmare[0].send(messages["sleepy_nightmare_2"].format(directions)) elif self.step == 3: if "correct" in self.on_path: - self.having_nightmare.send(messages["sleepy_nightmare_wake"]) - self.having_nightmare = None + self.having_nightmare[0].send(messages["sleepy_nightmare_wake"]) + del self.having_nightmare[0] elif "fake1" in self.on_path: - self.having_nightmare.send(messages["sleepy_nightmare_fake_1"]) + self.having_nightmare[0].send(messages["sleepy_nightmare_fake_1"]) self.step = 0 self.on_path = set() self.prev_direction = self.start_direction self.nightmare_step() elif "fake2" in self.on_path: - self.having_nightmare.send(messages["sleepy_nightmare_fake_2"]) + self.having_nightmare[0].send(messages["sleepy_nightmare_fake_2"]) self.step = 0 self.on_path = set() self.prev_direction = self.start_direction self.nightmare_step() def move(self, direction, var, wrapper, message): - if self.having_nightmare is not wrapper.source: + if self.having_nightmare[0] is not wrapper.source: return opposite = {"n": "s", "e": "w", "s": "n", "w": "e"} if self.prev_direction == opposite[direction]: @@ -1032,14 +1027,14 @@ class SleepyMode(GameMode): self.nightmare_step() def prolong_night(self, evt, var): - if self.having_nightmare is not None: + if self.having_nightmare: evt.data["actedcount"] = -1 def nightmare_kill(self, evt, var): - # if True, it means night ended before 1 minute - if self.having_nightmare is not None and self.having_nightmare in get_players(): - var.DYING.add(self.having_nightmare) - self.having_nightmare.send(messages["sleepy_nightmare_death"]) + if self.having_nightmare and self.having_nightmare[0] in get_players(): + var.DYING.add(self.having_nightmare[0]) + self.having_nightmare[0].send(messages["sleepy_nightmare_death"]) + del self.having_nightmare[0] def happy_fun_times(self, evt, var, user, mainrole, allroles, death_triggers): if death_triggers: @@ -1125,13 +1120,12 @@ class MaelstromMode(GameMode): def _on_join(self, var, wrapper): from src import hooks, channels role = random.choice(self.roles) - rolemap = copy.deepcopy(var.ROLES) - rolemap[role].add(wrapper.source) - mainroles = copy.deepcopy(var.MAIN_ROLES) - mainroles[wrapper.source] = role + with copy.deepcopy(var.ROLES) as rolemap, copy.deepcopy(var.MAIN_ROLES) as mainroles: + rolemap[role].add(wrapper.source) + mainroles[wrapper.source] = role - if self.chk_win_conditions(wrapper.client, rolemap, mainroles, end_game=False): - return self._on_join(var, wrapper) + if self.chk_win_conditions(rolemap, mainroles, end_game=False): + return self._on_join(var, wrapper) if not wrapper.source.is_fake or not botconfig.DEBUG_MODE: cmodes = [("+v", wrapper.source)] @@ -1173,22 +1167,23 @@ class MaelstromMode(GameMode): pl[i] = player.nick + " (cursed)" wrapper.pm("Players: " + ", ".join(pl)) - def role_attribution(self, evt, cli, var, chk_win_conditions, villagers): + def role_attribution(self, evt, var, chk_win_conditions, villagers): self.chk_win_conditions = chk_win_conditions - evt.data["addroles"] = self._role_attribution(cli, var, villagers, True) + evt.data["addroles"] = self._role_attribution(var, villagers, True) - def transition_night_begin(self, evt, cli, var): + def transition_night_begin(self, evt, var): # don't do this n1 if var.FIRST_NIGHT: return villagers = get_players() lpl = len(villagers) - addroles = self._role_attribution(cli, var, villagers, False) + addroles = self._role_attribution(var, villagers, False) # shameless copy/paste of regular role attribution for role, count in addroles.items(): selected = random.sample(villagers, count) - var.ROLES[role] = set(selected) + var.ROLES[role].clear() + var.ROLES[role].update(selected) for x in selected: villagers.remove(x) @@ -1216,7 +1211,7 @@ class MaelstromMode(GameMode): var.FINAL_ROLES[p.nick] = role # FIXME var.MAIN_ROLES[p] = role - def _role_attribution(self, cli, var, villagers, do_templates): + def _role_attribution(self, var, villagers, do_templates): lpl = len(villagers) - 1 addroles = {} for role in var.ROLE_GUIDE: @@ -1255,8 +1250,8 @@ class MaelstromMode(GameMode): mainroles[u] = role i += count - if self.chk_win_conditions(cli, rolemap, mainroles, end_game=False): - return self._role_attribution(cli, var, villagers, do_templates) + if self.chk_win_conditions(rolemap, mainroles, end_game=False): + return self._role_attribution(var, villagers, do_templates) return addroles @@ -1375,7 +1370,7 @@ class MudkipMode(GameMode): def daylight_warning(self, evt, var): evt.data["message"] = "daylight_warning_killtie" - def transition_night_begin(self, evt, cli, var): + def transition_night_begin(self, evt, var): if var.FIRST_NIGHT: # ensure shaman gets death totem on the first night var.TOTEM_CHANCES["pestilence"] = (0, 1, 0) diff --git a/src/roles/angel.py b/src/roles/angel.py index f8d3988..4258883 100644 --- a/src/roles/angel.py +++ b/src/roles/angel.py @@ -10,6 +10,7 @@ from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.functions import get_players, get_all_players from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event @@ -63,7 +64,7 @@ def pass_cmd(cli, nick, chan, rest): debuglog("{0} ({1}) PASS".format(nick, get_role(nick))) @event_listener("rename_player") -def on_rename(evt, cli, var, prefix, nick): +def on_rename(evt, var, prefix, nick): for dictvar in (GUARDED, LASTGUARDED): kvp = {} for a,b in dictvar.items(): @@ -223,7 +224,7 @@ def on_transition_day_resolve_end(evt, var, victims): evt.data["dead"].append(gangel) @event_listener("transition_night_begin") -def on_transition_night_begin(evt, cli, var): +def on_transition_night_begin(evt, 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() diff --git a/src/roles/blessed.py b/src/roles/blessed.py index 4c2f46d..2344031 100644 --- a/src/roles/blessed.py +++ b/src/roles/blessed.py @@ -10,6 +10,7 @@ from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.functions import get_players, get_all_players from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event @@ -51,7 +52,7 @@ def on_transition_night_end(evt, var): blessed.send(messages[to_send]) @event_listener("desperation_totem") -def on_desperation(evt, cli, var, votee, target, prot): +def on_desperation(evt, var, votee, target, prot): if prot == "blessing": var.ACTIVE_PROTECTIONS[target].remove("blessing") evt.prevent_default = True diff --git a/src/roles/cursed.py b/src/roles/cursed.py index 0a90adb..55c090d 100644 --- a/src/roles/cursed.py +++ b/src/roles/cursed.py @@ -9,11 +9,12 @@ import src.settings as var from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event @event_listener("see") -def on_see(evt, cli, var, nick, victim): +def on_see(evt, var, nick, victim): if users._get(victim) in var.ROLES["cursed villager"]: # FIXME evt.data["role"] = "wolf" diff --git a/src/roles/detective.py b/src/roles/detective.py index f34a713..976b282 100644 --- a/src/roles/detective.py +++ b/src/roles/detective.py @@ -7,6 +7,7 @@ from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.functions import get_players, get_all_players from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event @@ -59,7 +60,7 @@ def investigate(cli, nick, chan, rest): debuglog("{0} ({1}) PAPER DROP".format(nick, get_role(nick))) @event_listener("rename_player") -def on_rename(evt, cli, var, prefix, nick): +def on_rename(evt, var, prefix, nick): if prefix in INVESTIGATED: INVESTIGATED.remove(prefix) INVESTIGATED.add(nick) @@ -96,7 +97,7 @@ def on_transition_night_end(evt, var): dttv.send(messages[to_send].format(warning), "Players: " + ", ".join(p.nick for p in pl), sep="\n") @event_listener("transition_night_begin") -def on_transition_night_begin(evt, cli, var): +def on_transition_night_begin(evt, var): INVESTIGATED.clear() @event_listener("reset") diff --git a/src/roles/doomsayer.py b/src/roles/doomsayer.py index 1dd1d6c..be84981 100644 --- a/src/roles/doomsayer.py +++ b/src/roles/doomsayer.py @@ -6,6 +6,7 @@ from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.functions import get_players, get_all_players from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event @@ -55,7 +56,7 @@ def see(cli, nick, chan, rest): SEEN.add(nick) @event_listener("rename_player") -def on_rename(evt, cli, var, prefix, nick): +def on_rename(evt, var, prefix, nick): if prefix in SEEN: SEEN.remove(prefix) SEEN.add(nick) @@ -97,7 +98,7 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers): del dictvar[k] @event_listener("doctor_immunize") -def on_doctor_immunize(evt, cli, var, doctor, target): +def on_doctor_immunize(evt, var, doctor, target): if target in SICK.values(): for n, v in list(SICK.items()): if v == target: @@ -161,7 +162,7 @@ def on_begin_day(evt, var): LYCANS.clear() @event_listener("transition_night_begin") -def on_transition_night_begin(evt, cli, var): +def on_transition_night_begin(evt, var): SICK.clear() @event_listener("reset") diff --git a/src/roles/dullahan.py b/src/roles/dullahan.py index 1adbbec..101b0d6 100644 --- a/src/roles/dullahan.py +++ b/src/roles/dullahan.py @@ -7,12 +7,13 @@ from src.utilities import * from src.functions import get_players, get_all_players, get_target, get_main_role, get_reveal_role from src import users, channels, debuglog, errlog, plog from src.decorators import command, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event import botconfig -KILLS = {} # type: Dict[users.User, users.User] -TARGETS = {} # type: Dict[users.User, Set[users.User]] +KILLS = UserDict() # type: Dict[users.User, users.User] +TARGETS = UserDict() # type: Dict[users.User, Set[users.User]] @command("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("dullahan",)) def dullahan_kill(var, wrapper, message): @@ -62,66 +63,50 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers): del KILLS[h] if death_triggers and "dullahan" in allroles: pl = evt.data["pl"] - targets = TARGETS[user].intersection(pl) - if targets: - target = random.choice(list(targets)) - prots = deque(var.ACTIVE_PROTECTIONS[target.nick]) - aevt = Event("assassinate", {"pl": evt.data["pl"], "target": target}, - del_player=evt.params.del_player, - deadlist=evt.params.deadlist, - original=evt.params.original, - refresh_pl=evt.params.refresh_pl, - message_prefix="dullahan_die_", - source="dullahan", - killer=user, - killer_mainrole=mainrole, - killer_allroles=allroles, - prots=prots) - while len(prots) > 0: - # an event can read the current active protection and cancel or redirect the assassination - # if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS - # so that it cannot be used again (if the protection is meant to be usable once-only) - if not aevt.dispatch(var, user, target, prots[0]): - evt.data["pl"] = aevt.data["pl"] - if target is not aevt.data["target"]: - target = aevt.data["target"] - prots = deque(var.ACTIVE_PROTECTIONS[target.nick]) - aevt.params.prots = prots - continue - return - prots.popleft() + with TARGETS[user].intersection(pl) as targets: + if targets: + target = random.choice(list(targets)) + prots = deque(var.ACTIVE_PROTECTIONS[target.nick]) + aevt = Event("assassinate", {"pl": evt.data["pl"], "target": target}, + del_player=evt.params.del_player, + deadlist=evt.params.deadlist, + original=evt.params.original, + refresh_pl=evt.params.refresh_pl, + message_prefix="dullahan_die_", + source="dullahan", + killer=user, + killer_mainrole=mainrole, + killer_allroles=allroles, + prots=prots) + while len(prots) > 0: + # an event can read the current active protection and cancel or redirect the assassination + # if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS + # so that it cannot be used again (if the protection is meant to be usable once-only) + if not aevt.dispatch(var, user, target, prots[0]): + evt.data["pl"] = aevt.data["pl"] + if target is not aevt.data["target"]: + target = aevt.data["target"] + prots = deque(var.ACTIVE_PROTECTIONS[target.nick]) + aevt.params.prots = prots + continue + return + prots.popleft() - if var.ROLE_REVEAL in ("on", "team"): - role = get_reveal_role(target) - an = "n" if role.startswith(("a", "e", "i", "o", "u")) else "" - channels.Main.send(messages["dullahan_die_success"].format(user, target, an, role)) - else: - channels.Main.send(messages["dullahan_die_success_noreveal"].format(user, target)) - debuglog("{0} (dullahan) DULLAHAN ASSASSINATE: {1} ({2})".format(user, target, get_main_role(target))) - evt.params.del_player(target, end_game=False, killer_role="dullahan", deadlist=evt.params.deadlist, original=evt.params.original, ismain=False) - evt.data["pl"] = evt.params.refresh_pl(pl) + if var.ROLE_REVEAL in ("on", "team"): + role = get_reveal_role(target) + an = "n" if role.startswith(("a", "e", "i", "o", "u")) else "" + channels.Main.send(messages["dullahan_die_success"].format(user, target, an, role)) + else: + channels.Main.send(messages["dullahan_die_success_noreveal"].format(user, target)) + debuglog("{0} (dullahan) DULLAHAN ASSASSINATE: {1} ({2})".format(user, target, get_main_role(target))) + evt.params.del_player(target, end_game=False, killer_role="dullahan", deadlist=evt.params.deadlist, original=evt.params.original, ismain=False) + evt.data["pl"] = evt.params.refresh_pl(pl) @event_listener("night_acted") def on_acted(evt, var, user, actor): if user in KILLS: evt.data["acted"] = True -@event_listener("swap_player") -def on_swap(evt, var, old_user, user): - if old_user in KILLS: - KILLS[user] = KILLS.pop(old_user) - if old_user in TARGETS: - TARGETS[user] = TARGETS.pop(old_user) - - for dullahan, target in KILLS.items(): - if target is old_user: - KILLS[dullahan] = user - - for dullahan, targets in TARGETS.items(): - if old_user in targets: - targets.remove(old_user) - targets.add(user) - @event_listener("get_special") def on_get_special(evt, var): evt.data["special"].update(get_players(("dullahan",))) @@ -172,19 +157,17 @@ def on_transition_night_end(evt, var): dullahan.send(messages[to_send], t + ", ".join(t.nick for t in targets), sep="\n") @event_listener("role_assignment") -def on_role_assignment(evt, cli, var, gamemode, pl, restart): +def on_role_assignment(evt, var, gamemode, pl): # assign random targets to dullahan to kill if var.ROLES["dullahan"]: max_targets = math.ceil(8.1 * math.log(len(pl), 10) - 5) for dull in var.ROLES["dullahan"]: - TARGETS[dull] = set() + TARGETS[dull] = UserSet() dull_targets = Event("dullahan_targets", {"targets": TARGETS}) # support sleepy - dull_targets.dispatch(cli, var, var.ROLES["dullahan"], max_targets) - - players = [users._get(x) for x in pl] # FIXME + dull_targets.dispatch(var, var.ROLES["dullahan"], max_targets) for dull, ts in TARGETS.items(): - ps = players[:] + ps = pl[:] ps.remove(dull) while len(ts) < max_targets: target = random.choice(ps) diff --git a/src/roles/fallenangel.py b/src/roles/fallenangel.py index 420d897..315d3a8 100644 --- a/src/roles/fallenangel.py +++ b/src/roles/fallenangel.py @@ -9,6 +9,7 @@ import src.settings as var from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.functions import get_players, get_all_players from src.messages import messages from src.events import Event diff --git a/src/roles/harlot.py b/src/roles/harlot.py index 43de95f..cba82c2 100644 --- a/src/roles/harlot.py +++ b/src/roles/harlot.py @@ -10,11 +10,12 @@ from src.utilities import * from src import channels, users, debuglog, errlog, plog from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target from src.decorators import command, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event -VISITED = {} # type: Dict[users.User, users.User] -PASSED = set() # type: Set[users.User] +VISITED = UserDict() # type: Dict[users.User, users.User] +PASSED = UserSet() # type: Set[users.User] @command("visit", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("harlot",)) def hvisit(var, wrapper, message): @@ -134,18 +135,6 @@ def on_begin_day(evt, var): VISITED.clear() PASSED.clear() -@event_listener("swap_player") -def on_swap(evt, var, old_user, user): - for harlot, target in set(VISITED.items()): - if target is old_user: - VISITED[harlot] = user - if harlot is old_user: - VISITED[user] = VISITED.pop(harlot) - - if old_user in PASSED: - PASSED.remove(old_user) - PASSED.add(user) - @event_listener("get_special") def on_get_special(evt, var): evt.data["special"].update(get_players(("harlot",))) diff --git a/src/roles/hunter.py b/src/roles/hunter.py index 335d64a..e660d59 100644 --- a/src/roles/hunter.py +++ b/src/roles/hunter.py @@ -7,12 +7,13 @@ from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.functions import get_players, get_all_players, get_target, get_main_role from src.decorators import command, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event -KILLS = {} # type: Dict[users.User, users.User] -HUNTERS = set() -PASSED = set() +KILLS = UserDict() # type: Dict[users.User, users.User] +HUNTERS = UserSet() +PASSED = UserSet() @command("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("hunter",)) def hunter_kill(var, wrapper, message): @@ -74,20 +75,6 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers): h.send(messages["hunter_discard"]) del KILLS[h] -@event_listener("swap_player") -def on_swap(evt, var, old_user, user): - for a, b in list(KILLS.items()): - if a is old_user: - KILLS[user] = KILLS.pop(old_user) - if b is old_user: - KILLS[user] = KILLS.pop(old_user) - if old_user in HUNTERS: - HUNTERS.discard(old_user) - HUNTERS.add(user) - if old_user in PASSED: - PASSED.discard(old_user) - PASSED.add(user) - @event_listener("night_acted") def on_acted(evt, var, user, actor): if user in KILLS: diff --git a/src/roles/investigator.py b/src/roles/investigator.py index 46c5558..4b886c9 100644 --- a/src/roles/investigator.py +++ b/src/roles/investigator.py @@ -10,10 +10,11 @@ from src.utilities import * from src import channels, users, debuglog, errlog, plog from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target from src.decorators import command, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event -INVESTIGATED = set() +INVESTIGATED = UserSet() @command("id", chan=False, pm=True, playing=True, silenced=True, phases=("day",), roles=("investigator",)) def investigate(var, wrapper, message): @@ -97,12 +98,6 @@ def investigate(var, wrapper, message): wrapper.source, target1, get_main_role(target1), target2, get_main_role(target2), "same" if same else "different")) -@event_listener("swap_player") -def on_swap(evt, var, old_user, user): - if old_user in INVESTIGATED: - INVESTIGATED.discard(old_user) - INVESTIGATED.add(user) - @event_listener("del_player") def on_del_player(evt, var, user, mainrole, allroles, death_triggers): INVESTIGATED.discard(user) @@ -116,7 +111,7 @@ def on_exchange(evt, var, actor, target, actor_role, target_role): if actor_role == "investigator" and target_role != "investigator": INVESTIGATED.discard(actor) elif target_role == "investigator" and actor_role != "investigator": - INVESTIGATED.discard(targe) + INVESTIGATED.discard(target) @event_listener("transition_night_end", priority=2) def on_transition_night_end(evt, var): @@ -131,7 +126,7 @@ def on_transition_night_end(evt, var): inv.send(messages[to_send], "Players: " + ", ".join(p.nick for p in pl), sep="\n") @event_listener("transition_night_begin") -def on_transition_night_begin(evt, cli, var): +def on_transition_night_begin(evt, var): INVESTIGATED.clear() @event_listener("reset") diff --git a/src/roles/madscientist.py b/src/roles/madscientist.py index 29f6902..81daec1 100644 --- a/src/roles/madscientist.py +++ b/src/roles/madscientist.py @@ -10,6 +10,7 @@ from src.utilities import * from src import channels, users, debuglog, errlog, plog from src.functions import get_players, get_all_players, get_main_role, get_reveal_role from src.decorators import command, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event diff --git a/src/roles/mayor.py b/src/roles/mayor.py index 2362e5f..2725cfe 100644 --- a/src/roles/mayor.py +++ b/src/roles/mayor.py @@ -9,13 +9,14 @@ import src.settings as var from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event REVEALED_MAYORS = set() @event_listener("rename_player") -def on_rename_player(evt, cli, var, prefix, nick): +def on_rename_player(evt, var, prefix, nick): if prefix in REVEALED_MAYORS: REVEALED_MAYORS.remove(prefix) REVEALED_MAYORS.add(nick) diff --git a/src/roles/mystic.py b/src/roles/mystic.py index 28a353c..d87d867 100644 --- a/src/roles/mystic.py +++ b/src/roles/mystic.py @@ -6,6 +6,7 @@ from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.functions import get_players, get_all_players from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event diff --git a/src/roles/piper.py b/src/roles/piper.py index 57a5469..2aa2042 100644 --- a/src/roles/piper.py +++ b/src/roles/piper.py @@ -10,11 +10,12 @@ from src.utilities import * from src.functions import get_players, get_all_players, get_target, get_main_role from src import channels, users, debuglog, errlog, plog from src.decorators import command, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event -TOBECHARMED = {} # type: Dict[users.User, Set[users.User]] -CHARMED = set() # type: Set[users.User] +TOBECHARMED = UserDict() # type: Dict[users.User, Set[users.User]] +CHARMED = UserSet() # type: Set[users.User] @command("charm", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("piper",)) def charm(var, wrapper, message): @@ -69,8 +70,12 @@ def charm(var, wrapper, message): wrapper.send(messages["target_already_charmed"].format(orig2)) return - TOBECHARMED[wrapper.source] = {target1, target2} - TOBECHARMED[wrapper.source].discard(None) + if wrapper.source in TOBECHARMED: + TOBECHARMED[wrapper.source].clear() + else: + TOBECHARMED[wrapper.source] = UserSet() + + TOBECHARMED[wrapper.source].update({target1, target2} - {None}) if orig2: debuglog("{0} (piper) CHARM {1} ({2}) && {3} ({4})".format(wrapper.source, @@ -109,7 +114,9 @@ def on_player_win(evt, var, player, mainrole, winner, survived): @event_listener("del_player") def on_del_player(evt, var, player, mainrole, allroles, death_triggers): CHARMED.discard(player) - TOBECHARMED.pop(player, None) + x = TOBECHARMED.pop(player, None) + if x is not None: + x.clear() @event_listener("transition_day_begin") def on_transition_day_begin(evt, var): @@ -196,18 +203,4 @@ def on_revealroles(evt, var, wrapper): if CHARMED: evt.data["output"].append("\u0002charmed players\u0002: {0}".format(", ".join(p.nick for p in CHARMED))) -@event_listener("swap_player") -def on_swap_player(evt, var, old, new): - if old in CHARMED: - CHARMED.remove(old) - CHARMED.add(new) - - if old in TOBECHARMED: - TOBECHARMED[new] = TOBECHARMED.pop(old) - - for s in TOBECHARMED.values(): - if old in s: - s.remove(old) - s.add(new) - # vim: set sw=4 expandtab: diff --git a/src/roles/seer.py b/src/roles/seer.py index 5bf4553..a94ca37 100644 --- a/src/roles/seer.py +++ b/src/roles/seer.py @@ -5,6 +5,7 @@ import src.settings as var from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.functions import get_players, get_all_players, get_main_role from src.messages import messages from src.events import Event @@ -47,7 +48,7 @@ def see(cli, nick, chan, rest): victimrole = "villager" evt = Event("see", {"role": victimrole}) - evt.dispatch(cli, var, nick, victim) + evt.dispatch(var, nick, victim) victimrole = evt.data["role"] else: if victimrole == "amnesiac": @@ -78,7 +79,7 @@ def see(cli, nick, chan, rest): SEEN.add(nick) @event_listener("rename_player") -def on_rename(evt, cli, var, prefix, nick): +def on_rename(evt, var, prefix, nick): if prefix in SEEN: SEEN.remove(prefix) SEEN.add(nick) diff --git a/src/roles/shaman.py b/src/roles/shaman.py index e40f1e5..c77720f 100644 --- a/src/roles/shaman.py +++ b/src/roles/shaman.py @@ -9,6 +9,7 @@ from src.utilities import * from src import debuglog, errlog, plog, users, channels from src.functions import get_players, get_all_players, get_main_role, get_reveal_role from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event @@ -65,7 +66,7 @@ def totem(cli, nick, chan, rest, prefix="You"): # XXX: The transition_day_begin return original_victim = victim - role = get_role(nick) # FIXME: this is bad, check if nick is in var.ROLES[thingy] instead once split + role = get_role(nick) # FIXME: this is bad, check if user is in var.ROLES[thingy] instead once converted totem = "" if role != "crazed shaman": totem = " of " + TOTEMS[nick] @@ -92,7 +93,7 @@ def totem(cli, nick, chan, rest, prefix="You"): # XXX: The transition_day_begin debuglog("{0} ({1}) TOTEM: {2} ({3})".format(nick, role, victim, TOTEMS[nick])) @event_listener("rename_player") -def on_rename(evt, cli, var, prefix, nick): +def on_rename(evt, var, prefix, nick): if prefix in TOTEMS: TOTEMS[nick] = TOTEMS.pop(prefix) @@ -130,7 +131,7 @@ def on_rename(evt, cli, var, prefix, nick): setvar.add(nick) @event_listener("see", priority=10) -def on_see(evt, cli, var, nick, victim): +def on_see(evt, var, nick, victim): if (victim in DECEIT) ^ (nick in DECEIT): if evt.data["role"] in var.SEEN_WOLF and evt.data["role"] not in var.SEEN_DEFAULT: evt.data["role"] = "villager" @@ -282,7 +283,7 @@ def on_chk_decision_lynch5(evt, cli, var, voters): # 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]): + if not desp_evt.dispatch(var, votee, target, prots[0]): return prots.popleft() if var.ROLE_REVEAL in ("on", "team"): @@ -627,26 +628,6 @@ def on_reset(evt, var): MISDIRECTION.clear() DECEIT.clear() -@event_listener("frole_role") -def on_frole_role(evt, cli, var, who, role, oldrole, args): - if role in var.TOTEM_ORDER: - if len(args) == 2: - TOTEMS[who] = args[1] - else: - max_totems = defaultdict(int) - for ix in range(len(var.TOTEM_ORDER)): - for c in var.TOTEM_CHANCES.values(): - max_totems[var.TOTEM_ORDER[ix]] += c[ix] - for shaman in list_players(var.TOTEM_ORDER): - indx = var.TOTEM_ORDER.index(role) - target = 0 - rand = random.random() * max_totems[var.TOTEM_ORDER[indx]] - for t in var.TOTEM_CHANCES.keys(): - target += var.TOTEM_CHANCES[t][indx] - if rand <= target: - TOTEMS[shaman] = t - break - @event_listener("get_role_metadata") def on_get_role_metadata(evt, var, kind): if kind == "night_kills": diff --git a/src/roles/skel.py b/src/roles/skel.py index 6921f43..174859d 100644 --- a/src/roles/skel.py +++ b/src/roles/skel.py @@ -10,11 +10,15 @@ from src.utilities import * from src import channels, users, debuglog, errlog, plog from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target from src.decorators import command, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event # Skeleton file for new roles. Not all events are represented, only the most common ones. +# Instead of using list, set or dict, please use UserList, UserSet or UserDict respectively +# Please refer to the notes in src/containers.py for proper use + # Add to evt.data["actedcount"] and evt.data["nightroles"] if this role can act during night # nightroles lists all Users who have this role and are capable of acting tonight # actedcount should be added to (via +=) with everyone who has this role and has already acted @@ -60,11 +64,6 @@ def on_del_player(evt, var, player, mainrole, allroles, death_triggers): def on_reset(evt, var): pass -# Swap out a user with a different one. Update all game state to use the new User. -@event_listener("swap_player") -def on_swap_player(evt, var, old, new): - pass - # Gets metadata about this role; kind will be a str with one of the following values: # night_kills: Add metadata about any deaths this role can cause at night which use the standard # death message (i.e. do not have a custom death message). Set the data as follows: diff --git a/src/roles/succubus.py b/src/roles/succubus.py index 63069b1..48d3eef 100644 --- a/src/roles/succubus.py +++ b/src/roles/succubus.py @@ -10,13 +10,14 @@ from src.utilities import * from src import channels, users, debuglog, errlog, plog from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target from src.decorators import command, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event -ENTRANCED = set() # type: Set[users.User] -ENTRANCED_DYING = set() # type: Set[users.User] -VISITED = {} # type: Dict[users.User, users.User] -PASSED = set() # type: Set[users.User] +ENTRANCED = UserSet() # type: Set[users.User] +ENTRANCED_DYING = UserSet() # type: Set[users.User] +VISITED = UserDict() # type: Dict[users.User, users.User] +PASSED = UserSet() # type: Set[users.User] ALL_SUCC_IDLE = True @command("visit", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("succubus",)) @@ -319,25 +320,6 @@ def on_vg_kill(evt, var, ghost, target): if ghost in ENTRANCED: evt.data["pl"] -= get_all_players(("succubus",)) -@event_listener("swap_player") -def on_swap(evt, var, old_user, user): - if old_user in ENTRANCED: - ENTRANCED.remove(old_user) - ENTRANCED.add(user) - if old_user in ENTRANCED_DYING: - ENTRANCED_DYING.remove(old_user) - ENTRANCED_DYING.add(user) - - for succubus, target in set(VISITED.items()): - if old_user is succubus: - VISITED[user] = VISITED.pop(succubus) - if old_user is target: - VISITED[succubus] = user - - if old_user in PASSED: - PASSED.remove(old_user) - PASSED.add(user) - @event_listener("reset") def on_reset(evt, var): global ALL_SUCC_IDLE diff --git a/src/roles/traitor.py b/src/roles/traitor.py index 976ae62..da1af87 100644 --- a/src/roles/traitor.py +++ b/src/roles/traitor.py @@ -9,6 +9,7 @@ import src.settings as var from src.utilities import * from src import debuglog, errlog, plog, users, channels from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event diff --git a/src/roles/vengefulghost.py b/src/roles/vengefulghost.py index 90cfcf3..6929a8a 100644 --- a/src/roles/vengefulghost.py +++ b/src/roles/vengefulghost.py @@ -7,14 +7,15 @@ from src.utilities import * from src import channels, users, debuglog, errlog, plog from src.functions import get_players, get_target, get_main_role from src.decorators import command, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event -KILLS = {} # type: Dict[users.User, users.User] -GHOSTS = {} # type: Dict[users.User, str] +KILLS = UserDict() # type: Dict[users.User, users.User] +GHOSTS = UserDict() # type: Dict[users.User, str] # temporary holding variable, only non-empty during transition_day -drivenoff = {} # type: Dict[users.User, str] +drivenoff = UserDict() # type: Dict[users.User, str] @command("kill", chan=False, pm=True, playing=False, silenced=True, phases=("night",), users=GHOSTS) def vg_kill(var, wrapper, message): diff --git a/src/roles/vigilante.py b/src/roles/vigilante.py index 3723eb1..adeb315 100644 --- a/src/roles/vigilante.py +++ b/src/roles/vigilante.py @@ -7,11 +7,12 @@ from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.functions import get_players, get_all_players, get_main_role, get_target from src.decorators import command, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event -KILLS = {} # type: Dict[users.User, users.User] -PASSED = set() # type: Set[users.User] +KILLS = UserDict() # type: Dict[users.User, users.User] +PASSED = UserSet() # type: Set[users.User] @command("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("vigilante",)) def vigilante_kill(var, wrapper, message): @@ -61,18 +62,6 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers): vigilante.send(messages["hunter_discard"]) del KILLS[vigilante] -@event_listener("swap_player") -def on_swap(evt, var, old_user, user): - for vigilante, target in set(KILLS.items()): - if vigilante is old_user: - KILLS[user] = KILLS.pop(vigilante) - if target is old_user: - KILLS[vigilante] = user - - if old_user in PASSED: - PASSED.remove(old_user) - PASSED.add(user) - @event_listener("night_acted") def on_acted(evt, var, target, spy): if target in KILLS: diff --git a/src/roles/villager.py b/src/roles/villager.py index fc277bc..5f43239 100644 --- a/src/roles/villager.py +++ b/src/roles/villager.py @@ -3,6 +3,7 @@ from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.functions import get_players from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event diff --git a/src/roles/wildchild.py b/src/roles/wildchild.py index dcdec29..45bb18f 100644 --- a/src/roles/wildchild.py +++ b/src/roles/wildchild.py @@ -6,6 +6,7 @@ from src.utilities import * from src import users, channels, debuglog, errlog, plog from src.functions import get_players, get_all_players, get_main_role from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event @@ -35,12 +36,12 @@ def choose_idol(cli, nick, chan, rest): debuglog("{0} (wild child) IDOLIZE: {1} ({2})".format(nick, victim, get_role(victim))) @event_listener("see") -def on_see(evt, cli, var, seer, victim): +def on_see(evt, var, seer, victim): if victim in WILD_CHILDREN: evt.data["role"] = "wild child" @event_listener("rename_player") -def on_rename(evt, cli, var, prefix, nick): +def on_rename(evt, var, prefix, nick): if prefix in WILD_CHILDREN: WILD_CHILDREN.remove(prefix) WILD_CHILDREN.add(nick) diff --git a/src/roles/wolf.py b/src/roles/wolf.py index ffaf52f..369d38f 100644 --- a/src/roles/wolf.py +++ b/src/roles/wolf.py @@ -7,6 +7,7 @@ from src.utilities import * from src.functions import get_players, get_all_players, get_main_role, get_all_roles from src import debuglog, errlog, plog, users, channels from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event @@ -127,7 +128,7 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers): del KILLS[a] @event_listener("rename_player") -def on_rename(evt, cli, var, prefix, nick): +def on_rename(evt, var, prefix, nick): kvp = [] for a,b in KILLS.items(): nl = [] diff --git a/src/roles/wolfcub.py b/src/roles/wolfcub.py index d75f114..d905df6 100644 --- a/src/roles/wolfcub.py +++ b/src/roles/wolfcub.py @@ -7,6 +7,7 @@ from src.utilities import * from src.functions import get_players from src import debuglog, errlog, plog, users, channels from src.decorators import cmd, event_listener +from src.containers import UserList, UserSet, UserDict from src.messages import messages from src.events import Event from src.roles import wolf diff --git a/src/settings.py b/src/settings.py index 585e2e2..0ec0029 100644 --- a/src/settings.py +++ b/src/settings.py @@ -3,8 +3,6 @@ import re import threading from collections import defaultdict, OrderedDict -import botconfig - LANGUAGE = 'en' MINIMUM_WAIT = 60 diff --git a/src/users.py b/src/users.py index 1495506..5331a5a 100644 --- a/src/users.py +++ b/src/users.py @@ -194,17 +194,10 @@ def _reset(evt, var): _users.discard(user) _ghosts.clear() -def _swap_player(evt, var, old_user, user): - """Mark the user as no longer being a ghost, if they are one.""" - _ghosts.discard(old_user) - if not old_user.channels: - _users.discard(old_user) - # Can't use @event_listener decorator since src/decorators.py imports us # (meaning decorator isn't defined at the point in time we are run) events.add_listener("cleanup_user", _cleanup_user) events.add_listener("reset", _reset) -events.add_listener("swap_player", _swap_player, priority=1) class User(IRCContext): @@ -220,6 +213,10 @@ class User(IRCContext): self.account = account self.channels = {} self.timestamp = time.time() + self.sets = [] + self.lists = [] + self.dict_keys = [] + self.dict_values = [] if Bot is not None and Bot.nick == nick and {Bot.ident, Bot.host, Bot.realname, Bot.account} == {None}: self = Bot @@ -314,6 +311,35 @@ class User(IRCContext): def __deepcopy__(self, memo): return self + def swap(self, new): + """Swap yourself out with the new user everywhere.""" + if self is new: + return # as far as the caller is aware, we've swapped + + _ghosts.discard(self) + if not self.channels: + _users.discard(self) # Goodbye, my old friend + + for l in self.lists[:]: + while self in l: + l[l.index(self)] = new + + for s in self.sets[:]: + s.remove(self) + s.add(new) + + for dk in self.dict_keys[:]: + dk[new] = dk.pop(self) + + for dv in self.dict_values[:]: + for key in dv: + if dv[key] is self: + dv[key] = new + + # It is the containers' reponsibility to properly remove themself from the users + # So if any list is non-empty, something went terribly wrong + assert not self.lists + self.sets + self.dict_keys + self.dict_values + def lower(self): temp = type(self)(self.client, lower(self.nick), lower(self.ident), lower(self.host, casemapping="ascii"), lower(self.realname), lower(self.account)) if temp is not self: # If everything is already lowercase, we'll get back the same instance @@ -613,7 +639,7 @@ class FakeUser(User): @rawnick.setter def rawnick(self, rawnick): - self.nick = parse_rawnick_as_dict(rawnick)["nick"] + raise ValueError("may not change the raw nick of a fake user") class BotUser(User): # TODO: change all the 'if x is Bot' for 'if isinstance(x, BotUser)' diff --git a/src/wolfgame.py b/src/wolfgame.py index ea4e48d..3ff239b 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -49,6 +49,7 @@ import src.settings as var from src.utilities import * from src import db, events, dispatcher, channels, users, hooks, logger, debuglog, errlog, plog from src.decorators import command, cmd, hook, handle_error, event_listener, COMMANDS +from src.containers import UserList, UserSet, UserDict from src.functions import get_players, get_all_players, get_participants, get_main_role, get_all_roles, get_reveal_role from src.messages import messages from src.warnings import * @@ -72,8 +73,8 @@ var.LAST_GOAT = {} var.USERS = {} var.ADMIN_PINGING = False -var.ORIGINAL_ROLES = {} # type: Dict[str, Set[users.User]] -var.DCED_LOSERS = set() # type: Set[users.User] +var.ORIGINAL_ROLES = UserDict() # type: Dict[str, Set[users.User]] +var.DCED_LOSERS = UserSet() # type: Set[users.User] var.PLAYERS = {} var.DCED_PLAYERS = {} var.ADMIN_TO_PING = None @@ -82,6 +83,16 @@ var.PINGING_IFS = False var.TIMERS = {} var.OLD_MODES = defaultdict(set) +var.ROLES = UserDict() # type: Dict[str, Set[users.User]] +var.MAIN_ROLES = UserDict() # type: Dict[users.User, str] +var.ALL_PLAYERS = UserList() + +var.DYING = UserSet() +var.DEADCHAT_PLAYERS = UserSet() + +var.SPECTATING_WOLFCHAT = UserSet() +var.SPECTATING_DEADCHAT = UserSet() + var.ORIGINAL_SETTINGS = {} var.CURRENT_GAMEMODE = var.GAME_MODES["default"][0]() @@ -297,11 +308,9 @@ def reset_modes_timers(var): def reset(): var.PHASE = "none" # "join", "day", or "night" var.GAME_ID = 0 + var.ALL_PLAYERS.clear() var.RESTART_TRIES = 0 var.DEAD = set() - var.ROLES = {"person" : set()} # type: Dict[str, Set[users.User]] - var.MAIN_ROLES = {} # type: Dict[users.User, str] - var.ALL_PLAYERS = [] var.JOINED_THIS_GAME = set() # keeps track of who already joined this game at least once (hostmasks) var.JOINED_THIS_GAME_ACCS = set() # same, except accounts var.PINGED_ALREADY = set() @@ -321,8 +330,13 @@ def reset(): var.DCED_PLAYERS.clear() var.DISCONNECTED.clear() var.DCED_LOSERS.clear() - var.SPECTATING_WOLFCHAT = set() - var.SPECTATING_DEADCHAT = set() + var.SPECTATING_WOLFCHAT.clear() + var.SPECTATING_DEADCHAT.clear() + + var.ROLES.clear() + var.ORIGINAL_ROLES.clear() + var.ROLES["person"] = UserSet() + var.MAIN_ROLES.clear() evt = Event("reset", {}) evt.dispatch(var) @@ -582,9 +596,8 @@ def replace(var, wrapper, message): return if users.equals(target.account, wrapper.source.account) and target is not wrapper.source: - evt = Event("swap_player", {}) - evt.dispatch(var, target, wrapper.source) rename_player(var, wrapper.source, target.nick) + target.swap(wrapper.source) if var.PHASE in var.GAME_PHASES: return_to_village(var, target, show_message=False) @@ -594,19 +607,6 @@ def replace(var, wrapper, message): channels.Main.send(messages["player_swap"].format(wrapper.source, target)) myrole.caller(wrapper.source.client, wrapper.source.nick, wrapper.target.name, "") # FIXME: Old API -@event_listener("swap_player", priority=0) -def swap_player(evt, var, old_user, user): - var.ALL_PLAYERS[var.ALL_PLAYERS.index(old_user)] = user - var.MAIN_ROLES[user] = var.MAIN_ROLES.pop(old_user) - for role, players in var.ROLES.items(): - if old_user in players: - players.remove(old_user) - players.add(user) - for role, players in var.ORIGINAL_ROLES.items(): - if old_user in players: - players.remove(old_user) - players.add(user) - @command("pingif", "pingme", "pingat", "pingpref", pm=True) def altpinger(var, wrapper, message): @@ -2006,41 +2006,41 @@ def stop_game(winner="", abort=False, additional_winners=None, log=True): roles_msg = [] origroles = {} # user-based list of original roles - rolelist = copy.deepcopy(var.ORIGINAL_ROLES) - for role, playerlist in var.ORIGINAL_ROLES.items(): - if role in var.TEMPLATE_RESTRICTIONS.keys(): - continue - for p in playerlist: - final = var.FINAL_ROLES.get(p.nick, role) - if role != final: - origroles[p] = role - rolelist[role].remove(p) - rolelist[final].add(p) - prev = False - for role in role_order(): - if len(rolelist[role]) == 0: - continue - playersformatted = [] - for p in rolelist[role]: - if p in origroles and role not in var.TEMPLATE_RESTRICTIONS.keys(): - playersformatted.append("\u0002{0}\u0002 ({1}{2})".format(p, - "" if prev else "was ", origroles[p])) - prev = True - elif role == "amnesiac": - playersformatted.append("\u0002{0}\u0002 (would be {1})".format(p, - var.AMNESIAC_ROLES[p.nick])) + with copy.deepcopy(var.ORIGINAL_ROLES) as rolelist: + for role, playerlist in var.ORIGINAL_ROLES.items(): + if role in var.TEMPLATE_RESTRICTIONS.keys(): + continue + for p in playerlist: + final = var.FINAL_ROLES.get(p.nick, role) + if role != final: + origroles[p] = role + rolelist[role].remove(p) + rolelist[final].add(p) + prev = False + for role in role_order(): + if len(rolelist[role]) == 0: + continue + playersformatted = [] + for p in rolelist[role]: + if p in origroles and role not in var.TEMPLATE_RESTRICTIONS.keys(): + playersformatted.append("\u0002{0}\u0002 ({1}{2})".format(p, + "" if prev else "was ", origroles[p])) + prev = True + elif role == "amnesiac": + playersformatted.append("\u0002{0}\u0002 (would be {1})".format(p, + var.AMNESIAC_ROLES[p.nick])) + else: + playersformatted.append("\u0002{0}\u0002".format(p)) + if len(rolelist[role]) == 2: + msg = "The {1} were {0[0]} and {0[1]}." + roles_msg.append(msg.format(playersformatted, plural(role))) + elif len(rolelist[role]) == 1: + roles_msg.append("The {1} was {0[0]}.".format(playersformatted, role)) else: - playersformatted.append("\u0002{0}\u0002".format(p)) - if len(rolelist[role]) == 2: - msg = "The {1} were {0[0]} and {0[1]}." - roles_msg.append(msg.format(playersformatted, plural(role))) - elif len(rolelist[role]) == 1: - roles_msg.append("The {1} was {0[0]}.".format(playersformatted, role)) - else: - msg = "The {2} were {0}, and {1}." - roles_msg.append(msg.format(", ".join(playersformatted[0:-1]), - playersformatted[-1], - plural(role))) + msg = "The {2} were {0}, and {1}." + roles_msg.append(msg.format(", ".join(playersformatted[0:-1]), + playersformatted[-1], + plural(role))) message = "" count = 0 if not abort: @@ -2919,8 +2919,7 @@ def return_to_village(var, target, *, show_message, new_user=None): if new_user is not target: # different users, perform a swap. This will clean up disconnected users. - evt = Event("swap_player", {}) - evt.dispatch(var, target, new_user) + target.swap(new_user) if target.nick != new_user.nick: # have a nickchange, update tracking vars @@ -2950,7 +2949,7 @@ def rename_player(var, user, prefix): nick = user.nick event = Event("rename_player", {}) - event.dispatch(user.client, var, prefix, nick) # FIXME: Need to update all the callbacks + event.dispatch(var, prefix, nick) if user in var.ALL_PLAYERS: if var.PHASE in var.GAME_PHASES: @@ -3247,7 +3246,7 @@ def begin_day(cli): var.LUCKY = set() var.DISEASED = set() var.MISDIRECTED = set() - var.DYING = set() + var.DYING.clear() var.LAST_GOAT.clear() msg = messages["villagers_lynch"].format(botconfig.CMD_CHAR, len(list_players()) // 2 + 1) cli.msg(chan, msg) @@ -4488,7 +4487,7 @@ def immunize(cli, nick, chan, rest): if check_exchange(cli, nick, victim): return evt = Event("doctor_immunize", {"success": True, "message": "villager_immunized"}) - if evt.dispatch(cli, var, nick, victim): + if evt.dispatch(var, nick, victim): pm(cli, nick, messages["doctor_success"].format(victim)) lycan = False if victim in var.DISEASED: @@ -4544,8 +4543,7 @@ def bite_cmd(cli, nick, chan, rest): relay_wolfchat_command(cli, nick, messages["alpha_bite_wolfchat"].format(nick, victim), ("alpha wolf",), is_wolf_command=True) debuglog("{0} ({1}) BITE: {2} ({3})".format(nick, get_role(nick), actual, get_role(actual))) -@cmd("pass", chan=False, pm=True, playing=True, phases=("night",), - roles=("turncoat", "warlock")) +@cmd("pass", chan=False, pm=True, playing=True, phases=("night",), roles=("turncoat", "warlock")) def pass_cmd(cli, nick, chan, rest): """Decline to use your special power for that night.""" nickrole = get_role(nick) @@ -4976,7 +4974,7 @@ def transition_night(cli): var.FIRST_NIGHT = (var.NIGHT_COUNT == 1) event_begin = Event("transition_night_begin", {}) - event_begin.dispatch(cli, var) + event_begin.dispatch(var) if var.DEVOICE_DURING_NIGHT: modes = [] @@ -5381,7 +5379,7 @@ def start(cli, nick, chan, forced = False, restart = ""): addroles = {} event = Event("role_attribution", {"addroles": addroles}) - if event.dispatch(cli, var, chk_win_conditions, villagers): + if event.dispatch(var, chk_win_conditions, villagers): addroles = event.data["addroles"] for index in range(len(var.ROLE_INDEX) - 1, -1, -1): if var.ROLE_INDEX[index] <= len(villagers): @@ -5438,7 +5436,9 @@ def start(cli, nick, chan, forced = False, restart = ""): for decor in (COMMANDS["join"] + COMMANDS["start"]): decor(_command_disabled) - var.ROLES = {var.DEFAULT_ROLE: set()} # type: Dict[str, Set[users.User]] + var.ROLES.clear() + var.ROLES[var.DEFAULT_ROLE] = UserSet() + var.MAIN_ROLES.clear() var.GUNNERS = {} var.OBSERVED = {} var.CLONED = {} @@ -5453,7 +5453,6 @@ def start(cli, nick, chan, forced = False, restart = ""): var.DAY_COUNT = 0 var.DISEASED_WOLVES = False var.TRAITOR_TURNED = False - var.MAIN_ROLES = {} var.FINAL_ROLES = {} var.ORIGINAL_LOVERS = {} var.LYCANTHROPES = set() @@ -5478,22 +5477,23 @@ def start(cli, nick, chan, forced = False, restart = ""): var.EXTRA_WOLVES = 0 var.PRIESTS = set() var.CONSECRATING = set() - var.DYING = set() + var.DYING.clear() var.PRAYED = {} - var.DEADCHAT_PLAYERS = set() - var.SPECTATING_WOLFCHAT = set() - var.SPECTATING_DEADCHAT = set() + var.DEADCHAT_PLAYERS.clear() + var.SPECTATING_WOLFCHAT.clear() + var.SPECTATING_DEADCHAT.clear() for role, count in addroles.items(): if role in var.TEMPLATE_RESTRICTIONS.keys(): var.ROLES[role] = [None] * count continue # We deal with those later, see below + selected = random.sample(villagers, count) for x in selected: var.MAIN_ROLES[users._get(x)] = role # FIXME villagers.remove(x) - var.ROLES[role] = set(users._get(x) for x in selected) # FIXME + var.ROLES[role] = UserSet(users._get(x) for x in selected) # FIXME fixed_count = count - roleset_roles[role] if fixed_count > 0: for pr in possible_rolesets: @@ -5523,17 +5523,18 @@ def start(cli, nick, chan, forced = False, restart = ""): if len(possible) < len(var.ROLES[template]): cli.msg(chan, messages["not_enough_targets"].format(template)) if var.ORIGINAL_SETTINGS: - var.ROLES = {"person": set(var.ALL_PLAYERS)} + var.ROLES.clear() + var.ROLES["person"] = UserSet(var.ALL_PLAYERS) reset_settings() cli.msg(chan, messages["default_reset"].format(botconfig.CMD_CHAR)) var.PHASE = "join" return else: cli.msg(chan, messages["role_skipped"]) - var.ROLES[template] = set() + var.ROLES[template] = UserSet() continue - var.ROLES[template] = set(users._get(x) for x in random.sample(possible, len(var.ROLES[template]))) # FIXME + var.ROLES[template] = UserSet(users._get(x) for x in random.sample(possible, len(var.ROLES[template]))) # FIXME # Handle gunner cannot_be_sharpshooter = get_players(var.TEMPLATE_RESTRICTIONS["sharpshooter"]) @@ -5550,8 +5551,7 @@ def start(cli, nick, chan, forced = False, restart = ""): else: var.GUNNERS[gunner.nick] = math.ceil(var.SHOTS_MULTIPLIER * len(pl)) - var.ROLES["sharpshooter"] = set(var.ROLES["sharpshooter"]) - var.ROLES["sharpshooter"].discard(None) + var.ROLES["sharpshooter"] = UserSet(p for p in var.ROLES["sharpshooter"] if p is not None) with var.WARNING_LOCK: # cancel timers for name in ("join", "join_pinger", "start_votes"): @@ -5564,7 +5564,7 @@ def start(cli, nick, chan, forced = False, restart = ""): var.LAST_VOTES = None event = Event("role_assignment", {}) - event.dispatch(cli, var, var.CURRENT_GAMEMODE.name, pl[:], restart) + event.dispatch(var, var.CURRENT_GAMEMODE.name, get_players()) if not restart: gamemode = var.CURRENT_GAMEMODE.name @@ -5605,7 +5605,9 @@ def start(cli, nick, chan, forced = False, restart = ""): cli.msg(chan, messages["welcome"].format(", ".join(pl), gamemode, options)) cli.mode(chan, "+m") - var.ORIGINAL_ROLES = copy.deepcopy(var.ROLES) # Make a copy + var.ORIGINAL_ROLES.clear() + for role, players in var.ROLES.items(): + var.ORIGINAL_ROLES[role] = players.copy() # Handle amnesiac; # matchmaker is blacklisted if AMNESIAC_NIGHTS > 1 due to only being able to act night 1 @@ -6921,9 +6923,6 @@ if botconfig.DEBUG_MODE: except Exception as e: wrapper.send("{e.__class__.__name__}: {e}".format(e=e)) - -if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: - # DO NOT MAKE THIS A PMCOMMAND ALSO @cmd("force", flag="d") def force(cli, nick, chan, rest): @@ -6987,7 +6986,7 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: elif who == "gunner": tgt = {users._get(g) for g in var.GUNNERS.keys()} # FIXME else: - tgt = var.ROLES[who].copy() + tgt = set(var.ROLES[who]) comm = rst.pop(0).lower().replace(botconfig.CMD_CHAR, "", 1) if comm in COMMANDS and not COMMANDS[comm][0].owner_only: @@ -7008,113 +7007,4 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: else: cli.msg(chan, messages["command_not_found"]) - - - @cmd("frole", flag="d", phases=("day", "night")) - def frole(cli, nick, chan, rest): - """Change the role or template of a player.""" - rst = re.split(" +",rest) - if len(rst) < 2: - cli.msg(chan, messages["incorrect_syntax"]) - return - who = rst.pop(0).strip() - rol = " ".join(rst).strip() - ul = list(var.USERS.keys()) - ull = [u.lower() for u in ul] - if who.lower() not in ull: - if not is_fake_nick(who): - cli.msg(chan, messages["invalid_target"]) - return - if not is_fake_nick(who): - who = ul[ull.index(who.lower())] - if who == users.Bot.nick or not who: - cli.msg(chan, messages["invalid_target"]) - return - pl = list_players() - rolargs = re.split("\s*=\s*", rol, 1) - rol = rolargs[0] - if rol[0] in ("+", "-"): - addrem = rol[0] - rol = rol[1:] - is_gunner = (rol == "gunner" or rol == "sharpshooter") - if addrem == "+" and who not in get_roles(rol): # FIXME - if is_gunner: - if len(rolargs) == 2 and rolargs[1].isdigit(): - if len(rolargs[1]) < 7: - var.GUNNERS[who] = int(rolargs[1]) - else: - var.GUNNERS[who] = 999 - elif rol == "gunner": - var.GUNNERS[who] = math.ceil(var.SHOTS_MULTIPLIER * len(pl)) - else: - var.GUNNERS[who] = math.ceil(var.SHARPSHOOTER_MULTIPLIER * len(pl)) - if who not in pl: - var.ROLES[var.DEFAULT_ROLE].add(users._get(who)) # FIXME - var.MAIN_ROLES[users._get(who)] = var.DEFAULT_ROLE # FIXME - var.ALL_PLAYERS.append(users._get(who)) # FIXME - if not is_fake_nick(who): - cli.mode(chan, "+v", who) - cli.msg(chan, messages["template_default_role"].format(var.DEFAULT_ROLE)) - - var.ROLES[rol].add(users._get(who)) # FIXME - evt = Event("frole_template", {}) - evt.dispatch(cli, var, addrem, who, rol, rolargs) - elif addrem == "-" and who in get_roles(rol) and get_role(who) != rol: - var.ROLES[rol].remove(users._get(who)) # FIXME - evt = Event("frole_template", {}) - evt.dispatch(cli, var, addrem, who, rol, rolargs) - if is_gunner and who in var.GUNNERS: - del var.GUNNERS[who] - else: - cli.msg(chan, messages["improper_template_mod"]) - return - elif rol in var.TEMPLATE_RESTRICTIONS.keys(): - cli.msg(chan, messages["template_mod_syntax"].format(rol)) - return - elif rol in var.ROLES: - oldrole = None - if who in pl: - oldrole = get_role(who) - change_role(users._get(who), oldrole, rol) # FIXME - else: - var.ALL_PLAYERS.append(users._get(who)) # FIXME - var.ROLES[rol].add(users._get(who)) # FIXME - var.MAIN_ROLES[users._get(who)] = rol # FIXME - var.ORIGINAL_ROLES[rol].add(users._get(who)) # FIXME - evt = Event("frole_role", {}) - evt.dispatch(cli, var, who, rol, oldrole, rolargs) - if rol == "amnesiac": - if len(rolargs) == 2 and rolargs[1] in var.ROLES: - var.AMNESIAC_ROLES[who] = rolargs[1] - else: - # Pick amnesiac role like normal - amnroles = var.ROLE_GUIDE.keys() - {var.DEFAULT_ROLE, "amnesiac", "clone", "traitor"} - if var.AMNESIAC_NIGHTS > 1 and "matchmaker" in amnroles: - amnroles.remove("matchmaker") - for nope in var.AMNESIAC_BLACKLIST: - amnroles.discard(nope) - for nope in var.TEMPLATE_RESTRICTIONS.keys(): - amnroles.discard(nope) - var.AMNESIAC_ROLES[who] = random.choice(list(amnroles)) - if not is_fake_nick(who): - cli.mode(chan, "+v", who) - else: - cli.msg(chan, messages["invalid_role"]) - return - cli.msg(chan, messages["operation_successful"]) - # default stats determination does not work if we're mucking with !frole - if var.STATS_TYPE == "default": - var.ORIGINAL_SETTINGS["STATS_TYPE"] = var.STATS_TYPE - var.STATS_TYPE = "accurate" - - cli.msg(chan, messages["stats_accurate"].format(botconfig.CMD_CHAR)) - chk_win() - - -if botconfig.ALLOWED_NORMAL_MODE_COMMANDS and not botconfig.DEBUG_MODE: - for comd in list(COMMANDS.keys()): - if (comd not in before_debug_mode_commands and - comd not in botconfig.ALLOWED_NORMAL_MODE_COMMANDS): - del COMMANDS[comd] - # vim: set sw=4 expandtab: