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

View File

@ -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.",

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.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,12 +1120,11 @@ class MaelstromMode(GameMode):
def _on_join(self, var, wrapper):
from src import hooks, channels
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)
mainroles = copy.deepcopy(var.MAIN_ROLES)
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)
if not wrapper.source.is_fake or not botconfig.DEBUG_MODE:
@ -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)

View File

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

View File

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

View File

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

View File

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

View File

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

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 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,7 +63,7 @@ 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)
with TARGETS[user].intersection(pl) as targets:
if targets:
target = random.choice(list(targets))
prots = deque(var.ACTIVE_PROTECTIONS[target.nick])
@ -106,22 +107,6 @@ 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)

View File

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

View File

@ -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",)))

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 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 = []

View File

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

View File

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

View File

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

View File

@ -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,7 +2006,7 @@ 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)
with copy.deepcopy(var.ORIGINAL_ROLES) as rolelist:
for role, playerlist in var.ORIGINAL_ROLES.items():
if role in var.TEMPLATE_RESTRICTIONS.keys():
continue
@ -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: