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.
This commit is contained in:
Em Barry 2018-04-13 16:37:04 -04:00 committed by Ryan Schmidt
parent c121c0f08f
commit 28f26e181b
32 changed files with 615 additions and 457 deletions

View File

@ -41,7 +41,6 @@ OWNERS_ACCOUNTS = ("1owner_acc",)
#RULES = "https://werewolf.chat/Freenode:Rules" #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 OWNERS_ONLY_COMMANDS = [] # Commands that should only be allowed for owners, regardless of their original permissions
DISABLE_DEBUG_MODE_REAPER = True DISABLE_DEBUG_MODE_REAPER = True

View File

@ -621,11 +621,6 @@
"invalid_target": "This can only be done on players in the channel or fake nicks.", "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.", "admin_only_force": "Only full admins can force an admin-only command.",
"operation_successful": "Operation successful.", "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.", "not_owner": "You are not the owner.",
"invalid_permissions": "You do not have permission to use that command.", "invalid_permissions": "You do not have permission to use that command.",
"player_joined_deadchat": "\u0002{0}\u0002 has joined the deadchat.", "player_joined_deadchat": "\u0002{0}\u0002 has joined the deadchat.",

341
src/containers.py Normal file
View File

@ -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

View File

@ -12,6 +12,7 @@ from src.utilities import *
from src.messages import messages from src.messages import messages
from src.functions import get_players, get_all_players, get_main_role from src.functions import get_players, get_all_players, get_main_role
from src.decorators import handle_error, command from src.decorators import handle_error, command
from src.containers import UserList, UserSet, UserDict
from src import events, channels, users from src import events, channels, users
def game_mode(name, minp, maxp, likelihood = 0): 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("role_attribution", self.role_attribution)
events.remove_listener("chk_win", self.lovers_chk_win) 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 lpl = len(villagers) - 1
addroles = evt.data["addroles"] addroles = evt.data["addroles"]
for role in var.ROLE_GUIDE: for role in var.ROLE_GUIDE:
@ -629,8 +630,8 @@ class RandomMode(GameMode):
mainroles[u] = role mainroles[u] = role
i += count i += count
if chk_win_conditions(cli, rolemap, mainroles, end_game=False): if chk_win_conditions(rolemap, mainroles, end_game=False):
return self.role_attribution(evt, cli, var, chk_win_conditions, villagers) return self.role_attribution(evt, var, chk_win_conditions, villagers)
evt.prevent_default = True evt.prevent_default = True
@ -875,20 +876,19 @@ class SleepyMode(GameMode):
# disable wolfchat # disable wolfchat
#self.RESTRICT_WOLFCHAT = 0x0f #self.RESTRICT_WOLFCHAT = 0x0f
self.having_nightmare = None
def startup(self): def startup(self):
events.add_listener("dullahan_targets", self.dullahan_targets) events.add_listener("dullahan_targets", self.dullahan_targets)
events.add_listener("transition_night_begin", self.setup_nightmares) events.add_listener("transition_night_begin", self.setup_nightmares)
events.add_listener("chk_nightdone", self.prolong_night) events.add_listener("chk_nightdone", self.prolong_night)
events.add_listener("transition_day_begin", self.nightmare_kill) events.add_listener("transition_day_begin", self.nightmare_kill)
events.add_listener("del_player", self.happy_fun_times) 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.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.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.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.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): def teardown(self):
from src import decorators from src import decorators
events.remove_listener("dullahan_targets", self.dullahan_targets) 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("chk_nightdone", self.prolong_night)
events.remove_listener("transition_day_begin", self.nightmare_kill) events.remove_listener("transition_day_begin", self.nightmare_kill)
events.remove_listener("del_player", self.happy_fun_times) events.remove_listener("del_player", self.happy_fun_times)
events.remove_listener("rename_player", self.rename_player)
def remove_command(name, command): def remove_command(name, command):
if len(decorators.COMMANDS[name]) > 1: if len(decorators.COMMANDS[name]) > 1:
decorators.COMMANDS[name].remove(command) decorators.COMMANDS[name].remove(command)
@ -911,23 +910,18 @@ class SleepyMode(GameMode):
remove_command("west", self.west_cmd) remove_command("west", self.west_cmd)
remove_command("w", 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: 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): def setup_nightmares(self, evt, cli, var):
if random.random() < 1/5: if random.random() < 1/5:
self.having_nightmare = True
with var.WARNING_LOCK: with var.WARNING_LOCK:
t = threading.Timer(60, self.do_nightmare, (var, random.choice(get_players()), var.NIGHT_COUNT)) t = threading.Timer(60, self.do_nightmare, (var, random.choice(get_players()), var.NIGHT_COUNT))
t.daemon = True t.daemon = True
t.start() 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 @handle_error
def do_nightmare(self, var, target, night): def do_nightmare(self, var, target, night):
@ -935,7 +929,8 @@ class SleepyMode(GameMode):
return return
if target not in get_players(): if target not in get_players():
return 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_begin"])
target.send(messages["sleepy_nightmare_navigate"]) target.send(messages["sleepy_nightmare_navigate"])
self.correct = [None, None, None] self.correct = [None, None, None]
@ -976,30 +971,30 @@ class SleepyMode(GameMode):
directions = "north, south, and west" directions = "north, south, and west"
if self.step == 0: 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: 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: 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: elif self.step == 3:
if "correct" in self.on_path: if "correct" in self.on_path:
self.having_nightmare.send(messages["sleepy_nightmare_wake"]) self.having_nightmare[0].send(messages["sleepy_nightmare_wake"])
self.having_nightmare = None del self.having_nightmare[0]
elif "fake1" in self.on_path: 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.step = 0
self.on_path = set() self.on_path = set()
self.prev_direction = self.start_direction self.prev_direction = self.start_direction
self.nightmare_step() self.nightmare_step()
elif "fake2" in self.on_path: 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.step = 0
self.on_path = set() self.on_path = set()
self.prev_direction = self.start_direction self.prev_direction = self.start_direction
self.nightmare_step() self.nightmare_step()
def move(self, direction, var, wrapper, message): 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 return
opposite = {"n": "s", "e": "w", "s": "n", "w": "e"} opposite = {"n": "s", "e": "w", "s": "n", "w": "e"}
if self.prev_direction == opposite[direction]: if self.prev_direction == opposite[direction]:
@ -1032,14 +1027,14 @@ class SleepyMode(GameMode):
self.nightmare_step() self.nightmare_step()
def prolong_night(self, evt, var): def prolong_night(self, evt, var):
if self.having_nightmare is not None: if self.having_nightmare:
evt.data["actedcount"] = -1 evt.data["actedcount"] = -1
def nightmare_kill(self, evt, var): def nightmare_kill(self, evt, var):
# if True, it means night ended before 1 minute if self.having_nightmare and self.having_nightmare[0] in get_players():
if self.having_nightmare is not None and self.having_nightmare in get_players(): var.DYING.add(self.having_nightmare[0])
var.DYING.add(self.having_nightmare) self.having_nightmare[0].send(messages["sleepy_nightmare_death"])
self.having_nightmare.send(messages["sleepy_nightmare_death"]) del self.having_nightmare[0]
def happy_fun_times(self, evt, var, user, mainrole, allroles, death_triggers): def happy_fun_times(self, evt, var, user, mainrole, allroles, death_triggers):
if death_triggers: if death_triggers:
@ -1125,13 +1120,12 @@ class MaelstromMode(GameMode):
def _on_join(self, var, wrapper): def _on_join(self, var, wrapper):
from src import hooks, channels from src import hooks, channels
role = random.choice(self.roles) role = random.choice(self.roles)
rolemap = copy.deepcopy(var.ROLES) with copy.deepcopy(var.ROLES) as rolemap, copy.deepcopy(var.MAIN_ROLES) as mainroles:
rolemap[role].add(wrapper.source) rolemap[role].add(wrapper.source)
mainroles = copy.deepcopy(var.MAIN_ROLES) mainroles[wrapper.source] = role
mainroles[wrapper.source] = role
if self.chk_win_conditions(wrapper.client, rolemap, mainroles, end_game=False): if self.chk_win_conditions(rolemap, mainroles, end_game=False):
return self._on_join(var, wrapper) return self._on_join(var, wrapper)
if not wrapper.source.is_fake or not botconfig.DEBUG_MODE: if not wrapper.source.is_fake or not botconfig.DEBUG_MODE:
cmodes = [("+v", wrapper.source)] cmodes = [("+v", wrapper.source)]
@ -1173,22 +1167,23 @@ class MaelstromMode(GameMode):
pl[i] = player.nick + " (cursed)" pl[i] = player.nick + " (cursed)"
wrapper.pm("Players: " + ", ".join(pl)) 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 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 # don't do this n1
if var.FIRST_NIGHT: if var.FIRST_NIGHT:
return return
villagers = get_players() villagers = get_players()
lpl = len(villagers) 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 # shameless copy/paste of regular role attribution
for role, count in addroles.items(): for role, count in addroles.items():
selected = random.sample(villagers, count) selected = random.sample(villagers, count)
var.ROLES[role] = set(selected) var.ROLES[role].clear()
var.ROLES[role].update(selected)
for x in selected: for x in selected:
villagers.remove(x) villagers.remove(x)
@ -1216,7 +1211,7 @@ class MaelstromMode(GameMode):
var.FINAL_ROLES[p.nick] = role # FIXME var.FINAL_ROLES[p.nick] = role # FIXME
var.MAIN_ROLES[p] = role 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 lpl = len(villagers) - 1
addroles = {} addroles = {}
for role in var.ROLE_GUIDE: for role in var.ROLE_GUIDE:
@ -1255,8 +1250,8 @@ class MaelstromMode(GameMode):
mainroles[u] = role mainroles[u] = role
i += count i += count
if self.chk_win_conditions(cli, rolemap, mainroles, end_game=False): if self.chk_win_conditions(rolemap, mainroles, end_game=False):
return self._role_attribution(cli, var, villagers, do_templates) return self._role_attribution(var, villagers, do_templates)
return addroles return addroles
@ -1375,7 +1370,7 @@ class MudkipMode(GameMode):
def daylight_warning(self, evt, var): def daylight_warning(self, evt, var):
evt.data["message"] = "daylight_warning_killtie" 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: if var.FIRST_NIGHT:
# ensure shaman gets death totem on the first night # ensure shaman gets death totem on the first night
var.TOTEM_CHANCES["pestilence"] = (0, 1, 0) var.TOTEM_CHANCES["pestilence"] = (0, 1, 0)

