Split turncoat and convert some of the exchange_roles listeners into new_role (#342)

- Split turncoat
- Add `new_role` and `swap_role_state` events, convert some `exchange_roles` listeners to them (handles both exchange as well as roleswaps such as clone->X or traitor->wolf)
- Refactor change_role(). Calling that is all that is needed for a roleswap now (no boilerplate)
- Always give player list to new wolf roles. This does mean they'll get double lists in some cases, dealwithit.jpg
- Make exchange totem no-op due to technical issues until we get role classes set up (exchanging role state across two new_role listeners doesn't work very well when the listeners get rid of old state)
This commit is contained in:
Em Barry 2018-07-03 10:41:51 -04:00 committed by Ryan Schmidt
parent 7d2eefd12a
commit 41262a148a
20 changed files with 383 additions and 496 deletions

View File

@ -196,10 +196,9 @@
"single_winner": "The winner is \u0002{0}\u0002.", "single_winner": "The winner is \u0002{0}\u0002.",
"two_winners": "The winners are \u0002{0}\u0002 and \u0002{1}\u0002.", "two_winners": "The winners are \u0002{0}\u0002 and \u0002{1}\u0002.",
"many_winners": "The winners are {0}, and \u0002{1}\u0002.", "many_winners": "The winners are {0}, and \u0002{1}\u0002.",
"clone_turn": "You are now a{0} \u0002{1}\u0002.", "new_role": "You are now a{0} \u0002{1}\u0002.",
"forever_aclone": "It appears that \u0002{0}\u0002 was cloning you, so you are now stuck as a clone forever. How sad.", "forever_aclone": "It appears that \u0002{0}\u0002 was cloning you, so you are now stuck as a clone forever. How sad.",
"clone_success": "You will now be cloning \u0002{0}\u0002 if they die.", "clone_success": "You will now be cloning \u0002{0}\u0002 if they die.",
"clone_wolf": "\u0002{0}\u0002 cloned \u0002{1}\u0002 and has now become a wolf!",
"no_other_wolves": "There are no other wolves.", "no_other_wolves": "There are no other wolves.",
"has_minions": "You have \u0002{0}\u0002 {1} at your command!", "has_minions": "You have \u0002{0}\u0002 {1} at your command!",
"lover_suicide": "Saddened by the loss of their lover, \u0002{0}\u0002, a{1} \u0002{2}\u0002, commits suicide.", "lover_suicide": "Saddened by the loss of their lover, \u0002{0}\u0002, a{1} \u0002{2}\u0002, commits suicide.",
@ -223,7 +222,6 @@
"hunter_discard": "Your target has died, so you may now pick a new one.", "hunter_discard": "Your target has died, so you may now pick a new one.",
"wild_child_already_picked": "You have already picked your idol for this game.", "wild_child_already_picked": "You have already picked your idol for this game.",
"wild_child_success": "You have picked {0} to be your idol for this game.", "wild_child_success": "You have picked {0} to be your idol for this game.",
"wild_child_as_wolf": "\u0002{0}\u0002's idol has died, and is now a \u0002wolf\u0002!",
"wild_child_random_idol": "Upon waking up, the first person you see is \u0002{0}\u0002, and they become your idol.", "wild_child_random_idol": "Upon waking up, the first person you see is \u0002{0}\u0002, and they become your idol.",
"wild_child_revealroles_picked": "picked {0} as idol", "wild_child_revealroles_picked": "picked {0} as idol",
"wild_child_revealroles_no_idol": "no idol picked yet", "wild_child_revealroles_no_idol": "no idol picked yet",
@ -281,7 +279,6 @@
"angel_protection": "\u0002{0}\u0002 was attacked last night, but luckily, the guardian angel was on duty.", "angel_protection": "\u0002{0}\u0002 was attacked last night, but luckily, the guardian angel was on duty.",
"bodyguard_protection": "\u0002{0}\u0002 sacrificed their life to guard that of another.", "bodyguard_protection": "\u0002{0}\u0002 sacrificed their life to guard that of another.",
"lycan_turn": "HOOOOOOOOOWL. You have become... a wolf!", "lycan_turn": "HOOOOOOOOOWL. You have become... a wolf!",
"lycan_wc_notification": "\u0002{0}\u0002 is now a wolf!",
"totem_banish": "\u0002{0}\u0002's totem emitted a brilliant flash of light last night. It appears that \u0002{1}\u0002's spirit was driven away by the flash.", "totem_banish": "\u0002{0}\u0002's totem emitted a brilliant flash of light last night. It appears that \u0002{1}\u0002's spirit was driven away by the flash.",
"totem_death": "\u0002{0}\u0002's totem emitted a brilliant flash of light last night. The dead body of \u0002{1}\u0002, a{2} \u0002{3}\u0002, was found at the scene.", "totem_death": "\u0002{0}\u0002's totem emitted a brilliant flash of light last night. The dead body of \u0002{1}\u0002, a{2} \u0002{3}\u0002, was found at the scene.",
"totem_death_no_reveal": "\u0002{0}\u0002's totem emitted a brilliant flash of light last night. The dead body of \u0002{1}\u0002 was found at the scene.", "totem_death_no_reveal": "\u0002{0}\u0002's totem emitted a brilliant flash of light last night. The dead body of \u0002{1}\u0002 was found at the scene.",
@ -453,9 +450,8 @@
"fallen_angel_turn": "While out last night, you were overpowered by a large werewolf and bitten. Shortly thereafter, you found your wings turning black as night and sadistic thoughts infiltrating your mind...", "fallen_angel_turn": "While out last night, you were overpowered by a large werewolf and bitten. Shortly thereafter, you found your wings turning black as night and sadistic thoughts infiltrating your mind...",
"harlot_turn": "While out visiting last night, you were overpowered by a large werewolf and bitten. Shortly thereafter, you found yourself turning into a werewolf yourself!", "harlot_turn": "While out visiting last night, you were overpowered by a large werewolf and bitten. Shortly thereafter, you found yourself turning into a werewolf yourself!",
"bitten_turn": "You woke suddenly last night to a sharp pain, as you were bit by a large werewolf. Shortly thereafter, you found yourself turning into a werewolf yourself!", "bitten_turn": "You woke suddenly last night to a sharp pain, as you were bit by a large werewolf. Shortly thereafter, you found yourself turning into a werewolf yourself!",
"wolfchat_new_member": "\u0002{0}\u0002 is now a \u0002{1}\u0002!", "wolfchat_new_member": "\u0002{0}\u0002 is now a{1} \u0002{2}\u0002!",
"amnesia_clear": "Your amnesia clears and you now remember that you are {0} \u0002{1}\u0002!", "amnesia_clear": "Your amnesia clears and you now remember that you are {0} \u0002{1}\u0002!",
"amnesia_wolfchat": "\u0002{0}\u0002 is now a \u0002{1}\u0002!",
"wolf_notify": "You are a \u0002{0}wolf\u0002. It is your job to kill all the villagers. Use \"kill <nick>\" to kill a villager.", "wolf_notify": "You are a \u0002{0}wolf\u0002. It is your job to kill all the villagers. Use \"kill <nick>\" to kill a villager.",
"cursed_traitor_notify": "You are a \u0002{0}cursed traitor\u0002. Normally, you would be seen as a villager by the seer and oracle, but since you're cursed, you are seen as a wolf.", "cursed_traitor_notify": "You are a \u0002{0}cursed traitor\u0002. Normally, you would be seen as a villager by the seer and oracle, but since you're cursed, you are seen as a wolf.",
"traitor_notify": "You are a \u0002{0}traitor\u0002. You are exactly like a villager and not even a seer or oracle can see your true identity, only detectives and augurs can.", "traitor_notify": "You are a \u0002{0}traitor\u0002. You are exactly like a villager and not even a seer or oracle can see your true identity, only detectives and augurs can.",
@ -737,7 +733,6 @@
"sleepy_nightmare_death": "As the sun starts rising, your legs give out, causing the beast to descend upon you and snuff out your life.", "sleepy_nightmare_death": "As the sun starts rising, your legs give out, causing the beast to descend upon you and snuff out your life.",
"sleepy_priest_death": "The sky suddenly darkens as a thunderstorm appears from nowhere. The bell on the newly-abandoned church starts ringing in sinister tones before the building is struck repeatedly by lightning, setting it alight in a raging inferno...", "sleepy_priest_death": "The sky suddenly darkens as a thunderstorm appears from nowhere. The bell on the newly-abandoned church starts ringing in sinister tones before the building is struck repeatedly by lightning, setting it alight in a raging inferno...",
"sleepy_doomsayer_turn": "You feel something rushing into you and taking control over your mind and body. It causes you to rapidly start transforming into a werewolf, and you realize your vision powers can now be used to inflict malady on the unwary. You are now a \u0002doomsayer\u0002.", "sleepy_doomsayer_turn": "You feel something rushing into you and taking control over your mind and body. It causes you to rapidly start transforming into a werewolf, and you realize your vision powers can now be used to inflict malady on the unwary. You are now a \u0002doomsayer\u0002.",
"sleepy_doomsayer_wolfchat": "\u0002{0}\u0002 is now a \u0002doomsayer\u0002.",
"sleepy_succubus_turn": "You feel something rushing into you and taking control over your mind and body. You are now a \u0002succubus\u0002. Your job is to entrance the village, bringing them all under your absolute control.", "sleepy_succubus_turn": "You feel something rushing into you and taking control over your mind and body. You are now a \u0002succubus\u0002. Your job is to entrance the village, bringing them all under your absolute control.",
"sleepy_demoniac_turn": "You feel something rushing into you and taking control over your mind and body, showing you your new purpose in life. There are far greater evils than the wolves lurking in the shadows, and by sacrificing all of the wolves, you can unleash those evils upon the world. You are now a \u0002demoniac\u0002.", "sleepy_demoniac_turn": "You feel something rushing into you and taking control over your mind and body, showing you your new purpose in life. There are far greater evils than the wolves lurking in the shadows, and by sacrificing all of the wolves, you can unleash those evils upon the world. You are now a \u0002demoniac\u0002.",
"fquit_fail": "Forcing a live player to leave must be done in channel.", "fquit_fail": "Forcing a live player to leave must be done in channel.",
@ -781,7 +776,7 @@
"vengeful_role": "You are a \u0002vengeful ghost\u0002 who is against the \u0002{0}\u0002.", "vengeful_role": "You are a \u0002vengeful ghost\u0002 who is against the \u0002{0}\u0002.",
"show_role": "You are a{0} \u0002{1}\u0002.", "show_role": "You are a{0} \u0002{1}\u0002.",
"original_wolves": "Original wolves: {0}", "original_wolves": "Original wolves: {0}",
"turncoat_side": "Current side: \u0002{0}\u0002.", "turncoat_side": "You are {0}.",
"assassin_role_info": "You are an \u0002assassin\u0002{0}.", "assassin_role_info": "You are an \u0002assassin\u0002{0}.",
"assassin_targeting": " and targeting {0}", "assassin_targeting": " and targeting {0}",
"bitten_info": "You were bitten by an alpha wolf and have \u0002{0} night{1}\u0002 until your transformation.", "bitten_info": "You were bitten by an alpha wolf and have \u0002{0} night{1}\u0002 until your transformation.",

View File

@ -5,7 +5,7 @@ from src import users
__all__ = [ __all__ = [
"get_players", "get_all_players", "get_participants", "get_players", "get_all_players", "get_participants",
"get_target", "get_target", "change_role"
"get_main_role", "get_all_roles", "get_reveal_role", "get_main_role", "get_all_roles", "get_reveal_role",
"is_known_wolf_ally", "is_known_wolf_ally",
] ]
@ -66,6 +66,34 @@ def get_target(var, wrapper, message, *, allow_self=False, allow_bot=False, not_
return match return match
def change_role(var, player, oldrole, newrole, *, inherit_from=None, message="new_role"):
# in_wolfchat is filled as part of priority 4
# if you wish to modify evt.data["role"], do so in priority 3 or sooner
evt = Event("new_role",
{"role": newrole, "messages": [], "in_wolfchat": False},
inherit_from=inherit_from)
evt.dispatch(var, player, oldrole)
newrole = evt.data["role"]
var.ROLES[oldrole].remove(player)
var.ROLES[newrole].add(player)
# only adjust MAIN_ROLES/FINAL_ROLES if we're changing the player's actual role
if var.MAIN_ROLES[player] == oldrole:
var.MAIN_ROLES[player] = newrole
var.FINAL_ROLES[player.nick] = newrole
sayrole = newrole
if sayrole in var.HIDDEN_VILLAGERS:
sayrole = "villager"
elif sayrole in var.HIDDEN_ROLES:
sayrole = var.DEFAULT_ROLE
an = "n" if sayrole.startswith(("a", "e", "i", "o", "u")) else ""
player.send(messages[message].format(an, sayrole))
player.send(*evt.data["messages"])
return newrole
def get_main_role(user): def get_main_role(user):
role = var.MAIN_ROLES.get(user) role = var.MAIN_ROLES.get(user)
if role is not None: if role is not None:

View File

@ -10,7 +10,7 @@ import botconfig
import src.settings as var import src.settings as var
from src.utilities import * from src.utilities import *
from src.messages import messages from src.messages import messages
from src.functions import get_players, get_all_players, get_main_role from src.functions import get_players, get_all_players, get_main_role, change_role
from src.decorators import handle_error, command from src.decorators import handle_error, command
from src.containers import UserList, UserSet, UserDict, DefaultUserDict from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src import events, channels, users from src import events, channels, users
@ -870,6 +870,7 @@ class SleepyMode(GameMode):
self.TEMPLATE_RESTRICTIONS["prophet"] = frozenset(self.ROLE_GUIDE.keys()) - {"priest", "blessed villager", "prophet"} self.TEMPLATE_RESTRICTIONS["prophet"] = frozenset(self.ROLE_GUIDE.keys()) - {"priest", "blessed villager", "prophet"}
# this ensures that village drunk will always receive the gunner template # this ensures that village drunk will always receive the gunner template
self.TEMPLATE_RESTRICTIONS["gunner"] = frozenset(self.ROLE_GUIDE.keys()) - {"village drunk", "cursed villager", "gunner"} self.TEMPLATE_RESTRICTIONS["gunner"] = frozenset(self.ROLE_GUIDE.keys()) - {"village drunk", "cursed villager", "gunner"}
self.cmd_params = dict(chan=False, pm=True, playing=True, phases=("night",))
# disable wolfchat # disable wolfchat
#self.RESTRICT_WOLFCHAT = 0x0f #self.RESTRICT_WOLFCHAT = 0x0f
@ -879,13 +880,17 @@ class SleepyMode(GameMode):
events.add_listener("chk_nightdone", self.prolong_night) events.add_listener("chk_nightdone", self.prolong_night)
events.add_listener("transition_day_begin", self.nightmare_kill) events.add_listener("transition_day_begin", self.nightmare_kill)
events.add_listener("del_player", self.happy_fun_times) events.add_listener("del_player", self.happy_fun_times)
self.north_cmd = command("north", "n", chan=False, pm=True, playing=True, phases=("night",))(functools.partial(self.move, "n")) events.add_listener("revealroles", self.on_revealroles)
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() self.having_nightmare = UserList()
cmd_params = dict(chan=False, pm=True, playing=True, phases=("night",), users=self.having_nightmare)
self.north_cmd = command("north", "n", **cmd_params)(functools.partial(self.move, "n"))
self.east_cmd = command("east", "e", **cmd_params)(functools.partial(self.move, "e"))
self.south_cmd = command("south", "s", **cmd_params)(functools.partial(self.move, "s"))
self.west_cmd = command("west", "w", **cmd_params)(functools.partial(self.move, "w"))
def teardown(self): def teardown(self):
from src import decorators from src import decorators
events.remove_listener("dullahan_targets", self.dullahan_targets) events.remove_listener("dullahan_targets", self.dullahan_targets)
@ -893,6 +898,8 @@ class SleepyMode(GameMode):
events.remove_listener("chk_nightdone", self.prolong_night) events.remove_listener("chk_nightdone", self.prolong_night)
events.remove_listener("transition_day_begin", self.nightmare_kill) events.remove_listener("transition_day_begin", self.nightmare_kill)
events.remove_listener("del_player", self.happy_fun_times) events.remove_listener("del_player", self.happy_fun_times)
events.remove_listener("revealroles", self.on_revealroles)
def remove_command(name, command): def remove_command(name, command):
if len(decorators.COMMANDS[name]) > 1: if len(decorators.COMMANDS[name]) > 1:
decorators.COMMANDS[name].remove(command) decorators.COMMANDS[name].remove(command)
@ -909,9 +916,8 @@ class SleepyMode(GameMode):
self.having_nightmare.clear() self.having_nightmare.clear()
def dullahan_targets(self, evt, var, dullahans, max_targets): def dullahan_targets(self, evt, var, dullahan, max_targets):
for dull in dullahans: evt.data["targets"].update(var.ROLES["priest"])
evt.data["targets"][dull] = UserSet(var.ROLES["priest"])
def setup_nightmares(self, evt, var): def setup_nightmares(self, evt, var):
if random.random() < 1/5: if random.random() < 1/5:
@ -991,8 +997,6 @@ class SleepyMode(GameMode):
self.nightmare_step() self.nightmare_step()
def move(self, direction, var, wrapper, message): def move(self, direction, var, wrapper, message):
if not self.having_nightmare or self.having_nightmare[0] is not wrapper.source:
return
opposite = {"n": "s", "e": "w", "s": "n", "w": "e"} opposite = {"n": "s", "e": "w", "s": "n", "w": "e"}
if self.prev_direction == opposite[direction]: if self.prev_direction == opposite[direction]:
wrapper.pm(messages["sleepy_nightmare_invalid_direction"]) wrapper.pm(messages["sleepy_nightmare_invalid_direction"])
@ -1043,17 +1047,17 @@ class SleepyMode(GameMode):
cultists = [p for p in get_players(("cultist",)) if p in pl and random.random() < turn_chance] cultists = [p for p in get_players(("cultist",)) if p in pl and random.random() < turn_chance]
channels.Main.send(messages["sleepy_priest_death"]) channels.Main.send(messages["sleepy_priest_death"])
for seer in seers: for seer in seers:
change_role(seer, "seer", "doomsayer") change_role(var, seer, "seer", "doomsayer", message="sleepy_doomsayer_turn")
seer.send(messages["sleepy_doomsayer_turn"])
relay_wolfchat_command(seer.client, seer.nick, messages["sleepy_doomsayer_wolfchat"].format(seer), var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True)
for harlot in harlots: for harlot in harlots:
change_role(harlot, "harlot", "succubus") change_role(var, harlot, "harlot", "succubus", message="sleepy_succubus_turn")
harlot.send(messages["sleepy_succubus_turn"])
for cultist in cultists: for cultist in cultists:
change_role(cultist, "cultist", "demoniac") change_role(var, cultist, "cultist", "demoniac", message="sleepy_demoniac_turn")
cultist.send(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 # NOTE: chk_win is called by del_player, don't need to call it here even though this has a chance of ending game
def on_revealroles(self, evt, var, wrapper):
if self.having_nightmare:
evt.data["output"].append("\u0002having nightmare\u0002: {0}".format(self.having_nightmare[0]))
@game_mode("maelstrom", minp=8, maxp=24, likelihood=0) @game_mode("maelstrom", minp=8, maxp=24, likelihood=0)
class MaelstromMode(GameMode): class MaelstromMode(GameMode):
"""Some people just want to watch the world burn.""" """Some people just want to watch the world burn."""
@ -1148,7 +1152,7 @@ class MaelstromMode(GameMode):
COMMANDS["myrole"][0].caller(wrapper.source.client, wrapper.source.nick, wrapper.target.name, "") # FIXME: New/old API COMMANDS["myrole"][0].caller(wrapper.source.client, wrapper.source.nick, wrapper.target.name, "") # FIXME: New/old API
# if they're a wolfchat role, alert the other wolves # if they're a wolfchat role, alert the other wolves
if role in var.WOLFCHAT_ROLES: if role in var.WOLFCHAT_ROLES:
relay_wolfchat_command(wrapper.source.client, wrapper.source.nick, messages["wolfchat_new_member"].format(wrapper.source.nick, role), var.WOLFCHAT_ROLES, is_wolf_command=True, is_kill_command=True) relay_wolfchat_command(wrapper.source.client, wrapper.source.nick, messages["wolfchat_new_member"].format(wrapper.source.nick, "", role), var.WOLFCHAT_ROLES, is_wolf_command=True, is_kill_command=True)
# TODO: make this part of !myrole instead, no reason we can't give out wofllist in that # TODO: make this part of !myrole instead, no reason we can't give out wofllist in that
wolves = list_players(var.WOLFCHAT_ROLES) wolves = list_players(var.WOLFCHAT_ROLES)
pl = get_players() pl = get_players()

View File

@ -23,7 +23,7 @@ def setup_variables(rolename, *, send_role, types):
def on_transition_night_end(evt, var): def on_transition_night_end(evt, var):
villagers = set(get_players(("priest", "doctor"))) villagers = set(get_players(("priest", "doctor")))
win_stealers = set(get_players(("fool", "monster", "demoniac"))) win_stealers = set(get_players(("fool", "monster", "demoniac")))
neutrals = set(get_players(("turncoat", "jester"))) neutrals = set(get_players(("jester",)))
special_evt = Event("get_special", {"villagers": villagers, "wolves": set(), "win_stealers": win_stealers, "neutrals": neutrals}) special_evt = Event("get_special", {"villagers": villagers, "wolves": set(), "win_stealers": win_stealers, "neutrals": neutrals})
special_evt.dispatch(var) special_evt.dispatch(var)

View File

@ -26,12 +26,10 @@ def setup_variables(rolename):
def on_get_special(evt, var): def on_get_special(evt, var):
evt.data["villagers"].update(get_players((rolename,))) evt.data["villagers"].update(get_players((rolename,)))
@event_listener("exchange_roles") @event_listener("new_role")
def on_exchange(evt, var, actor, target, actor_role, target_role): def on_new_role(evt, var, user, old_role):
if actor_role == rolename and target_role != rolename: if old_role == rolename and evt.data["role"] != rolename:
SEEN.discard(actor) SEEN.discard(user)
elif target_role == rolename and actor_role != rolename:
SEEN.discard(target)
@event_listener("chk_nightdone") @event_listener("chk_nightdone")
def on_chk_nightdone(evt, var): def on_chk_nightdone(evt, var):

View File

@ -6,7 +6,7 @@ from collections import defaultdict
from src.utilities import * from src.utilities import *
from src import channels, users, debuglog, errlog, plog from src import channels, users, debuglog, errlog, plog
from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target, is_known_wolf_ally from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target, is_known_wolf_ally, change_role
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages from src.messages import messages
@ -29,12 +29,6 @@ def _get_blacklist(var):
blacklist.add("matchmaker") blacklist.add("matchmaker")
return blacklist return blacklist
@event_listener("role_assignment")
def on_role_assignment(evt, var, gamemode, pl):
roles = var.ROLE_GUIDE.keys() - _get_blacklist(var)
for amnesiac in get_all_players(("amnesiac",)):
ROLES[amnesiac] = random.choice(list(roles))
@event_listener("transition_night_begin") @event_listener("transition_night_begin")
def on_transition_night_begin(evt, var): def on_transition_night_begin(evt, var):
global STATS_FLAG global STATS_FLAG
@ -44,34 +38,12 @@ def on_transition_night_begin(evt, var):
STATS_FLAG = True STATS_FLAG = True
for amn in amnesiacs: for amn in amnesiacs:
role = ROLES[amn] role = change_role(var, amn, "amnesiac", ROLES[amn], message="amnesia_clear")
change_role(amn, "amnesiac", role) debuglog("{0} REMEMBER: {1}".format(amn, role))
evt = Event("new_role", {})
evt.dispatch(var, amn, role)
if var.FIRST_NIGHT: # we don't need to tell them twice if they remember right away
continue
showrole = role
if showrole in var.HIDDEN_VILLAGERS:
showrole = "villager"
elif showrole in var.HIDDEN_ROLES:
showrole = var.DEFAULT_ROLE
a = "a"
if showrole.startswith(("a", "e", "i", "o", "u")):
a = "an"
amn.send(messages["amnesia_clear"].format(a, showrole))
if is_known_wolf_ally(amn, amn):
if role in var.WOLF_ROLES:
relay_wolfchat_command(amn.client, amn.nick, messages["amnesia_wolfchat"].format(amn, showrole), var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True)
else:
relay_wolfchat_command(amn.client, amn.nick, messages["amnesia_wolfchat"].format(amn, showrole), var.WOLFCHAT_ROLES)
debuglog("{0} REMEMBER: {1} as {2}".format(amn, role, showrole))
@event_listener("new_role") @event_listener("new_role")
def on_new_role(evt, var, user, role): def doctor_new_role(evt, var, user, old_role):
if role == "turncoat": # FIXME: Need to split into turncoat.py when split if evt.data["role"] == "doctor": # FIXME: Need to split into doctor.py when split
var.TURNCOATS[user.nick] = ("none", -1)
if role == "doctor": # FIXME: Need to split into doctor.py when split
var.DOCTORS[user.nick] = math.ceil(var.DOCTOR_IMMUNIZATION_MULTIPLIER * len(get_players())) var.DOCTORS[user.nick] = math.ceil(var.DOCTOR_IMMUNIZATION_MULTIPLIER * len(get_players()))
@event_listener("investigate") @event_listener("investigate")
@ -79,28 +51,17 @@ def on_investigate(evt, var, actor, target):
if evt.data["role"] == "amnesiac": if evt.data["role"] == "amnesiac":
evt.data["role"] = ROLES[target] evt.data["role"] = ROLES[target]
@event_listener("exchange_roles") @event_listener("new_role", priority=1) # Exchange, clone, etc. - assign the amnesiac's final role
def on_exchange_roles(evt, var, actor, target, actor_role, target_role): def update_amnesiac(evt, var, user, old_role):
# FIXME: exchange totem messes with var.HIDDEN_AMNESIAC (the new amnesiac is no longer hidden should they die) # FIXME: exchange totem messes with var.HIDDEN_AMNESIAC (the new amnesiac is no longer hidden should they die)
if actor_role == "amnesiac": if evt.params.inherit_from is not None and evt.data["role"] == "amnesiac" and old_role != "amnesiac":
actor_role = ROLES[actor] evt.data["role"] = ROLES[evt.params.inherit_from]
if target in ROLES:
ROLES[actor] = ROLES[target]
ROLES[target] = actor_role
else:
del ROLES[actor]
ROLES[target] = actor_role
if target_role == "amnesiac": @event_listener("new_role")
if actor not in ROLES: def on_new_role(evt, var, user, old_role):
target_role = ROLES[target] if evt.params.inherit_from is None and evt.data["role"] == "amnesiac":
ROLES[actor] = target_role roles = var.ROLE_GUIDE.keys() - _get_blacklist(var)
del ROLES[target] ROLES[user] = random.choice(list(roles))
else: # we swapped amnesiac_roles earlier on, get our version back
target_role = ROLES[actor]
evt.data["actor_role"] = actor_role
evt.data["target_role"] = target_role
@event_listener("revealing_totem") @event_listener("revealing_totem")
def on_revealing_totem(evt, var, votee): def on_revealing_totem(evt, var, votee):
@ -108,14 +69,8 @@ def on_revealing_totem(evt, var, votee):
global STATS_FLAG global STATS_FLAG
STATS_FLAG = True STATS_FLAG = True
if evt.data["role"] == "amnesiac": if evt.data["role"] == "amnesiac":
role = ROLES[votee]
change_role(votee, "amnesiac", role)
votee.send(messages["totem_amnesia_clear"]) votee.send(messages["totem_amnesia_clear"])
nevt = Event("new_role", {}) change_role(var, votee, "amnesiac", ROLES[votee])
nevt.dispatch(var, votee, role)
evt.data["role"] = role
# 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
@event_listener("get_reveal_role") @event_listener("get_reveal_role")
def on_reveal_role(evt, var, user): def on_reveal_role(evt, var, user):

View File

@ -5,8 +5,8 @@ import math
from collections import defaultdict from collections import defaultdict
from src.utilities import * from src.utilities import *
from src import channels, users, debuglog, errlog, plog from src import events, channels, users, debuglog, errlog, plog
from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target, change_role
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages from src.messages import messages
@ -45,14 +45,15 @@ def clone(var, wrapper, message):
debuglog("{0} (clone) CLONE: {1} ({2})".format(wrapper.source, target, get_main_role(target))) debuglog("{0} (clone) CLONE: {1} ({2})".format(wrapper.source, target, get_main_role(target)))
@event_listener("init") def setup_clone(evt):
def on_init(evt):
# We need to add "clone" to the role command exceptions so there's no error # We need to add "clone" to the role command exceptions so there's no error
# This is done here so that var isn't imported at the global scope # This is done here so that var isn't imported at the global scope
# (when we implement proper game state this will be in a different event) # (when we implement proper game state this will be in a different event)
from src import settings as var from src import settings as var
var.ROLE_COMMAND_EXCEPTIONS.add("clone") var.ROLE_COMMAND_EXCEPTIONS.add("clone")
events.add_listener("init", setup_clone) # no IRC connection, so no possible error handler yet
@event_listener("get_reveal_role") @event_listener("get_reveal_role")
def on_get_reveal_role(evt, var, user): def on_get_reveal_role(evt, var, user):
if var.HIDDEN_CLONE and user in var.ORIGINAL_ROLES["clone"]: if var.HIDDEN_CLONE and user in var.ORIGINAL_ROLES["clone"]:
@ -72,19 +73,7 @@ def on_del_player(evt, var, player, mainrole, allroles, death_triggers):
# clone is cloning target, so clone becomes target's role # clone is cloning target, so clone becomes target's role
# clone does NOT get any of target's templates (gunner/assassin/etc.) # clone does NOT get any of target's templates (gunner/assassin/etc.)
del CLONED[clone] del CLONED[clone]
if mainrole == "amnesiac": mainrole = change_role(var, clone, "clone", mainrole, inherit_from=target)
from src.roles.amnesiac import ROLES as amn_roles
# clone gets the amnesiac's real role
mainrole = amn_roles[player]
change_role(clone, "clone", mainrole)
debuglog("{0} (clone) CLONE DEAD PLAYER: {1} ({2})".format(clone, target, mainrole))
sayrole = mainrole
if sayrole in var.HIDDEN_VILLAGERS:
sayrole = "villager"
elif sayrole in var.HIDDEN_ROLES:
sayrole = var.DEFAULT_ROLE
an = "n" if sayrole.startswith(("a", "e", "i", "o", "u")) else ""
clone.send(messages["clone_turn"].format(an, sayrole))
# if a clone is cloning a clone, clone who the old clone cloned # if a clone is cloning a clone, clone who the old clone cloned
if mainrole == "clone" and player in CLONED: if mainrole == "clone" and player in CLONED:
if CLONED[player] is clone: if CLONED[player] is clone:
@ -93,30 +82,8 @@ def on_del_player(evt, var, player, mainrole, allroles, death_triggers):
CLONED[clone] = CLONED[player] CLONED[clone] = CLONED[player]
clone.send(messages["clone_success"].format(CLONED[clone])) clone.send(messages["clone_success"].format(CLONED[clone]))
debuglog("{0} (clone) CLONE: {1} ({2})".format(clone, CLONED[clone], get_main_role(CLONED[clone]))) debuglog("{0} (clone) CLONE: {1} ({2})".format(clone, CLONED[clone], get_main_role(CLONED[clone])))
elif mainrole in var.WOLFCHAT_ROLES:
wolves = get_players(var.WOLFCHAT_ROLES)
wolves.remove(clone) # remove self from list
for wolf in wolves:
wolf.queue_message(messages["clone_wolf"].format(clone, player))
if wolves:
wolf.send_messages()
if var.PHASE == "day":
random.shuffle(wolves)
for i, wolf in enumerate(wolves):
wolfrole = get_main_role(wolf)
wevt = Event("wolflist", {"tags": set()})
wevt.dispatch(var, wolf, clone)
tags = " ".join(wevt.data["tags"])
if tags:
tags += " "
wolves[i] = "\u0002{0}\u0002 ({1}{2})".format(wolf, tags, wolfrole)
if wolves: debuglog("{0} (clone) CLONE DEAD PLAYER: {1} ({2})".format(clone, target, mainrole))
clone.send(messages["wolves_list"].format(wolves))
else:
clone.send(messages["no_other_wolves"])
elif mainrole == "turncoat":
var.TURNCOATS[clone.nick] = ("none", -1) # FIXME
if mainrole == "clone" and player in CLONED: if mainrole == "clone" and player in CLONED:
del CLONED[player] del CLONED[player]
@ -155,23 +122,12 @@ def on_transition_day_begin(evt, var):
CLONED[clone] = target CLONED[clone] = target
clone.send(messages["random_clone"].format(target)) clone.send(messages["random_clone"].format(target))
@event_listener("exchange_roles") @event_listener("swap_role_state")
def on_exchange_roles(evt, var, actor, target, actor_role, target_role): def on_swap_role_state(evt, var, actor, target, role):
actor_target = None if role == "clone":
target_target = None CLONED[target], CLONED[actor] = CLONED.pop(actor), CLONED.pop(target)
if actor_role == "clone": evt.data["target_messages"].append(messages["clone_target"].format(CLONED[target]))
if actor in CLONED: evt.data["actor_messages"].append(messages["clone_target"].format(CLONED[actor]))
actor_target = CLONED.pop(actor)
evt.data["target_messages"].append(messages["clone_target"].format(actor_target))
if target_role == "clone":
if target in CLONED:
target_target = CLONED.pop(target)
evt.data["actor_messages"].append(messages["clone_target"].format(target_target))
if actor_target is not None:
CLONED[target] = actor_target
if target_target is not None:
CLONED[actor] = target_target
@event_listener("player_win") @event_listener("player_win")
def on_player_win(evt, var, player, role, winner, survived): def on_player_win(evt, var, player, role, winner, survived):
@ -179,17 +135,17 @@ def on_player_win(evt, var, player, role, winner, survived):
if role == "clone" and survived and not winner.startswith("@") and singular(winner) not in var.WIN_STEALER_ROLES: if role == "clone" and survived and not winner.startswith("@") and singular(winner) not in var.WIN_STEALER_ROLES:
evt.data["iwon"] = True evt.data["iwon"] = True
@event_listener("del_player") @event_listener("del_player", priority=1)
def first_death_occured(evt, var, player, mainrole, allroles, death_triggers): def first_death_occured(evt, var, player, mainrole, allroles, death_triggers):
global CLONE_ENABLED global CLONE_ENABLED
if CLONE_ENABLED: if CLONE_ENABLED:
return return
if var.PHASE in var.GAME_PHASES and (CLONED or get_all_players(("clone",))) and not var.FIRST_NIGHT: if CLONED and var.PHASE in var.GAME_PHASES:
CLONE_ENABLED = True CLONE_ENABLED = True
@event_listener("update_stats") @event_listener("update_stats")
def on_update_stats(evt, var, player, mainrole, revealrole, allroles): def on_update_stats(evt, var, player, mainrole, revealrole, allroles):
if CLONE_ENABLED: if CLONE_ENABLED and not var.HIDDEN_CLONE:
evt.data["possible"].add("clone") evt.data["possible"].add("clone")
@event_listener("myrole") @event_listener("myrole")
@ -212,3 +168,5 @@ def on_reset(evt, var):
global CLONE_ENABLED global CLONE_ENABLED
CLONE_ENABLED = False CLONE_ENABLED = False
CLONED.clear() CLONED.clear()
# vim: set sw=4 expandtab:

View File

@ -64,12 +64,10 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
def on_get_special(evt, var): def on_get_special(evt, var):
evt.data["villagers"].update(get_players(("detective",))) evt.data["villagers"].update(get_players(("detective",)))
@event_listener("exchange_roles") @event_listener("new_role")
def on_exchange(evt, var, actor, target, actor_role, target_role): def on_new_role(evt, var, user, old_role):
if actor_role == "detective" and target_role != "detective": if old_role == "detective" and evt.data["role"] != "detective":
INVESTIGATED.discard(actor) INVESTIGATED.discard(user)
elif target_role == "detective" and actor_role != "detective":
INVESTIGATED.discard(target)
@event_listener("transition_night_end", priority=2) @event_listener("transition_night_end", priority=2)
def on_transition_night_end(evt, var): def on_transition_night_end(evt, var):

View File

@ -120,25 +120,40 @@ def on_transition_day(evt, var):
evt.data["onlybywolves"].discard(d) evt.data["onlybywolves"].discard(d)
evt.data["killers"][d].append(k) evt.data["killers"][d].append(k)
@event_listener("exchange_roles") @event_listener("new_role")
def on_exchange(evt, var, actor, target, actor_role, target_role): def on_new_role(evt, var, player, old_role):
for k in set(KILLS): if player in TARGETS and old_role == "dullahan" and evt.data["role"] != "dullahan":
if k is actor or k is target: del KILLS[:player:]
del KILLS[k] del TARGETS[player]
for k in set(TARGETS): if player not in TARGETS and evt.data["role"] == "dullahan":
if actor_role == "dullahan" and target_role != "dullahan" and k is actor: ps = get_players()
targets = TARGETS.pop(k) max_targets = math.ceil(8.1 * math.log(len(ps), 10) - 5)
if target in targets: TARGETS[player] = UserSet()
targets.remove(target)
targets.add(actor) dull_targets = Event("dullahan_targets", {"targets": TARGETS[player]}) # support sleepy
TARGETS[target] = targets dull_targets.dispatch(var, player, max_targets)
if target_role == "dullahan" and actor_role != "dullahan" and k is target:
targets = TARGETS.pop(k) ps.remove(player)
if actor in targets: while len(TARGETS[player]) < max_targets:
targets.remove(actor) target = random.choice(ps)
targets.add(target) ps.remove(target)
TARGETS[actor] = targets TARGETS[player].add(target)
@event_listener("swap_role_state")
def on_swap_role_state(evt, var, actor, target, role):
if role == "dullahan":
targ_targets = TARGETS.pop(target)
if actor in targ_targets:
targ_targets.remove(actor)
targ_targets.add(target)
act_targets = TARGETS.pop(actor)
if target in act_targets:
act_targets.remove(target)
act_targets.add(actor)
TARGETS[actor] = targ_targets
TARGETS[target] = act_targets
@event_listener("chk_nightdone") @event_listener("chk_nightdone")
def on_chk_nightdone(evt, var): def on_chk_nightdone(evt, var):
@ -165,24 +180,6 @@ def on_transition_night_end(evt, var):
t = messages["dullahan_targets"] if var.FIRST_NIGHT else messages["dullahan_remaining_targets"] t = messages["dullahan_targets"] if var.FIRST_NIGHT else messages["dullahan_remaining_targets"]
dullahan.send(messages[to_send], t + ", ".join(t.nick for t in targets), sep="\n") dullahan.send(messages[to_send], t + ", ".join(t.nick for t in targets), sep="\n")
@event_listener("role_assignment")
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] = UserSet()
dull_targets = Event("dullahan_targets", {"targets": TARGETS}) # support sleepy
dull_targets.dispatch(var, var.ROLES["dullahan"], max_targets)
for dull, ts in TARGETS.items():
ps = pl[:]
ps.remove(dull)
while len(ts) < max_targets:
target = random.choice(ps)
ps.remove(target)
ts.add(target)
@event_listener("succubus_visit") @event_listener("succubus_visit")
def on_succubus_visit(evt, var, succubus, target): def on_succubus_visit(evt, var, succubus, target):
succubi = get_all_players(("succubus",)) succubi = get_all_players(("succubus",))

View File

@ -96,14 +96,12 @@ def on_transition_day(evt, var):
# important, otherwise our del_player listener lets hunter kill again # important, otherwise our del_player listener lets hunter kill again
del KILLS[k] del KILLS[k]
@event_listener("exchange_roles") @event_listener("new_role")
def on_exchange(evt, var, actor, target, actor_role, target_role): def on_new_role(evt, var, user, old_role):
del KILLS[:actor:] if old_role == "hunter":
del KILLS[:target:] del KILLS[:user:]
HUNTERS.discard(actor) HUNTERS.discard(user)
HUNTERS.discard(target) PASSED.discard(user)
PASSED.discard(actor)
PASSED.discard(target)
@event_listener("chk_nightdone") @event_listener("chk_nightdone")
def on_chk_nightdone(evt, var): def on_chk_nightdone(evt, var):

View File

@ -97,12 +97,10 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
def on_get_special(evt, var): def on_get_special(evt, var):
evt.data["villagers"].update(get_players(("investigator",))) evt.data["villagers"].update(get_players(("investigator",)))
@event_listener("exchange_roles") @event_listener("new_role")
def on_exchange(evt, var, actor, target, actor_role, target_role): def on_new_role(evt, var, user, old_role):
if actor_role == "investigator" and target_role != "investigator": if old_role == "investigator" and evt.data["role"] != "investigator":
INVESTIGATED.discard(actor) INVESTIGATED.discard(user)
elif target_role == "investigator" and actor_role != "investigator":
INVESTIGATED.discard(target)
@event_listener("transition_night_end", priority=2) @event_listener("transition_night_end", priority=2)
def on_transition_night_end(evt, var): def on_transition_night_end(evt, var):

View File

@ -113,3 +113,5 @@ def on_begin_day(evt, var):
@event_listener("reset") @event_listener("reset")
def on_reset(evt, var): def on_reset(evt, var):
PRAYED.clear() PRAYED.clear()
# vim: set sw=4 expandtab:

View File

@ -195,8 +195,8 @@ def on_get_special(evt, var):
evt.data["win_stealers"].update(get_players(("succubus",))) evt.data["win_stealers"].update(get_players(("succubus",)))
@event_listener("new_role") @event_listener("new_role")
def on_new_role(evt, var, user, role): def on_new_role(evt, var, user, old_role):
if role == "succubus" and user in ENTRANCED: if evt.data["role"] == "succubus" and user in ENTRANCED:
ENTRANCED.remove(user) ENTRANCED.remove(user)
user.send(messages["no_longer_entranced"]) user.send(messages["no_longer_entranced"])

134
src/roles/turncoat.py Normal file
View File

@ -0,0 +1,134 @@
import re
import random
import itertools
import math
from collections import defaultdict
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, DefaultUserDict
from src.messages import messages
from src.events import Event
TURNCOATS = UserDict() # type: Dict[users.User, Tuple[str, int]]
PASSED = UserSet() # type: Set[users.User]
def _get_side(user):
side = "currently with \u0002{0}\u0002".format(TURNCOATS[user][0])
if TURNCOATS[user][0] == "none":
side = "not currently on any side"
return side
@command("side", chan=False, pm=True, playing=True, phases=("night",), roles=("turncoat",))
def change_sides(var, wrapper, message, sendmsg=True):
if TURNCOATS[wrapper.source][1] == var.NIGHT_COUNT - 1:
wrapper.pm(messages["turncoat_already_turned"])
return
team = re.split(" +", message)[0]
team = complete_one_match(team, ("villagers", "wolves"))
if not team:
wrapper.pm(messages["turncoat_error"])
return
wrapper.pm(messages["turncoat_success"].format(team))
TURNCOATS[wrapper.source] = (team, var.NIGHT_COUNT)
PASSED.discard(wrapper.source)
debuglog("{0} (turncoat) SIDE {1}".format(wrapper.source, team))
@command("pass", chan=False, pm=True, playing=True, phases=("night",), roles=("turncoat",))
def pass_cmd(var, wrapper, message):
"""Decline to use your special power for that night."""
if TURNCOATS[wrapper.source][1] == var.NIGHT_COUNT:
# theoretically passing would revert them to how they were before, but
# we aren't tracking that, so just tell them to change it back themselves.
wrapper.pm(messages["turncoat_fail"])
return
wrapper.pm(messages["turncoat_pass"])
if TURNCOATS[wrapper.source][1] == var.NIGHT_COUNT - 1:
# don't add to PASSED since we aren't counting them anyway for nightdone
# let them still use !pass though to make them feel better or something
return
PASSED.add(wrapper.source)
debuglog("{0} (turncoat) PASS".format(wrapper.source))
@event_listener("transition_night_end")
def on_transition_night_end(evt, var):
for turncoat in get_all_players(("turncoat",)):
# they start out as unsided, but can change n1
if turncoat not in TURNCOATS:
TURNCOATS[turncoat] = ("none", -1)
if turncoat.prefers_simple():
turncoat.send(messages["turncoat_simple"].format(TURNCOATS[turncoat][0]))
else:
message = messages["turncoat_notify"]
if TURNCOATS[turncoat][0] != "none":
message += messages["turncoat_current_team"].format(TURNCOATS[turncoat][0])
else:
message += messages["turncoat_no_team"]
turncoat.send(message)
@event_listener("chk_nightdone")
def on_chk_nightdone(evt, var):
# add in turncoats who should be able to act or who passed
# but if they can act they're in TURNCOATS where the second tuple item is the current night
# (if said tuple item is the previous night, then they are not allowed to act tonight)
pl = get_players()
evt.data["actedcount"] += len(PASSED)
for turncoat, (team, night) in TURNCOATS.items():
if turncoat not in pl:
continue
if night == var.NIGHT_COUNT:
evt.data["nightroles"].append(turncoat)
evt.data["actedcount"] += 1
elif night < var.NIGHT_COUNT - 1:
evt.data["nightroles"].append(turncoat)
@event_listener("player_win")
def on_player_win(evt, var, player, role, winner, survived):
if role == "turncoat" and player in TURNCOATS and TURNCOATS[player][0] != "none":
evt.data["won"] = (winner == TURNCOATS[player][0])
@event_listener("myrole")
def on_myrole(evt, var, user):
if evt.data["role"] == "turncoat" and user in TURNCOATS:
evt.data["messages"].append(messages["turncoat_side"].format(_get_side(user)))
@event_listener("revealroles_role")
def on_revealroles_role(evt, var, user, role):
if role == "turncoat" and user in TURNCOATS:
evt.data["special_case"].append(_get_side(user))
@event_listener("get_special")
def on_get_special(evt, var):
evt.data["neutrals"].update(get_players(("turncoat",)))
@event_listener("new_role")
def on_new_role(evt, var, player, old_role):
if old_role == "turncoat" and evt.data["role"] != "turncoat":
del TURNCOATS[player]
elif evt.data["role"] == "turncoat" and old_role != "turncoat":
TURNCOATS[player] = ("none", -1)
@event_listener("swap_role_state")
def on_swap_role_state(evt, var, actor, target, role):
if role == "turncoat":
TURNCOATS[actor], TURNCOATS[target] = TURNCOATS.pop(target), TURNCOATS.pop(actor)
evt.data["actor_messages"].append(messages["turncoat_side"].format(_get_side(actor)))
evt.data["target_messages"].append(messages["turncoat_side"].format(_get_side(target)))
@event_listener("begin_day")
def on_begin_day(evt, var):
PASSED.clear()
@event_listener("reset")
def on_reset(evt, var):
PASSED.clear()
TURNCOATS.clear()
# vim: set sw=4 expandtab:

View File

@ -83,12 +83,11 @@ def on_transition_day(evt, var):
if get_main_role(target) not in var.WOLF_ROLES | var.WIN_STEALER_ROLES: if get_main_role(target) not in var.WOLF_ROLES | var.WIN_STEALER_ROLES:
var.DYING.add(vigilante) var.DYING.add(vigilante)
@event_listener("exchange_roles") @event_listener("new_role")
def on_exchange(evt, var, actor, target, actor_role, target_role): def on_new_role(evt, var, user, old_role):
del KILLS[:actor:] if old_role == "vigilante":
del KILLS[:target:] del KILLS[:user:]
PASSED.discard(actor) PASSED.discard(user)
PASSED.discard(target)
@event_listener("chk_nightdone") @event_listener("chk_nightdone")
def on_chk_nightdone(evt, var): def on_chk_nightdone(evt, var):

View File

@ -6,7 +6,7 @@ from collections import defaultdict
from src.utilities import * from src.utilities import *
from src import channels, users, debuglog, errlog, plog from src import channels, users, debuglog, errlog, plog
from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target, change_role
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages from src.messages import messages
@ -73,39 +73,10 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
continue continue
# Change their main role to wolf # Change their main role to wolf
child.send(messages["wild_child_idol_died"])
WILD_CHILDREN.add(child) WILD_CHILDREN.add(child)
change_role(child, get_main_role(child), "wolf") change_role(var, child, get_main_role(child), "wolf", message="wild_child_idol_died")
var.ROLES["wild child"].discard(child) var.ROLES["wild child"].discard(child)
if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES:
if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF:
wcroles = var.WOLF_ROLES
else:
wcroles = var.WOLF_ROLES | {"traitor"}
wolves = get_players(wcroles)
wolves.remove(child)
if wolves:
for wolf in wolves:
wolf.queue_message(messages["wild_child_as_wolf"].format(child))
wolf.send_messages()
# Send wolf list
if var.PHASE == "day":
random.shuffle(wolves)
names = []
cursed_list = get_all_players(("cursed villager",))
for i, wolf in enumerate(wolves):
role = get_main_role(wolf)
cursed = "cursed " if wolf in cursed_list else ""
names.append("\u0002{0}\u0002 ({1}{2})".format(wolf, cursed, role))
if names:
child.send(messages["wolves_list"].format(", ".join(names)))
else:
child.send(messages["no_other_wolves"])
@event_listener("chk_nightdone") @event_listener("chk_nightdone")
def on_chk_nightdone(evt, var): def on_chk_nightdone(evt, var):
if var.FIRST_NIGHT: if var.FIRST_NIGHT:

View File

@ -226,8 +226,8 @@ def on_retribution_kill(evt, var, victim, orig_target):
wolves = get_players(CAN_KILL) wolves = get_players(CAN_KILL)
evt.data["target"] = random.choice(wolves) evt.data["target"] = random.choice(wolves)
@event_listener("exchange_roles", priority=2) @event_listener("new_role", priority=4)
def on_exchange(evt, var, actor, target, actor_role, target_role): def on_new_role(evt, var, player, old_role):
wcroles = var.WOLFCHAT_ROLES wcroles = var.WOLFCHAT_ROLES
if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES: if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES:
if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF: if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF:
@ -235,77 +235,61 @@ def on_exchange(evt, var, actor, target, actor_role, target_role):
else: else:
wcroles = var.WOLF_ROLES | {"traitor"} wcroles = var.WOLF_ROLES | {"traitor"}
if target_role in wcroles and actor_role not in wcroles: if old_role is None:
# initial role assignment; don't do all the logic below about notifying other wolves and such
if evt.data["role"] in wcroles:
evt.data["in_wolfchat"] = True
return
sayrole = evt.data["role"]
if sayrole in var.HIDDEN_VILLAGERS:
sayrole = "villager"
elif sayrole in var.HIDDEN_ROLES:
sayrole = var.DEFAULT_ROLE
an = "n" if sayrole.startswith(("a", "e", "i", "o", "u")) else ""
if player.nick in KILLS: # FIXME
del KILLS[player.nick]
if old_role not in wcroles and evt.data["role"] in wcroles:
# a new wofl has joined the party, give them tummy rubs and the wolf list
# and let the other wolves know to break out the confetti and villager steaks
wofls = get_players(wcroles)
evt.data["in_wolfchat"] = True
if wofls:
new_wolves = []
for wofl in wofls:
wofl.queue_message(messages["wolfchat_new_member"].format(player, an, sayrole))
wofl.send_messages()
pl = get_players() pl = get_players()
pl.remove(player)
random.shuffle(pl) random.shuffle(pl)
pl.remove(actor) # remove self from list pt = []
notify = [] wevt = Event("wolflist", {"tags": set()})
to_send = [] for p in pl:
for player in pl: prole = get_main_role(p)
prole = get_main_role(player) wevt.data["tags"].clear()
if player is target: wevt.dispatch(var, p, player)
prole = actor_role
wevt = Event("wolflist", {"tags": set()})
wevt.dispatch(var, player, actor)
tags = " ".join(wevt.data["tags"]) tags = " ".join(wevt.data["tags"])
if prole in wcroles: if prole in wcroles:
if tags: if tags:
tags += " " tags += " "
to_send.append("\u0002{0}\u0002 ({1}{2})".format(player, tags, prole)) pt.append("\u0002{0}\u0002 ({1}{2})".format(p, tags, prole))
notify.append(player)
elif tags: elif tags:
to_send.append("{0} ({1})".format(player, tags)) pt.append("{0} ({1})".format(p, tags))
else: else:
to_send.append(player.nick) pt.append(p.nick)
for player in notify: evt.data["messages"].append(messages["players_list"].format(", ".join(pt)))
player.queue_message(messages["players_exchanged_roles"].format(target, actor))
if notify:
player.send_messages()
evt.data["actor_messages"].append(messages["players_list"].format(", ".join(to_send))) if var.PHASE == "night":
if target_role in CAN_KILL and var.DISEASED_WOLVES: # inform the new wolf that they can kill and stuff
evt.data["actor_messages"].append(messages["ill_wolves"]) if evt.data["role"] in CAN_KILL and var.DISEASED_WOLVES:
if var.ALPHA_ENABLED and target_role == "alpha wolf" and actor.nick not in var.ALPHA_WOLVES: evt.data["messages"].append(messages["ill_wolves"])
evt.data["actor_messages"].append(messages["wolf_bite"]) # FIXME: split when alpha wolf is split
elif actor_role in wcroles and target_role not in wcroles: if var.ALPHA_ENABLED and evt.data["role"] == "alpha wolf" and player.nick not in var.ALPHA_WOLVES:
pl = get_players() evt.data["messages"].append(messages["wolf_bite"])
random.shuffle(pl)
pl.remove(target) # remove self from list
notify = []
to_send = []
for player in pl:
prole = get_main_role(player)
if player is actor:
prole = target_role
wevt = Event("wolflist", {"tags": set()})
wevt.dispatch(var, player, target)
tags = " ".join(wevt.data["tags"])
if prole in wcroles:
if tags:
tags += " "
to_send.append("\u0002{0}\u0002 ({1}{2})".format(player, tags, prole))
notify.append(player)
elif tags:
to_send.append("{0} ({1})".format(player, tags))
else:
to_send.append(player.nick)
for player in notify:
player.queue_message(messages["players_exchanged_roles"].format(actor, target))
if notify:
player.send_messages()
evt.data["target_messages"].append(messages["players_list"].format(", ".join(to_send)))
if actor_role in CAN_KILL and var.DISEASED_WOLVES:
evt.data["target_messages"].append(messages["ill_wolves"])
if var.ALPHA_ENABLED and actor_role == "alpha wolf" and target.nick not in var.ALPHA_WOLVES:
evt.data["target_messages"].append(messages["wolf_bite"])
if actor.nick in KILLS:
del KILLS[actor.nick]
if target.nick in KILLS:
del KILLS[target.nick]
@event_listener("chk_nightdone", priority=3) @event_listener("chk_nightdone", priority=3)
def on_chk_nightdone(evt, var): def on_chk_nightdone(evt, var):
@ -320,7 +304,7 @@ def on_chk_nightdone(evt, var):
evt.data["actedcount"] += len(KILLS) evt.data["actedcount"] += len(KILLS)
evt.data["nightroles"].append(users.FakeUser.from_nick("@WolvesAgree@")) evt.data["nightroles"].append(users.FakeUser.from_nick("@WolvesAgree@"))
# check if wolves are actually agreeing or not; # check if wolves are actually agreeing or not;
# only count agreement_user if they are # only add to count if they actually agree
# (this is *slighty* less hacky than deducting 1 from actedcount as we did previously) # (this is *slighty* less hacky than deducting 1 from actedcount as we did previously)
kills = set() kills = set()
for ls in KILLS.values(): for ls in KILLS.values():

View File

@ -25,23 +25,10 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
global ANGRY_WOLVES global ANGRY_WOLVES
ANGRY_WOLVES = True ANGRY_WOLVES = True
# wolf fires on priority 2, so we can add our extra messages now (at default priority 5) @event_listener("new_role")
@event_listener("exchange_roles") def on_new_role(evt, var, player, old_role):
def on_exchange(evt, var, actor, target, actor_role, target_role): if ANGRY_WOLVES and evt.data["in_wolfchat"] and wolf.wolf_can_kill(var, player):
if not ANGRY_WOLVES: evt.data["messages"].append(messages["angry_wolves"])
return
wcroles = var.WOLFCHAT_ROLES
if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES:
if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF:
wcroles = var.WOLF_ROLES
else:
wcroles = var.WOLF_ROLES | {"traitor"}
if target_role in wcroles and actor_role not in wcroles and wolf.wolf_can_kill(var, target):
evt.data["actor_messages"].append(messages["angry_wolves"])
elif actor_role in wcroles and target_role not in wcroles and wolf.wolf_can_kill(var, actor):
evt.data["target_messages"].append(messages["angry_wolves"])
@event_listener("transition_night_end", priority=3) @event_listener("transition_night_end", priority=3)
def on_transition_night_end(evt, var): def on_transition_night_end(evt, var):

View File

@ -12,7 +12,7 @@ __all__ = ["pm", "is_fake_nick", "mass_mode", "mass_privmsg", "reply",
"is_user_simple", "is_user_notice", "in_wolflist", "is_user_simple", "is_user_notice", "in_wolflist",
"relay_wolfchat_command", "irc_lower", "irc_equals", "match_hostmask", "relay_wolfchat_command", "irc_lower", "irc_equals", "match_hostmask",
"is_owner", "is_admin", "plural", "singular", "list_players", "is_owner", "is_admin", "plural", "singular", "list_players",
"get_role", "get_roles", "change_role", "role_order", "break_long_message", "get_role", "get_roles", "role_order", "break_long_message",
"complete_match", "complete_one_match", "get_victim", "InvalidModeException"] "complete_match", "complete_one_match", "get_victim", "InvalidModeException"]
# message either privmsg or notice, depending on user settings # message either privmsg or notice, depending on user settings
def pm(cli, target, message): def pm(cli, target, message):
@ -316,16 +316,6 @@ def get_roles(*roles, rolemap=None):
all_roles.append(rolemap[role]) all_roles.append(rolemap[role])
return [u.nick for u in itertools.chain(*all_roles)] return [u.nick for u in itertools.chain(*all_roles)]
# TODO: move this to functions.py
def change_role(user, oldrole, newrole, set_final=True):
var.ROLES[oldrole].remove(user)
var.ROLES[newrole].add(user)
# 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 role_order = lambda: var.ROLE_GUIDE
def break_long_message(phrases, joinstr = " "): def break_long_message(phrases, joinstr = " "):

View File

@ -21,8 +21,9 @@
import copy import copy
import fnmatch import fnmatch
import itertools
import functools import functools
import itertools
import json
import math import math
import os import os
import platform import platform
@ -37,8 +38,8 @@ import threading
import time import time
import traceback import traceback
import urllib.request import urllib.request
from collections import defaultdict, deque, Counter from collections import defaultdict, deque, Counter
import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
from oyoyo.parse import parse_nick from oyoyo.parse import parse_nick
@ -48,13 +49,19 @@ import src
import src.settings as var import src.settings as var
from src.utilities import * from src.utilities import *
from src import db, events, dispatcher, channels, users, hooks, logger, debuglog, errlog, plog from src import db, events, dispatcher, channels, users, hooks, logger, debuglog, errlog, plog
from src.decorators import command, cmd, hook, handle_error, event_listener, COMMANDS
from src.containers import UserList, UserSet, UserDict, DefaultUserDict from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.functions import get_players, get_all_players, get_participants, get_main_role, get_all_roles, get_reveal_role, get_target from src.decorators import command, cmd, hook, handle_error, event_listener, COMMANDS
from src.messages import messages from src.messages import messages
from src.warnings import * from src.warnings import *
from src.context import IRCContext from src.context import IRCContext
from src.functions import (
get_players, get_all_players, get_participants,
get_main_role, get_all_roles, get_reveal_role,
get_target, change_role,
)
# done this way so that events is accessible in !eval (useful for debugging) # done this way so that events is accessible in !eval (useful for debugging)
Event = events.Event Event = events.Event
@ -2109,8 +2116,6 @@ def stop_game(var, winner="", abort=False, additional_winners=None, log=True):
teams = {"monster":"monsters", "demoniac":"demoniacs"} teams = {"monster":"monsters", "demoniac":"demoniacs"}
if rol in teams and winner == teams[rol]: if rol in teams and winner == teams[rol]:
won = True won = True
elif rol == "turncoat" and splr in var.TURNCOATS and var.TURNCOATS[splr][0] != "none":
won = (winner == var.TURNCOATS[splr][0])
elif rol == "fool" and "@" + splr == winner: elif rol == "fool" and "@" + splr == winner:
won = True won = True
@ -2735,8 +2740,7 @@ def rename_player(var, user, prefix):
dictvar.update(kvp) dictvar.update(kvp)
if prefix in dictvar.keys(): if prefix in dictvar.keys():
del dictvar[prefix] del dictvar[prefix]
for dictvar in (var.FINAL_ROLES, var.TURNCOATS, for dictvar in (var.FINAL_ROLES, var.DOCTORS, var.BITTEN_ROLES, var.LYCAN_ROLES):
var.DOCTORS, var.BITTEN_ROLES, var.LYCAN_ROLES):
if prefix in dictvar.keys(): if prefix in dictvar.keys():
dictvar[nick] = dictvar.pop(prefix) dictvar[nick] = dictvar.pop(prefix)
# defaultdict(list), where keys are nicks and items in list do not matter # defaultdict(list), where keys are nicks and items in list do not matter
@ -3246,28 +3250,9 @@ def transition_day(gameid=0):
if vrole not in var.WOLFCHAT_ROLES: if vrole not in var.WOLFCHAT_ROLES:
revt.data["message"].append(messages["new_wolf"]) revt.data["message"].append(messages["new_wolf"])
var.EXTRA_WOLVES += 1 var.EXTRA_WOLVES += 1
victim.send(messages["lycan_turn"])
var.LYCAN_ROLES[victim.nick] = vrole var.LYCAN_ROLES[victim.nick] = vrole
change_role(victim, vrole, "wolf") change_role(var, victim, vrole, "wolf", message="lycan_turn")
var.ROLES["lycan"].discard(victim) # in the event lycan was a template, we want to ensure it gets purged var.ROLES["lycan"].discard(victim) # in the event lycan was a template, we want to ensure it gets purged
wolves = get_players(var.WOLFCHAT_ROLES)
random.shuffle(wolves)
wolves.remove(victim) # remove self from list
to_send = []
for wolf in wolves:
wolf.queue_message(messages["lycan_wc_notification"].format(victim))
role = get_main_role(wolf)
wevt = Event("wolflist", {"tags": set()})
wevt.dispatch(var, wolf, victim)
tags = " ".join(wevt.data["tags"])
if tags:
tags += " "
to_send.append("\u0002{0}\u0002 ({1}{2})".format(wolf, tags, role))
if wolves:
wolf.send_messages()
victim.send(messages["wolves_list"].format(", ".join(to_send)))
revt.data["novictmsg"] = False revt.data["novictmsg"] = False
elif victim not in revt.data["dead"]: # not already dead via some other means elif victim not in revt.data["dead"]: # not already dead via some other means
if var.ROLE_REVEAL in ("on", "team"): if var.ROLE_REVEAL in ("on", "team"):
@ -3368,29 +3353,28 @@ def transition_day(gameid=0):
continue continue
newrole = "wolf" newrole = "wolf"
to_send = "bitten_turn"
if chumprole == "guardian angel": if chumprole == "guardian angel":
chump.send(messages["fallen_angel_turn"]) to_send = "fallen_angel_turn"
# fallen angels also automatically gain the assassin template if they don't already have it # fallen angels also automatically gain the assassin template if they don't already have it
newrole = "fallen angel" newrole = "fallen angel"
var.ROLES["assassin"].add(chump) var.ROLES["assassin"].add(chump)
debuglog("{0} (guardian angel) TURNED FALLEN ANGEL".format(chump)) debuglog("{0} (guardian angel) TURNED FALLEN ANGEL".format(chump))
elif chumprole in ("seer", "oracle", "augur"): elif chumprole in ("seer", "oracle", "augur"):
chump.send(messages["seer_turn"]) to_send = "seer_turn"
newrole = "doomsayer" newrole = "doomsayer"
debuglog("{0} ({1}) TURNED DOOMSAYER".format(chump, chumprole)) debuglog("{0} ({1}) TURNED DOOMSAYER".format(chump, chumprole))
elif chumprole in var.TOTEM_ORDER: elif chumprole in var.TOTEM_ORDER:
chump.send(messages["shaman_turn"]) to_send = "shaman_turn"
newrole = "wolf shaman" newrole = "wolf shaman"
debuglog("{0} ({1}) TURNED WOLF SHAMAN".format(chump, chumprole)) debuglog("{0} ({1}) TURNED WOLF SHAMAN".format(chump, chumprole))
elif chumprole == "harlot": elif chumprole == "harlot":
chump.send(messages["harlot_turn"]) to_send = "harlot_turn"
debuglog("{0} (harlot) TURNED WOLF".format(chump)) debuglog("{0} (harlot) TURNED WOLF".format(chump))
else: else:
chump.send(messages["bitten_turn"])
debuglog("{0} ({1}) TURNED WOLF".format(chump, chumprole)) debuglog("{0} ({1}) TURNED WOLF".format(chump, chumprole))
var.BITTEN_ROLES[chump.nick] = chumprole var.BITTEN_ROLES[chump.nick] = chumprole
change_role(chump, chumprole, newrole) change_role(var, chump, chumprole, newrole, message=to_send)
relay_wolfchat_command(chump.client, chump.nick, messages["wolfchat_new_member"].format(chump, newrole), var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True)
killer_role = {} killer_role = {}
for deadperson in dead: for deadperson in dead:
@ -3427,8 +3411,6 @@ def chk_nightdone():
if var.PHASE != "night": if var.PHASE != "night":
return return
pl = get_players()
spl = set(pl)
actedcount = sum(map(len, (var.PASSED, var.OBSERVED, var.HEXED, var.CURSED))) actedcount = sum(map(len, (var.PASSED, var.OBSERVED, var.HEXED, var.CURSED)))
nightroles = list(get_all_players(("sorcerer", "hag", "warlock", "werecrow"))) nightroles = list(get_all_players(("sorcerer", "hag", "warlock", "werecrow")))
@ -3438,19 +3420,6 @@ def chk_nightdone():
nightroles.extend(get_all_players(("alpha wolf",))) nightroles.extend(get_all_players(("alpha wolf",)))
actedcount += len([p for p in var.ALPHA_WOLVES if p in get_roles("alpha wolf")]) # FIXME actedcount += len([p for p in var.ALPHA_WOLVES if p in get_roles("alpha wolf")]) # FIXME
# add in turncoats who should be able to act -- if they passed they're already in var.PASSED
# but if they can act they're in var.TURNCOATS where the second tuple item is the current night
# (if said tuple item is the previous night, then they are not allowed to act tonight)
for tc, tu in var.TURNCOATS.items():
user = users._get(tc) # FIXME
if user not in pl:
continue
if tu[1] == var.NIGHT_COUNT:
nightroles.append(user)
actedcount += 1
elif tu[1] < var.NIGHT_COUNT - 1:
nightroles.append(user)
event = Event("chk_nightdone", {"actedcount": actedcount, "nightroles": nightroles, "transition_day": transition_day}) event = Event("chk_nightdone", {"actedcount": actedcount, "nightroles": nightroles, "transition_day": transition_day})
event.dispatch(var) event.dispatch(var)
actedcount = event.data["actedcount"] actedcount = event.data["actedcount"]
@ -3603,6 +3572,11 @@ def choose_target(actor, nick):
# returns true if a swap happened # returns true if a swap happened
# check for that to short-circuit the nightrole # check for that to short-circuit the nightrole
def check_exchange(cli, actor, nick): def check_exchange(cli, actor, nick):
# July 2nd, 2018 - The exchanging mechanic has been updated and no longer handles
# some forms of exchanging properly. As a result, we are disabling exchanging until
# role classes are implemented, which needs all roles to be fully split first.
# Until then, this function is a no-op. -Vgr & woffle
return False
#some roles can act on themselves, ignore this #some roles can act on themselves, ignore this
if actor == nick: if actor == nick:
return False return False
@ -3636,8 +3610,6 @@ def check_exchange(cli, actor, nick):
var.ALPHA_WOLVES.discard(actor) var.ALPHA_WOLVES.discard(actor)
elif actor_role == "warlock": elif actor_role == "warlock":
var.CURSED.discard(actor) var.CURSED.discard(actor)
elif actor_role == "turncoat":
del var.TURNCOATS[actor]
# var.PASSED is used by many roles # var.PASSED is used by many roles
@ -3660,17 +3632,20 @@ def check_exchange(cli, actor, nick):
var.ALPHA_WOLVES.discard(nick) var.ALPHA_WOLVES.discard(nick)
elif nick_role == "warlock": elif nick_role == "warlock":
var.CURSED.discard(nick) var.CURSED.discard(nick)
elif nick_role == "turncoat":
del var.TURNCOATS[nick]
evt = Event("exchange_roles", {"actor_messages": [], "target_messages": [], "actor_role": actor_role, "target_role": nick_role}) evt = Event("exchange_roles", {"actor_messages": [], "target_messages": [], "actor_role": actor_role, "target_role": nick_role})
evt.dispatch(var, user, target, actor_role, nick_role) evt.dispatch(var, user, target, actor_role, nick_role) # FIXME: Deprecated, change in favor of new_role and swap_role_state
actor_role = evt.data["actor_role"] nick_role = change_role(var, user, actor_role, nick_role, inherit_from=target)
nick_role = evt.data["target_role"] actor_role = change_role(var, target, nick_role, actor_role, inherit_from=user)
if nick_role == actor_role: # make sure that two players with the same role exchange their role state properly (e.g. dullahan)
evt_same = Event("swap_role_state", {"actor_messages": [], "target_messages": []})
evt_same.dispatch(var, user, target, actor_role)
user.send(*evt_same.data["actor_messages"])
target.send(*evt_same.data["target_messages"])
change_role(user, actor_role, nick_role)
change_role(target, nick_role, actor_role)
if actor in var.BITTEN_ROLES.keys(): if actor in var.BITTEN_ROLES.keys():
if nick 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] var.BITTEN_ROLES[actor], var.BITTEN_ROLES[nick] = var.BITTEN_ROLES[nick], var.BITTEN_ROLES[actor]
@ -3691,25 +3666,6 @@ def check_exchange(cli, actor, nick):
var.LYCAN_ROLES[actor] = var.LYCAN_ROLES[nick] var.LYCAN_ROLES[actor] = var.LYCAN_ROLES[nick]
del var.LYCAN_ROLES[nick] del var.LYCAN_ROLES[nick]
actor_rev_role = actor_role
if actor_role in var.HIDDEN_ROLES:
actor_rev_role = var.DEFAULT_ROLE
elif actor_role in var.HIDDEN_VILLAGERS:
actor_rev_role = "villager"
nick_rev_role = nick_role
if nick_role in var.HIDDEN_ROLES:
nick_rev_role = var.DEFAULT_ROLE
elif actor_role in var.HIDDEN_VILLAGERS:
nick_rev_role = "villager"
# don't say who, since misdirection/luck totem may have switched it
# and this makes life far more interesting
user.send(messages["role_swap"].format(nick_rev_role))
target.send(messages["role_swap"].format(actor_rev_role))
user.send(*evt.data["actor_messages"])
target.send(*evt.data["target_messages"])
wcroles = var.WOLFCHAT_ROLES wcroles = var.WOLFCHAT_ROLES
if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES: if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES:
if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF: if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF:
@ -3726,8 +3682,6 @@ def check_exchange(cli, actor, nick):
if player in get_roles("cursed villager"): # FIXME if player in get_roles("cursed villager"): # FIXME
pl[i] = player + " (cursed)" pl[i] = player + " (cursed)"
pm(cli, actor, messages["players_list"].format(", ".join(pl))) pm(cli, actor, messages["players_list"].format(", ".join(pl)))
elif nick_role == "turncoat":
var.TURNCOATS[actor] = ("none", -1)
if actor_role not in wcroles and actor_role == "warlock": if actor_role not in wcroles and actor_role == "warlock":
# this means warlock isn't in wolfchat, so only give cursed list # this means warlock isn't in wolfchat, so only give cursed list
@ -3738,8 +3692,6 @@ def check_exchange(cli, actor, nick):
if player in get_roles("cursed villager"): # FIXME if player in get_roles("cursed villager"): # FIXME
pl[i] = player + " (cursed)" pl[i] = player + " (cursed)"
pm(cli, nick, messages["players_list"].format(", ".join(pl))) pm(cli, nick, messages["players_list"].format(", ".join(pl)))
elif actor_role == "turncoat":
var.TURNCOATS[nick] = ("none", -1)
var.EXCHANGED_ROLES.append((actor, nick)) var.EXCHANGED_ROLES.append((actor, nick))
return True return True
@ -3994,15 +3946,14 @@ def immunize(cli, nick, chan, rest):
var.DISEASED.remove(victim) var.DISEASED.remove(victim)
if victim in get_roles("lycan"): # FIXME if victim in get_roles("lycan"): # FIXME
lycan = True lycan = True
lycan_message = (messages["lycan_cured"])
if get_role(victim) == "lycan": if get_role(victim) == "lycan":
change_role(users._get(victim), "lycan", "villager") # FIXME change_role(var, users._get(victim), "lycan", "villager", message="lycan_cured") # FIXME
else: else:
var.ROLES["lycan"].remove(users._get(victim)) # FIXME var.ROLES["lycan"].remove(users._get(victim)) # FIXME
var.CURED_LYCANS.add(victim) var.CURED_LYCANS.add(victim)
else: else:
lycan_message = messages[evt.data["message"]] lycan_message = messages[evt.data["message"]]
pm(cli, victim, (messages["immunization_success"]).format(lycan_message)) pm(cli, victim, (messages["immunization_success"]).format(lycan_message))
if evt.data["success"]: if evt.data["success"]:
var.IMMUNIZED.add(victim) var.IMMUNIZED.add(victim)
var.DOCTORS[nick] -= 1 var.DOCTORS[nick] -= 1
@ -4040,57 +3991,18 @@ def bite_cmd(cli, nick, chan, rest):
relay_wolfchat_command(cli, nick, messages["alpha_bite_wolfchat"].format(nick, victim), ("alpha wolf",), is_wolf_command=True) relay_wolfchat_command(cli, nick, messages["alpha_bite_wolfchat"].format(nick, victim), ("alpha wolf",), is_wolf_command=True)
debuglog("{0} ({1}) BITE: {2} ({3})".format(nick, get_role(nick), actual, get_role(actual))) debuglog("{0} ({1}) BITE: {2} ({3})".format(nick, get_role(nick), actual, get_role(actual)))
@cmd("pass", chan=False, pm=True, playing=True, phases=("night",), roles=("turncoat", "warlock")) @cmd("pass", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("warlock",))
def pass_cmd(cli, nick, chan, rest): def pass_cmd(cli, nick, chan, rest):
"""Decline to use your special power for that night.""" """Decline to use your special power for that night."""
nickrole = get_role(nick) if nick in var.CURSED:
pm(cli, nick, messages["already_cursed"])
# turncoats can change roles and pass even if silenced
if nickrole != "turncoat" and nick in var.SILENCED:
if chan == nick:
pm(cli, nick, messages["silenced"])
else:
cli.notice(nick, messages["silenced"])
return return
pm(cli, nick, messages["warlock_pass"])
if nickrole == "turncoat": relay_wolfchat_command(cli, nick, messages["warlock_pass_wolfchat"].format(nick), ("warlock",))
if var.TURNCOATS[nick][1] == var.NIGHT_COUNT: var.PASSED.add(nick)
# theoretically passing would revert them to how they were before, but
# we aren't tracking that, so just tell them to change it back themselves.
pm(cli, nick, messages["turncoat_fail"])
return
pm(cli, nick, messages["turncoat_pass"])
if var.TURNCOATS[nick][1] == var.NIGHT_COUNT - 1:
# don't add to var.PASSED since we aren't counting them anyway for nightdone
# let them still use !pass though to make them feel better or something
return
var.PASSED.add(nick)
elif nickrole == "warlock":
if nick in var.CURSED:
pm(cli, nick, messages["already_cursed"])
return
pm(cli, nick, messages["warlock_pass"])
relay_wolfchat_command(cli, nick, messages["warlock_pass_wolfchat"].format(nick), ("warlock",))
var.PASSED.add(nick)
debuglog("{0} ({1}) PASS".format(nick, get_role(nick))) debuglog("{0} ({1}) PASS".format(nick, get_role(nick)))
@cmd("side", chan=False, pm=True, playing=True, phases=("night",), roles=("turncoat",))
def change_sides(cli, nick, chan, rest, sendmsg=True):
if var.TURNCOATS[nick][1] == var.NIGHT_COUNT - 1:
pm(cli, nick, messages["turncoat_already_turned"])
return
team = re.split(" +", rest)[0]
team = complete_one_match(team, ("villagers", "wolves"))
if not team:
pm(cli, nick, messages["turncoat_error"])
return
pm(cli, nick, messages["turncoat_success"].format(team))
var.TURNCOATS[nick] = (team, var.NIGHT_COUNT)
debuglog("{0} ({1}) SIDE {2}".format(nick, get_role(nick), team))
@cmd("hex", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("hag",)) @cmd("hex", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("hag",))
def hex_target(cli, nick, chan, rest): def hex_target(cli, nick, chan, rest):
"""Hex someone, preventing them from acting the next day and night.""" """Hex someone, preventing them from acting the next day and night."""
@ -4437,21 +4349,6 @@ def transition_night():
else: else:
lycan.send(messages["lycan_notify"]) lycan.send(messages["lycan_notify"])
for turncoat in get_all_players(("turncoat",)):
# they start out as unsided, but can change n1
if turncoat.nick not in var.TURNCOATS:
var.TURNCOATS[turncoat.nick] = ("none", -1)
if turncoat.prefers_simple():
turncoat.send(messages["turncoat_simple"].format(var.TURNCOATS[turncoat.nick][0]))
else:
message = messages["turncoat_notify"]
if var.TURNCOATS[turncoat.nick][0] != "none":
message += messages["turncoat_current_team"].format(var.TURNCOATS[turncoat.nick][0])
else:
message += messages["turncoat_no_team"]
turncoat.send(message)
for priest in get_all_players(("priest",)): for priest in get_all_players(("priest",)):
if priest.prefers_simple(): if priest.prefers_simple():
priest.send(messages["priest_simple"]) priest.send(messages["priest_simple"])
@ -4738,7 +4635,6 @@ def start(cli, nick, chan, forced = False, restart = ""):
var.BITTEN_ROLES = {} var.BITTEN_ROLES = {}
var.LYCAN_ROLES = {} var.LYCAN_ROLES = {}
var.ACTIVE_PROTECTIONS = defaultdict(list) var.ACTIVE_PROTECTIONS = defaultdict(list)
var.TURNCOATS = {}
var.EXCHANGED_ROLES = [] var.EXCHANGED_ROLES = []
var.EXTRA_WOLVES = 0 var.EXTRA_WOLVES = 0
var.PRIESTS = set() var.PRIESTS = set()
@ -4857,8 +4753,10 @@ def start(cli, nick, chan, forced = False, restart = ""):
var.LAST_TIME = None var.LAST_TIME = None
var.LAST_VOTES = None var.LAST_VOTES = None
event = Event("role_assignment", {}) for role, players in var.ROLES.items():
event.dispatch(var, var.CURRENT_GAMEMODE.name, get_players()) for player in players:
evt = Event("new_role", {"messages": [], "role": role}, inherit_from=None)
evt.dispatch(var, player, None)
if not restart: if not restart:
gamemode = var.CURRENT_GAMEMODE.name gamemode = var.CURRENT_GAMEMODE.name
@ -5567,10 +5465,6 @@ def myrole(var, wrapper, message):
for msg in evt.data["messages"]: for msg in evt.data["messages"]:
wrapper.pm(msg) wrapper.pm(msg)
# Remind turncoats of their side
if role == "turncoat":
wrapper.pm(messages["turncoat_side"].format(var.TURNCOATS.get(wrapper.source.nick, "none")[0]))
# Check for gun/bullets # Check for gun/bullets
if wrapper.source not in var.ROLES["amnesiac"] and wrapper.source in var.GUNNERS and var.GUNNERS[wrapper.source]: if wrapper.source not in var.ROLES["amnesiac"] and wrapper.source in var.GUNNERS and var.GUNNERS[wrapper.source]:
role = "gunner" role = "gunner"
@ -6041,9 +5935,6 @@ def revealroles(var, wrapper, message):
# print how many bullets normal gunners have # print how many bullets normal gunners have
if (role == "gunner" or role == "sharpshooter") and user in var.GUNNERS: if (role == "gunner" or role == "sharpshooter") and user in var.GUNNERS:
special_case.append("{0} bullet{1}".format(var.GUNNERS[user], "" if var.GUNNERS[user] == 1 else "s")) special_case.append("{0} bullet{1}".format(var.GUNNERS[user], "" if var.GUNNERS[user] == 1 else "s"))
elif role == "turncoat" and user.nick in var.TURNCOATS:
special_case.append("currently with \u0002{0}\u0002".format(var.TURNCOATS[user.nick][0])
if var.TURNCOATS[user.nick][0] != "none" else "not currently on any side")
evt = Event("revealroles_role", {"special_case": special_case}) evt = Event("revealroles_role", {"special_case": special_case})
evt.dispatch(var, user, role) evt.dispatch(var, user, role)