From 8a8a79627f0f00a6b7d73e20ac56035173ec1149 Mon Sep 17 00:00:00 2001 From: skizzerz Date: Fri, 31 Mar 2017 12:45:53 -0500 Subject: [PATCH] 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. --- src/functions.py | 23 +++++---- src/gamemodes.py | 60 ++++++++++++--------- src/roles/seer.py | 1 + src/roles/shaman.py | 15 +++--- src/roles/succubus.py | 20 ++++--- src/roles/traitor.py | 5 +- src/roles/vengefulghost.py | 27 +++++----- src/roles/villager.py | 2 +- src/roles/wildchild.py | 16 +++--- src/roles/wolf.py | 11 ++-- src/utilities.py | 70 ++++++++++++------------- src/wolfgame.py | 103 ++++++++++++++++--------------------- 12 files changed, 176 insertions(+), 177 deletions(-) diff --git a/src/functions.py b/src/functions.py index f50d826..32d64c0 100644 --- a/src/functions.py +++ b/src/functions.py @@ -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): diff --git a/src/gamemodes.py b/src/gamemodes.py index 87e3f43..6b53942 100644 --- a/src/gamemodes.py +++ b/src/gamemodes.py @@ -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 diff --git a/src/roles/seer.py b/src/roles/seer.py index ef728f7..e2091b7 100644 --- a/src/roles/seer.py +++ b/src/roles/seer.py @@ -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.""" diff --git a/src/roles/shaman.py b/src/roles/shaman.py index b97fc4a..00b23a4 100644 --- a/src/roles/shaman.py +++ b/src/roles/shaman.py @@ -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: diff --git a/src/roles/succubus.py b/src/roles/succubus.py index 0277c63..427f367 100644 --- a/src/roles/succubus.py +++ b/src/roles/succubus.py @@ -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: diff --git a/src/roles/traitor.py b/src/roles/traitor.py index 2e6d3c4..1770d7b 100644 --- a/src/roles/traitor.py +++ b/src/roles/traitor.py @@ -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" diff --git a/src/roles/vengefulghost.py b/src/roles/vengefulghost.py index d7a2741..ca17e2c 100644 --- a/src/roles/vengefulghost.py +++ b/src/roles/vengefulghost.py @@ -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": diff --git a/src/roles/villager.py b/src/roles/villager.py index cbb4160..28aad85 100644 --- a/src/roles/villager.py +++ b/src/roles/villager.py @@ -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: diff --git a/src/roles/wildchild.py b/src/roles/wildchild.py index 7a29aeb..bb12616 100644 --- a/src/roles/wildchild.py +++ b/src/roles/wildchild.py @@ -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") diff --git a/src/roles/wolf.py b/src/roles/wolf.py index f8f91fd..7f4169a 100644 --- a/src/roles/wolf.py +++ b/src/roles/wolf.py @@ -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 diff --git a/src/utilities.py b/src/utilities.py index b9bc88f..61535b2 100644 --- a/src/utilities.py +++ b/src/utilities.py @@ -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 = " "): diff --git a/src/wolfgame.py b/src/wolfgame.py index 3f07fe6..7c96d48 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -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):