View File

@ -10,6 +10,7 @@ from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players from src.functions import get_players, get_all_players
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event 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))) debuglog("{0} ({1}) PASS".format(nick, get_role(nick)))
@event_listener("rename_player") @event_listener("rename_player")
def on_rename(evt, cli, var, prefix, nick): def on_rename(evt, var, prefix, nick):
for dictvar in (GUARDED, LASTGUARDED): for dictvar in (GUARDED, LASTGUARDED):
kvp = {} kvp = {}
for a,b in dictvar.items(): for a,b in dictvar.items():
@ -223,7 +224,7 @@ def on_transition_day_resolve_end(evt, var, victims):
evt.data["dead"].append(gangel) evt.data["dead"].append(gangel)
@event_listener("transition_night_begin") @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 # 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) # (right now they don't due to other reasons, but that may change)
GUARDED.clear() GUARDED.clear()

View File

@ -10,6 +10,7 @@ from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players from src.functions import get_players, get_all_players
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
@ -51,7 +52,7 @@ def on_transition_night_end(evt, var):
blessed.send(messages[to_send]) blessed.send(messages[to_send])
@event_listener("desperation_totem") @event_listener("desperation_totem")
def on_desperation(evt, cli, var, votee, target, prot): def on_desperation(evt, var, votee, target, prot):
if prot == "blessing": if prot == "blessing":
var.ACTIVE_PROTECTIONS[target].remove("blessing") var.ACTIVE_PROTECTIONS[target].remove("blessing")
evt.prevent_default = True evt.prevent_default = True

View File

@ -9,11 +9,12 @@ import src.settings as var
from src.utilities import * from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
@event_listener("see") @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 if users._get(victim) in var.ROLES["cursed villager"]: # FIXME
evt.data["role"] = "wolf" evt.data["role"] = "wolf"

View File

@ -7,6 +7,7 @@ from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players from src.functions import get_players, get_all_players
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event 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))) debuglog("{0} ({1}) PAPER DROP".format(nick, get_role(nick)))
@event_listener("rename_player") @event_listener("rename_player")
def on_rename(evt, cli, var, prefix, nick): def on_rename(evt, var, prefix, nick):
if prefix in INVESTIGATED: if prefix in INVESTIGATED:
INVESTIGATED.remove(prefix) INVESTIGATED.remove(prefix)
INVESTIGATED.add(nick) 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") dttv.send(messages[to_send].format(warning), "Players: " + ", ".join(p.nick for p in pl), sep="\n")
@event_listener("transition_night_begin") @event_listener("transition_night_begin")
def on_transition_night_begin(evt, cli, var): def on_transition_night_begin(evt, var):
INVESTIGATED.clear() INVESTIGATED.clear()
@event_listener("reset") @event_listener("reset")

View File

@ -6,6 +6,7 @@ from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players from src.functions import get_players, get_all_players
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
@ -55,7 +56,7 @@ def see(cli, nick, chan, rest):
SEEN.add(nick) SEEN.add(nick)
@event_listener("rename_player") @event_listener("rename_player")
def on_rename(evt, cli, var, prefix, nick): def on_rename(evt, var, prefix, nick):
if prefix in SEEN: if prefix in SEEN:
SEEN.remove(prefix) SEEN.remove(prefix)
SEEN.add(nick) SEEN.add(nick)
@ -97,7 +98,7 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
del dictvar[k] del dictvar[k]
@event_listener("doctor_immunize") @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(): if target in SICK.values():
for n, v in list(SICK.items()): for n, v in list(SICK.items()):
if v == target: if v == target:
@ -161,7 +162,7 @@ def on_begin_day(evt, var):
LYCANS.clear() LYCANS.clear()
@event_listener("transition_night_begin") @event_listener("transition_night_begin")
def on_transition_night_begin(evt, cli, var): def on_transition_night_begin(evt, var):
SICK.clear() SICK.clear()
@event_listener("reset") @event_listener("reset")

View File

@ -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.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 import users, channels, debuglog, errlog, plog
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
import botconfig import botconfig
KILLS = {} # type: Dict[users.User, users.User] KILLS = UserDict() # type: Dict[users.User, users.User]
TARGETS = {} # type: Dict[users.User, Set[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",)) @command("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("dullahan",))
def dullahan_kill(var, wrapper, message): def dullahan_kill(var, wrapper, message):
@ -62,66 +63,50 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
del KILLS[h] del KILLS[h]
if death_triggers and "dullahan" in allroles: if death_triggers and "dullahan" in allroles:
pl = evt.data["pl"] pl = evt.data["pl"]
targets = TARGETS[user].intersection(pl) with TARGETS[user].intersection(pl) as targets:
if targets: if targets:
target = random.choice(list(targets)) target = random.choice(list(targets))
prots = deque(var.ACTIVE_PROTECTIONS[target.nick]) prots = deque(var.ACTIVE_PROTECTIONS[target.nick])
aevt = Event("assassinate", {"pl": evt.data["pl"], "target": target}, aevt = Event("assassinate", {"pl": evt.data["pl"], "target": target},
del_player=evt.params.del_player, del_player=evt.params.del_player,
deadlist=evt.params.deadlist, deadlist=evt.params.deadlist,
original=evt.params.original, original=evt.params.original,
refresh_pl=evt.params.refresh_pl, refresh_pl=evt.params.refresh_pl,
message_prefix="dullahan_die_", message_prefix="dullahan_die_",
source="dullahan", source="dullahan",
killer=user, killer=user,
killer_mainrole=mainrole, killer_mainrole=mainrole,
killer_allroles=allroles, killer_allroles=allroles,
prots=prots) prots=prots)
while len(prots) > 0: while len(prots) > 0:
# an event can read the current active protection and cancel or redirect the assassination # 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 # 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) # 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]): if not aevt.dispatch(var, user, target, prots[0]):
evt.data["pl"] = aevt.data["pl"] evt.data["pl"] = aevt.data["pl"]
if target is not aevt.data["target"]: if target is not aevt.data["target"]:
target = aevt.data["target"] target = aevt.data["target"]
prots = deque(var.ACTIVE_PROTECTIONS[target.nick]) prots = deque(var.ACTIVE_PROTECTIONS[target.nick])
aevt.params.prots = prots aevt.params.prots = prots
continue continue
return return
prots.popleft() prots.popleft()
if var.ROLE_REVEAL in ("on", "team"): if var.ROLE_REVEAL in ("on", "team"):
role = get_reveal_role(target) role = get_reveal_role(target)
an = "n" if role.startswith(("a", "e", "i", "o", "u")) else "" an = "n" if role.startswith(("a", "e", "i", "o", "u")) else ""
channels.Main.send(messages["dullahan_die_success"].format(user, target, an, role)) channels.Main.send(messages["dullahan_die_success"].format(user, target, an, role))
else: else:
channels.Main.send(messages["dullahan_die_success_noreveal"].format(user, target)) 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))) 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.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) evt.data["pl"] = evt.params.refresh_pl(pl)
@event_listener("night_acted") @event_listener("night_acted")
def on_acted(evt, var, user, actor): def on_acted(evt, var, user, actor):
if user in KILLS: if user in KILLS:
evt.data["acted"] = True 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") @event_listener("get_special")
def on_get_special(evt, var): def on_get_special(evt, var):
evt.data["special"].update(get_players(("dullahan",))) 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") dullahan.send(messages[to_send], t + ", ".join(t.nick for t in targets), sep="\n")
@event_listener("role_assignment") @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 # assign random targets to dullahan to kill
if var.ROLES["dullahan"]: if var.ROLES["dullahan"]:
max_targets = math.ceil(8.1 * math.log(len(pl), 10) - 5) max_targets = math.ceil(8.1 * math.log(len(pl), 10) - 5)
for dull in var.ROLES["dullahan"]: for dull in var.ROLES["dullahan"]:
TARGETS[dull] = set() TARGETS[dull] = UserSet()
dull_targets = Event("dullahan_targets", {"targets": TARGETS}) # support sleepy dull_targets = Event("dullahan_targets", {"targets": TARGETS}) # support sleepy
dull_targets.dispatch(cli, var, var.ROLES["dullahan"], max_targets) dull_targets.dispatch(var, var.ROLES["dullahan"], max_targets)
players = [users._get(x) for x in pl] # FIXME
for dull, ts in TARGETS.items(): for dull, ts in TARGETS.items():
ps = players[:] ps = pl[:]
ps.remove(dull) ps.remove(dull)
while len(ts) < max_targets: while len(ts) < max_targets:
target = random.choice(ps) target = random.choice(ps)

