Split succubus (#284)
* Split + buff succubus When all succubi die, all entranced people now die along with them. This should prevent an entranced person from ratting out the succubus early on so that they go back to their team, as they lose now even if succubus dies. One exception is if EVERY succubus idles out, then everyone that is entranced is freed of entrancement, as it isn't their fault that they didn't protect their friends in that case. Dullahans now have succubi entirely removed from their list as the likelihood they get unentranced is low, and it's easier to implement this way. Ensure that entranced people can vote along with ANY succubus, even if that vote isn't the one that succeeded. Before there were cases where they could vote along with succubus but still end up dying (particularly in respect to a vote passing when a succubus abstained). Clear up some message wording with regards to succubi. Cleaned up chk_win_conditions and eliminated chk_traitor, so they make much more sense now. Also fixed minor issues, such as end-game saying "same number" of wolves even if there are more wolves than villagers, hunter/vigilante dying during night sometimes not clearing variables correctly (thus causing premature night end) and some various stupidity going on with some old code I wrote that doesn't have any visible effects. * Combine all players into the same succubus death message * Fix stylistic issues and succubus idling not working
This commit is contained in:
parent
54ab59a36f
commit
69fa7d377f
@ -108,7 +108,8 @@
|
||||
"monster_win": "Game over! All the wolves are dead! As the villagers start preparing the BBQ, the monster{0} quickly kill{1} the remaining villagers, causing the monster{0} to win.",
|
||||
"monster_wolf_win": "Game over! There are the same number of wolves as uninjured villagers. The wolves overpower the villagers but then get destroyed by the monster{0}, causing the monster{0} to win.",
|
||||
"villager_win": "Game over! All the wolves are dead! The villagers chop them up, BBQ them, and have a hearty meal.",
|
||||
"wolf_win": "Game over! There are the same number of wolves as uninjured villagers. The wolves overpower the villagers and win.",
|
||||
"wolf_win_equal": "Game over! There are the same number of wolves as uninjured villagers. The wolves overpower the villagers and win.",
|
||||
"wolf_win_greater": "Game over! There are more wolves than uninjured villagers. The wolves overpower the villagers and win.",
|
||||
"new_game": "\u0002{0}\u0002 has started a game of Werewolf. Type \"{1}join\" to join. Type \"{1}start\" to vote to start the game. Type \"{1}wait\" to increase the start wait time.",
|
||||
"stasis": "Sorry, but {0} in stasis for {1} game{2}.",
|
||||
"your_current_stasis": "You are currently in stasis for \u0002{0}\u0002 game{1}.",
|
||||
@ -631,7 +632,8 @@
|
||||
"vision_none": "You receive a vision that there are no \u0002{0}\u0002.",
|
||||
"vision_recovering": "You are still recovering from your previous vision and are unable to receive any more visions tonight.",
|
||||
"succubus_already_visited": "You are already entrancing \u0002{0}\u0002 tonight.",
|
||||
"notify_succubus_target": "You have become entranced by \u0002{0}\u0002. From this point on, you must vote along with them or risk dying. For as long as they are alive, you \u0002cannot win with your own team\u0002, but you will win if \u0002{0}\u0002 wins as well.",
|
||||
"succubus_not_self": "You may not entrance yourself. Use \"pass\" to not entrance anyone tonight.",
|
||||
"notify_succubus_target": "You have become entranced by \u0002{0}\u0002. From this point on, you must vote along with them or risk dying. You \u0002cannot win with your own team\u0002, but you will win should all alive players become entranced.",
|
||||
"succubus_harlot_success": "You have entranced \u0002{0}\u0002.",
|
||||
"succubus_target_success": "You are entrancing \u0002{0}\u0002 tonight.",
|
||||
"no_kill_succubus": "You discover that \u0002{0}\u0002 is a succubus and have retracted your kill as a result.",
|
||||
@ -639,7 +641,7 @@
|
||||
"drunk_target": " In your drunken stupor, you have selected \u0002{0}\u0002 as your target.",
|
||||
"retract_totem_succubus": "You discover that \u0002{0}\u0002 is a succubus and have retracted your totem as a result.",
|
||||
"retract_hex_succubus": "You discover that \u0002{0}\u0002 is a succubus and have retracted your hex as a result.",
|
||||
"dullahan_no_kill_succubus": "While you remain entranced, the succubus does not need to die for you to win.",
|
||||
"dullahan_no_kill_succubus": "The succubus no longer needs to die for you to win.",
|
||||
"no_see_wolf": "Seeing another wolf would be a waste.",
|
||||
"doomsayer_death": "You have a vision that \u0002{0}\u0002 will meet an untimely end tonight.",
|
||||
"doomsayer_lycan": "You have a vision that \u0002{0}\u0002 is transforming into a savage beast tomorrow night.",
|
||||
@ -723,7 +725,8 @@
|
||||
"assassin_fail_blessed": "\u0002{0}\u0002 seems to be blessed, causing your assassination attempt to fail.",
|
||||
"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.",
|
||||
"entranced_revert_win": "With all of the succubi dead, 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.",
|
||||
"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.",
|
||||
|
@ -3,7 +3,7 @@ import math
|
||||
import threading
|
||||
import copy
|
||||
from datetime import datetime
|
||||
from collections import OrderedDict
|
||||
from collections import defaultdict, OrderedDict
|
||||
|
||||
import botconfig
|
||||
import src.settings as var
|
||||
@ -104,7 +104,7 @@ class GameMode:
|
||||
pass
|
||||
|
||||
# Here so any game mode can use it
|
||||
def lovers_chk_win(self, evt, var, lpl, lwolves, lrealwolves):
|
||||
def lovers_chk_win(self, evt, cli, var, rolemap, lpl, lwolves, lrealwolves):
|
||||
winner = evt.data["winner"]
|
||||
if winner is not None and winner.startswith("@"):
|
||||
return # fool won, lovers can't win even if they would
|
||||
@ -119,7 +119,7 @@ class GameMode:
|
||||
evt.data["additional_winners"] = list(lovers)
|
||||
evt.data["message"] = messages["lovers_win"]
|
||||
|
||||
def all_dead_chk_win(self, evt, var, lpl, lwolves, lrealwolves):
|
||||
def all_dead_chk_win(self, evt, cli, var, rolemap, lpl, lwolves, lrealwolves):
|
||||
if evt.data["winner"] == "no_team_wins":
|
||||
evt.data["winner"] = "everyone"
|
||||
evt.data["message"] = messages["everyone_died_won"]
|
||||
@ -614,17 +614,12 @@ class RandomMode(GameMode):
|
||||
addroles["gunner"] = random.randrange(int(len(villagers) ** 1.2 / 4))
|
||||
addroles["assassin"] = random.randrange(max(int(len(villagers) ** 1.2 / 8), 1))
|
||||
|
||||
lpl = len(villagers)
|
||||
lwolves = sum(addroles[r] for r in var.WOLFCHAT_ROLES)
|
||||
lcubs = addroles["wolf cub"]
|
||||
lrealwolves = sum(addroles[r] for r in var.WOLF_ROLES - {"wolf cub"})
|
||||
lmonsters = addroles["monster"]
|
||||
ldemoniacs = addroles["demoniac"]
|
||||
ltraitors = addroles["traitor"]
|
||||
lpipers = addroles["piper"]
|
||||
lsuccubi = addroles["succubus"]
|
||||
rolemap = defaultdict(set)
|
||||
for r,c in addroles.items():
|
||||
if c > 0:
|
||||
rolemap[r] = set(range(c))
|
||||
|
||||
if chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs, ltraitors, lpipers, lsuccubi, 0, cli, end_game=False):
|
||||
if chk_win_conditions(cli, rolemap, end_game=False):
|
||||
return self.role_attribution(evt, cli, var, chk_win_conditions, villagers)
|
||||
|
||||
evt.prevent_default = True
|
||||
@ -1221,38 +1216,13 @@ class MaelstromMode(GameMode):
|
||||
|
||||
def _on_join(self, var, wrapper):
|
||||
role = random.choice(self.roles)
|
||||
newlist = copy.deepcopy(var.ROLES)
|
||||
newlist[role].add(wrapper.source)
|
||||
|
||||
lpl = len(list_players()) + 1
|
||||
lwolves = len(list_players(var.WOLFCHAT_ROLES))
|
||||
lcubs = len(var.ROLES["wolf cub"])
|
||||
lrealwolves = len(list_players(var.WOLF_ROLES)) - lcubs
|
||||
lmonsters = len(var.ROLES["monster"])
|
||||
ldemoniacs = len(var.ROLES["demoniac"])
|
||||
ltraitors = len(var.ROLES["traitor"])
|
||||
lpipers = len(var.ROLES["piper"])
|
||||
lsuccubi = len(var.ROLES["succubus"])
|
||||
|
||||
if role in var.WOLFCHAT_ROLES:
|
||||
lwolves += 1
|
||||
if role == "wolf cub":
|
||||
lcubs += 1
|
||||
elif role == "traitor":
|
||||
ltraitors += 1
|
||||
elif role in var.WOLF_ROLES:
|
||||
lrealwolves += 1
|
||||
elif role == "monster":
|
||||
lmonsters += 1
|
||||
elif role == "demoniac":
|
||||
ldemoniacs += 1
|
||||
elif role == "piper":
|
||||
lpipers += 1
|
||||
elif role == "succubus":
|
||||
lsuccubi += 1
|
||||
|
||||
if self.chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs, ltraitors, lpipers, lsuccubi, 0, wrapper.client, end_game=False):
|
||||
if self.chk_win_conditions(wrapper.client, newlist, end_game=False):
|
||||
return self._on_join(var, wrapper)
|
||||
|
||||
var.ROLES[role].add(wrapper.source.nick)
|
||||
var.ROLES[role].add(wrapper.source.nick) # FIXME: add user instead of nick
|
||||
var.ORIGINAL_ROLES[role].add(wrapper.source.nick)
|
||||
var.FINAL_ROLES[wrapper.source.nick] = role
|
||||
var.LAST_SAID_TIME[wrapper.source.nick] = datetime.now()
|
||||
@ -1353,17 +1323,12 @@ class MaelstromMode(GameMode):
|
||||
if random.randrange(100) == 0 and addroles.get("villager", 0) > 0:
|
||||
addroles["blessed villager"] = 1
|
||||
|
||||
lpl = len(villagers)
|
||||
lwolves = sum(addroles[r] for r in var.WOLFCHAT_ROLES)
|
||||
lcubs = addroles["wolf cub"]
|
||||
lrealwolves = sum(addroles[r] for r in var.WOLF_ROLES - {"wolf cub"})
|
||||
lmonsters = addroles["monster"]
|
||||
ldemoniacs = addroles["demoniac"]
|
||||
ltraitors = addroles["traitor"]
|
||||
lpipers = addroles["piper"]
|
||||
lsuccubi = addroles["succubus"]
|
||||
rolemap = defaultdict(list)
|
||||
for r,c in addroles.items():
|
||||
if c > 0:
|
||||
rolemap[r] = list(range(c))
|
||||
|
||||
if self.chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs, ltraitors, lpipers, lsuccubi, 0, cli, end_game=False):
|
||||
if self.chk_win_conditions(cli, rolemap, end_game=False):
|
||||
return self._role_attribution(cli, var, villagers, do_templates)
|
||||
|
||||
return addroles
|
||||
|
@ -181,7 +181,7 @@ def on_transition_day_resolve(evt, cli, var, victim):
|
||||
# TODO: remove these checks once everything is split
|
||||
# right now they're needed because otherwise protection may fire off even if the person isn't home
|
||||
# that will not be an issue once everything is using the event
|
||||
if victim in var.ROLES["harlot"] | var.ROLES["succubus"] and var.HVISITED.get(victim) and victim not in evt.data["dead"] and victim in evt.data["onlybywolves"]:
|
||||
if victim in var.ROLES["harlot"] and var.HVISITED.get(victim) and victim not in evt.data["dead"] and victim in evt.data["onlybywolves"]:
|
||||
return
|
||||
# END checks to remove
|
||||
|
||||
|
@ -40,7 +40,7 @@ def on_transition_day_resolve(evt, cli, var, victim):
|
||||
# TODO: remove these checks once everything is split
|
||||
# right now they're needed because otherwise protection may fire off even if the person isn't home
|
||||
# that will not be an issue once everything is using the event
|
||||
if victim in var.ROLES["harlot"] | var.ROLES["succubus"] and var.HVISITED.get(victim) and victim not in evt.data["dead"] and victim in evt.data["onlybywolves"]:
|
||||
if victim in var.ROLES["harlot"] and var.HVISITED.get(victim) and victim not in evt.data["dead"] and victim in evt.data["onlybywolves"]:
|
||||
return
|
||||
# END checks to remove
|
||||
|
||||
|
@ -66,7 +66,7 @@ def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers):
|
||||
|
||||
@event_listener("get_special")
|
||||
def on_get_special(evt, cli, var):
|
||||
evt.data["special"].update(list_players(("detective",)))
|
||||
evt.data["special"].update(var.ROLES["detective"])
|
||||
|
||||
@event_listener("exchange_roles")
|
||||
def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role):
|
||||
|
@ -101,6 +101,10 @@ def on_doctor_immunize(evt, cli, var, doctor, target):
|
||||
del SICK[n]
|
||||
evt.data["message"] = "not_sick"
|
||||
|
||||
@event_listener("get_special")
|
||||
def on_get_special(evt, cli, var):
|
||||
evt.data["special"].update(var.ROLES["doomsayer"])
|
||||
|
||||
@event_listener("chk_nightdone")
|
||||
def on_chk_nightdone(evt, cli, var):
|
||||
evt.data["actedcount"] += len(SEEN)
|
||||
|
@ -59,8 +59,6 @@ def on_player_win(evt, var, user, role, winner, survived):
|
||||
if role != "dullahan":
|
||||
return
|
||||
alive = set(list_players())
|
||||
if user.nick in var.ENTRANCED:
|
||||
alive -= var.ROLES["succubus"]
|
||||
if not TARGETS[user.nick] & alive:
|
||||
evt.data["iwon"] = True
|
||||
|
||||
@ -138,7 +136,7 @@ def on_acted(evt, cli, var, nick, sender):
|
||||
|
||||
@event_listener("get_special")
|
||||
def on_get_special(evt, cli, var):
|
||||
evt.data["special"].update(list_players(("dullahan",)))
|
||||
evt.data["special"].update(var.ROLES["dullahan"])
|
||||
|
||||
@event_listener("transition_day", priority=2)
|
||||
def on_transition_day(evt, cli, var):
|
||||
@ -205,6 +203,15 @@ def on_role_assignment(evt, cli, var, gamemode, pl, restart):
|
||||
ps.remove(target)
|
||||
ts.add(target)
|
||||
|
||||
@event_listener("succubus_visit")
|
||||
def on_succubus_visit(evt, cli, var, nick, victim):
|
||||
if victim in TARGETS and TARGETS[victim] & var.ROLES["succubus"]:
|
||||
TARGETS.difference_update(var.ROLES["succubus"])
|
||||
pm(cli, victim, messages["dullahan_no_kill_succubus"])
|
||||
if KILLS.get(victim) in var.ROLES["succubus"]:
|
||||
pm(cli, victim, messages["no_kill_succubus"].format(KILLS[victim]))
|
||||
del KILLS[victim]
|
||||
|
||||
@event_listener("myrole")
|
||||
def on_myrole(evt, cli, var, nick):
|
||||
role = get_role(nick)
|
||||
|
@ -16,9 +16,7 @@ from src.events import Event
|
||||
def on_transition_day(evt, cli, var):
|
||||
# now that all protections are finished, add people back to onlybywolves
|
||||
# if they're down to 1 active kill and wolves were a valid killer
|
||||
# TODO: split out var.ENTRANCED_DYING when succubus is split
|
||||
# that should probably be a priority 4.7 listener
|
||||
victims = set(list_players()) & set(evt.data["victims"]) - var.DYING - var.ENTRANCED_DYING
|
||||
victims = set(list_players()) & set(evt.data["victims"]) - var.DYING
|
||||
for v in victims:
|
||||
if evt.data["numkills"][v] == 1 and v in evt.data["bywolves"]:
|
||||
evt.data["onlybywolves"].add(v)
|
||||
|
@ -72,14 +72,15 @@ def hunter_pass(cli, nick, chan, rest):
|
||||
|
||||
@event_listener("del_player")
|
||||
def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers):
|
||||
HUNTERS.discard(nick)
|
||||
PASSED.discard(nick)
|
||||
if nick in KILLS:
|
||||
del KILLS[nick]
|
||||
for h,v in list(KILLS.items()):
|
||||
if v == nick:
|
||||
HUNTERS.discard(h)
|
||||
PASSED.discard(h)
|
||||
pm(cli, h, messages["hunter_discard"])
|
||||
del KILLS[h]
|
||||
elif h == nick:
|
||||
del KILLS[h]
|
||||
|
||||
@event_listener("rename_player")
|
||||
def on_rename(evt, cli, var, prefix, nick):
|
||||
@ -107,7 +108,7 @@ def on_acted(evt, cli, var, nick, sender):
|
||||
|
||||
@event_listener("get_special")
|
||||
def on_get_special(evt, cli, var):
|
||||
evt.data["special"].update(list_players(("hunter",)))
|
||||
evt.data["special"].update(var.ROLES["hunter"])
|
||||
|
||||
@event_listener("transition_day", priority=2)
|
||||
def on_transition_day(evt, cli, var):
|
||||
@ -149,6 +150,13 @@ def on_transition_night_end(evt, cli, var):
|
||||
pm(cli, hunter, messages["hunter_simple"])
|
||||
pm(cli, hunter, "Players: " + ", ".join(pl))
|
||||
|
||||
@event_listener("succubus_visit")
|
||||
def on_succubus_visit(evt, cli, var, nick, victim):
|
||||
if KILLS.get(victim) in var.ROLES["succubus"]:
|
||||
pm(cli, victim, messages["no_kill_succubus"].format(KILLS[victim]))
|
||||
del KILLS[victim]
|
||||
HUNTERS.discard(victim)
|
||||
|
||||
@event_listener("begin_day")
|
||||
def on_begin_day(evt, cli, var):
|
||||
KILLS.clear()
|
||||
|
@ -10,9 +10,8 @@ from src.events import Event
|
||||
|
||||
@event_listener("exchange_roles")
|
||||
def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role):
|
||||
special = set(list_players(("harlot", "guardian angel", "bodyguard", "priest", "prophet", "matchmaker",
|
||||
"shaman", "doctor", "hag", "sorcerer", "turncoat", "clone", "crazed shaman",
|
||||
"piper", "succubus")))
|
||||
special = set(list_players(("harlot", "priest", "prophet", "matchmaker",
|
||||
"doctor", "hag", "sorcerer", "turncoat", "clone", "piper")))
|
||||
evt2 = Event("get_special", {"special": special})
|
||||
evt2.dispatch(cli, var)
|
||||
pl = set(list_players())
|
||||
@ -39,9 +38,8 @@ def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role):
|
||||
@event_listener("transition_night_end", priority=2.01)
|
||||
def on_transition_night_end(evt, cli, var):
|
||||
# init with all roles that haven't been split yet
|
||||
special = set(list_players(("harlot", "guardian angel", "bodyguard", "priest", "prophet", "matchmaker",
|
||||
"shaman", "doctor", "hag", "sorcerer", "turncoat", "clone", "crazed shaman",
|
||||
"piper", "succubus")))
|
||||
special = set(list_players(("harlot", "priest", "prophet", "matchmaker",
|
||||
"doctor", "hag", "sorcerer", "turncoat", "clone", "piper")))
|
||||
evt2 = Event("get_special", {"special": special})
|
||||
evt2.dispatch(cli, var)
|
||||
pl = set(list_players())
|
||||
|
@ -146,7 +146,7 @@ def on_acted(evt, cli, var, nick, sender):
|
||||
|
||||
@event_listener("get_special")
|
||||
def on_get_special(evt, cli, var):
|
||||
evt.data["special"].update(list_players(("shaman",)))
|
||||
evt.data["special"].update(list_players(("shaman", "crazed shaman", "wolf shaman")))
|
||||
|
||||
@event_listener("exchange_roles")
|
||||
def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role):
|
||||
@ -312,12 +312,9 @@ def on_transition_day_begin(evt, cli, var):
|
||||
ps = pl[:]
|
||||
if LASTGIVEN.get(shaman) in ps:
|
||||
ps.remove(LASTGIVEN.get(shaman))
|
||||
# TODO: somehow split this off into succubus.py,
|
||||
# probably via a new event
|
||||
if shaman in var.ENTRANCED:
|
||||
for succubus in var.ROLES["succubus"]:
|
||||
if succubus in ps:
|
||||
ps.remove(succubus)
|
||||
levt = Event("get_random_totem_targets", {"targets": ps})
|
||||
levt.dispatch(cli, var, shaman)
|
||||
ps = levt.data["targets"]
|
||||
if ps:
|
||||
target = random.choice(ps)
|
||||
totem.func(cli, shaman, shaman, target, messages["random_totem_prefix"]) # XXX: Old API
|
||||
@ -440,7 +437,7 @@ def on_transition_day_resolve2(evt, cli, var, victim):
|
||||
# TODO: remove these checks once everything is split
|
||||
# right now they're needed because otherwise protection may fire off even if the person isn't home
|
||||
# that will not be an issue once everything is using the event
|
||||
if victim in var.ROLES["harlot"] | var.ROLES["succubus"] and var.HVISITED.get(victim) and victim not in evt.data["dead"] and victim in evt.data["onlybywolves"]:
|
||||
if victim in var.ROLES["harlot"] and var.HVISITED.get(victim) and victim not in evt.data["dead"] and victim in evt.data["onlybywolves"]:
|
||||
return
|
||||
# END checks to remove
|
||||
|
||||
@ -455,7 +452,7 @@ def on_transition_day_resolve6(evt, cli, var, victim):
|
||||
# TODO: remove these checks once everything is split
|
||||
# right now they're needed because otherwise retribution may fire off when the target isn't actually dying
|
||||
# that will not be an issue once everything is using the event
|
||||
if victim in var.ROLES["harlot"] | var.ROLES["succubus"] and var.HVISITED.get(victim) and victim not in evt.data["dead"] and victim in evt.data["onlybywolves"]:
|
||||
if victim in var.ROLES["harlot"] and var.HVISITED.get(victim) and victim not in evt.data["dead"] and victim in evt.data["onlybywolves"]:
|
||||
return
|
||||
if evt.data["protected"].get(victim):
|
||||
return
|
||||
@ -587,6 +584,13 @@ def on_assassinate(evt, cli, var, nick, target, prot):
|
||||
evt.stop_processing = True
|
||||
cli.msg(botconfig.CHANNEL, messages[evt.params.message_prefix + "totem"].format(nick, target))
|
||||
|
||||
@event_listener("succubus_visit")
|
||||
def on_succubus_visit(evt, cli, var, nick, victim):
|
||||
if (SHAMANS.get(victim, (None, None))[1] in var.ROLES["succubus"] and
|
||||
(get_role(victim) == "crazed shaman" or TOTEMS[victim] not in var.BENEFICIAL_TOTEMS)):
|
||||
pm(cli, victim, messages["retract_totem_succubus"].format(SHAMANS[victim]))
|
||||
del SHAMANS[victim]
|
||||
|
||||
@event_listener("myrole")
|
||||
def on_myrole(evt, cli, var, nick):
|
||||
role = evt.data["role"]
|
||||
|
317
src/roles/succubus.py
Normal file
317
src/roles/succubus.py
Normal file
@ -0,0 +1,317 @@
|
||||
import re
|
||||
import random
|
||||
import itertools
|
||||
import math
|
||||
from collections import defaultdict
|
||||
|
||||
import botconfig
|
||||
import src.settings as var
|
||||
from src.utilities import *
|
||||
from src import channels, users, debuglog, errlog, plog
|
||||
from src.decorators import cmd, event_listener
|
||||
from src.messages import messages
|
||||
from src.events import Event
|
||||
|
||||
ENTRANCED = set()
|
||||
ENTRANCED_DYING = set()
|
||||
VISITED = {}
|
||||
ALL_SUCC_IDLE = True
|
||||
|
||||
@cmd("visit", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("succubus",))
|
||||
def hvisit(cli, nick, chan, rest):
|
||||
"""Entrance a player, converting them to your team."""
|
||||
if VISITED.get(nick):
|
||||
pm(cli, nick, messages["succubus_already_visited"].format(VISITED[nick]))
|
||||
return
|
||||
victim = get_victim(cli, nick, re.split(" +",rest)[0], False, True)
|
||||
if not victim:
|
||||
return
|
||||
if nick == victim:
|
||||
pm(cli, nick, messages["succubus_not_self"])
|
||||
return
|
||||
evt = Event("targeted_command", {"target": victim, "misdirection": True, "exchange": False})
|
||||
evt.dispatch(cli, var, "visit", nick, victim, frozenset({"detrimental", "immediate"}))
|
||||
if evt.prevent_default:
|
||||
return
|
||||
victim = evt.data["target"]
|
||||
vrole = get_role(victim)
|
||||
|
||||
VISITED[nick] = victim
|
||||
if vrole != "succubus":
|
||||
ENTRANCED.add(victim)
|
||||
pm(cli, nick, messages["succubus_target_success"].format(victim))
|
||||
else:
|
||||
pm(cli, nick, messages["harlot_success"].format(victim))
|
||||
if nick != victim:
|
||||
if vrole != "succubus":
|
||||
pm(cli, victim, messages["notify_succubus_target"].format(nick))
|
||||
else:
|
||||
pm(cli, victim, messages["harlot_success"].format(nick))
|
||||
revt = Event("succubus_visit", {})
|
||||
revt.dispatch(cli, var, nick, victim)
|
||||
|
||||
# TODO: split these into assassin, hag, and alpha wolf when they are split off
|
||||
if var.TARGETED.get(victim) in var.ROLES["succubus"]:
|
||||
msg = messages["no_target_succubus"].format(var.TARGETED[victim])
|
||||
del var.TARGETED[victim]
|
||||
if victim in var.ROLES["village drunk"]:
|
||||
target = random.choice(list(set(list_players()) - var.ROLES["succubus"] - {victim}))
|
||||
msg += messages["drunk_target"].format(target)
|
||||
var.TARGETED[victim] = target
|
||||
pm(cli, victim, nick)
|
||||
if victim in var.HEXED and var.LASTHEXED[victim] in var.ROLES["succubus"]:
|
||||
pm(cli, victim, messages["retract_hex_succubus"].format(var.LASTHEXED[victim]))
|
||||
var.TOBESILENCED.remove(nick)
|
||||
var.HEXED.remove(victim)
|
||||
del var.LASTHEXED[victim]
|
||||
if var.BITE_PREFERENCES.get(victim) in var.ROLES["succubus"]:
|
||||
pm(cli, victim, messages["no_kill_succubus"].format(var.BITE_PREFERENCES[victim]))
|
||||
del var.BITE_PREFERENCES[victim]
|
||||
|
||||
debuglog("{0} ({1}) VISIT: {2} ({3})".format(nick, get_role(nick), victim, vrole))
|
||||
chk_nightdone(cli)
|
||||
|
||||
@cmd("pass", chan=False, pm=True, playing=True, phases=("night",), roles=("succubus",))
|
||||
def pass_cmd(cli, nick, chan, rest):
|
||||
"""Do not entrance someone tonight."""
|
||||
if VISITED.get(nick):
|
||||
pm(cli, nick, messages["succubus_already_visited"].format(VISITED[nick]))
|
||||
return
|
||||
VISITED[nick] = None
|
||||
pm(cli, nick, messages["succubus_pass"])
|
||||
debuglog("{0} ({1}) PASS".format(nick, get_role(nick)))
|
||||
chk_nightdone(cli)
|
||||
|
||||
@event_listener("get_random_totem_targets")
|
||||
def on_get_random_totem_targets(evt, cli, var, shaman):
|
||||
if shaman in ENTRANCED:
|
||||
for succubus in var.ROLES["succubus"]:
|
||||
if succubus in evt.data["targets"]:
|
||||
evt.data["targets"].remove(succubus)
|
||||
|
||||
@event_listener("chk_decision", priority=0)
|
||||
def on_chk_decision(evt, cli, var, force):
|
||||
for votee, voters in evt.data["votelist"].items():
|
||||
if votee in var.ROLES["succubus"]:
|
||||
for vtr in ENTRANCED:
|
||||
if vtr in voters:
|
||||
voters.remove(vtr)
|
||||
|
||||
def _kill_entranced_voters(var, votelist, not_lynching, votee):
|
||||
if not var.ROLES["succubus"] & (set(itertools.chain(*votelist.values())) | 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
|
||||
ENTRANCED_DYING.update(ENTRANCED - var.DEAD)
|
||||
for other_votee, other_voters in votelist.items():
|
||||
if var.ROLES["succubus"] & set(other_voters):
|
||||
if votee == other_votee:
|
||||
ENTRANCED_DYING.clear()
|
||||
return
|
||||
ENTRANCED_DYING.difference_update(other_voters)
|
||||
if var.ROLES["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, cli, 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, cli, 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)
|
||||
# we do not want to override the win conditions for neutral roles should they win while entranced
|
||||
# For example, entranced monsters should win with other monsters should mosnters win, and be
|
||||
# properly credited with a team win in that event.
|
||||
@event_listener("player_win", priority=6)
|
||||
def on_player_win(evt, var, user, role, winner, survived):
|
||||
nick = user.nick
|
||||
if nick in ENTRANCED:
|
||||
evt.data["special"].append("entranced")
|
||||
if winner != "succubi" and role not in var.TRUE_NEUTRAL_ROLES:
|
||||
evt.data["won"] = False
|
||||
else:
|
||||
evt.data["iwon"] = True
|
||||
if role == "succubus" and winner == "succubi":
|
||||
evt.data["won"] = True
|
||||
|
||||
@event_listener("chk_win", priority=2)
|
||||
def on_chk_win(evt, cli, var, rolemap, lpl, lwolves, lrealwolves):
|
||||
lsuccubi = len(rolemap.get("succubus", ()))
|
||||
lentranced = len(ENTRANCED - var.DEAD)
|
||||
if var.PHASE == "day" and lpl - lsuccubi == lentranced:
|
||||
evt.data["winner"] = "succubi"
|
||||
evt.data["message"] = messages["succubus_win"].format(plural("succubus", lsuccubi), plural("has", lsuccubi), plural("master's", lsuccubi))
|
||||
|
||||
@event_listener("del_player")
|
||||
def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers):
|
||||
global ALL_SUCC_IDLE
|
||||
if nickrole != "succubus":
|
||||
return
|
||||
if nick in VISITED:
|
||||
# if it's night, also unentrance the person they visited
|
||||
if var.PHASE == "night" and var.GAMEPHASE == "night":
|
||||
if VISITED[nick] in ENTRANCED:
|
||||
ENTRANCED.discard(visited[nick])
|
||||
ENTRANCED_DYING.discard(visited[nick])
|
||||
pm(cli, VISITED[nick], messages["entranced_revert_win"])
|
||||
del VISITED[nick]
|
||||
|
||||
# if all succubi are dead, one of two things happen:
|
||||
# 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
|
||||
if death_triggers:
|
||||
ALL_SUCC_IDLE = False
|
||||
if len(var.ROLES["succubus"]) == 0:
|
||||
if ALL_SUCC_IDLE:
|
||||
while ENTRANCED:
|
||||
e = ENTRANCED.pop()
|
||||
pm(cli, e, messages["entranced_revert_win"])
|
||||
elif ENTRANCED:
|
||||
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:
|
||||
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:
|
||||
cli.msg(botconfig.CHANNEL, messages["succubus_die_kill"].format(msg[0] + comma))
|
||||
elif len(msg) == 2:
|
||||
cli.msg(botconfig.CHANNEL, messages["succubus_die_kill"].format(msg[0] + comma + " and " + msg[1] + comma))
|
||||
else:
|
||||
cli.msg(botconfig.CHANNEL, messages["succubus_die_kill"].format(", ".join(msg[:-1]) + ", and " + msg[-1] + comma))
|
||||
for e in ENTRANCED:
|
||||
# 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 - {e})
|
||||
debuglog("{0} ({1}) SUCCUBUS DEATH KILL: {2} ({3})".format(nick, nickrole, e, get_role(e)))
|
||||
evt.params.del_player(cli, e, end_game=False, killer_role="succubus",
|
||||
deadlist=dlc, original=evt.params.original, ismain=False)
|
||||
evt.data["pl"] = evt.params.refresh_pl(evt.data["pl"])
|
||||
ENTRANCED_DYING.clear()
|
||||
|
||||
@event_listener("transition_day_resolve", priority=1)
|
||||
def on_transition_day_resolve(evt, cli, var, victim):
|
||||
if victim in var.ROLES["succubus"] and VISITED.get(victim) and victim not in evt.data["dead"] and victim in evt.data["onlybywolves"]:
|
||||
# TODO: check if this is necessary for succubus, it's to prevent a message playing if alpha bites
|
||||
# a harlot that is visiting a wolf, since the bite succeeds in that case.
|
||||
if victim not in evt.data["bitten"]:
|
||||
evt.data["message"].append(messages["target_not_home"])
|
||||
evt.data["novictmsg"] = False
|
||||
evt.stop_processing = True
|
||||
evt.prevent_default = True
|
||||
|
||||
@event_listener("transition_day_resolve_end", priority=1)
|
||||
def on_transition_day_resolve_end(evt, cli, var, victims):
|
||||
for victim in victims + evt.data["bitten"]:
|
||||
if victim in evt.data["dead"] and victim in VISITED.values() and (victim in evt.data["bywolves"] or victim in evt.data["bitten"]):
|
||||
for succ in VISITED:
|
||||
if VISITED[succ] == victim and succ not in evt.data["bitten"] and succ not in evt.data["dead"]:
|
||||
if var.ROLE_REVEAL in ("on", "team"):
|
||||
evt.data["message"].append(messages["visited_victim"].format(succ, get_role(succ)))
|
||||
else:
|
||||
evt.data["message"].append(messages["visited_victim_noreveal"].format(succ))
|
||||
evt.data["bywolves"].add(succ)
|
||||
evt.data["onlybywolves"].add(succ)
|
||||
evt.data["dead"].append(succ)
|
||||
|
||||
@event_listener("night_acted")
|
||||
def on_night_acted(evt, cli, var, nick, sender):
|
||||
if VISITED.get(nick):
|
||||
evt.data["acted"] = True
|
||||
|
||||
@event_listener("chk_nightdone")
|
||||
def on_chk_nightdone(evt, cli, var):
|
||||
evt.data["actedcount"] += len(VISITED)
|
||||
evt.data["nightroles"].extend(var.ROLES["succubus"])
|
||||
|
||||
@event_listener("targeted_command")
|
||||
def on_targeted_command(evt, cli, var, cmd, actor, orig_target, tags):
|
||||
if "beneficial" not in tags and actor in ENTRANCED and evt.data["target"] in var.ROLES["succubus"]:
|
||||
try:
|
||||
what = evt.params.action
|
||||
except AttributeError:
|
||||
what = cmd
|
||||
pm(cli, actor, messages["no_acting_on_succubus"].format(what))
|
||||
evt.stop_processing = True
|
||||
evt.prevent_default = True
|
||||
|
||||
@event_listener("transition_night_end", priority=2)
|
||||
def on_transition_night_end(evt, cli, var):
|
||||
for succubus in var.ROLES["succubus"]:
|
||||
pl = list_players()
|
||||
random.shuffle(pl)
|
||||
pl.remove(succubus)
|
||||
if succubus in var.PLAYERS and not is_user_simple(succubus):
|
||||
pm(cli, succubus, messages["succubus_notify"])
|
||||
else:
|
||||
pm(cli, succubus, messages["succubus_simple"])
|
||||
pm(cli, succubus, "Players: " + ", ".join(("{0} ({1})".format(x, get_role(x)) if x in var.ROLES["succubus"] else x for x in pl)))
|
||||
|
||||
@event_listener("begin_day")
|
||||
def on_begin_day(evt, cli, var):
|
||||
VISITED.clear()
|
||||
ENTRANCED_DYING.clear()
|
||||
|
||||
@event_listener("transition_day", priority=2)
|
||||
def on_transition_day(evt, cli, 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")
|
||||
def on_get_special(evt, cli, var):
|
||||
evt.data["special"].update(var.ROLES["succubus"])
|
||||
|
||||
@event_listener("rename_player")
|
||||
def on_rename(evt, cli, var, prefix, nick):
|
||||
if prefix in ENTRANCED:
|
||||
ENTRANCED.remove(prefix)
|
||||
ENTRANCED.add(nick)
|
||||
if prefix in ENTRANCED_DYING:
|
||||
ENTRANCED_DYING.remove(prefix)
|
||||
ENTRANCED_DYING.add(nick)
|
||||
kvp = {}
|
||||
for a,b in VISITED.items():
|
||||
s = nick if a == prefix else a
|
||||
t = nick if b == prefix else b
|
||||
kvp[s] = t
|
||||
VISITED.update(kvp)
|
||||
if prefix in VISITED:
|
||||
del VISITED[prefix]
|
||||
|
||||
@event_listener("reset")
|
||||
def on_reset(evt, var):
|
||||
global ALL_SUCC_IDLE
|
||||
ALL_SUCC_IDLE = True
|
||||
ENTRANCED.clear()
|
||||
ENTRANCED_DYING.clear()
|
||||
VISITED.clear()
|
||||
|
||||
@event_listener("revealroles")
|
||||
def on_revealroles(evt, var, wrapper):
|
||||
if ENTRANCED:
|
||||
evt.data["output"].append("\u0002entranced players\u0002: {0}".format(", ".join(ENTRANCED)))
|
||||
|
||||
if ENTRANCED_DYING:
|
||||
evt.data["output"].append("\u0002dying entranced players\u0002: {0}".format(", ".join(ENTRANCED_DYING)))
|
||||
|
||||
# vim: set sw=4 expandtab:
|
@ -59,5 +59,24 @@ def on_update_stats3(evt, cli, var, nick, nickrole, nickreveal, nicktpls):
|
||||
# and a wolf kill, and a wolf + villager died, we know the villager was the wolf kill
|
||||
# and therefore cannot be traitor. However, we currently do not have the logic to deduce this
|
||||
|
||||
@event_listener("chk_win", priority=1.1)
|
||||
def on_chk_win(evt, cli, var, rolemap, lpl, lwolves, lrealwolves):
|
||||
did_something = False
|
||||
if lrealwolves == 0:
|
||||
for traitor in list(rolemap["traitor"]):
|
||||
rolemap["wolf"].add(traitor)
|
||||
rolemap["traitor"].remove(traitor)
|
||||
rolemap["cursed villager"].discard(traitor)
|
||||
did_something = True
|
||||
if var.PHASE in var.GAME_PHASES:
|
||||
var.FINAL_ROLES[traitor] = "wolf"
|
||||
pm(cli, traitor, messages["traitor_turn"])
|
||||
debuglog(traitor, "(traitor) TURNING")
|
||||
if did_something:
|
||||
if var.PHASE in var.GAME_PHASES:
|
||||
var.TRAITOR_TURNED = True
|
||||
cli.msg(botconfig.CHANNEL, messages["traitor_turn_channel"])
|
||||
evt.prevent_default = True
|
||||
evt.stop_processing = True
|
||||
|
||||
# vim: set sw=4 expandtab:
|
||||
|
@ -63,13 +63,13 @@ def vigilante_pass(cli, nick, chan, rest):
|
||||
|
||||
@event_listener("del_player")
|
||||
def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers):
|
||||
PASSED.discard(nick)
|
||||
if nick in KILLS:
|
||||
del KILLS[nick]
|
||||
for h,v in list(KILLS.items()):
|
||||
if v == nick:
|
||||
PASSED.discard(h)
|
||||
pm(cli, h, messages["hunter_discard"])
|
||||
del KILLS[h]
|
||||
elif h == nick:
|
||||
del KILLS[h]
|
||||
|
||||
@event_listener("rename_player")
|
||||
def on_rename(evt, cli, var, prefix, nick):
|
||||
@ -94,7 +94,7 @@ def on_acted(evt, cli, var, nick, sender):
|
||||
|
||||
@event_listener("get_special")
|
||||
def on_get_special(evt, cli, var):
|
||||
evt.data["special"].update(list_players(("vigilante",)))
|
||||
evt.data["special"].update(var.ROLES["vigilante"])
|
||||
|
||||
@event_listener("transition_day", priority=2)
|
||||
def on_transition_day(evt, cli, var):
|
||||
@ -135,6 +135,12 @@ def on_transition_night_end(evt, cli, var):
|
||||
pm(cli, vigilante, messages["vigilante_simple"])
|
||||
pm(cli, vigilante, "Players: " + ", ".join(pl))
|
||||
|
||||
@event_listener("succubus_visit")
|
||||
def on_succubus_visit(evt, cli, var, nick, victim):
|
||||
if KILLS.get(victim) in var.ROLES["succubus"]:
|
||||
pm(cli, victim, messages["no_kill_succubus"].format(KILLS[victim]))
|
||||
del KILLS[victim]
|
||||
|
||||
@event_listener("begin_day")
|
||||
def on_begin_day(evt, cli, var):
|
||||
KILLS.clear()
|
||||
|
@ -49,4 +49,18 @@ def on_player_win(evt, var, user, role, winner, survived):
|
||||
evt.data["won"] = True
|
||||
evt.data["iwon"] = survived
|
||||
|
||||
@event_listener("chk_win", priority=3)
|
||||
def on_chk_win(evt, cli, var, rolemap, lpl, lwolves, lrealwolves):
|
||||
if evt.data["winner"] is not None:
|
||||
return
|
||||
if lrealwolves == 0:
|
||||
evt.data["winner"] = "villagers"
|
||||
evt.data["message"] = messages["villager_win"]
|
||||
elif lwolves == lpl / 2:
|
||||
evt.data["winner"] = "wolves"
|
||||
evt.data["message"] = messages["wolf_win_equal"]
|
||||
elif lwolves > lpl / 2:
|
||||
evt.data["winner"] = "wolves"
|
||||
evt.data["message"] = messages["wolf_win_greater"]
|
||||
|
||||
# vim: set sw=4 expandtab:
|
||||
|
@ -394,6 +394,33 @@ def on_transition_night_end(evt, cli, var):
|
||||
if var.ALPHA_ENABLED and role == "alpha wolf" and wolf not in var.ALPHA_WOLVES:
|
||||
pm(cli, wolf, messages["wolf_bite"])
|
||||
|
||||
@event_listener("chk_win", priority=1)
|
||||
def on_chk_win(evt, cli, var, rolemap, lpl, lwolves, lrealwolves):
|
||||
# TODO: split into cub
|
||||
did_something = False
|
||||
if lrealwolves == 0:
|
||||
for wc in list(rolemap["wolf cub"]):
|
||||
rolemap["wolf"].add(wc)
|
||||
rolemap["wolf cub"].remove(wc)
|
||||
did_something = True
|
||||
if var.PHASE in var.GAME_PHASES:
|
||||
var.FINAL_ROLES[wc] = "wolf"
|
||||
pm(cli, wc, messages["cub_grow_up"])
|
||||
debuglog(wc, "(wolf cub) GROW UP")
|
||||
if did_something:
|
||||
evt.prevent_default = True
|
||||
evt.stop_processing = True
|
||||
|
||||
@event_listener("succubus_visit")
|
||||
def on_succubus_visit(evt, cli, var, nick, victim):
|
||||
if var.ROLES["succubus"].intersection(KILLS.get(victim, ())):
|
||||
for s in var.ROLES["succubus"]:
|
||||
if s in KILLS[victim]:
|
||||
pm(cli, victim, messages["no_kill_succubus"].format(nick))
|
||||
KILLS[victim].remove(s)
|
||||
if not KILLS[victim]:
|
||||
del KILLS[victim]
|
||||
|
||||
@event_listener("begin_day")
|
||||
def on_begin_day(evt, cli, var):
|
||||
KILLS.clear()
|
||||
|
@ -309,15 +309,20 @@ def singular(plural):
|
||||
# otherwise we just added an s on the end
|
||||
return plural[:-1]
|
||||
|
||||
def list_players(roles=None):
|
||||
def list_players(roles=None, *, rolemap=None):
|
||||
if rolemap is None:
|
||||
rolemap = var.ROLES
|
||||
if roles is None:
|
||||
roles = var.ROLES.keys()
|
||||
roles = rolemap.keys()
|
||||
pl = set()
|
||||
for x in roles:
|
||||
if x in var.TEMPLATE_RESTRICTIONS.keys():
|
||||
if x in var.TEMPLATE_RESTRICTIONS:
|
||||
continue
|
||||
for p in var.ROLES.get(x, ()):
|
||||
pl.add(p)
|
||||
pl.update(rolemap.get(x, ()))
|
||||
if rolemap is not var.ROLES:
|
||||
# we weren't given an actual player list (possibly),
|
||||
# so the elements of pl are not necessarily in var.ALL_PLAYERS
|
||||
return list(pl)
|
||||
return [p.nick for p in var.ALL_PLAYERS if p.nick in pl]
|
||||
|
||||
def list_players_and_roles():
|
||||
@ -352,10 +357,12 @@ def get_role(p):
|
||||
raise ValueError("Nick {0} isn't playing and has no defined participant role".format(p))
|
||||
return role
|
||||
|
||||
def get_roles(*roles):
|
||||
def get_roles(*roles, rolemap=None):
|
||||
if rolemap is None:
|
||||
rolemap = var.ROLES
|
||||
all_roles = []
|
||||
for role in roles:
|
||||
all_roles.append(var.ROLES[role])
|
||||
all_roles.append(rolemap[role])
|
||||
return list(itertools.chain(*all_roles))
|
||||
|
||||
def get_reveal_role(nick):
|
||||
|
367
src/wolfgame.py
367
src/wolfgame.py
@ -312,7 +312,6 @@ def reset():
|
||||
var.GAMEMODE_VOTES = {} #list of players who have used !game
|
||||
var.START_VOTES.clear() # list of players who have voted to !start
|
||||
var.LOVERS = {} # need to be here for purposes of random
|
||||
var.ENTRANCED = set()
|
||||
var.ROLE_STATS = frozenset() # type: FrozenSet[FrozenSet[Tuple[str, int]]]
|
||||
var.ROLE_SETS = [] # type: List[Tuple[Counter[str], int]]
|
||||
|
||||
@ -1800,33 +1799,25 @@ def chk_decision(cli, force=""):
|
||||
# we only need 50%+ to not lynch, instead of an actual majority, because a tie would time out day anyway
|
||||
# don't check for ABSTAIN_ENABLED here since we may have a case where the majority of people have pacifism totems or something
|
||||
if len(not_lynching) >= math.ceil(avail / 2):
|
||||
abs_evt = Event("chk_decision_abstain", {})
|
||||
abs_evt = Event("chk_decision_abstain", {}, votelist=votelist, numvotes=numvotes)
|
||||
abs_evt.dispatch(cli, var, not_lynching)
|
||||
cli.msg(botconfig.CHANNEL, messages["village_abstain"])
|
||||
if var.ROLES["succubus"] & var.NO_LYNCH:
|
||||
var.ENTRANCED_DYING.update(var.ENTRANCED - var.NO_LYNCH - var.DEAD)
|
||||
else:
|
||||
var.ENTRANCED_DYING.update(var.ENTRANCED & var.NO_LYNCH)
|
||||
var.ABSTAINED = True
|
||||
event.data["transition_night"](cli)
|
||||
return
|
||||
for votee, voters in votelist.items():
|
||||
# split this into a priority 0 event when it comes time to split off succubus
|
||||
if votee in var.ROLES["succubus"]:
|
||||
for vtr in var.ENTRANCED:
|
||||
if vtr in voters:
|
||||
voters.remove(vtr)
|
||||
|
||||
if numvotes[votee] >= votesneeded or votee == force:
|
||||
# priorities:
|
||||
# 1 = displaying impatience totem messages
|
||||
# 3 = mayor/revealing totem
|
||||
# 4 = fool
|
||||
# 5 = desperation totem, other things that happen on generic lynch
|
||||
vote_evt = Event("chk_decision_lynch", {
|
||||
"votee": votee,
|
||||
"deadlist": deadlist
|
||||
}, del_player=del_player)
|
||||
vote_evt = Event("chk_decision_lynch", {"votee": votee, "deadlist": deadlist},
|
||||
del_player=del_player,
|
||||
original_votee=votee,
|
||||
force=(votee == force),
|
||||
votelist=votelist,
|
||||
not_lynching=not_lynching)
|
||||
if vote_evt.dispatch(cli, var, voters):
|
||||
votee = vote_evt.data["votee"]
|
||||
# roles that end the game upon being lynched
|
||||
@ -1844,23 +1835,6 @@ def chk_decision(cli, force=""):
|
||||
if votee in var.ROLES["jester"]:
|
||||
var.JESTERS.add(votee)
|
||||
|
||||
# this checks if any succubus have voted the current votee
|
||||
# if it is NOT the case, then it checks if any succubus voted at all
|
||||
# it does so by joining together all the values from var.VOTES and casting them all into a single set
|
||||
# if any succubus voted for anyone else and nobody for the current votee, then proceed to block
|
||||
if not var.ROLES["succubus"] & set(var.VOTES[votee]) and var.ROLES["succubus"] & set(itertools.chain(*var.VOTES.values())):
|
||||
voted_along = set()
|
||||
for person, all_voters in var.VOTES.items():
|
||||
if var.ROLES["succubus"] & set(all_voters):
|
||||
for v in all_voters:
|
||||
if v in var.ENTRANCED:
|
||||
voted_along.add(v)
|
||||
|
||||
var.ENTRANCED_DYING.update(var.ENTRANCED - voted_along - var.DEAD) # can be the empty set
|
||||
|
||||
elif var.ROLES["succubus"] & var.NO_LYNCH: # any succubus abstained
|
||||
var.ENTRANCED_DYING.update(var.ENTRANCED - var.NO_LYNCH - var.DEAD)
|
||||
|
||||
if var.ROLE_REVEAL in ("on", "team"):
|
||||
rrole = get_reveal_role(votee)
|
||||
an = "n" if rrole.startswith(("a", "e", "i", "o", "u")) else ""
|
||||
@ -1954,38 +1928,6 @@ def show_votes(cli, nick, chan, rest):
|
||||
|
||||
reply(cli, nick, chan, the_message)
|
||||
|
||||
# TODO: need to generalize this logic (as well as the logic in chk_win_conditions)
|
||||
# once refactored, it should be split off into individual role files
|
||||
def chk_traitor(cli):
|
||||
realwolves = var.WOLF_ROLES - {"wolf cub"}
|
||||
if len(list_players(realwolves)) > 0:
|
||||
return # actual wolves still alive
|
||||
|
||||
wcl = copy.copy(var.ROLES["wolf cub"])
|
||||
ttl = copy.copy(var.ROLES["traitor"])
|
||||
|
||||
event = Event("chk_traitor", {})
|
||||
if event.dispatch(cli, var, wcl, ttl):
|
||||
for wc in wcl:
|
||||
var.ROLES["wolf"].add(wc)
|
||||
var.ROLES["wolf cub"].remove(wc)
|
||||
var.FINAL_ROLES[wc] = "wolf"
|
||||
pm(cli, wc, messages["cub_grow_up"])
|
||||
debuglog(wc, "(wolf cub) GROW UP")
|
||||
|
||||
if len(var.ROLES["wolf"]) == 0:
|
||||
for tt in ttl:
|
||||
var.ROLES["wolf"].add(tt)
|
||||
var.ROLES["traitor"].remove(tt)
|
||||
var.FINAL_ROLES[tt] = "wolf"
|
||||
var.ROLES["cursed villager"].discard(tt)
|
||||
pm(cli, tt, messages["traitor_turn"])
|
||||
debuglog(tt, "(traitor) TURNING")
|
||||
|
||||
if len(var.ROLES["wolf"]) > 0:
|
||||
var.TRAITOR_TURNED = True
|
||||
cli.msg(botconfig.CHANNEL, messages["traitor_turn_channel"])
|
||||
|
||||
def stop_game(cli, winner="", abort=False, additional_winners=None, log=True):
|
||||
chan = botconfig.CHANNEL
|
||||
if abort:
|
||||
@ -2131,8 +2073,6 @@ def stop_game(cli, winner="", abort=False, additional_winners=None, log=True):
|
||||
pentry["templates"] = pltp[plr]
|
||||
if splr in var.LOVERS:
|
||||
pentry["special"].append("lover")
|
||||
if splr in var.ENTRANCED:
|
||||
pentry["special"].append("entranced")
|
||||
|
||||
won = False
|
||||
iwon = False
|
||||
@ -2152,16 +2092,13 @@ def stop_game(cli, winner="", abort=False, additional_winners=None, log=True):
|
||||
# determine if this player's team won
|
||||
if rol in var.TRUE_NEUTRAL_ROLES:
|
||||
# most true neutral roles never have a team win, only individual wins. Exceptions to that are here
|
||||
teams = {"monster":"monsters", "piper":"pipers", "succubus":"succubi", "demoniac":"demoniacs"}
|
||||
teams = {"monster":"monsters", "piper":"pipers", "demoniac":"demoniacs"}
|
||||
if rol in teams and winner == teams[rol]:
|
||||
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:
|
||||
won = True
|
||||
elif winner != "succubi" and splr in var.ENTRANCED:
|
||||
# entranced players can't win with villager or wolf teams
|
||||
won = False
|
||||
|
||||
if pentry["dced"]:
|
||||
# You get NOTHING! You LOSE! Good DAY, sir!
|
||||
@ -2179,7 +2116,7 @@ def stop_game(cli, winner="", abort=False, additional_winners=None, log=True):
|
||||
if lvr in plrl:
|
||||
lvrrol = plrl[lvr]
|
||||
|
||||
if not winner.startswith("@") and winner not in ("monsters", "demoniacs", "pipers"):
|
||||
if not winner.startswith("@") and singular(winner) not in var.WIN_STEALER_ROLES:
|
||||
iwon = True
|
||||
break
|
||||
elif winner.startswith("@") and winner == "@" + lvr and var.LOVER_WINS_WITH_FOOL:
|
||||
@ -2202,12 +2139,10 @@ def stop_game(cli, winner="", abort=False, additional_winners=None, log=True):
|
||||
iwon = True
|
||||
elif rol == "clone":
|
||||
# this means they ended game while being clone and not some other role
|
||||
if splr in survived and not winner.startswith("@") and winner not in ("monsters", "demoniacs", "pipers"):
|
||||
if splr in survived and not winner.startswith("@") and singular(winner) not in var.WIN_STEALER_ROLES:
|
||||
iwon = True
|
||||
elif rol == "jester" and splr in var.JESTERS:
|
||||
iwon = True
|
||||
elif winner == "succubi" and splr in var.ENTRANCED | var.ROLES["succubus"]:
|
||||
iwon = True
|
||||
elif not iwon:
|
||||
iwon = won and splr in survived # survived, team won = individual win
|
||||
|
||||
@ -2301,12 +2236,25 @@ def chk_win(cli, end_game=True, winner=None):
|
||||
var.ADMIN_TO_PING = None
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
if var.PHASE not in var.GAME_PHASES:
|
||||
return False #some other thread already ended game probably
|
||||
|
||||
return chk_win_conditions(cli, var.ROLES, end_game, winner)
|
||||
|
||||
def chk_win_conditions(cli, rolemap, end_game=True, winner=None):
|
||||
"""Internal handler for the chk_win function."""
|
||||
chan = botconfig.CHANNEL
|
||||
with var.GRAVEYARD_LOCK:
|
||||
if var.PHASE not in ("day", "night"):
|
||||
return False #some other thread already ended game probably
|
||||
if var.PHASE == "day":
|
||||
pl = set(list_players()) - (var.WOUNDED | var.CONSECRATING)
|
||||
evt = Event("get_voters", {"voters": pl})
|
||||
evt.dispatch(cli, var)
|
||||
pl = evt.data["voters"]
|
||||
lpl = len(pl)
|
||||
else:
|
||||
pl = set(list_players(rolemap=rolemap))
|
||||
lpl = len(pl)
|
||||
|
||||
if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES:
|
||||
if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF:
|
||||
@ -2315,32 +2263,16 @@ def chk_win(cli, end_game=True, winner=None):
|
||||
wcroles = var.WOLF_ROLES | {"traitor"}
|
||||
else:
|
||||
wcroles = var.WOLFCHAT_ROLES
|
||||
wolves = set(list_players(wcroles))
|
||||
lwolves = len(wolves)
|
||||
lcubs = len(var.ROLES.get("wolf cub", ()))
|
||||
lrealwolves = len(list_players(var.WOLF_ROLES - {"wolf cub"}))
|
||||
lmonsters = len(var.ROLES.get("monster", ()))
|
||||
ldemoniacs = len(var.ROLES.get("demoniac", ()))
|
||||
ltraitors = len(var.ROLES.get("traitor", ()))
|
||||
lpipers = len(var.ROLES.get("piper", ()))
|
||||
lsuccubi = len(var.ROLES.get("succubus", ()))
|
||||
lentranced = len(var.ENTRANCED - var.DEAD)
|
||||
|
||||
if var.PHASE == "day":
|
||||
pl = set(list_players()) - (var.WOUNDED | var.CONSECRATING)
|
||||
evt = Event("get_voters", {"voters": pl})
|
||||
evt.dispatch(cli, var)
|
||||
pl = evt.data["voters"]
|
||||
wolves = set(list_players(wcroles, rolemap=rolemap))
|
||||
lwolves = len(wolves & pl)
|
||||
lcubs = len(rolemap.get("wolf cub", ()))
|
||||
lrealwolves = len(list_players(var.WOLF_ROLES - {"wolf cub"}, rolemap=rolemap))
|
||||
lmonsters = len(rolemap.get("monster", ()))
|
||||
ldemoniacs = len(rolemap.get("demoniac", ()))
|
||||
ltraitors = len(rolemap.get("traitor", ()))
|
||||
lpipers = len(rolemap.get("piper", ()))
|
||||
|
||||
lpl = len(pl)
|
||||
lwolves = len(wolves & pl)
|
||||
|
||||
return chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs, ltraitors, lpipers, lsuccubi, lentranced, cli, end_game, winner)
|
||||
|
||||
def chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs, ltraitors, lpipers, lsuccubi, lentranced, cli, end_game=True, winner=None):
|
||||
"""Internal handler for the chk_win function."""
|
||||
chan = botconfig.CHANNEL
|
||||
with var.GRAVEYARD_LOCK:
|
||||
message = ""
|
||||
# fool won, chk_win was called from !lynch
|
||||
if winner and winner.startswith("@"):
|
||||
@ -2349,9 +2281,6 @@ def chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs,
|
||||
message = messages["no_win"]
|
||||
# still want people like jesters, dullahans, etc. to get wins if they fulfilled their win conds
|
||||
winner = "no_team_wins"
|
||||
elif var.PHASE == "day" and lpl - lsuccubi == lentranced:
|
||||
winner = "succubi"
|
||||
message = messages["succubus_win"].format(plural("succubus", lsuccubi), plural("has", lsuccubi), plural("master's", lsuccubi))
|
||||
elif var.PHASE == "day" and lpipers and len(list_players()) - lpipers == len(var.CHARMED - var.ROLES["piper"]):
|
||||
winner = "pipers"
|
||||
message = messages["piper_win"].format("s" if lpipers > 1 else "", "s" if lpipers == 1 else "")
|
||||
@ -2364,52 +2293,32 @@ def chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs,
|
||||
s = "s" if lmonsters > 1 else ""
|
||||
message = messages["monster_win"].format(s, "" if s else "s")
|
||||
winner = "monsters"
|
||||
else:
|
||||
message = messages["villager_win"]
|
||||
winner = "villagers"
|
||||
elif lwolves == lpl / 2:
|
||||
if lmonsters > 0:
|
||||
s = "s" if lmonsters > 1 else ""
|
||||
message = messages["monster_wolf_win"].format(s)
|
||||
winner = "monsters"
|
||||
else:
|
||||
message = messages["wolf_win"]
|
||||
winner = "wolves"
|
||||
elif lwolves > lpl / 2:
|
||||
if lmonsters > 0:
|
||||
s = "s" if lmonsters > 1 else ""
|
||||
message = messages["monster_wolf_win"].format(s)
|
||||
winner = "monsters"
|
||||
else:
|
||||
message = messages["wolf_win"]
|
||||
winner = "wolves"
|
||||
elif lrealwolves == 0:
|
||||
chk_traitor(cli)
|
||||
# update variables for recursive call (this shouldn't happen when checking 'random' role attribution, where it would probably fail)
|
||||
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"}
|
||||
else:
|
||||
wcroles = var.WOLFCHAT_ROLES
|
||||
wolves = set(list_players(wcroles))
|
||||
lwolves = len(wolves)
|
||||
lcubs = len(var.ROLES.get("wolf cub", ()))
|
||||
lrealwolves = len(list_players(var.WOLF_ROLES - {"wolf cub"}))
|
||||
ltraitors = len(var.ROLES.get("traitor", ()))
|
||||
if var.PHASE == "day":
|
||||
pl = set(list_players()) - (var.WOUNDED | var.CONSECRATING)
|
||||
evt = Event("get_voters", {"voters": pl})
|
||||
evt.dispatch(cli, var)
|
||||
pl = evt.data["voters"]
|
||||
|
||||
lpl = len(pl)
|
||||
lwolves = len(wolves & pl)
|
||||
return chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs, ltraitors, lpipers, lsuccubi, lentranced, cli, end_game)
|
||||
|
||||
# Priorities:
|
||||
# 0 = fool, other roles that end game immediately
|
||||
# 1 = things that could short-circuit game ending, such as cub growing up or traitor turning
|
||||
# Such events should also set stop_processing and prevent_default to True to force a re-calcuation
|
||||
# 2 = win stealers not dependent on winners, such as succubus
|
||||
# Events in priority 3 and 4 should check if a winner was already set and short-circuit if so
|
||||
# it is NOT recommended that events in priorities 0 and 2 set stop_processing to True, as doing so
|
||||
# will prevent gamemode-specific win conditions from happening
|
||||
# 3 = normal roles
|
||||
# 4 = win stealers dependent on who won, such as demoniac and monster
|
||||
# (monster's message changes based on who would have otherwise won)
|
||||
# 5 = gamemode-specific win conditions
|
||||
event = Event("chk_win", {"winner": winner, "message": message, "additional_winners": None})
|
||||
event.dispatch(var, lpl, lwolves, lrealwolves)
|
||||
if not event.dispatch(cli, var, rolemap, lpl, lwolves, lrealwolves):
|
||||
return chk_win_conditions(cli, rolemap, end_game, winner)
|
||||
winner = event.data["winner"]
|
||||
message = event.data["message"]
|
||||
|
||||
@ -2417,34 +2326,7 @@ def chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs,
|
||||
return False
|
||||
|
||||
if end_game:
|
||||
if event.data["additional_winners"] is None:
|
||||
players = []
|
||||
else:
|
||||
players = ["{0} ({1})".format(x, get_role(x)) for x in event.data["additional_winners"]]
|
||||
if winner == "monsters":
|
||||
for plr in var.ROLES["monster"]:
|
||||
players.append("{0} ({1})".format(plr, get_role(plr)))
|
||||
elif winner == "demoniacs":
|
||||
for plr in var.ROLES["demoniac"]:
|
||||
players.append("{0} ({1})".format(plr, get_role(plr)))
|
||||
elif winner == "wolves":
|
||||
for plr in list_players(var.WOLFTEAM_ROLES):
|
||||
players.append("{0} ({1})".format(plr, get_role(plr)))
|
||||
elif winner == "villagers":
|
||||
# There is a regression issue in the 3.3 collections module where OrderedDict.keys is not set-like
|
||||
# this was fixed in later releases, and since development and main instance are on 3.4 or 3.5, this was not noticed
|
||||
# collections.OrderedDict being a dict subclass, dict methods all work. Thus, we can pass the instance to dict.keys and be done with it (since it's set-like)
|
||||
vroles = (role for role in var.ROLES.keys() if var.ROLES[role] and role not in (var.WOLFTEAM_ROLES | var.TRUE_NEUTRAL_ROLES | dict.keys(var.TEMPLATE_RESTRICTIONS)))
|
||||
for plr in list_players(vroles):
|
||||
players.append("{0} ({1})".format(plr, get_role(plr)))
|
||||
elif winner == "pipers":
|
||||
for plr in var.ROLES["piper"]:
|
||||
players.append("{0} ({1})".format(plr, get_role(plr)))
|
||||
elif winner == "succubi":
|
||||
for plr in var.ROLES["succubus"] | var.ENTRANCED:
|
||||
players.append("{0} ({1})".format(plr, get_role(plr)))
|
||||
debuglog("WIN:", winner)
|
||||
debuglog("PLAYERS:", ", ".join(players))
|
||||
cli.msg(chan, message)
|
||||
stop_game(cli, winner, additional_winners=event.data["additional_winners"])
|
||||
return True
|
||||
@ -2826,11 +2708,6 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death
|
||||
del x[k]
|
||||
if nick in var.DISCONNECTED:
|
||||
del var.DISCONNECTED[nick]
|
||||
if nickrole == "succubus" and not var.ROLES["succubus"]:
|
||||
while var.ENTRANCED:
|
||||
entranced = var.ENTRANCED.pop()
|
||||
pm(cli, entranced, messages["entranced_revert_win"])
|
||||
var.ENTRANCED_DYING.clear() # for good measure
|
||||
if var.PHASE == "night":
|
||||
# remove players from night variables
|
||||
# the dicts are handled above, these are the lists of who has acted which is used to determine whether night should end
|
||||
@ -3093,9 +2970,6 @@ def rename_player(var, user, prefix):
|
||||
nick = user.nick
|
||||
|
||||
if var.PHASE in var.GAME_PHASES:
|
||||
if prefix in var.ENTRANCED: # need to update this after death, too
|
||||
var.ENTRANCED.remove(prefix)
|
||||
var.ENTRANCED.add(nick)
|
||||
if prefix in var.SPECTATING_WOLFCHAT:
|
||||
var.SPECTATING_WOLFCHAT.remove(prefix)
|
||||
var.SPECTATING_WOLFCHAT.add(nick)
|
||||
@ -3199,7 +3073,7 @@ def rename_player(var, user, prefix):
|
||||
var.JESTERS, var.AMNESIACS, var.LYCANTHROPES, var.LUCKY, var.DISEASED,
|
||||
var.MISDIRECTED, var.EXCHANGED, var.IMMUNIZED, var.CURED_LYCANS,
|
||||
var.ALPHA_WOLVES, var.CURSED, var.CHARMERS, var.CHARMED, var.TOBECHARMED,
|
||||
var.PRIESTS, var.CONSECRATING, var.ENTRANCED_DYING, var.DYING):
|
||||
var.PRIESTS, var.CONSECRATING, var.DYING):
|
||||
if prefix in setvar:
|
||||
setvar.remove(prefix)
|
||||
setvar.add(nick)
|
||||
@ -3450,7 +3324,6 @@ def begin_day(cli):
|
||||
var.LUCKY = set()
|
||||
var.DISEASED = set()
|
||||
var.MISDIRECTED = set()
|
||||
var.ENTRANCED_DYING = set()
|
||||
var.DYING = set()
|
||||
|
||||
msg = messages["villagers_lynch"].format(botconfig.CMD_CHAR, len(list_players()) // 2 + 1)
|
||||
@ -3643,9 +3516,6 @@ def transition_day(cli, gameid=0):
|
||||
bitten = evt.data["bitten"]
|
||||
numkills = evt.data["numkills"]
|
||||
|
||||
for v in var.ENTRANCED_DYING:
|
||||
var.DYING.add(v)
|
||||
|
||||
for player in var.DYING:
|
||||
victims.append(player)
|
||||
onlybywolves.discard(player)
|
||||
@ -3802,7 +3672,7 @@ def transition_day(cli, gameid=0):
|
||||
for victim in vlist:
|
||||
if not revt.dispatch(cli, var, victim):
|
||||
continue
|
||||
if victim in var.ROLES["harlot"] | var.ROLES["succubus"] and var.HVISITED.get(victim) and victim not in revt.data["dead"] and victim in revt.data["onlybywolves"]:
|
||||
if victim in var.ROLES["harlot"] and var.HVISITED.get(victim) and victim not in revt.data["dead"] and victim in revt.data["onlybywolves"]:
|
||||
# alpha wolf can bite a harlot visiting another wolf, don't play a message in that case
|
||||
# kept as a nested if so that the other victim logic does not run
|
||||
if victim not in revt.data["bitten"]:
|
||||
@ -3863,6 +3733,17 @@ def transition_day(cli, gameid=0):
|
||||
out = out.strip() # remove surrounding whitespace
|
||||
revt.data["message"].append(out)
|
||||
|
||||
# Priorities:
|
||||
# 1 = harlot/succubus visiting victim
|
||||
# 2 = determining whether or not we should print the "no victims" message
|
||||
# 3 = harlot visiting wolf
|
||||
# 4 = gunner shooting wolf
|
||||
# 5 = wolves killing diseased, wolves stealing gun
|
||||
# 10 = alpha wolf bite
|
||||
# Note that changing the "novictmsg" data item only makes sense for priority 1 events,
|
||||
# as after that point the message was already added. Events that could kill more people
|
||||
# should do so before priority 10. Events that require everyone that can be killed to
|
||||
# be listed as dead should be priority 10 or later.
|
||||
revt2 = Event("transition_day_resolve_end", {
|
||||
"message": revt.data["message"],
|
||||
"novictmsg": revt.data["novictmsg"],
|
||||
@ -4018,7 +3899,7 @@ def chk_nightdone(cli):
|
||||
actedcount = sum(map(len, (var.HVISITED, var.PASSED, var.OBSERVED,
|
||||
var.HEXED, var.CURSED, var.CHARMERS)))
|
||||
|
||||
nightroles = get_roles("harlot", "succubus", "sorcerer", "hag", "warlock", "werecrow", "piper", "prophet")
|
||||
nightroles = get_roles("harlot", "sorcerer", "hag", "warlock", "werecrow", "piper", "prophet")
|
||||
|
||||
for nick, info in var.PRAYED.items():
|
||||
if info[0] > 0:
|
||||
@ -4475,6 +4356,7 @@ def shoot(var, wrapper, message):
|
||||
else:
|
||||
chances = var.GUN_CHANCES
|
||||
|
||||
# TODO: make this into an event once we split off gunner
|
||||
if victim in var.ROLES["succubus"]:
|
||||
chances = chances[:3] + (0,)
|
||||
|
||||
@ -4527,8 +4409,9 @@ def shoot(var, wrapper, message):
|
||||
if not del_player(wrapper.source.client, wrapper.source.nick, killer_role="villager"): # blame explosion on villager's shoddy gun construction or something
|
||||
return # Someone won.
|
||||
|
||||
def is_safe(nick, victim): # helper function
|
||||
return nick in var.ENTRANCED and victim in var.ROLES["succubus"]
|
||||
def is_safe(nick, victim): # replace calls to this with targeted_command event when splitting roles
|
||||
from src.roles import succubus
|
||||
return nick in succubus.ENTRANCED and victim in var.ROLES["succubus"]
|
||||
|
||||
@cmd("bless", chan=False, pm=True, playing=True, silenced=True, phases=("day",), roles=("priest",))
|
||||
def bless(cli, nick, chan, rest):
|
||||
@ -4740,16 +4623,13 @@ def pray(cli, nick, chan, rest):
|
||||
|
||||
chk_nightdone(cli)
|
||||
|
||||
@cmd("visit", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("harlot", "succubus"))
|
||||
@cmd("visit", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("harlot",))
|
||||
def hvisit(cli, nick, chan, rest):
|
||||
"""Visit a player. You will die if you visit a wolf or a target of the wolves."""
|
||||
role = get_role(nick)
|
||||
|
||||
if var.HVISITED.get(nick):
|
||||
if role == "harlot":
|
||||
pm(cli, nick, messages["harlot_already_visited"].format(var.HVISITED[nick]))
|
||||
else:
|
||||
pm(cli, nick, messages["succubus_already_visited"].format(var.HVISITED[nick]))
|
||||
pm(cli, nick, messages["harlot_already_visited"].format(var.HVISITED[nick]))
|
||||
return
|
||||
victim = get_victim(cli, nick, re.split(" +",rest)[0], False, True)
|
||||
if not victim:
|
||||
@ -4760,64 +4640,18 @@ def hvisit(cli, nick, chan, rest):
|
||||
return
|
||||
else:
|
||||
victim = choose_target(nick, victim)
|
||||
if role != "succubus" and check_exchange(cli, nick, victim):
|
||||
if check_exchange(cli, nick, victim):
|
||||
return
|
||||
var.HVISITED[nick] = victim
|
||||
if role == "harlot":
|
||||
pm(cli, nick, messages["harlot_success"].format(victim))
|
||||
if nick != victim: #prevent luck/misdirection totem weirdness
|
||||
pm(cli, victim, messages["harlot_success"].format(nick))
|
||||
if get_role(victim) == "succubus":
|
||||
pm(cli, nick, messages["notify_succubus_target"].format(victim))
|
||||
pm(cli, victim, messages["succubus_harlot_success"].format(nick))
|
||||
var.ENTRANCED.add(nick)
|
||||
else:
|
||||
if get_role(victim) != "succubus":
|
||||
var.ENTRANCED.add(victim)
|
||||
pm(cli, nick, messages["succubus_target_success"].format(victim))
|
||||
if nick != victim:
|
||||
pm(cli, victim, (messages["notify_succubus_target"]).format(nick))
|
||||
|
||||
if var.TARGETED.get(victim) in var.ROLES["succubus"]:
|
||||
msg = messages["no_target_succubus"].format(var.TARGETED[victim])
|
||||
del var.TARGETED[victim]
|
||||
if victim in var.ROLES["village drunk"]:
|
||||
target = random.choice(list(set(list_players()) - var.ROLES["succubus"] - {victim}))
|
||||
msg += messages["drunk_target"].format(target)
|
||||
var.TARGETED[victim] = target
|
||||
pm(cli, victim, nick)
|
||||
# temp hack, will do something better once succubus is split off
|
||||
from src.roles import wolf, hunter, dullahan, vigilante, shaman
|
||||
if (shaman.SHAMANS.get(victim, (None, None))[1] in var.ROLES["succubus"] and
|
||||
(get_role(victim) == "crazed shaman" or shaman.TOTEMS[victim] not in var.BENEFICIAL_TOTEMS)):
|
||||
pm(cli, victim, messages["retract_totem_succubus"].format(shaman.SHAMANS[victim]))
|
||||
del shaman.SHAMANS[victim]
|
||||
if victim in var.HEXED and var.LASTHEXED[victim] in var.ROLES["succubus"]:
|
||||
pm(cli, victim, messages["retract_hex_succubus"].format(var.LASTHEXED[victim]))
|
||||
var.TOBESILENCED.remove(nick)
|
||||
var.HEXED.remove(victim)
|
||||
del var.LASTHEXED[victim]
|
||||
if set(wolf.KILLS.get(victim, ())) & var.ROLES["succubus"]:
|
||||
for s in var.ROLES["succubus"]:
|
||||
if s in wolf.KILLS[victim]:
|
||||
pm(cli, victim, messages["no_kill_succubus"].format(nick))
|
||||
wolf.KILLS[victim].remove(s)
|
||||
if not wolf.KILLS[victim]:
|
||||
del wolf.KILLS[victim]
|
||||
if hunter.KILLS.get(victim) in var.ROLES["succubus"]:
|
||||
pm(cli, victim, messages["no_kill_succubus"].format(hunter.KILLS[victim]))
|
||||
del hunter.KILLS[victim]
|
||||
hunter.HUNTERS.discard(victim)
|
||||
if vigilante.KILLS.get(victim) in var.ROLES["succubus"]:
|
||||
pm(cli, victim, messages["no_kill_succubus"].format(vigilante.KILLS[victim]))
|
||||
del vigilante.KILLS[victim]
|
||||
if var.BITE_PREFERENCES.get(victim) in var.ROLES["succubus"]:
|
||||
pm(cli, victim, messages["no_kill_succubus"].format(var.BITE_PREFERENCES[victim]))
|
||||
del var.BITE_PREFERENCES[victim]
|
||||
if dullahan.TARGETS.get(victim, set()) & var.ROLES["succubus"]:
|
||||
pm(cli, victim, messages["dullahan_no_kill_succubus"])
|
||||
if dullahan.KILLS.get(victim) in var.ROLES["succubus"]:
|
||||
del dullahan.KILLS[victim]
|
||||
pm(cli, nick, messages["harlot_success"].format(victim))
|
||||
if nick != victim: #prevent luck/misdirection totem weirdness
|
||||
pm(cli, victim, messages["harlot_success"].format(nick))
|
||||
# TODO: turn this into an event once harlot is split off
|
||||
if get_role(victim) == "succubus":
|
||||
pm(cli, nick, messages["notify_succubus_target"].format(victim))
|
||||
pm(cli, victim, messages["succubus_harlot_success"].format(nick))
|
||||
from src.roles import succubus
|
||||
succubus.ENTRANCED.add(nick)
|
||||
|
||||
debuglog("{0} ({1}) VISIT: {2} ({3})".format(nick, role, victim, get_role(victim)))
|
||||
chk_nightdone(cli)
|
||||
@ -4896,7 +4730,7 @@ def bite_cmd(cli, nick, chan, rest):
|
||||
chk_nightdone(cli)
|
||||
|
||||
@cmd("pass", chan=False, pm=True, playing=True, phases=("night",),
|
||||
roles=("harlot", "turncoat", "warlock", "succubus"))
|
||||
roles=("harlot", "turncoat", "warlock"))
|
||||
def pass_cmd(cli, nick, chan, rest): # XXX: hvisit (3 functions above this one) also needs updating alongside this
|
||||
"""Decline to use your special power for that night."""
|
||||
nickrole = get_role(nick)
|
||||
@ -4915,12 +4749,6 @@ def pass_cmd(cli, nick, chan, rest): # XXX: hvisit (3 functions above this one)
|
||||
return
|
||||
var.HVISITED[nick] = None
|
||||
pm(cli, nick, messages["no_visit"])
|
||||
elif nickrole == "succubus":
|
||||
if var.HVISITED.get(nick):
|
||||
pm(cli, nick, messages["succubus_already_visited"].format(var.HVISITED[nick]))
|
||||
return
|
||||
var.HVISITED[nick] = None
|
||||
pm(cli, nick, messages["succubus_pass"])
|
||||
elif nickrole == "turncoat":
|
||||
if var.TURNCOATS[nick][1] == var.NIGHT_COUNT:
|
||||
# theoretically passing would revert them to how they were before, but
|
||||
@ -5242,25 +5070,12 @@ def charm(cli, nick, chan, rest):
|
||||
|
||||
@event_listener("targeted_command", priority=9)
|
||||
def on_targeted_command(evt, cli, var, cmd, actor, orig_target, tags):
|
||||
# TODO: move beneficial command check into roles/succubus.py
|
||||
if "beneficial" not in tags and is_safe(actor, evt.data["target"]):
|
||||
try:
|
||||
what = evt.params.action
|
||||
except AttributeError:
|
||||
what = cmd
|
||||
pm(cli, actor, messages["no_acting_on_succubus"].format(what))
|
||||
evt.stop_processing = True
|
||||
evt.prevent_default = True
|
||||
return
|
||||
|
||||
if evt.data["misdirection"]:
|
||||
evt.data["target"] = choose_target(actor, evt.data["target"])
|
||||
|
||||
if evt.data["exchange"] and check_exchange(cli, actor, evt.data["target"]):
|
||||
evt.stop_processing = True
|
||||
evt.prevent_default = True
|
||||
return
|
||||
|
||||
|
||||
@hook("featurelist") # For multiple targets with PRIVMSG
|
||||
def getfeatures(cli, nick, *rest):
|
||||
@ -5475,8 +5290,10 @@ def transition_night(cli):
|
||||
var.ROLES[amnrole].add(amn)
|
||||
var.AMNESIACS.add(amn)
|
||||
var.FINAL_ROLES[amn] = amnrole
|
||||
if amnrole == "succubus" and amn in var.ENTRANCED:
|
||||
var.ENTRANCED.remove(amn)
|
||||
# TODO: turn into event when amnesiac is split
|
||||
from src.roles import succubus
|
||||
if amnrole == "succubus" and amn in succubus.ENTRANCED:
|
||||
succubus.ENTRANCED.remove(amn)
|
||||
pm(cli, amn, messages["no_longer_entranced"])
|
||||
if var.FIRST_NIGHT: # we don't need to tell them twice if they remember right away
|
||||
continue
|
||||
@ -5540,16 +5357,6 @@ def transition_night(cli):
|
||||
else:
|
||||
pm(cli, drunk, messages["drunk_simple"])
|
||||
|
||||
for succubus in var.ROLES["succubus"]:
|
||||
pl = ps[:]
|
||||
random.shuffle(pl)
|
||||
pl.remove(succubus)
|
||||
if succubus in var.PLAYERS and not is_user_simple(succubus):
|
||||
pm(cli, succubus, messages["succubus_notify"])
|
||||
else:
|
||||
pm(cli, succubus, messages["succubus_simple"])
|
||||
pm(cli, succubus, "Players: " + ", ".join(("{0} ({1})".format(x, get_role(x)) if x in var.ROLES["succubus"] else x for x in pl)))
|
||||
|
||||
for ms in var.ROLES["mad scientist"]:
|
||||
pl = ps[:]
|
||||
for index, user in enumerate(var.ALL_PLAYERS):
|
||||
@ -5982,8 +5789,6 @@ def start(cli, nick, chan, forced = False, restart = ""):
|
||||
var.EXTRA_WOLVES = 0
|
||||
var.PRIESTS = set()
|
||||
var.CONSECRATING = set()
|
||||
var.ENTRANCED = set()
|
||||
var.ENTRANCED_DYING = set()
|
||||
var.DYING = set()
|
||||
var.PRAYED = {}
|
||||
|
||||
@ -7327,12 +7132,6 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS:
|
||||
if var.IMMUNIZED:
|
||||
output.append("\u0002immunized\u0002: {0}".format(", ".join(var.IMMUNIZED)))
|
||||
|
||||
if var.ENTRANCED:
|
||||
output.append("\u0002entranced players\u0002: {0}".format(", ".join(var.ENTRANCED)))
|
||||
|
||||
if var.ENTRANCED_DYING:
|
||||
output.append("\u0002dying entranced players\u0002: {0}".format(", ".join(var.ENTRANCED_DYING)))
|
||||
|
||||
# get charmed players
|
||||
if var.CHARMED | var.TOBECHARMED:
|
||||
output.append("\u0002charmed players\u0002: {0}".format(", ".join(var.CHARMED | var.TOBECHARMED)))
|
||||
|
Loading…
Reference in New Issue
Block a user