Overhaul succubus (first pass)

This commit is contained in:
Vgr E. Barry 2018-05-01 17:47:44 -04:00
parent 5331cda68f
commit 810d2ba3cc
22 changed files with 53 additions and 273 deletions

View File

@ -753,7 +753,6 @@
"dullahan_die_success": "Before dying, \u0002{0}\u0002 snaps a whip made of a human spine at \u0002{1}\u0002, killing them. The village mourns the loss of a{2} \u0002{3}\u0002.", "dullahan_die_success": "Before dying, \u0002{0}\u0002 snaps a whip made of a human spine at \u0002{1}\u0002, killing them. The village mourns the loss of a{2} \u0002{3}\u0002.",
"dullahan_die_success_noreveal": "Before dying, \u0002{0}\u0002 snaps a whip made of a human spine at \u0002{1}\u0002, killing them.", "dullahan_die_success_noreveal": "Before dying, \u0002{0}\u0002 snaps a whip made of a human spine at \u0002{1}\u0002, killing them.",
"entranced_revert_win": "You are no longer entranced. \u0002Your win conditions have reset to normal.\u0002", "entranced_revert_win": "You are no longer entranced. \u0002Your win conditions have reset to normal.\u0002",
"succubus_die_kill": "As the last remaining succubus dies, a foul curse causes {0} to wither away and die in front of the astonished village.",
"player_sick": "You woke up today not feeling very well, you think it best to stay home for the remainder of the day and night.", "player_sick": "You woke up today not feeling very well, you think it best to stay home for the remainder of the day and night.",
"consecrating_no_vote": "You are consecrating someone today and cannot participate in the vote.", "consecrating_no_vote": "You are consecrating someone today and cannot participate in the vote.",
"illness_no_vote": "You are staying home due to your illness and cannot participate in the vote.", "illness_no_vote": "You are staying home due to your illness and cannot participate in the vote.",
@ -763,7 +762,6 @@
"blessed_notify_target": "You suddenly feel very safe.", "blessed_notify_target": "You suddenly feel very safe.",
"consecrate_fail": "\u0002{0}\u0002 is not currently playing or is not dead.", "consecrate_fail": "\u0002{0}\u0002 is not currently playing or is not dead.",
"consecrate_success": "You have consecrated the body of \u0002{0}\u0002.", "consecrate_success": "You have consecrated the body of \u0002{0}\u0002.",
"no_acting_on_succubus": "You may not {0} a succubus.",
"coin_toss": "\u0002{0}\u0002 tosses a coin into the air...", "coin_toss": "\u0002{0}\u0002 tosses a coin into the air...",
"coin_land": "The coin lands on \u0002{0}\u0002.", "coin_land": "The coin lands on \u0002{0}\u0002.",
"coin_choices": [ "coin_choices": [

View File

@ -48,14 +48,13 @@ brokentotem = set() # type: Set[users.User]
# 1. Expand var.TOTEM_ORDER and upate var.TOTEM_CHANCES to account for the new width # 1. Expand var.TOTEM_ORDER and upate var.TOTEM_CHANCES to account for the new width
# 2. Add the role to var.ROLE_GUIDE # 2. Add the role to var.ROLE_GUIDE
# 3. Add the role to whatever other holding vars are necessary based on what it does # 3. Add the role to whatever other holding vars are necessary based on what it does
# 4. Setup initial variables and events with setup_variables(rolename, knows_totem, get_tags) # 4. Setup initial variables and events with setup_variables(rolename, knows_totem)
# knows_totem is a bool and keyword-only. get_tags is a function in the form get_tags(var, totem) # knows_totem is a bool and keyword-only
# and should return a set
# 5. Implement custom events if the role does anything else beyond giving totems. # 5. Implement custom events if the role does anything else beyond giving totems.
# #
# Modifying this file to add new totems or new shaman roles is generally never required # Modifying this file to add new totems or new shaman roles is generally never required
def setup_variables(rolename, *, knows_totem, get_tags): def setup_variables(rolename, *, knows_totem):
"""Setup role variables and shared events.""" """Setup role variables and shared events."""
TOTEMS = UserDict() # type: Dict[users.User, str] TOTEMS = UserDict() # type: Dict[users.User, str]
LASTGIVEN = UserDict() # type: Dict[users.User, users.User] LASTGIVEN = UserDict() # type: Dict[users.User, users.User]
@ -178,14 +177,6 @@ def setup_variables(rolename, *, knows_totem, get_tags):
evt.data["target_messages"].append(messages["shaman_totem"].format(actor_totem)) evt.data["target_messages"].append(messages["shaman_totem"].format(actor_totem))
TOTEMS[target] = actor_totem TOTEMS[target] = actor_totem
@event_listener("succubus_visit")
def on_succubus_visit(evt, var, succubus, target):
if target in SHAMANS and SHAMANS[target][1] in get_all_players(("succubus",)):
tags = get_tags(var, TOTEMS[target])
if "beneficial" not in tags:
target.send(messages["retract_totem_succubus"].format(SHAMANS[target][1]))
del SHAMANS[target]
if knows_totem: if knows_totem:
@event_listener("myrole") @event_listener("myrole")
def on_myrole(evt, var, user): def on_myrole(evt, var, user):
@ -206,16 +197,14 @@ def get_totem_target(var, wrapper, message, lastgiven):
return target return target
def give_totem(var, wrapper, target, prefix, tags, role, msg): def give_totem(var, wrapper, target, prefix, role, msg):
"""Give a totem to a player. Return the value of SHAMANS[user].""" """Give a totem to a player. Return the value of SHAMANS[user]."""
orig_target = target orig_target = target
orig_role = get_main_role(orig_target) orig_role = get_main_role(orig_target)
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}, evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
action="give a totem{0} to".format(msg)) if not evt.dispatch(var, wrapper.source, target):
if not evt.dispatch(var, "totem", wrapper.source, target, frozenset(tags)):
return return
target = evt.data["target"] target = evt.data["target"]