View File

@ -9,6 +9,7 @@ import src.settings as var
from src.utilities import * from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.decorators import cmd, event_listener 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.functions import get_players, get_all_players
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event

View File

@ -10,11 +10,12 @@ from src.utilities import *
from src import channels, users, debuglog, errlog, plog 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.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
VISITED = {} # type: Dict[users.User, users.User] VISITED = UserDict() # type: Dict[users.User, users.User]
PASSED = set() # type: Set[users.User] PASSED = UserSet() # type: Set[users.User]
@command("visit", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("harlot",)) @command("visit", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("harlot",))
def hvisit(var, wrapper, message): def hvisit(var, wrapper, message):
@ -134,18 +135,6 @@ def on_begin_day(evt, var):
VISITED.clear() VISITED.clear()
PASSED.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") @event_listener("get_special")
def on_get_special(evt, var): def on_get_special(evt, var):
evt.data["special"].update(get_players(("harlot",))) evt.data["special"].update(get_players(("harlot",)))

View File

@ -7,12 +7,13 @@ from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players, get_target, get_main_role from src.functions import get_players, get_all_players, get_target, get_main_role
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
KILLS = {} # type: Dict[users.User, users.User] KILLS = UserDict() # type: Dict[users.User, users.User]
HUNTERS = set() HUNTERS = UserSet()
PASSED = set() PASSED = UserSet()
@command("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("hunter",)) @command("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("hunter",))
def hunter_kill(var, wrapper, message): 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"]) h.send(messages["hunter_discard"])
del KILLS[h] 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") @event_listener("night_acted")
def on_acted(evt, var, user, actor): def on_acted(evt, var, user, actor):
if user in KILLS: if user in KILLS:

View File

@ -10,10 +10,11 @@ from src.utilities import *
from src import channels, users, debuglog, errlog, plog 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.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
INVESTIGATED = set() INVESTIGATED = UserSet()
@command("id", chan=False, pm=True, playing=True, silenced=True, phases=("day",), roles=("investigator",)) @command("id", chan=False, pm=True, playing=True, silenced=True, phases=("day",), roles=("investigator",))
def investigate(var, wrapper, message): 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), wrapper.source, target1, get_main_role(target1), target2, get_main_role(target2),
"same" if same else "different")) "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") @event_listener("del_player")
def on_del_player(evt, var, user, mainrole, allroles, death_triggers): def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
INVESTIGATED.discard(user) 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": if actor_role == "investigator" and target_role != "investigator":
INVESTIGATED.discard(actor) INVESTIGATED.discard(actor)
elif target_role == "investigator" and actor_role != "investigator": elif target_role == "investigator" and actor_role != "investigator":
INVESTIGATED.discard(targe) INVESTIGATED.discard(target)
@event_listener("transition_night_end", priority=2) @event_listener("transition_night_end", priority=2)
def on_transition_night_end(evt, var): 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") inv.send(messages[to_send], "Players: " + ", ".join(p.nick for p in pl), sep="\n")
@event_listener("transition_night_begin") @event_listener("transition_night_begin")
def on_transition_night_begin(evt, cli, var): def on_transition_night_begin(evt, var):
INVESTIGATED.clear() INVESTIGATED.clear()
@event_listener("reset") @event_listener("reset")

View File

@ -10,6 +10,7 @@ from src.utilities import *
from src import channels, users, debuglog, errlog, plog 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.functions import get_players, get_all_players, get_main_role, get_reveal_role
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event

View File

@ -9,13 +9,14 @@ import src.settings as var
from src.utilities import * from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
REVEALED_MAYORS = set() REVEALED_MAYORS = set()
@event_listener("rename_player") @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: if prefix in REVEALED_MAYORS:
REVEALED_MAYORS.remove(prefix) REVEALED_MAYORS.remove(prefix)
REVEALED_MAYORS.add(nick) REVEALED_MAYORS.add(nick)

View File

@ -6,6 +6,7 @@ from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players from src.functions import get_players, get_all_players
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event

View File

