Overhaul how templates work

Templates are still applied on game start according to
var.TEMPLATE_RESTRICTIONS, but now any arbitrary role can be applied as
a template during gameplay without breaking things horribly.

Speaking of breaking things horribly, things are probably broken
horribly due to this. It was lightly tested, but there's a lot of
fundamental stuff that changed.
This commit is contained in:
skizzerz 2017-03-31 12:45:53 -05:00
parent f096814fec
commit 8a8a79627f
12 changed files with 176 additions and 177 deletions

View File

@ -4,18 +4,21 @@ from src import users
__all__ = ["get_players", "get_target"]
def get_players(roles=None):
def get_players(roles=None, *, mainroles=None):
if mainroles is None:
mainroles = var.MAIN_ROLES
if roles is None:
roles = var.ROLES
roles = set(mainroles.values())
pl = set()
for user, role in mainroles.items():
if role in roles:
pl.add(user)
players = set()
for x in roles:
if x in var.TEMPLATE_RESTRICTIONS:
continue
for p in var.ROLES.get(x, ()):
players.add(p)
return [p for p in var.ALL_PLAYERS if p.nick in players]
if mainroles is not var.MAIN_ROLES:
# we weren't given an actual player list (possibly),
# so the elements of pl are not necessarily in var.ALL_PLAYERS
return list(pl)
return [p for p in var.ALL_PLAYERS if p in pl]
def get_target(var, wrapper, message, *, allow_self=False, allow_bot=False):

View File

@ -352,7 +352,7 @@ class EvilVillageMode(GameMode):
def teardown(self):
events.remove_listener("chk_win", self.chk_win)
def chk_win(self, evt, cli, var, rolemap, lpl, lwolves, lrealwolves):
def chk_win(self, evt, cli, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
lsafes = len(list_players(["oracle", "seer", "guardian angel", "shaman", "hunter", "villager"]))
lcultists = len(list_players(["cultist"]))
evt.stop_processing = True
@ -615,11 +615,18 @@ class RandomMode(GameMode):
addroles["assassin"] = random.randrange(max(int(len(villagers) ** 1.2 / 8), 1))
rolemap = defaultdict(set)
for r,c in addroles.items():
if c > 0:
rolemap[r] = set(range(c))
mainroles = {}
i = 0
for role, count in addroles.items():
if count > 0:
for j in range(count):
u = users.FakeUser.from_nick(str(i + j))
rolemap[role].add(u.nick)
if role not in var.TEMPLATE_RESTRICTIONS:
mainroles[u] = role
i += count
if chk_win_conditions(cli, rolemap, end_game=False):
if chk_win_conditions(cli, rolemap, mainroles, end_game=False):
return self.role_attribution(evt, cli, var, chk_win_conditions, villagers)
evt.prevent_default = True
@ -769,7 +776,7 @@ class GuardianMode(GameMode):
def teardown(self):
events.remove_listener("chk_win", self.chk_win)
def chk_win(self, evt, cli, var, rolemap, lpl, lwolves, lrealwolves):
def chk_win(self, evt, cli, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
lguardians = len(list_players(["guardian angel", "bodyguard"]))
if lpl < 1:
@ -1137,20 +1144,14 @@ class SleepyMode(GameMode):
cultists = [p for p in var.ROLES["cultist"] if p in pl and random.random() < turn_chance]
cli.msg(botconfig.CHANNEL, messages["sleepy_priest_death"])
for seer in seers:
var.ROLES["seer"].remove(seer)
var.ROLES["doomsayer"].add(seer)
var.FINAL_ROLES[seer] = "doomsayer"
change_role(users._get(seer), "seer", "doomsayer") # FIXME
pm(cli, seer, messages["sleepy_doomsayer_turn"])
relay_wolfchat_command(cli, seer, messages["sleepy_doomsayer_wolfchat"].format(seer), var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True)
for harlot in harlots:
var.ROLES["harlot"].remove(harlot)
var.ROLES["succubus"].add(harlot)
var.FINAL_ROLES[harlot] = "succubus"
change_role(users._get(harlot), "harlot", "succubus") # FIXME
pm(cli, harlot, messages["sleepy_succubus_turn"])
for cultist in cultists:
var.ROLES["cultist"].remove(cultist)
var.ROLES["demoniac"].add(cultist)
var.FINAL_ROLES[cultist] = "demoniac"
change_role(users._get(cultist), "cultist", "demoniac") # FIXME
pm(cli, cultist, messages["sleepy_demoniac_turn"])
# NOTE: chk_win is called by del_player, don't need to call it here even though this has a chance of ending game
@ -1216,15 +1217,18 @@ class MaelstromMode(GameMode):
def _on_join(self, var, wrapper):
role = random.choice(self.roles)
newlist = copy.deepcopy(var.ROLES)
newlist[role].add(wrapper.source)
rolemap = copy.deepcopy(var.ROLES)
rolemap[role].add(wrapper.source.nick) # FIXME: add user instead of nick (can only be done once var.ROLES itself uses users)
mainroles = copy.deepcopy(var.MAIN_ROLES)
mainroles[wrapper.source] = role
if self.chk_win_conditions(wrapper.client, newlist, end_game=False):
if self.chk_win_conditions(wrapper.client, rolemap, mainroles, end_game=False):
return self._on_join(var, wrapper)
var.ROLES[role].add(wrapper.source.nick) # FIXME: add user instead of nick
var.ORIGINAL_ROLES[role].add(wrapper.source.nick)
var.FINAL_ROLES[wrapper.source.nick] = role
var.MAIN_ROLES[wrapper.source] = role
var.LAST_SAID_TIME[wrapper.source.nick] = datetime.now()
if wrapper.source.nick in var.USERS:
var.PLAYERS[wrapper.source.nick] = var.USERS[wrapper.source.nick]
@ -1295,6 +1299,7 @@ class MaelstromMode(GameMode):
var.ORIGINAL_ROLES[r].discard(p)
var.ORIGINAL_ROLES[role].add(p)
var.FINAL_ROLES[p] = role
var.MAIN_ROLES[users._get(p)] = role # FIXME
def _role_attribution(self, cli, var, villagers, do_templates):
lpl = len(villagers) - 1
@ -1323,14 +1328,19 @@ class MaelstromMode(GameMode):
if random.randrange(100) == 0 and addroles.get("villager", 0) > 0:
addroles["blessed villager"] = 1
rolemap = defaultdict(list)
pcount = 0
for r,c in addroles.items():
if c > 0:
rolemap[r] = list(range(pcount, pcount+c))
pcount += c
rolemap = defaultdict(set)
mainroles = {}
i = 0
for role, count in addroles.items():
if count > 0:
for j in range(count):
u = users.FakeUser.from_nick(str(i + j))
rolemap[role].add(u.nick)
if role not in var.TEMPLATE_RESTRICTIONS:
mainroles[u] = role
i += count
if self.chk_win_conditions(cli, rolemap, end_game=False):
if self.chk_win_conditions(cli, rolemap, mainroles, end_game=False):
return self._role_attribution(cli, var, villagers, do_templates)
return addroles

View File

@ -10,6 +10,7 @@ from src.events import Event
SEEN = set()
# FIXME: this needs to be split into seer.py, oracle.py, and augur.py
@cmd("see", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("seer", "oracle", "augur"))
def see(cli, nick, chan, rest):
"""Use your paranormal powers to determine the role or alignment of a player."""

View File

@ -6,7 +6,7 @@ from collections import defaultdict, deque
import botconfig
import src.settings as var
from src.utilities import *
from src import debuglog, errlog, plog
from src import debuglog, errlog, plog, users
from src.decorators import cmd, event_listener
from src.messages import messages
from src.events import Event
@ -51,6 +51,7 @@ DECEIT = set() # type: Set[str]
havetotem = [] # type: List[str]
brokentotem = set() # type: Set[str]
# FIXME: this needs to be split into shaman.py, wolfshaman.py, and crazedshaman.py
@cmd("give", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=var.TOTEM_ORDER)
@cmd("totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=var.TOTEM_ORDER)
def totem(cli, nick, chan, rest, prefix="You"): # XXX: The transition_day_begin event needs updating alongside this
@ -63,7 +64,7 @@ def totem(cli, nick, chan, rest, prefix="You"): # XXX: The transition_day_begin
return
original_victim = victim
role = get_role(nick)
role = get_role(nick) # FIXME: this is bad, check if nick is in var.ROLES[thingy] instead once split
totem = ""
if role != "crazed shaman":
totem = " of " + TOTEMS[nick]
@ -252,12 +253,11 @@ def on_chk_decision_lynch3(evt, cli, var, voters):
rev_evt = Event("revealing_totem", {"role": role})
rev_evt.dispatch(cli, var, votee)
role = rev_evt.data["role"]
# TODO: once amnesiac is split, roll this into the revealing_totem event
if role == "amnesiac":
var.ROLES["amnesiac"].remove(votee)
role = var.AMNESIAC_ROLES[votee]
var.ROLES[role].add(votee)
change_role(users._get(votee), "amnesiac", role) # FIXME
var.AMNESIACS.add(votee)
var.FINAL_ROLES[votee] = role
pm(cli, votee, messages["totem_amnesia_clear"])
# If wolfteam, don't bother giving list of wolves since night is about to start anyway
# Existing wolves also know that someone just joined their team because revealing totem says what they are
@ -514,7 +514,7 @@ def on_transition_night_end(evt, cli, var):
random.shuffle(pl)
if shaman in LASTGIVEN and LASTGIVEN[shaman] in pl:
pl.remove(LASTGIVEN[shaman])
role = get_role(shaman)
role = get_role(shaman) # FIXME: don't use get_role here once split into one file per role
indx = var.TOTEM_ORDER.index(role)
target = 0
rand = random.random() * max_totems[var.TOTEM_ORDER[indx]]
@ -645,7 +645,8 @@ def on_get_role_metadata(evt, var, kind):
if kind == "night_kills":
# only add shamans here if they were given a death totem
# even though retribution kills, it is given a special kill message
# note that all shaman types (shaman/CS/wolf shaman) are lumped under the "shaman" key
# note that all shaman types (shaman/CS/wolf shaman) are lumped under the "shaman" key (for now),
# this will change so they all get their own key in the future (once this is split into 3 files)
evt.data["shaman"] = list(TOTEMS.values()).count("death")
# vim: set sw=4 expandtab:

View File

@ -34,16 +34,15 @@ def hvisit(cli, nick, chan, rest):
if evt.prevent_default:
return
victim = evt.data["target"]
vrole = get_role(victim)
VISITED[nick] = victim
if vrole != "succubus":
if victim not in var.ROLES["succubus"]:
ENTRANCED.add(victim)
pm(cli, nick, messages["succubus_target_success"].format(victim))
else:
pm(cli, nick, messages["harlot_success"].format(victim))
if nick != victim:
if vrole != "succubus":
if victim not in var.ROLES["succubus"]:
pm(cli, victim, messages["notify_succubus_target"].format(nick))
else:
pm(cli, victim, messages["harlot_success"].format(nick))
@ -68,7 +67,7 @@ def hvisit(cli, nick, chan, rest):
pm(cli, victim, messages["no_kill_succubus"].format(var.BITE_PREFERENCES[victim]))
del var.BITE_PREFERENCES[victim]
debuglog("{0} ({1}) VISIT: {2} ({3})".format(nick, get_role(nick), victim, vrole))
debuglog("{0} (succubus) VISIT: {1} ({2})".format(nick, victim, get_role(victim)))
chk_nightdone(cli)
@cmd("pass", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("succubus",))
@ -79,12 +78,12 @@ def pass_cmd(cli, nick, chan, rest):
return
VISITED[nick] = None
pm(cli, nick, messages["succubus_pass"])
debuglog("{0} ({1}) PASS".format(nick, get_role(nick)))
debuglog("{0} (succubus) PASS".format(nick))
chk_nightdone(cli)
@event_listener("harlot_visit")
def on_harlot_visit(evt, cli, var, nick, victim):
if get_role(victim) == "succubus":
if victim in var.ROLES["succubus"]:
pm(cli, nick, messages["notify_succubus_target"].format(victim))
pm(cli, victim, messages["succubus_harlot_success"].format(nick))
ENTRANCED.add(nick)
@ -150,7 +149,7 @@ def on_player_win(evt, var, user, role, winner, survived):
evt.data["won"] = True
@event_listener("chk_win", priority=2)
def on_chk_win(evt, cli, var, rolemap, lpl, lwolves, lrealwolves):
def on_chk_win(evt, cli, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
lsuccubi = len(rolemap.get("succubus", ()))
lentranced = len(ENTRANCED - var.DEAD)
if var.PHASE == "day" and lpl - lsuccubi == lentranced:
@ -214,7 +213,7 @@ def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers):
# killing off everyone else that is entranced so they don't need to bother
dlc = list(evt.params.deadlist)
dlc.extend(entranced_alive - {e})
debuglog("{0} ({1}) SUCCUBUS DEATH KILL: {2} ({3})".format(nick, nickrole, e, get_role(e)))
debuglog("{0} (succubus) SUCCUBUS DEATH KILL: {1} ({2})".format(nick, e, get_role(e)))
evt.params.del_player(cli, e, end_game=False, killer_role="succubus",
deadlist=dlc, original=evt.params.original, ismain=False)
evt.data["pl"] = evt.params.refresh_pl(evt.data["pl"])
@ -295,6 +294,11 @@ def on_transition_day(evt, cli, var):
def on_get_special(evt, cli, var):
evt.data["special"].update(var.ROLES["succubus"])
@event_listener("vg_kill")
def on_vg_kill(evt, var, ghost, target):
if ghost.nick in ENTRANCED:
evt.data["pl"] -= var.ROLES["succubus"]
@event_listener("rename_player")
def on_rename(evt, cli, var, prefix, nick):
if prefix in ENTRANCED:

View File

@ -7,7 +7,7 @@ from collections import defaultdict
import botconfig
import src.settings as var
from src.utilities import *
from src import debuglog, errlog, plog
from src import debuglog, errlog, plog, users
from src.decorators import cmd, event_listener
from src.messages import messages
from src.events import Event
@ -60,13 +60,14 @@ def on_update_stats3(evt, cli, var, nick, nickrole, nickreveal, nicktpls):
# and therefore cannot be traitor. However, we currently do not have the logic to deduce this
@event_listener("chk_win", priority=1.1)
def on_chk_win(evt, cli, var, rolemap, lpl, lwolves, lrealwolves):
def on_chk_win(evt, cli, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
did_something = False
if lrealwolves == 0:
for traitor in list(rolemap["traitor"]):
rolemap["wolf"].add(traitor)
rolemap["traitor"].remove(traitor)
rolemap["cursed villager"].discard(traitor)
mainroles[users._get(traitor)] = "wolf" # FIXME
did_something = True
if var.PHASE in var.GAME_PHASES:
var.FINAL_ROLES[traitor] = "wolf"

View File

@ -13,8 +13,7 @@ KILLS = {} # type: Dict[str, str]
GHOSTS = {} # type: Dict[users.User, str]
# temporary holding variable, only non-empty during transition_day
# as such, no need to track nick changes, etc. with it
drivenoff = {} # type: Dict[str, str]
drivenoff = {} # 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):
@ -49,7 +48,7 @@ def vg_kill(var, wrapper, message):
wrapper.pm(messages["player_kill"].format(orig))
debuglog("{0} ({1}) KILL: {2} ({3})".format(wrapper.source.nick, get_role(wrapper.source.nick), victim, get_role(victim)))
debuglog("{0} (vengeful ghost) KILL: {1} ({2})".format(wrapper.source.nick, victim, get_role(victim)))
chk_nightdone(wrapper.source.client)
@command("retract", "r", chan=False, pm=True, playing=False, phases=("night",))
@ -64,7 +63,7 @@ def vg_retract(var, wrapper, message):
@event_listener("list_participants")
def on_list_participants(evt, var):
evt.data["pl"].extend([p.nick for p in GHOSTS if GHOSTS[p][0] != "!"])
evt.data["pl"].extend([p for p in drivenoff])
evt.data["pl"].extend([p.nick for p in drivenoff])
@event_listener("player_win", priority=1)
def on_player_win(evt, var, user, role, winner, survived):
@ -141,9 +140,6 @@ def on_transition_day_begin(evt, cli, var):
evt = Event("vg_kill", {"pl": choice})
evt.dispatch(var, ghost, target)
choice = evt.data["pl"]
# roll this into the above event once succubus is split off
if ghost.nick in var.ENTRANCED:
choice -= var.ROLES["succubus"]
if choice:
KILLS[ghost.nick] = random.choice(list(choice))
@ -173,19 +169,20 @@ def on_transition_day6(evt, cli, var):
@event_listener("retribution_kill", priority=6) # FIXME: This function, and all of the event
def on_retribution_kill(evt, cli, var, victim, orig_target):
t = evt.data["target"]
if users._get(t) in GHOSTS:
drivenoff[t] = GHOSTS[users._get(t)]
GHOSTS[users._get(t)] = "!" + GHOSTS[users._get(t)]
user = users._get(t)
if user in GHOSTS:
drivenoff[user] = GHOSTS[user]
GHOSTS[user] = "!" + GHOSTS[user]
evt.data["message"].append(messages["totem_banish"].format(victim, t))
evt.data["target"] = None
@event_listener("get_participant_role")
def on_get_participant_role(evt, var, nick):
if users._get(nick) in GHOSTS: # FIXME
if nick in drivenoff:
against = drivenoff[nick]
def on_get_participant_role(evt, var, user):
if user in GHOSTS:
if user in drivenoff:
against = drivenoff[user]
else:
against = GHOSTS[users._get(nick)]
against = GHOSTS[user]
if against == "villagers":
evt.data["role"] = "wolf"
elif against == "wolves":

View File

@ -50,7 +50,7 @@ def on_player_win(evt, var, user, role, winner, survived):
evt.data["iwon"] = survived
@event_listener("chk_win", priority=3)
def on_chk_win(evt, cli, var, rolemap, lpl, lwolves, lrealwolves):
def on_chk_win(evt, cli, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
if evt.data["winner"] is not None:
return
if lrealwolves == 0:

View File

@ -31,12 +31,12 @@ def choose_idol(cli, nick, chan, rest):
IDOLS[nick] = victim
pm(cli, nick, messages["wild_child_success"].format(victim))
debuglog("{0} ({1}) IDOLIZE: {2} ({3})".format(nick, get_role(nick), victim, get_role(victim)))
debuglog("{0} (wild child) IDOLIZE: {1} ({2})".format(nick, victim, get_role(victim)))
chk_nightdone(cli)
@event_listener("see")
def on_see(evt, cli, var, seer, victim):
if get_role(seer) != "augur" and victim in WILD_CHILDREN:
if victim in WILD_CHILDREN:
evt.data["role"] = "wild child"
@event_listener("rename_player")
@ -77,7 +77,7 @@ def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role):
@event_listener("myrole")
def on_myrole(evt, cli, var, nick):
if evt.data["role"] == "wild child" and nick in IDOLS:
if nick in IDOLS:
evt.data["messages"].append(messages["wild_child_idol"].format(IDOLS[nick]))
@event_listener("del_player")
@ -89,11 +89,11 @@ def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers):
if child not in IDOLS or child in evt.params.deadlist or IDOLS[child] not in evt.params.deadlist:
continue
# change their main role to wolf, even if wild child was a template
pm(cli, child, messages["idol_died"])
WILD_CHILDREN.add(child)
var.ROLES["wild child"].remove(child)
var.ROLES["wolf"].add(child)
var.FINAL_ROLES[child] = "wolf"
change_role(users._get(child), get_role(child), "wolf") # FIXME
var.ROLES["wild child"].discard(child)
wcroles = var.WOLFCHAT_ROLES
if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES:
@ -153,8 +153,8 @@ def on_revealroles_role(evt, var, wrapper, nick, role):
evt.data["special_case"].append("no idol picked yet")
@event_listener("get_reveal_role")
def on_get_reveal_role(evt, var, nick):
if nick in WILD_CHILDREN:
def on_get_reveal_role(evt, var, user):
if user.nick in WILD_CHILDREN:
evt.data["role"] = "wild child"
@event_listener("reset")

View File

@ -4,7 +4,7 @@ from collections import defaultdict
import src.settings as var
from src.utilities import *
from src import debuglog, errlog, plog
from src import debuglog, errlog, plog, users
from src.decorators import cmd, event_listener
from src.messages import messages
from src.events import Event
@ -89,7 +89,7 @@ def wolf_retract(cli, nick, chan, rest):
del KILLS[nick]
pm(cli, nick, messages["retracted_kill"])
relay_wolfchat_command(cli, nick, messages["wolfchat_retracted_kill"].format(nick), var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True)
if get_role(nick) == "alpha wolf" and nick in var.BITE_PREFERENCES:
if nick in var.ROLES["alpha wolf"] and nick in var.BITE_PREFERENCES:
del var.BITE_PREFERENCES[nick]
var.ALPHA_WOLVES.remove(nick)
pm(cli, nick, messages["no_bite"])
@ -99,7 +99,7 @@ def wolf_retract(cli, nick, chan, rest):
def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers):
if death_triggers:
# TODO: split into cub
if nickrole == "wolf cub":
if nick in var.ROLES["wolf cub"]:
var.ANGRY_WOLVES = True
# TODO: split into alpha
if nickrole in var.WOLF_ROLES:
@ -402,13 +402,16 @@ def on_transition_night_end(evt, cli, var):
pm(cli, wolf, messages["wolf_bite"])
@event_listener("chk_win", priority=1)
def on_chk_win(evt, cli, var, rolemap, lpl, lwolves, lrealwolves):
def on_chk_win(evt, cli, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
# TODO: split into cub
did_something = False
if lrealwolves == 0:
for wc in list(rolemap["wolf cub"]):
rolemap["wolf"].add(wc)
rolemap["wolf cub"].remove(wc)
wcu = users._get(wc) # FIXME
if mainroles[wcu] == "wolf cub":
mainroles[wcu] = "wolf"
did_something = True
if var.PHASE in var.GAME_PHASES:
# don't set cub's FINAL_ROLE to wolf, since we want them listed in endgame

View File

@ -14,7 +14,7 @@ __all__ = ["pm", "is_fake_nick", "mass_mode", "mass_privmsg", "reply",
"chk_win", "irc_lower", "irc_equals", "is_role", "match_hostmask",
"is_owner", "is_admin", "plural", "singular", "list_players",
"list_players_and_roles", "list_participants", "get_role", "get_roles",
"get_reveal_role", "get_templates", "role_order", "break_long_message",
"get_reveal_role", "get_templates", "change_role", "role_order", "break_long_message",
"complete_match","complete_one_match", "get_victim", "get_nick", "InvalidModeException"]
# message either privmsg or notice, depending on user settings
def pm(cli, target, message):
@ -309,30 +309,14 @@ def singular(plural):
# otherwise we just added an s on the end
return plural[:-1]
def list_players(roles=None, *, rolemap=None):
if rolemap is None:
rolemap = var.ROLES
if roles is None:
roles = rolemap.keys()
pl = set()
for x in roles:
if x in var.TEMPLATE_RESTRICTIONS:
continue
pl.update(rolemap.get(x, ()))
if rolemap is not var.ROLES:
# we weren't given an actual player list (possibly),
# so the elements of pl are not necessarily in var.ALL_PLAYERS
return list(pl)
return [p.nick for p in var.ALL_PLAYERS if p.nick in pl]
def list_players(roles=None, *, mainroles=None):
from src.functions import get_players
return [p.nick for p in get_players(roles, mainroles=mainroles)]
def list_players_and_roles():
plr = {}
for x in var.ROLES.keys():
if x in var.TEMPLATE_RESTRICTIONS.keys():
continue # only get actual roles
for p in var.ROLES[x]:
plr[p] = x
return plr
# TODO DEPRECATED: replace with iterating over var.MAIN_ROLES directly
# (and working with user objects instead of nicks)
return {u.nick: r for u, r in var.MAIN_ROLES.items()}
def list_participants():
"""List all people who are still able to participate in the game in some fashion."""
@ -342,19 +326,19 @@ def list_participants():
return evt.data["pl"][:]
def get_role(p):
for role, pl in var.ROLES.items():
if role in var.TEMPLATE_RESTRICTIONS.keys():
continue # only get actual roles
if p in pl:
return role
# FIXME: make the arg a user instead of a nick
from src import users
user = users._get(p)
role = var.MAIN_ROLES.get(user, None)
if role is not None:
return role
# not found in player list, see if they're a special participant
role = None
if p in list_participants():
evt = Event("get_participant_role", {"role": None})
evt.dispatch(var, p)
evt.dispatch(var, user)
role = evt.data["role"]
if role is None:
raise ValueError("Nick {0} isn't playing and has no defined participant role".format(p))
raise ValueError("User {0} isn't playing and has no defined participant role".format(user))
return role
def get_roles(*roles, rolemap=None):
@ -366,6 +350,8 @@ def get_roles(*roles, rolemap=None):
return list(itertools.chain(*all_roles))
def get_reveal_role(nick):
# FIXME: make the arg a user instead of a nick
from src import users
if var.HIDDEN_AMNESIAC and nick in var.ORIGINAL_ROLES["amnesiac"]:
role = "amnesiac"
elif var.HIDDEN_CLONE and nick in var.ORIGINAL_ROLES["clone"]:
@ -374,7 +360,7 @@ def get_reveal_role(nick):
role = get_role(nick)
evt = Event("get_reveal_role", {"role": role})
evt.dispatch(var, nick)
evt.dispatch(var, users._get(nick))
role = evt.data["role"]
if var.ROLE_REVEAL != "team":
@ -388,16 +374,24 @@ def get_reveal_role(nick):
return "village member"
def get_templates(nick):
# FIXME: make the arg a user instead of a nick
mainrole = get_role(nick)
tpl = []
for x in var.TEMPLATE_RESTRICTIONS.keys():
try:
if nick in var.ROLES[x]:
tpl.append(x)
except KeyError:
pass
for role, nicks in var.ROLES.items():
if nick in nicks and role != mainrole:
tpl.append(role)
return tpl
def change_role(user, oldrole, newrole, set_final=True):
var.ROLES[oldrole].remove(user.nick)
var.ROLES[newrole].add(user.nick)
# only adjust MAIN_ROLES/FINAL_ROLES if we're changing the user's actual role
if var.MAIN_ROLES[user] == oldrole:
var.MAIN_ROLES[user] = newrole
if set_final:
var.FINAL_ROLES[user.nick] = newrole
role_order = lambda: var.ROLE_GUIDE
def break_long_message(phrases, joinstr = " "):

View File

@ -300,6 +300,7 @@ def reset():
var.RESTART_TRIES = 0
var.DEAD = set()
var.ROLES = {"person" : set()}
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
@ -865,6 +866,7 @@ def join_player(var, wrapper, who=None, forced=False, *, sanity=True):
cmodes.append(("-" + mode, wrapper.source))
var.OLD_MODES[wrapper.source].add(mode)
var.ROLES["person"].add(wrapper.source.nick) # FIXME: Need to store Users, not nicks
var.MAIN_ROLES[wrapper.source] = "person"
var.ALL_PLAYERS.append(wrapper.source)
var.PHASE = "join"
with var.WAIT_TB_LOCK:
@ -923,6 +925,7 @@ def join_player(var, wrapper, who=None, forced=False, *, sanity=True):
var.SPECTATING_WOLFCHAT.discard(wrapper.source.nick)
return True
var.ROLES["person"].add(wrapper.source.nick)
var.MAIN_ROLES[wrapper.source] = "person"
if not wrapper.source.is_fake:
if wrapper.source.userhost not in var.JOINED_THIS_GAME and wrapper.source.account not in var.JOINED_THIS_GAME_ACCS:
# make sure this only happens once
@ -1856,10 +1859,9 @@ def chk_decision(cli, force=""):
# roles that end the game upon being lynched
if votee in var.ROLES["fool"]:
# ends game immediately, with fool as only winner
# we don't need get_reveal_role as the game ends on this point
# point: games with role reveal turned off will still call out fool
# games with team reveal will be inconsistent, but this is by design, not a bug
lmsg = random.choice(messages["lynch_reveal"]).format(votee, "", get_role(votee))
# hardcode "fool" as the role since game is ending due to them being lynched,
# so we want to show "fool" even if it's a template
lmsg = random.choice(messages["lynch_reveal"]).format(votee, "", "fool")
cli.msg(botconfig.CHANNEL, lmsg)
if chk_win(cli, winner="@" + votee):
return
@ -2270,9 +2272,9 @@ def chk_win(cli, end_game=True, winner=None):
if var.PHASE not in var.GAME_PHASES:
return False #some other thread already ended game probably
return chk_win_conditions(cli, var.ROLES, end_game, winner)
return chk_win_conditions(cli, var.ROLES, var.MAIN_ROLES, end_game, winner)
def chk_win_conditions(cli, rolemap, end_game=True, winner=None):
def chk_win_conditions(cli, rolemap, mainroles, end_game=True, winner=None):
"""Internal handler for the chk_win function."""
chan = botconfig.CHANNEL
with var.GRAVEYARD_LOCK:
@ -2283,7 +2285,7 @@ def chk_win_conditions(cli, rolemap, end_game=True, winner=None):
pl = evt.data["voters"]
lpl = len(pl)
else:
pl = set(list_players(rolemap=rolemap))
pl = set(list_players(mainroles=mainroles))
lpl = len(pl)
if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES:
@ -2294,10 +2296,10 @@ def chk_win_conditions(cli, rolemap, end_game=True, winner=None):
else:
wcroles = var.WOLFCHAT_ROLES
wolves = set(list_players(wcroles, rolemap=rolemap))
wolves = set(list_players(wcroles, mainroles=mainroles))
lwolves = len(wolves & pl)
lcubs = len(rolemap.get("wolf cub", ()))
lrealwolves = len(list_players(var.WOLF_ROLES - {"wolf cub"}, rolemap=rolemap))
lrealwolves = len(list_players(var.WOLF_ROLES - {"wolf cub"}, mainroles=mainroles))
lmonsters = len(rolemap.get("monster", ()))
ldemoniacs = len(rolemap.get("demoniac", ()))
ltraitors = len(rolemap.get("traitor", ()))
@ -2347,8 +2349,8 @@ def chk_win_conditions(cli, rolemap, end_game=True, winner=None):
# (monster's message changes based on who would have otherwise won)
# 5 = gamemode-specific win conditions
event = Event("chk_win", {"winner": winner, "message": message, "additional_winners": None})
if not event.dispatch(cli, var, rolemap, lpl, lwolves, lrealwolves):
return chk_win_conditions(cli, rolemap, end_game, winner)
if not event.dispatch(cli, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
return chk_win_conditions(cli, rolemap, mainroles, end_game, winner)
winner = event.data["winner"]
message = event.data["message"]
@ -2389,6 +2391,7 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death
nickrole = get_role(nick)
nickreveal = get_reveal_role(nick)
nicktpls = get_templates(nick)
del var.MAIN_ROLES[users._get(nick)] # FIXME
var.ROLES[nickrole].remove(nick)
for t in nicktpls:
var.ROLES[t].remove(nick)
@ -2409,16 +2412,12 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death
# clone is cloning nick, so clone becomes nick's role
# clone does NOT get any of nick's templates (gunner/assassin/etc.)
del var.CLONED[clone]
var.ROLES["clone"].remove(clone)
if nickrole == "amnesiac":
# clone gets the amnesiac's real role
sayrole = var.AMNESIAC_ROLES[nick]
var.FINAL_ROLES[clone] = sayrole
var.ROLES[sayrole].add(clone)
else:
var.ROLES[nickrole].add(clone)
var.FINAL_ROLES[clone] = nickrole
sayrole = nickrole
change_role(users._get(clone), "clone", sayrole) # FIXME
debuglog("{0} (clone) CLONE DEAD PLAYER: {1} ({2})".format(clone, target, sayrole))
if sayrole in var.HIDDEN_VILLAGERS:
sayrole = "villager"
@ -3027,17 +3026,10 @@ def rename_player(var, user, prefix):
event.dispatch(user.client, var, prefix, nick) # FIXME: Need to update all the callbacks
if user in var.ALL_PLAYERS:
try:
r = var.ROLES[get_role(prefix)]
r.add(user.nick)
r.remove(prefix)
tpls = get_templates(prefix)
for t in tpls:
var.ROLES[t].add(user.nick)
var.ROLES[t].remove(prefix)
except ValueError:
# User is in ALL_PLAYERS but dead
pass
for role, nicks in var.ROLES.items():
if prefix in nicks:
nicks.remove(prefix)
nicks.add(user.nick)
if var.PHASE in var.GAME_PHASES:
for k,v in var.ORIGINAL_ROLES.items():
@ -3724,9 +3716,8 @@ def transition_day(cli, gameid=0):
var.EXTRA_WOLVES += 1
pm(cli, victim, messages["lycan_turn"])
var.LYCAN_ROLES[victim] = vrole
var.ROLES[vrole].remove(victim)
var.ROLES["wolf"].add(victim)
var.FINAL_ROLES[victim] = "wolf"
change_role(users._get(victim), vrole, "wolf") # FIXME
var.ROLES["lycan"].discard(victim) # in the event lycan was a template, we want to ensure it gets purged
wolves = list_players(var.WOLFCHAT_ROLES)
random.shuffle(wolves)
wolves.remove(victim) # remove self from list
@ -3875,9 +3866,7 @@ def transition_day(cli, gameid=0):
pm(cli, chump, messages["bitten_turn"])
debuglog("{0} ({1}) TURNED WOLF".format(chump, chumprole))
var.BITTEN_ROLES[chump] = chumprole
var.ROLES[chumprole].remove(chump)
var.ROLES[newrole].add(chump)
var.FINAL_ROLES[chump] = newrole
change_role(users._get(chump), chumprole, newrole) # FIXME
relay_wolfchat_command(cli, chump, messages["wolfchat_new_member"].format(chump, newrole), var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True)
killer_role = {}
@ -4199,12 +4188,8 @@ def check_exchange(cli, actor, nick):
evt = Event("exchange_roles", {"actor_messages": [], "nick_messages": []})
evt.dispatch(cli, var, actor, nick, actor_role, nick_role)
var.FINAL_ROLES[actor] = nick_role
var.FINAL_ROLES[nick] = actor_role
var.ROLES[actor_role].add(nick)
var.ROLES[actor_role].remove(actor)
var.ROLES[nick_role].add(actor)
var.ROLES[nick_role].remove(nick)
change_role(users._get(actor), actor_role, nick_role) # FIXME
change_role(users._get(nick), nick_role, actor_role) # FIXME
if actor in var.BITTEN_ROLES.keys():
if nick in var.BITTEN_ROLES.keys():
var.BITTEN_ROLES[actor], var.BITTEN_ROLES[nick] = var.BITTEN_ROLES[nick], var.BITTEN_ROLES[actor]
@ -4652,7 +4637,6 @@ def immunize(cli, nick, chan, rest):
if not victim:
return
victim = choose_target(nick, victim)
vrole = get_role(victim)
if check_exchange(cli, nick, victim):
return
evt = Event("doctor_immunize", {"success": True, "message": "villager_immunized"})
@ -4661,12 +4645,13 @@ def immunize(cli, nick, chan, rest):
lycan = False
if victim in var.DISEASED:
var.DISEASED.remove(victim)
if vrole == "lycan":
if victim in var.ROLES["lycan"]:
lycan = True
lycan_message = (messages["lycan_cured"])
var.ROLES["lycan"].remove(victim)
var.ROLES["villager"].add(victim)
var.FINAL_ROLES[victim] = "villager"
if get_role(victim) == "lycan":
change_role(users._get(victim), "lycan", "villager") # FIXME
else:
var.ROLES["lycan"].remove(victim)
var.CURED_LYCANS.add(victim)
else:
lycan_message = messages[evt.data["message"]]
@ -4674,7 +4659,7 @@ def immunize(cli, nick, chan, rest):
if evt.data["success"]:
var.IMMUNIZED.add(victim)
var.DOCTORS[nick] -= 1
debuglog("{0} ({1}) IMMUNIZE: {2} ({3})".format(nick, get_role(nick), victim, "lycan" if lycan else get_role(victim)))
debuglog("{0} (doctor) IMMUNIZE: {1} ({2})".format(nick, victim, "lycan" if lycan else get_role(victim)))
@cmd("bite", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("alpha wolf",))
def bite_cmd(cli, nick, chan, rest):
@ -5278,10 +5263,8 @@ def transition_night(cli):
event = Event("amnesiac_turn", {})
if event.dispatch(var, amn, var.AMNESIAC_ROLES[amn]):
amnrole = var.AMNESIAC_ROLES[amn]
var.ROLES["amnesiac"].remove(amn)
var.ROLES[amnrole].add(amn)
change_role(users._get(amn), "amnesiac", amnrole) # FIXME
var.AMNESIACS.add(amn)
var.FINAL_ROLES[amn] = amnrole
# TODO: turn into event when amnesiac is split
from src.roles import succubus
if amnrole == "succubus" and amn in succubus.ENTRANCED:
@ -5727,7 +5710,7 @@ def start(cli, nick, chan, forced = False, restart = ""):
for decor in (COMMANDS["join"] + COMMANDS["start"]):
decor(_command_disabled)
var.ROLES = {}
var.ROLES = {var.DEFAULT_ROLE: set()}
var.GUNNERS = {}
var.OBSERVED = {}
var.HVISITED = {}
@ -5744,6 +5727,7 @@ def start(cli, nick, chan, forced = False, restart = ""):
var.ANGRY_WOLVES = False
var.DISEASED_WOLVES = False
var.TRAITOR_TURNED = False
var.MAIN_ROLES = {}
var.FINAL_ROLES = {}
var.ORIGINAL_LOVERS = {}
var.LYCANTHROPES = set()
@ -5784,14 +5768,16 @@ def start(cli, nick, chan, forced = False, restart = ""):
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(selected)
fixed_count = count - roleset_roles[role]
if fixed_count > 0:
for pr in possible_rolesets:
pr[role] += fixed_count
for v in villagers:
var.ROLES[var.DEFAULT_ROLE].add(v)
var.ROLES[var.DEFAULT_ROLE].update(villagers)
for x in villagers:
var.MAIN_ROLES[users._get(x)] = var.DEFAULT_ROLE # FIXME
if villagers:
for pr in possible_rolesets:
pr[var.DEFAULT_ROLE] += len(villagers)
@ -7309,7 +7295,7 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS:
pl = list_players()
rolargs = re.split("\s*=\s*", rol, 1)
rol = rolargs[0]
if rol[1:] in var.TEMPLATE_RESTRICTIONS.keys():
if rol[0] in ("+", "-"):
addrem = rol[0]
rol = rol[1:]
is_gunner = (rol == "gunner" or rol == "sharpshooter")
@ -7326,6 +7312,7 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS:
var.GUNNERS[who] = math.ceil(var.SHARPSHOOTER_MULTIPLIER * len(pl))
if who not in pl:
var.ROLES[var.DEFAULT_ROLE].add(who)
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)
@ -7334,7 +7321,7 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS:
var.ROLES[rol].add(who)
evt = Event("frole_template", {})
evt.dispatch(cli, var, addrem, who, rol, rolargs)
elif addrem == "-" and who in var.ROLES[rol]:
elif addrem == "-" and who in var.ROLES[rol] and get_role(who) != rol:
var.ROLES[rol].remove(who)
evt = Event("frole_template", {})
evt.dispatch(cli, var, addrem, who, rol, rolargs)
@ -7346,18 +7333,16 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS:
elif rol in var.TEMPLATE_RESTRICTIONS.keys():
cli.msg(chan, messages["template_mod_syntax"].format(rol))
return
elif rol in var.ROLES.keys():
elif rol in var.ROLES:
oldrole = None
if who in pl:
oldrole = get_role(who)
var.ROLES[oldrole].remove(who)
change_role(users._get(who), oldrole, rol) # FIXME
else:
var.ALL_PLAYERS.append(users._get(who)) # FIXME
var.ROLES[rol].add(who)
if who not in pl:
var.ROLES[rol].add(who)
var.MAIN_ROLES[users._get(who)] = rol # FIXME
var.ORIGINAL_ROLES[rol].add(who)
else:
var.FINAL_ROLES[who] = rol
evt = Event("frole_role", {})
evt.dispatch(cli, var, who, rol, oldrole, rolargs)
if not is_fake_nick(who):