View File

@ -41,7 +41,7 @@ def guard(cli, nick, chan, rest):
# self-guard ignores luck/misdirection/exchange totem # self-guard ignores luck/misdirection/exchange totem
evt = Event("targeted_command", {"target": target, "misdirection": (angel is not target), "exchange": (angel is not target)}) evt = Event("targeted_command", {"target": target, "misdirection": (angel is not target), "exchange": (angel is not target)})
if not evt.dispatch(var, "guard", angel, target, frozenset({"beneficial"})): if not evt.dispatch(var, angel, target):
return return
victim = evt.data["target"].nick victim = evt.data["target"].nick
GUARDED[nick] = victim GUARDED[nick] = victim

View File

@ -27,7 +27,7 @@ def target(var, wrapper, message):
return return
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
if not evt.dispatch(var, "target", wrapper.source, target, frozenset({"detrimental"})): if not evt.dispatch(var, wrapper.source, target):
return return
target = evt.data["target"] target = evt.data["target"]
@ -132,17 +132,6 @@ def on_del_player(evt, var, player, mainrole, allroles, death_triggers):
evt.params.del_player(target, end_game=False, killer_role=mainrole, deadlist=evt.params.deadlist, original=evt.params.original, ismain=False) evt.params.del_player(target, end_game=False, killer_role=mainrole, deadlist=evt.params.deadlist, original=evt.params.original, ismain=False)
evt.data["pl"] = evt.params.refresh_pl(aevt.data["pl"]) evt.data["pl"] = evt.params.refresh_pl(aevt.data["pl"])
@event_listener("succubus_visit")
def on_succubus_visit(evt, var, actor, target):
if target in TARGETED and TARGETED[target] in get_all_players(("succubus",)):
msg = messages["no_target_succubus"].format(TARGETED[target])
del TARGETED[target]
if target in get_all_players(("village drunk",)):
victim = random.choice(list(get_all_players() - get_all_players(("succubus",)) - {target}))
msg += messages["drunk_target"].format(victim)
TARGETED[target] = victim
target.send(msg)
@event_listener("myrole") @event_listener("myrole")
def on_myrole(evt, var, user): def on_myrole(evt, var, user):
if user in get_all_players(("assassin",)): if user in get_all_players(("assassin",)):

View File

@ -26,7 +26,7 @@ def see(var, wrapper, message):
return return
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
if not evt.dispatch(var, "identify", wrapper.source, target, frozenset({"info", "immediate"})): if not evt.dispatch(var, wrapper.source, target):
return return
target = evt.data["target"] target = evt.data["target"]

View File

@ -15,10 +15,7 @@ from src.events import Event
from src.roles._shaman_helper import setup_variables, get_totem_target, give_totem from src.roles._shaman_helper import setup_variables, get_totem_target, give_totem
def get_tags(var, totem): TOTEMS, LASTGIVEN, SHAMANS = setup_variables("crazed shaman", knows_totem=False)
return set()
TOTEMS, LASTGIVEN, SHAMANS = setup_variables("crazed shaman", knows_totem=False, get_tags=get_tags)
@command("give", "totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("crazed shaman",)) @command("give", "totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("crazed shaman",))
def crazed_shaman_totem(var, wrapper, message): def crazed_shaman_totem(var, wrapper, message):
@ -28,9 +25,7 @@ def crazed_shaman_totem(var, wrapper, message):
if not target: if not target:
return return
totem = TOTEMS[wrapper.source] SHAMANS[wrapper.source] = give_totem(var, wrapper, target, prefix="You", role="crazed shaman", msg="")
SHAMANS[wrapper.source] = give_totem(var, wrapper, target, prefix="You", tags=get_tags(var, totem), role="crazed shaman", msg="")
@event_listener("player_win") @event_listener("player_win")
def on_player_win(evt, var, user, role, winner, survived): def on_player_win(evt, var, user, role, winner, survived):
@ -47,16 +42,11 @@ def on_transition_day_begin(evt, var):
if shaman in LASTGIVEN: if shaman in LASTGIVEN:
if LASTGIVEN[shaman] in ps: if LASTGIVEN[shaman] in ps:
ps.remove(LASTGIVEN[shaman]) ps.remove(LASTGIVEN[shaman])
levt = Event("get_random_totem_targets", {"targets": ps})
levt.dispatch(var, shaman)
ps = levt.data["targets"]
if ps: if ps:
target = random.choice(ps) target = random.choice(ps)
dispatcher = MessageDispatcher(shaman, shaman) dispatcher = MessageDispatcher(shaman, shaman)
tags = get_tags(var, TOTEMS[shaman]) SHAMANS[shaman] = give_totem(var, dispatcher, target, prefix=messages["random_totem_prefix"], role="crazed shaman", msg="")
SHAMANS[shaman] = give_totem(var, dispatcher, target, prefix=messages["random_totem_prefix"], tags=tags, role="crazed shaman", msg="")
else: else:
LASTGIVEN[shaman] = None LASTGIVEN[shaman] = None
elif shaman not in SHAMANS: elif shaman not in SHAMANS:

View File

@ -25,7 +25,7 @@ def investigate(var, wrapper, message):
return return
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
if not evt.dispatch(var, "identify", wrapper.source, target, frozenset({"info", "immediate"})): if not evt.dispatch(var, wrapper.source, target):
return return
target = evt.data["target"] target = evt.data["target"]

View File

@ -31,7 +31,7 @@ def see(var, wrapper, message):
return return
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
evt.dispatch(var, "see", wrapper.source, target, frozenset({"detrimental", "immediate"})) evt.dispatch(var, wrapper.source, target)
if evt.prevent_default: if evt.prevent_default:
return return

View File

@ -28,7 +28,7 @@ def dullahan_kill(var, wrapper, message):
orig = target orig = target
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
evt.dispatch(var, "kill", wrapper.source, target, frozenset({"detrimental"})) evt.dispatch(var, wrapper.source, target)
if evt.prevent_default: if evt.prevent_default:
return return
target = evt.data["target"] target = evt.data["target"]
@ -186,12 +186,10 @@ def on_role_assignment(evt, var, gamemode, pl):
@event_listener("succubus_visit") @event_listener("succubus_visit")
def on_succubus_visit(evt, var, succubus, target): def on_succubus_visit(evt, var, succubus, target):
if target in TARGETS and succubus in TARGETS[target]: succubi = get_all_players(("succubus",))
TARGETS[target].remove(succubus) if target in TARGETS and TARGETS[target].intersection(succubi):
TARGETS[target].difference_update(succubi)
target.send(messages["dullahan_no_kill_succubus"]) target.send(messages["dullahan_no_kill_succubus"])
if target in KILLS and KILLS[target] in get_all_players(("succubus",)):
target.send(messages["no_kill_succubus"].format(KILLS[target]))
del KILLS[target]
@event_listener("myrole") @event_listener("myrole")
def on_myrole(evt, var, user): def on_myrole(evt, var, user):

View File

@ -28,7 +28,7 @@ def hvisit(var, wrapper, message):
return return
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
evt.dispatch(var, "visit", wrapper.source, target, frozenset({"immediate"})) evt.dispatch(var, wrapper.source, target)
if evt.prevent_default: if evt.prevent_default:
return return
target = evt.data["target"] target = evt.data["target"]

View File

@ -26,7 +26,7 @@ def hunter_kill(var, wrapper, message):
orig = target orig = target
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
evt.dispatch(var, "kill", wrapper.source, target, frozenset({"detrimental"})) evt.dispatch(var, wrapper.source, target)
if evt.prevent_default: if evt.prevent_default:
return return
@ -125,13 +125,6 @@ def on_transition_night_end(evt, var):
to_send = "hunter_simple" to_send = "hunter_simple"
hunter.send(messages[to_send], "Players: " + ", ".join(p.nick for p in pl), sep="\n") hunter.send(messages[to_send], "Players: " + ", ".join(p.nick for p in pl), sep="\n")
@event_listener("succubus_visit")
def on_succubus_visit(evt, var, succubus, target):
if target in KILLS and KILLS[target] in get_all_players(("succubus",)):
target.send(messages["no_kill_succubus"].format(KILLS[target]))
del KILLS[target]
HUNTERS.discard(target)
@event_listener("begin_day") @event_listener("begin_day")
def on_begin_day(evt, var): def on_begin_day(evt, var):
KILLS.clear() KILLS.clear()

View File

@ -38,13 +38,13 @@ def investigate(var, wrapper, message):
return return
evt = Event("targeted_command", {"target": target1, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target1, "misdirection": True, "exchange": True})
evt.dispatch(var, "identify", wrapper.source, target1, frozenset({"info", "immediate"})) evt.dispatch(var, wrapper.source, target1)
if evt.prevent_default: if evt.prevent_default:
return return
target1 = evt.data["target"] target1 = evt.data["target"]
evt = Event("targeted_command", {"target": target2, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target2, "misdirection": True, "exchange": True})
evt.dispatch(var, "identify", wrapper.source, target2, frozenset({"info", "immediate"})) evt.dispatch(var, wrapper.source, target2)
if evt.prevent_default: if evt.prevent_default:
return return
target2 = evt.data["target"] target2 = evt.data["target"]

View File

@ -26,7 +26,7 @@ def see(var, wrapper, message):
return return
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
if not evt.dispatch(var, "see", wrapper.source, target, frozenset({"info", "immediate"})): if not evt.dispatch(var, wrapper.source, target):
return return
target = evt.data["target"] target = evt.data["target"]

View File

@ -43,14 +43,14 @@ def charm(var, wrapper, message):
orig2 = target2 orig2 = target2
evt1 = Event("targeted_command", {"target": target1, "misdirection": True, "exchange": True}) evt1 = Event("targeted_command", {"target": target1, "misdirection": True, "exchange": True})
evt1.dispatch(var, "charm", wrapper.source, target1, frozenset({"detrimental"})) evt1.dispatch(var, wrapper.source, target1)
if evt1.prevent_default: if evt1.prevent_default:
return return
target1 = evt1.data["target"] target1 = evt1.data["target"]
if target2 is not None: if target2 is not None:
evt2 = Event("targeted_command", {"target": target2, "misdirection": True, "exchange": True}) evt2 = Event("targeted_command", {"target": target2, "misdirection": True, "exchange": True})
evt2.dispatch(var, "charm", wrapper.source, target2, frozenset({"detrimental"})) evt2.dispatch(var, wrapper.source, target2)
if evt2.prevent_default: if evt2.prevent_default:
return return
target2 = evt2.data["target"] target2 = evt2.data["target"]

View File

@ -26,7 +26,7 @@ def see(var, wrapper, message):
return return
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
if not evt.dispatch(var, "see", wrapper.source, target, frozenset({"info", "immediate"})): if not evt.dispatch(var, wrapper.source, target):
return return
target = evt.data["target"] target = evt.data["target"]

View File

@ -15,13 +15,7 @@ from src.events import Event
from src.roles._shaman_helper import setup_variables, get_totem_target, give_totem from src.roles._shaman_helper import setup_variables, get_totem_target, give_totem
def get_tags(var, totem): TOTEMS, LASTGIVEN, SHAMANS = setup_variables("shaman", knows_totem=True)
tags = set()
if totem in var.BENEFICIAL_TOTEMS:
tags.add("beneficial")
return tags
TOTEMS, LASTGIVEN, SHAMANS = setup_variables("shaman", knows_totem=True, get_tags=get_tags)
@command("give", "totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("shaman",)) @command("give", "totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("shaman",))
def shaman_totem(var, wrapper, message): def shaman_totem(var, wrapper, message):
@ -31,9 +25,7 @@ def shaman_totem(var, wrapper, message):
if not target: if not target:
return return
totem = TOTEMS[wrapper.source] SHAMANS[wrapper.source] = give_totem(var, wrapper, target, prefix="You", role="shaman", msg=" of {0}".format(TOTEMS[wrapper.source]))
SHAMANS[wrapper.source] = give_totem(var, wrapper, target, prefix="You", tags=get_tags(var, totem), role="shaman", msg=" of {0}".format(totem))
@event_listener("transition_day_begin", priority=4) @event_listener("transition_day_begin", priority=4)
def on_transition_day_begin(evt, var): def on_transition_day_begin(evt, var):
@ -45,16 +37,11 @@ def on_transition_day_begin(evt, var):
if shaman in LASTGIVEN: if shaman in LASTGIVEN:
if LASTGIVEN[shaman] in ps: if LASTGIVEN[shaman] in ps:
ps.remove(LASTGIVEN[shaman]) ps.remove(LASTGIVEN[shaman])
levt = Event("get_random_totem_targets", {"targets": ps})
levt.dispatch(var, shaman)
ps = levt.data["targets"]
if ps: if ps:
target = random.choice(ps) target = random.choice(ps)
dispatcher = MessageDispatcher(shaman, shaman) dispatcher = MessageDispatcher(shaman, shaman)
tags = get_tags(var, TOTEMS[shaman]) SHAMANS[shaman] = give_totem(var, dispatcher, target, prefix=messages["random_totem_prefix"], role="shaman", msg=" of {0}".format(TOTEMS[shaman]))
SHAMANS[shaman] = give_totem(var, dispatcher, target, prefix=messages["random_totem_prefix"], tags=tags, role="shaman", msg=" of {0}".format(TOTEMS[shaman]))
else: else:
LASTGIVEN[shaman] = None LASTGIVEN[shaman] = None
elif shaman not in SHAMANS: elif shaman not in SHAMANS:

View File

@ -14,7 +14,6 @@ from src.messages import messages
from src.events import Event from src.events import Event
ENTRANCED = UserSet() # type: Set[users.User] ENTRANCED = UserSet() # type: Set[users.User]
ENTRANCED_DYING = UserSet() # type: Set[users.User]
VISITED = UserDict() # type: Dict[users.User, users.User] VISITED = UserDict() # type: Dict[users.User, users.User]
PASSED = UserSet() # type: Set[users.User] PASSED = UserSet() # type: Set[users.User]
ALL_SUCC_IDLE = True ALL_SUCC_IDLE = True
@ -32,7 +31,7 @@ def hvisit(var, wrapper, message):
return return
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": False}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": False})
evt.dispatch(var, "visit", wrapper.source, target, frozenset({"detrimental", "immediate"})) evt.dispatch(var, wrapper.source, target)
if evt.prevent_default: if evt.prevent_default:
return return
target = evt.data["target"] target = evt.data["target"]
@ -55,16 +54,6 @@ def hvisit(var, wrapper, message):
revt = Event("succubus_visit", {}) revt = Event("succubus_visit", {})
revt.dispatch(var, wrapper.source, target) revt.dispatch(var, wrapper.source, target)
# TODO: split these into hag and alpha wolf when they are split off
if target.nick in var.HEXED and users._get(var.LASTHEXED[target.nick]) in get_all_players(("succubus",)): # FIXME
target.send(messages["retract_hex_succubus"].format(var.LASTHEXED[target.nick]))
var.TOBESILENCED.remove(wrapper.source.nick)
var.HEXED.remove(target.nick)
del var.LASTHEXED[target.nick]
if users._get(var.BITE_PREFERENCES.get(target.nick), allow_none=True) in get_all_players(("succubus",)): # FIXME
target.send(messages["no_kill_succubus"].format(var.BITE_PREFERENCES[target.nick]))
del var.BITE_PREFERENCES[target.nick]
debuglog("{0} (succubus) VISIT: {1} ({2})".format(wrapper.source, target, get_main_role(target))) debuglog("{0} (succubus) VISIT: {1} ({2})".format(wrapper.source, target, get_main_role(target)))
@command("pass", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("succubus",)) @command("pass", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("succubus",))
@ -85,58 +74,6 @@ def on_harlot_visit(evt, var, harlot, victim):
victim.send(messages["succubus_harlot_success"].format(harlot)) victim.send(messages["succubus_harlot_success"].format(harlot))
ENTRANCED.add(harlot) ENTRANCED.add(harlot)
@event_listener("get_random_totem_targets")
def on_get_random_totem_targets(evt, var, shaman):
if shaman in ENTRANCED:
for succubus in get_all_players(("succubus",)):
if succubus in evt.data["targets"]:
evt.data["targets"].remove(succubus)
@event_listener("chk_decision")
def on_chk_decision(evt, var, force):
for votee, voters in evt.data["votelist"].items():
if votee in get_all_players(("succubus",)):
for vtr in ENTRANCED:
if vtr in voters:
evt.data["numvotes"][votee] -= evt.data["weights"][votee][vtr]
evt.data["weights"][votee][vtr] = 0
def _kill_entranced_voters(var, votelist, not_lynching, votee):
voters = set(itertools.chain(*votelist.values()))
if not get_all_players(("succubus",)) & (voters | not_lynching):
# none of the succubi voted (or there aren't any succubi), so short-circuit
return
# kill off everyone entranced that did not follow one of the succubi's votes or abstain
# unless a succubus successfully voted the target, then people that didn't follow are spared
for x in ENTRANCED:
if x.nick not in var.DEAD:
ENTRANCED_DYING.add(x)
for other_votee, other_voters in votelist.items():
if get_all_players(("succubus",)) & set(other_voters):
if votee is other_votee:
ENTRANCED_DYING.clear()
return
ENTRANCED_DYING.difference_update(other_voters)
if get_all_players(("succubus",)) & not_lynching:
if votee is None:
ENTRANCED_DYING.clear()
return
ENTRANCED_DYING.difference_update(not_lynching)
@event_listener("chk_decision_lynch", priority=5)
def on_chk_decision_lynch(evt, var, voters):
# a different event may override the original votee, but people voting along with succubus
# won't necessarily know that, so base whether or not they risk death on the person originally voted
_kill_entranced_voters(var, evt.params.votelist, evt.params.not_lynching, evt.params.original_votee)
@event_listener("chk_decision_abstain")
def on_chk_decision_abstain(evt, var, not_lynching):
_kill_entranced_voters(var, evt.params.votelist, not_lynching, None)
# entranced logic should run after team wins have already been determined (aka run last) # entranced logic should run after team wins have already been determined (aka run last)
@event_listener("player_win", priority=6) @event_listener("player_win", priority=6)
def on_player_win(evt, var, user, role, winner, survived): def on_player_win(evt, var, user, role, winner, survived):
@ -153,15 +90,23 @@ def on_player_win(evt, var, user, role, winner, survived):
def on_chk_win(evt, var, rolemap, mainroles, lpl, lwolves, lrealwolves): def on_chk_win(evt, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
lsuccubi = len(rolemap.get("succubus", ())) lsuccubi = len(rolemap.get("succubus", ()))
lentranced = len([x for x in ENTRANCED if x.nick not in var.DEAD]) lentranced = len([x for x in ENTRANCED if x.nick not in var.DEAD])
if lsuccubi and var.PHASE == "day" and lpl - lsuccubi == lentranced: if var.PHASE == "day" and lpl - lsuccubi == lentranced:
evt.data["winner"] = "succubi" evt.data["winner"] = "succubi"
evt.data["message"] = messages["succubus_win"].format(plural("succubus", lsuccubi), plural("has", lsuccubi), plural("master's", lsuccubi)) evt.data["message"] = messages["succubus_win"].format(plural("succubus", lsuccubi), plural("has", lsuccubi), plural("master's", lsuccubi))
@event_listener("can_exchange") @event_listener("exchange_roles")
def on_can_exchange(evt, var, actor, target): def on_exchange_roles(evt, var, actor, target, actor_role, target_role):
if actor in get_all_players(("succubus",)) or target in get_all_players(("succubus",)): del VISITED[:actor:]
evt.prevent_default = True del VISITED[:target:]
evt.stop_processing = True PASSED.discard(actor)
PASSED.discard(target)
if actor in ENTRANCED:
ENTRANCED.remove(actor)
actor.send(messages["no_longer_entranced"])
if target in ENTRANCED:
ENTRANCED.remove(target)
target.send(messages["no_longer_entranced"])
@event_listener("del_player") @event_listener("del_player")
def on_del_player(evt, var, user, mainrole, allroles, death_triggers): def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
@ -173,13 +118,10 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
if var.PHASE == "night" and var.GAMEPHASE == "night": if var.PHASE == "night" and var.GAMEPHASE == "night":
if VISITED[user] in ENTRANCED: if VISITED[user] in ENTRANCED:
ENTRANCED.discard(VISITED[user]) ENTRANCED.discard(VISITED[user])
ENTRANCED_DYING.discard(VISITED[user])
VISITED[user].send(messages["entranced_revert_win"]) VISITED[user].send(messages["entranced_revert_win"])
del VISITED[user] del VISITED[user]
# if all succubi are dead, one of two things happen: # if all succubi idled out (every last one of them), un-entrance people
# 1. if all succubi idled out (every last one of them), un-entrance people
# 2. otherwise, kill all entranced people immediately, they still remain entranced (and therefore lose)
# death_triggers is False for an idle-out, so we use that to determine which it is # death_triggers is False for an idle-out, so we use that to determine which it is
if death_triggers: if death_triggers:
ALL_SUCC_IDLE = False ALL_SUCC_IDLE = False
@ -189,36 +131,6 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
while ENTRANCED: while ENTRANCED:
e = ENTRANCED.pop() e = ENTRANCED.pop()
e.send(messages["entranced_revert_win"]) e.send(messages["entranced_revert_win"])
elif entranced_alive:
msg = []
# Run in two loops so we can play the message for everyone dying at once before we actually
# kill any of them off (if we killed off first, the message order would be wrong wrt death chains)
comma = ""
if var.ROLE_REVEAL in ("on", "team"):
comma = ","
for e in entranced_alive:
if var.ROLE_REVEAL in ("on", "team"):
role = get_reveal_role(e)
an = "n" if role.startswith(("a", "e", "i", "o", "u")) else ""
msg.append("\u0002{0}\u0002, a{1} \u0002{2}\u0002".format(e, an, role))
else:
msg.append("\u0002{0}\u0002".format(e))
if len(msg) == 1:
channels.Main.send(messages["succubus_die_kill"].format(msg[0] + comma))
elif len(msg) == 2:
channels.Main.send(messages["succubus_die_kill"].format(msg[0] + comma + " and " + msg[1] + comma))
else:
channels.Main.send(messages["succubus_die_kill"].format(", ".join(msg[:-1]) + ", and " + msg[-1] + comma))
for e in entranced_alive:
# to ensure we do not double-kill someone, notify all child deaths that we'll be
# 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} (succubus) SUCCUBUS DEATH KILL: {1} ({2})".format(user, e, get_main_role(e)))
evt.params.del_player(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"])
ENTRANCED_DYING.clear()
@event_listener("transition_day_resolve", priority=1) @event_listener("transition_day_resolve", priority=1)
def on_transition_day_resolve(evt, var, victim): def on_transition_day_resolve(evt, var, victim):
@ -255,17 +167,6 @@ def on_chk_nightdone(evt, var):
evt.data["actedcount"] += len(VISITED) + len(PASSED) evt.data["actedcount"] += len(VISITED) + len(PASSED)
evt.data["nightroles"].extend(get_all_players(("succubus",))) evt.data["nightroles"].extend(get_all_players(("succubus",)))
@event_listener("targeted_command")
def on_targeted_command(evt, var, name, actor, orig_target, tags):
if "beneficial" not in tags and actor in ENTRANCED and evt.data["target"] in get_all_players(("succubus",)):
try:
what = evt.params.action
except AttributeError:
what = name
actor.send(messages["no_acting_on_succubus"].format(what))
evt.stop_processing = True
evt.prevent_default = True
@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):
succubi = get_all_players(("succubus",)) succubi = get_all_players(("succubus",))
@ -287,17 +188,8 @@ def on_transition_night_end(evt, var):
@event_listener("begin_day") @event_listener("begin_day")
def on_begin_day(evt, var): def on_begin_day(evt, var):
VISITED.clear() VISITED.clear()
ENTRANCED_DYING.clear()
PASSED.clear() PASSED.clear()
@event_listener("transition_day", priority=2)
def on_transition_day(evt, var):
for v in ENTRANCED_DYING:
var.DYING.add(v) # indicate that the death bypasses protections
evt.data["victims"].append(v)
evt.data["onlybywolves"].discard(v)
# we do not add to killers as retribution totem should not work on entranced not following succubus
@event_listener("get_special") @event_listener("get_special")
def on_get_special(evt, var): def on_get_special(evt, var):
evt.data["win_stealers"].update(get_players(("succubus",))) evt.data["win_stealers"].update(get_players(("succubus",)))
@ -318,7 +210,6 @@ def on_reset(evt, var):
global ALL_SUCC_IDLE global ALL_SUCC_IDLE
ALL_SUCC_IDLE = True ALL_SUCC_IDLE = True
ENTRANCED.clear() ENTRANCED.clear()
ENTRANCED_DYING.clear()
VISITED.clear() VISITED.clear()
PASSED.clear() PASSED.clear()
@ -327,7 +218,4 @@ def on_revealroles(evt, var, wrapper):
if ENTRANCED: if ENTRANCED:
evt.data["output"].append("\u0002entranced players\u0002: {0}".format(", ".join(p.nick for p in ENTRANCED))) evt.data["output"].append("\u0002entranced players\u0002: {0}".format(", ".join(p.nick for p in ENTRANCED)))
if ENTRANCED_DYING:
evt.data["output"].append("\u0002dying entranced players\u0002: {0}".format(", ".join(p.nick for p in ENTRANCED_DYING)))
# vim: set sw=4 expandtab: # vim: set sw=4 expandtab:

View File

@ -40,7 +40,7 @@ def vg_kill(var, wrapper, message):
orig = target orig = target
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": False}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": False})
evt.dispatch(var, "kill", wrapper.source, target, frozenset({"detrimental"})) evt.dispatch(var, wrapper.source, target)
if evt.prevent_default: if evt.prevent_default:
return return
target = evt.data["target"] target = evt.data["target"]

View File

@ -21,7 +21,7 @@ def vigilante_kill(var, wrapper, message):
orig = target orig = target
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
evt.dispatch(var, "kill", wrapper.source, target, frozenset({"detrimental"})) evt.dispatch(var, wrapper.source, target)
if evt.prevent_default: if evt.prevent_default:
return return
target = evt.data["target"] target = evt.data["target"]
@ -107,12 +107,6 @@ def on_transition_night_end(evt, var):
to_send = "vigilante_simple" to_send = "vigilante_simple"
vigilante.send(messages[to_send], "Players: " + ", ".join(p.nick for p in pl), sep="\n") vigilante.send(messages[to_send], "Players: " + ", ".join(p.nick for p in pl), sep="\n")
@event_listener("succubus_visit")
def on_succubus_visit(evt, var, succubus, target):
if target in KILLS and KILLS[target] in get_all_players(("succubus",)):
target.send(messages["no_kill_succubus"].format(KILLS[target]))
del KILLS[target]
@event_listener("begin_day") @event_listener("begin_day")
def on_begin_day(evt, var): def on_begin_day(evt, var):
KILLS.clear() KILLS.clear()

View File

@ -74,7 +74,7 @@ def wolf_kill(cli, nick, chan, rest):
target = users._get(victim) # FIXME target = users._get(victim) # FIXME
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
evt.dispatch(var, "kill", wolf, target, frozenset({"detrimental"})) evt.dispatch(var, wolf, target)
if evt.prevent_default: if evt.prevent_default:
return return
victim = evt.data["target"].nick victim = evt.data["target"].nick
@ -437,16 +437,6 @@ def on_transition_night_end(evt, var):
if var.ALPHA_ENABLED and role == "alpha wolf" and wolf.nick not in var.ALPHA_WOLVES: # FIXME: Fix once var.ALPHA_WOLVES holds User instances if var.ALPHA_ENABLED and role == "alpha wolf" and wolf.nick not in var.ALPHA_WOLVES: # FIXME: Fix once var.ALPHA_WOLVES holds User instances
wolf.send(messages["wolf_bite"]) wolf.send(messages["wolf_bite"])
@event_listener("succubus_visit")
def on_succubus_visit(evt, var, succubus, target):
if get_all_players(("succubus",)).intersection(users._get(x) for x in KILLS.get(target.nick, ())): # FIXME: once KILLS holds User instances
for s in get_all_players(("succubus",)):
if s.nick in KILLS[target.nick]:
target.send(messages["no_kill_succubus"].format(succubus))
KILLS[target.nick].remove(s.nick)
if not KILLS[target.nick]:
del KILLS[target.nick]
@event_listener("begin_day") @event_listener("begin_day")
def on_begin_day(evt, var): def on_begin_day(evt, var):
KILLS.clear() KILLS.clear()

View File

@ -15,13 +15,7 @@ from src.events import Event
from src.roles._shaman_helper import setup_variables, get_totem_target, give_totem from src.roles._shaman_helper import setup_variables, get_totem_target, give_totem
def get_tags(var, totem): TOTEMS, LASTGIVEN, SHAMANS = setup_variables("wolf shaman", knows_totem=True)
tags = set()
if totem in var.BENEFICIAL_TOTEMS:
tags.add("beneficial")
return tags
TOTEMS, LASTGIVEN, SHAMANS = setup_variables("wolf shaman", knows_totem=True, get_tags=get_tags)
@command("give", "totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("wolf shaman",)) @command("give", "totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("wolf shaman",))
def wolf_shaman_totem(var, wrapper, message): def wolf_shaman_totem(var, wrapper, message):
@ -31,9 +25,7 @@ def wolf_shaman_totem(var, wrapper, message):
if not target: if not target:
return return
totem = TOTEMS[wrapper.source] SHAMANS[wrapper.source] = give_totem(var, wrapper, target, prefix="You", role="wolf shaman", msg=" of {0}".format(TOTEMS[wrapper.source]))
SHAMANS[wrapper.source] = give_totem(var, wrapper, target, prefix="You", tags=get_tags(var, totem), role="wolf shaman", msg=" of {0}".format(totem))
relay_wolfchat_command(wrapper.client, wrapper.source.nick, messages["shaman_wolfchat"].format(wrapper.source, target), ("wolf shaman",), is_wolf_command=True) relay_wolfchat_command(wrapper.client, wrapper.source.nick, messages["shaman_wolfchat"].format(wrapper.source, target), ("wolf shaman",), is_wolf_command=True)
@ -47,16 +39,11 @@ def on_transition_day_begin(evt, var):
if shaman in LASTGIVEN: if shaman in LASTGIVEN:
if LASTGIVEN[shaman] in ps: if LASTGIVEN[shaman] in ps:
ps.remove(LASTGIVEN[shaman]) ps.remove(LASTGIVEN[shaman])
levt = Event("get_random_totem_targets", {"targets": ps})
levt.dispatch(var, shaman)
ps = levt.data["targets"]
if ps: if ps:
target = random.choice(ps) target = random.choice(ps)
dispatcher = MessageDispatcher(shaman, shaman) dispatcher = MessageDispatcher(shaman, shaman)
tags = get_tags(var, TOTEMS[shaman]) SHAMANS[shaman] = give_totem(var, dispatcher, target, prefix=messages["random_totem_prefix"], role="wolf shaman", msg=" of {0}".format(TOTEMS[shaman]))
SHAMANS[shaman] = give_totem(var, dispatcher, target, prefix=messages["random_totem_prefix"], tags=tags, role="wolf shaman", msg=" of {0}".format(TOTEMS[shaman]))
relay_wolfchat_command(shaman.client, shaman.nick, messages["shaman_wolfchat"].format(shaman, target), ("wolf shaman",), is_wolf_command=True) relay_wolfchat_command(shaman.client, shaman.nick, messages["shaman_wolfchat"].format(shaman, target), ("wolf shaman",), is_wolf_command=True)
else: else:
LASTGIVEN[shaman] = None LASTGIVEN[shaman] = None

View File

@ -3717,9 +3717,6 @@ def check_exchange(cli, actor, nick):
user = users._get(actor) # FIXME user = users._get(actor) # FIXME
target = users._get(nick) # FIXME target = users._get(nick) # FIXME
event = Event("can_exchange", {})
if not event.dispatch(var, user, target):
return False # some roles such as succubus cannot be affected by exchange totem
if nick in var.EXCHANGED: if nick in var.EXCHANGED:
var.EXCHANGED.remove(nick) var.EXCHANGED.remove(nick)
actor_role = get_role(actor) actor_role = get_role(actor)
@ -3926,7 +3923,7 @@ def shoot(var, wrapper, message):
# get actual victim # get actual victim
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
if not evt.dispatch(var, "shoot", wrapper.source, target, frozenset({"detrimental"})): if not evt.dispatch(var, wrapper.source, target):
return return
target = evt.data["target"] target = evt.data["target"]
@ -3995,12 +3992,6 @@ def shoot(var, wrapper, message):
wrapper.send(messages["gunner_suicide_no_reveal"].format(wrapper.source)) wrapper.send(messages["gunner_suicide_no_reveal"].format(wrapper.source))
del_player(wrapper.source, killer_role="villager") # blame explosion on villager's shoddy gun construction or something del_player(wrapper.source, killer_role="villager") # blame explosion on villager's shoddy gun construction or something
def is_safe(nick, victim): # replace calls to this with targeted_command event when splitting roles
from src.roles import succubus
user = users._get(nick) # FIXME
target = users._get(victim) # FIXME
return user in succubus.ENTRANCED and target in get_all_players(("succubus",))
@cmd("bless", chan=False, pm=True, playing=True, silenced=True, phases=("day",), roles=("priest",)) @cmd("bless", chan=False, pm=True, playing=True, silenced=True, phases=("day",), roles=("priest",))
def bless(cli, nick, chan, rest): def bless(cli, nick, chan, rest):
"""Bless a player, preventing them from being killed for the remainder of the game.""" """Bless a player, preventing them from being killed for the remainder of the game."""
@ -4083,9 +4074,6 @@ def observe(cli, nick, chan, rest):
else: else:
pm(cli, nick, messages["no_observe_wolf"]) pm(cli, nick, messages["no_observe_wolf"])
return return
if is_safe(nick, victim):
pm(cli, nick, messages["no_acting_on_succubus"].format("observe"))
return
victim = choose_target(nick, victim) victim = choose_target(nick, victim)
if check_exchange(cli, nick, victim): if check_exchange(cli, nick, victim):
return return
@ -4263,9 +4251,6 @@ def bite_cmd(cli, nick, chan, rest):
if not victim: if not victim:
pm(cli, nick, messages["bite_error"]) pm(cli, nick, messages["bite_error"])
return return
if is_safe(nick, victim):
pm(cli, nick, messages["no_acting_on_succubus"].format("bite"))
return
vrole = get_role(victim) vrole = get_role(victim)
actual = choose_target(nick, victim) actual = choose_target(nick, victim)
@ -4351,10 +4336,6 @@ def hex_target(cli, nick, chan, rest):
pm(cli, nick, messages["no_multiple_hex"].format(victim)) pm(cli, nick, messages["no_multiple_hex"].format(victim))
return return
if is_safe(nick, victim):
pm(cli, nick, messages["no_acting_on_succubus"].format("hex"))
return
victim = choose_target(nick, victim) victim = choose_target(nick, victim)
if in_wolflist(nick, victim): if in_wolflist(nick, victim):
pm(cli, nick, messages["no_hex_wolf"]) pm(cli, nick, messages["no_hex_wolf"])
@ -4386,10 +4367,6 @@ def curse(cli, nick, chan, rest):
victim = get_victim(cli, nick, re.split(" +",rest)[0], False) victim = get_victim(cli, nick, re.split(" +",rest)[0], False)
if not victim: if not victim:
return return
if is_safe(nick, victim):
pm(cli, nick, messages["no_acting_on_succubus"].format("curse"))
return
# There may actually be valid strategy in cursing other wolfteam members, # There may actually be valid strategy in cursing other wolfteam members,
# but for now it is not allowed. If someone seems suspicious and shows as # but for now it is not allowed. If someone seems suspicious and shows as
# villager across multiple nights, safes can use that as a tell that the # villager across multiple nights, safes can use that as a tell that the
@ -4461,7 +4438,7 @@ def clone(cli, nick, chan, rest):
var.ROLE_COMMAND_EXCEPTIONS.add("clone") var.ROLE_COMMAND_EXCEPTIONS.add("clone")
@event_listener("targeted_command", priority=9) @event_listener("targeted_command", priority=9)
def on_targeted_command(evt, var, name, actor, orig_target, tags): def on_targeted_command(evt, var, actor, orig_target):
if evt.data["misdirection"]: if evt.data["misdirection"]:
evt.data["target"] = users._get(choose_target(actor.nick, evt.data["target"].nick)) # FIXME evt.data["target"] = users._get(choose_target(actor.nick, evt.data["target"].nick)) # FIXME