@ -10,11 +10,12 @@ from src.utilities import *
from src.functions import get_players, get_all_players, get_target, get_main_role from src.functions import get_players, get_all_players, get_target, get_main_role
from src import channels, users, debuglog, errlog, plog from src import channels, users, debuglog, errlog, plog
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
TOBECHARMED = {} # type: Dict[users.User, Set[users.User]] TOBECHARMED = UserDict() # type: Dict[users.User, Set[users.User]]
CHARMED = set() # type: Set[users.User] CHARMED = UserSet() # type: Set[users.User]
@command("charm", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("piper",)) @command("charm", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("piper",))
def charm(var, wrapper, message): def charm(var, wrapper, message):
@ -69,8 +70,12 @@ def charm(var, wrapper, message):
wrapper.send(messages["target_already_charmed"].format(orig2)) wrapper.send(messages["target_already_charmed"].format(orig2))
return return
TOBECHARMED[wrapper.source] = {target1, target2} if wrapper.source in TOBECHARMED:
TOBECHARMED[wrapper.source].discard(None) TOBECHARMED[wrapper.source].clear()
else:
TOBECHARMED[wrapper.source] = UserSet()
TOBECHARMED[wrapper.source].update({target1, target2} - {None})
if orig2: if orig2:
debuglog("{0} (piper) CHARM {1} ({2}) && {3} ({4})".format(wrapper.source, 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") @event_listener("del_player")
def on_del_player(evt, var, player, mainrole, allroles, death_triggers): def on_del_player(evt, var, player, mainrole, allroles, death_triggers):
CHARMED.discard(player) CHARMED.discard(player)
TOBECHARMED.pop(player, None) x = TOBECHARMED.pop(player, None)
if x is not None:
x.clear()
@event_listener("transition_day_begin") @event_listener("transition_day_begin")
def on_transition_day_begin(evt, var): def on_transition_day_begin(evt, var):
@ -196,18 +203,4 @@ def on_revealroles(evt, var, wrapper):
if CHARMED: if CHARMED:
evt.data["output"].append("\u0002charmed players\u0002: {0}".format(", ".join(p.nick for p in 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: # vim: set sw=4 expandtab:

View File

@ -5,6 +5,7 @@ import src.settings as var
from src.utilities import * from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.decorators import cmd, event_listener 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.functions import get_players, get_all_players, get_main_role
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
@ -47,7 +48,7 @@ def see(cli, nick, chan, rest):
victimrole = "villager" victimrole = "villager"
evt = Event("see", {"role": victimrole}) evt = Event("see", {"role": victimrole})
evt.dispatch(cli, var, nick, victim) evt.dispatch(var, nick, victim)
victimrole = evt.data["role"] victimrole = evt.data["role"]
else: else:
if victimrole == "amnesiac": if victimrole == "amnesiac":
@ -78,7 +79,7 @@ def see(cli, nick, chan, rest):
SEEN.add(nick) SEEN.add(nick)
@event_listener("rename_player") @event_listener("rename_player")
def on_rename(evt, cli, var, prefix, nick): def on_rename(evt, var, prefix, nick):
if prefix in SEEN: if prefix in SEEN:
SEEN.remove(prefix) SEEN.remove(prefix)
SEEN.add(nick) SEEN.add(nick)

View File

@ -9,6 +9,7 @@ from src.utilities import *
from src import debuglog, errlog, plog, users, channels 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.functions import get_players, get_all_players, get_main_role, get_reveal_role
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
@ -65,7 +66,7 @@ def totem(cli, nick, chan, rest, prefix="You"): # XXX: The transition_day_begin
return return
original_victim = victim 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 = "" totem = ""
if role != "crazed shaman": if role != "crazed shaman":
totem = " of " + TOTEMS[nick] 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])) debuglog("{0} ({1}) TOTEM: {2} ({3})".format(nick, role, victim, TOTEMS[nick]))
@event_listener("rename_player") @event_listener("rename_player")
def on_rename(evt, cli, var, prefix, nick): def on_rename(evt, var, prefix, nick):
if prefix in TOTEMS: if prefix in TOTEMS:
TOTEMS[nick] = TOTEMS.pop(prefix) TOTEMS[nick] = TOTEMS.pop(prefix)
@ -130,7 +131,7 @@ def on_rename(evt, cli, var, prefix, nick):
setvar.add(nick) setvar.add(nick)
@event_listener("see", priority=10) @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 (victim in DECEIT) ^ (nick in DECEIT):
if evt.data["role"] in var.SEEN_WOLF and evt.data["role"] not in var.SEEN_DEFAULT: if evt.data["role"] in var.SEEN_WOLF and evt.data["role"] not in var.SEEN_DEFAULT:
evt.data["role"] = "villager" 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 # 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) # so that it cannot be used again (if the protection is meant to be usable once-only)
desp_evt = Event("desperation_totem", {}) 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 return
prots.popleft() prots.popleft()
if var.ROLE_REVEAL in ("on", "team"): if var.ROLE_REVEAL in ("on", "team"):
@ -627,26 +628,6 @@ def on_reset(evt, var):
MISDIRECTION.clear() MISDIRECTION.clear()
DECEIT.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") @event_listener("get_role_metadata")
def on_get_role_metadata(evt, var, kind): def on_get_role_metadata(evt, var, kind):
if kind == "night_kills": if kind == "night_kills":

View File

@ -10,11 +10,15 @@ from src.utilities import *
from src import channels, users, debuglog, errlog, plog 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.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
# Skeleton file for new roles. Not all events are represented, only the most common ones. # 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 # 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 # 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 # 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): def on_reset(evt, var):
pass 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: # 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 # 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: # death message (i.e. do not have a custom death message). Set the data as follows:

View File

@ -10,13 +10,14 @@ from src.utilities import *
from src import channels, users, debuglog, errlog, plog 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.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
ENTRANCED = set() # type: Set[users.User] ENTRANCED = UserSet() # type: Set[users.User]
ENTRANCED_DYING = set() # type: Set[users.User] ENTRANCED_DYING = UserSet() # type: Set[users.User]
VISITED = {} # type: Dict[users.User, users.User] VISITED = UserDict() # type: Dict[users.User, users.User]
PASSED = set() # type: Set[users.User] PASSED = UserSet() # type: Set[users.User]
ALL_SUCC_IDLE = True ALL_SUCC_IDLE = True
@command("visit", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("succubus",)) @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: if ghost in ENTRANCED:
evt.data["pl"] -= get_all_players(("succubus",)) 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") @event_listener("reset")
def on_reset(evt, var): def on_reset(evt, var):
global ALL_SUCC_IDLE global ALL_SUCC_IDLE

View File

@ -9,6 +9,7 @@ import src.settings as var
from src.utilities import * from src.utilities import *
from src import debuglog, errlog, plog, users, channels from src import debuglog, errlog, plog, users, channels
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event

View File

@ -7,14 +7,15 @@ from src.utilities import *
from src import channels, users, debuglog, errlog, plog from src import channels, users, debuglog, errlog, plog
from src.functions import get_players, get_target, get_main_role from src.functions import get_players, get_target, get_main_role
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
KILLS = {} # type: Dict[users.User, users.User] KILLS = UserDict() # type: Dict[users.User, users.User]
GHOSTS = {} # type: Dict[users.User, str] GHOSTS = UserDict() # type: Dict[users.User, str]
# temporary holding variable, only non-empty during transition_day # 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) @command("kill", chan=False, pm=True, playing=False, silenced=True, phases=("night",), users=GHOSTS)
def vg_kill(var, wrapper, message): def vg_kill(var, wrapper, message):

View File

@ -7,11 +7,12 @@ from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players, get_main_role, get_target from src.functions import get_players, get_all_players, get_main_role, get_target
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
KILLS = {} # type: Dict[users.User, users.User] KILLS = UserDict() # type: Dict[users.User, users.User]
PASSED = set() # type: Set[users.User] PASSED = UserSet() # type: Set[users.User]
@command("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("vigilante",)) @command("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("vigilante",))
def vigilante_kill(var, wrapper, message): 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"]) vigilante.send(messages["hunter_discard"])
del KILLS[vigilante] 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") @event_listener("night_acted")
def on_acted(evt, var, target, spy): def on_acted(evt, var, target, spy):
if target in KILLS: if target in KILLS:

View File

@ -3,6 +3,7 @@ from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.functions import get_players from src.functions import get_players
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event

View File

@ -6,6 +6,7 @@ from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players, get_main_role from src.functions import get_players, get_all_players, get_main_role
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event 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))) debuglog("{0} (wild child) IDOLIZE: {1} ({2})".format(nick, victim, get_role(victim)))
@event_listener("see") @event_listener("see")
def on_see(evt, cli, var, seer, victim): def on_see(evt, var, seer, victim):
if victim in WILD_CHILDREN: if victim in WILD_CHILDREN:
evt.data["role"] = "wild child" evt.data["role"] = "wild child"
@event_listener("rename_player") @event_listener("rename_player")
def on_rename(evt, cli, var, prefix, nick): def on_rename(evt, var, prefix, nick):
if prefix in WILD_CHILDREN: if prefix in WILD_CHILDREN:
WILD_CHILDREN.remove(prefix) WILD_CHILDREN.remove(prefix)
WILD_CHILDREN.add(nick) WILD_CHILDREN.add(nick)

View File

@ -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.functions import get_players, get_all_players, get_main_role, get_all_roles
from src import debuglog, errlog, plog, users, channels from src import debuglog, errlog, plog, users, channels
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
@ -127,7 +128,7 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
del KILLS[a] del KILLS[a]
@event_listener("rename_player") @event_listener("rename_player")
def on_rename(evt, cli, var, prefix, nick): def on_rename(evt, var, prefix, nick):
kvp = [] kvp = []
for a,b in KILLS.items(): for a,b in KILLS.items():
nl = [] nl = []

View File

@ -7,6 +7,7 @@ from src.utilities import *
from src.functions import get_players from src.functions import get_players
from src import debuglog, errlog, plog, users, channels from src import debuglog, errlog, plog, users, channels
from src.decorators import cmd, event_listener from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
from src.roles import wolf from src.roles import wolf

View File

@ -3,8 +3,6 @@ import re
import threading import threading
from collections import defaultdict, OrderedDict from collections import defaultdict, OrderedDict
import botconfig
LANGUAGE = 'en' LANGUAGE = 'en'
MINIMUM_WAIT = 60 MINIMUM_WAIT = 60

View File

@ -194,17 +194,10 @@ def _reset(evt, var):
_users.discard(user) _users.discard(user)
_ghosts.clear() _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 # 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) # (meaning decorator isn't defined at the point in time we are run)
events.add_listener("cleanup_user", _cleanup_user) events.add_listener("cleanup_user", _cleanup_user)
events.add_listener("reset", _reset) events.add_listener("reset", _reset)
events.add_listener("swap_player", _swap_player, priority=1)
class User(IRCContext): class User(IRCContext):
@ -220,6 +213,10 @@ class User(IRCContext):
self.account = account self.account = account
self.channels = {} self.channels = {}
self.timestamp = time.time() 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}: if Bot is not None and Bot.nick == nick and {Bot.ident, Bot.host, Bot.realname, Bot.account} == {None}:
self = Bot self = Bot
@ -314,6 +311,35 @@ class User(IRCContext):
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
return self 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): 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)) 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 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 @rawnick.setter
def rawnick(self, rawnick): 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)' class BotUser(User): # TODO: change all the 'if x is Bot' for 'if isinstance(x, BotUser)'

View File

@ -49,6 +49,7 @@ import src.settings as var
from src.utilities import * from src.utilities import *
from src import db, events, dispatcher, channels, users, hooks, logger, debuglog, errlog, plog 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.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.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.messages import messages
from src.warnings import * from src.warnings import *
@ -72,8 +73,8 @@ var.LAST_GOAT = {}
var.USERS = {} var.USERS = {}
var.ADMIN_PINGING = False var.ADMIN_PINGING = False
var.ORIGINAL_ROLES = {} # type: Dict[str, Set[users.User]] var.ORIGINAL_ROLES = UserDict() # type: Dict[str, Set[users.User]]
var.DCED_LOSERS = set() # type: Set[users.User] var.DCED_LOSERS = UserSet() # type: Set[users.User]
var.PLAYERS = {} var.PLAYERS = {}
var.DCED_PLAYERS = {} var.DCED_PLAYERS = {}
var.ADMIN_TO_PING = None var.ADMIN_TO_PING = None
@ -82,6 +83,16 @@ var.PINGING_IFS = False
var.TIMERS = {} var.TIMERS = {}
var.OLD_MODES = defaultdict(set) 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.ORIGINAL_SETTINGS = {}
var.CURRENT_GAMEMODE = var.GAME_MODES["default"][0]() var.CURRENT_GAMEMODE = var.GAME_MODES["default"][0]()
@ -297,11 +308,9 @@ def reset_modes_timers(var):
def reset(): def reset():
var.PHASE = "none" # "join", "day", or "night" var.PHASE = "none" # "join", "day", or "night"
var.GAME_ID = 0 var.GAME_ID = 0
var.ALL_PLAYERS.clear()
var.RESTART_TRIES = 0 var.RESTART_TRIES = 0
var.DEAD = set() 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 = set() # keeps track of who already joined this game at least once (hostmasks)
var.JOINED_THIS_GAME_ACCS = set() # same, except accounts var.JOINED_THIS_GAME_ACCS = set() # same, except accounts
var.PINGED_ALREADY = set() var.PINGED_ALREADY = set()
@ -321,8 +330,13 @@ def reset():
var.DCED_PLAYERS.clear() var.DCED_PLAYERS.clear()
var.DISCONNECTED.clear() var.DISCONNECTED.clear()
var.DCED_LOSERS.clear() var.DCED_LOSERS.clear()
var.SPECTATING_WOLFCHAT = set() var.SPECTATING_WOLFCHAT.clear()
var.SPECTATING_DEADCHAT = set() var.SPECTATING_DEADCHAT.clear()
var.ROLES.clear()
var.ORIGINAL_ROLES.clear()
var.ROLES["person"] = UserSet()
var.MAIN_ROLES.clear()
evt = Event("reset", {}) evt = Event("reset", {})
evt.dispatch(var) evt.dispatch(var)
@ -582,9 +596,8 @@ def replace(var, wrapper, message):
return return
if users.equals(target.account, wrapper.source.account) and target is not wrapper.source: 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) rename_player(var, wrapper.source, target.nick)
target.swap(wrapper.source)
if var.PHASE in var.GAME_PHASES: if var.PHASE in var.GAME_PHASES:
return_to_village(var, target, show_message=False) 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)) channels.Main.send(messages["player_swap"].format(wrapper.source, target))
myrole.caller(wrapper.source.client, wrapper.source.nick, wrapper.target.name, "") # FIXME: Old API 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) @command("pingif", "pingme", "pingat", "pingpref", pm=True)
def altpinger(var, wrapper, message): def altpinger(var, wrapper, message):
@ -2006,41 +2006,41 @@ def stop_game(winner="", abort=False, additional_winners=None, log=True):
roles_msg = [] roles_msg = []
origroles = {} # user-based list of original roles origroles = {} # user-based list of original roles
rolelist = copy.deepcopy(var.ORIGINAL_ROLES) with copy.deepcopy(var.ORIGINAL_ROLES) as rolelist:
for role, playerlist in var.ORIGINAL_ROLES.items(): for role, playerlist in var.ORIGINAL_ROLES.items():
if role in var.TEMPLATE_RESTRICTIONS.keys(): if role in var.TEMPLATE_RESTRICTIONS.keys():
continue continue
for p in playerlist: for p in playerlist:
final = var.FINAL_ROLES.get(p.nick, role) final = var.FINAL_ROLES.get(p.nick, role)
if role != final: if role != final:
origroles[p] = role origroles[p] = role
rolelist[role].remove(p) rolelist[role].remove(p)
rolelist[final].add(p) rolelist[final].add(p)
prev = False prev = False
for role in role_order(): for role in role_order():
if len(rolelist[role]) == 0: if len(rolelist[role]) == 0:
continue continue
playersformatted = [] playersformatted = []
for p in rolelist[role]: for p in rolelist[role]:
if p in origroles and role not in var.TEMPLATE_RESTRICTIONS.keys(): if p in origroles and role not in var.TEMPLATE_RESTRICTIONS.keys():
playersformatted.append("\u0002{0}\u0002 ({1}{2})".format(p, playersformatted.append("\u0002{0}\u0002 ({1}{2})".format(p,
"" if prev else "was ", origroles[p])) "" if prev else "was ", origroles[p]))
prev = True prev = True
elif role == "amnesiac": elif role == "amnesiac":
playersformatted.append("\u0002{0}\u0002 (would be {1})".format(p, playersformatted.append("\u0002{0}\u0002 (would be {1})".format(p,
var.AMNESIAC_ROLES[p.nick])) 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: else:
playersformatted.append("\u0002{0}\u0002".format(p)) msg = "The {2} were {0}, and {1}."
if len(rolelist[role]) == 2: roles_msg.append(msg.format(", ".join(playersformatted[0:-1]),
msg = "The {1} were {0[0]} and {0[1]}." playersformatted[-1],
roles_msg.append(msg.format(playersformatted, plural(role))) 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)))
message = "" message = ""
count = 0 count = 0
if not abort: if not abort:
@ -2919,8 +2919,7 @@ def return_to_village(var, target, *, show_message, new_user=None):
if new_user is not target: if new_user is not target:
# different users, perform a swap. This will clean up disconnected users. # different users, perform a swap. This will clean up disconnected users.
evt = Event("swap_player", {}) target.swap(new_user)
evt.dispatch(var, target, new_user)
if target.nick != new_user.nick: if target.nick != new_user.nick:
# have a nickchange, update tracking vars # have a nickchange, update tracking vars
@ -2950,7 +2949,7 @@ def rename_player(var, user, prefix):
nick = user.nick nick = user.nick
event = Event("rename_player", {}) 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 user in var.ALL_PLAYERS:
if var.PHASE in var.GAME_PHASES: if var.PHASE in var.GAME_PHASES:
@ -3247,7 +3246,7 @@ def begin_day(cli):
var.LUCKY = set() var.LUCKY = set()
var.DISEASED = set() var.DISEASED = set()
var.MISDIRECTED = set() var.MISDIRECTED = set()
var.DYING = set() var.DYING.clear()
var.LAST_GOAT.clear() var.LAST_GOAT.clear()
msg = messages["villagers_lynch"].format(botconfig.CMD_CHAR, len(list_players()) // 2 + 1) msg = messages["villagers_lynch"].format(botconfig.CMD_CHAR, len(list_players()) // 2 + 1)
cli.msg(chan, msg) cli.msg(chan, msg)
@ -4488,7 +4487,7 @@ def immunize(cli, nick, chan, rest):
if check_exchange(cli, nick, victim): if check_exchange(cli, nick, victim):
return return
evt = Event("doctor_immunize", {"success": True, "message": "villager_immunized"}) 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)) pm(cli, nick, messages["doctor_success"].format(victim))
lycan = False lycan = False
if victim in var.DISEASED: 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) 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))) 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",), @cmd("pass", chan=False, pm=True, playing=True, phases=("night",), roles=("turncoat", "warlock"))
roles=("turncoat", "warlock"))
def pass_cmd(cli, nick, chan, rest): def pass_cmd(cli, nick, chan, rest):
"""Decline to use your special power for that night.""" """Decline to use your special power for that night."""
nickrole = get_role(nick) nickrole = get_role(nick)
@ -4976,7 +4974,7 @@ def transition_night(cli):
var.FIRST_NIGHT = (var.NIGHT_COUNT == 1) var.FIRST_NIGHT = (var.NIGHT_COUNT == 1)
event_begin = Event("transition_night_begin", {}) event_begin = Event("transition_night_begin", {})
event_begin.dispatch(cli, var) event_begin.dispatch(var)
if var.DEVOICE_DURING_NIGHT: if var.DEVOICE_DURING_NIGHT:
modes = [] modes = []
@ -5381,7 +5379,7 @@ def start(cli, nick, chan, forced = False, restart = ""):
addroles = {} addroles = {}
event = Event("role_attribution", {"addroles": 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"] addroles = event.data["addroles"]
for index in range(len(var.ROLE_INDEX) - 1, -1, -1): for index in range(len(var.ROLE_INDEX) - 1, -1, -1):
if var.ROLE_INDEX[index] <= len(villagers): 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"]): for decor in (COMMANDS["join"] + COMMANDS["start"]):
decor(_command_disabled) 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.GUNNERS = {}
var.OBSERVED = {} var.OBSERVED = {}
var.CLONED = {} var.CLONED = {}
@ -5453,7 +5453,6 @@ def start(cli, nick, chan, forced = False, restart = ""):
var.DAY_COUNT = 0 var.DAY_COUNT = 0
var.DISEASED_WOLVES = False var.DISEASED_WOLVES = False
var.TRAITOR_TURNED = False var.TRAITOR_TURNED = False
var.MAIN_ROLES = {}
var.FINAL_ROLES = {} var.FINAL_ROLES = {}
var.ORIGINAL_LOVERS = {} var.ORIGINAL_LOVERS = {}
var.LYCANTHROPES = set() var.LYCANTHROPES = set()
@ -5478,22 +5477,23 @@ def start(cli, nick, chan, forced = False, restart = ""):
var.EXTRA_WOLVES = 0 var.EXTRA_WOLVES = 0
var.PRIESTS = set() var.PRIESTS = set()
var.CONSECRATING = set() var.CONSECRATING = set()
var.DYING = set() var.DYING.clear()
var.PRAYED = {} var.PRAYED = {}
var.DEADCHAT_PLAYERS = set() var.DEADCHAT_PLAYERS.clear()
var.SPECTATING_WOLFCHAT = set() var.SPECTATING_WOLFCHAT.clear()
var.SPECTATING_DEADCHAT = set() var.SPECTATING_DEADCHAT.clear()
for role, count in addroles.items(): for role, count in addroles.items():
if role in var.TEMPLATE_RESTRICTIONS.keys(): if role in var.TEMPLATE_RESTRICTIONS.keys():
var.ROLES[role] = [None] * count var.ROLES[role] = [None] * count
continue # We deal with those later, see below continue # We deal with those later, see below
selected = random.sample(villagers, count) selected = random.sample(villagers, count)
for x in selected: for x in selected:
var.MAIN_ROLES[users._get(x)] = role # FIXME var.MAIN_ROLES[users._get(x)] = role # FIXME
villagers.remove(x) 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] fixed_count = count - roleset_roles[role]
if fixed_count > 0: if fixed_count > 0:
for pr in possible_rolesets: for pr in possible_rolesets:
@ -5523,17 +5523,18 @@ def start(cli, nick, chan, forced = False, restart = ""):
if len(possible) < len(var.ROLES[template]): if len(possible) < len(var.ROLES[template]):
cli.msg(chan, messages["not_enough_targets"].format(template)) cli.msg(chan, messages["not_enough_targets"].format(template))
if var.ORIGINAL_SETTINGS: if var.ORIGINAL_SETTINGS:
var.ROLES = {"person": set(var.ALL_PLAYERS)} var.ROLES.clear()
var.ROLES["person"] = UserSet(var.ALL_PLAYERS)
reset_settings() reset_settings()
cli.msg(chan, messages["default_reset"].format(botconfig.CMD_CHAR)) cli.msg(chan, messages["default_reset"].format(botconfig.CMD_CHAR))
var.PHASE = "join" var.PHASE = "join"
return return
else: else:
cli.msg(chan, messages["role_skipped"]) cli.msg(chan, messages["role_skipped"])
var.ROLES[template] = set() var.ROLES[template] = UserSet()
continue 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 # Handle gunner
cannot_be_sharpshooter = get_players(var.TEMPLATE_RESTRICTIONS["sharpshooter"]) cannot_be_sharpshooter = get_players(var.TEMPLATE_RESTRICTIONS["sharpshooter"])
@ -5550,8 +5551,7 @@ def start(cli, nick, chan, forced = False, restart = ""):
else: else:
var.GUNNERS[gunner.nick] = math.ceil(var.SHOTS_MULTIPLIER * len(pl)) var.GUNNERS[gunner.nick] = math.ceil(var.SHOTS_MULTIPLIER * len(pl))
var.ROLES["sharpshooter"] = set(var.ROLES["sharpshooter"]) var.ROLES["sharpshooter"] = UserSet(p for p in var.ROLES["sharpshooter"] if p is not None)
var.ROLES["sharpshooter"].discard(None)
with var.WARNING_LOCK: # cancel timers with var.WARNING_LOCK: # cancel timers
for name in ("join", "join_pinger", "start_votes"): for name in ("join", "join_pinger", "start_votes"):
@ -5564,7 +5564,7 @@ def start(cli, nick, chan, forced = False, restart = ""):
var.LAST_VOTES = None var.LAST_VOTES = None
event = Event("role_assignment", {}) 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: if not restart:
gamemode = var.CURRENT_GAMEMODE.name 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.msg(chan, messages["welcome"].format(", ".join(pl), gamemode, options))
cli.mode(chan, "+m") 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; # Handle amnesiac;
# matchmaker is blacklisted if AMNESIAC_NIGHTS > 1 due to only being able to act night 1 # 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: except Exception as e:
wrapper.send("{e.__class__.__name__}: {e}".format(e=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 # DO NOT MAKE THIS A PMCOMMAND ALSO
@cmd("force", flag="d") @cmd("force", flag="d")
def force(cli, nick, chan, rest): def force(cli, nick, chan, rest):
@ -6987,7 +6986,7 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS:
elif who == "gunner": elif who == "gunner":
tgt = {users._get(g) for g in var.GUNNERS.keys()} # FIXME tgt = {users._get(g) for g in var.GUNNERS.keys()} # FIXME
else: else:
tgt = var.ROLES[who].copy() tgt = set(var.ROLES[who])
comm = rst.pop(0).lower().replace(botconfig.CMD_CHAR, "", 1) comm = rst.pop(0).lower().replace(botconfig.CMD_CHAR, "", 1)
if comm in COMMANDS and not COMMANDS[comm][0].owner_only: 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: else:
cli.msg(chan, messages["command_not_found"]) 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: # vim: set sw=4 expandtab: