From 01dfdc7ac4267e697b70b296c44dbb67185489af Mon Sep 17 00:00:00 2001 From: skizzerz Date: Tue, 13 Sep 2016 16:35:54 -0500 Subject: [PATCH] Split blessed and cursed villager --- src/roles/angel.py | 18 +++++++ src/roles/blessed.py | 91 ++++++++++++++++++++++++++++++++ src/roles/cursed.py | 25 +++++++++ src/roles/dullahan.py | 53 +++++++++---------- src/roles/seer.py | 2 +- src/roles/shaman.py | 38 +++++++++++--- src/roles/wolf.py | 81 ++++++++++++++++------------- src/wolfgame.py | 117 +++++++++++++++++------------------------- 8 files changed, 281 insertions(+), 144 deletions(-) create mode 100644 src/roles/blessed.py create mode 100644 src/roles/cursed.py diff --git a/src/roles/angel.py b/src/roles/angel.py index fa7601b..6da32db 100644 --- a/src/roles/angel.py +++ b/src/roles/angel.py @@ -241,6 +241,24 @@ def on_transition_night_end(evt, cli, var): pm(cli, gangel, messages["guardian_simple"]) # !simple pm(cli, gangel, "Players: " + ", ".join(pl)) +@event_listener("assassinate") +def on_assassinate(evt, cli, var, nick, target, prot): + if prot == "angel" and var.GAMEPHASE == "night": + var.ACTIVE_PROTECTIONS[target].remove("angel") + evt.prevent_default = True + evt.stop_propagation = True + cli.msg(botconfig.CHANNEL, messages[evt.params.message_prefix + "angel"].format(nick, target)) + elif prot == "bodyguard": + var.ACTIVE_PROTECTIONS[target].remove("bodyguard") + evt.prevent_default = True + evt.stop_propagation = True + for bg in var.ROLES["bodyguard"]: + if GUARDED.get(bg) == target: + cli.msg(botconfig.CHANNEL, messages[evt.params.message_prefix + "bodyguard"].format(nick, target, bg)) + evt.params.del_player(cli, bg, True, end_game=False, killer_role=nickrole, deadlist=evt.params.deadlist, original=evt.params.original, ismain=False) + evt.data["pl"] = evt.params.refresh_pl(pl) + break + @event_listener("begin_day") def on_begin_day(evt, cli, var): PASSED.clear() diff --git a/src/roles/blessed.py b/src/roles/blessed.py new file mode 100644 index 0000000..111e512 --- /dev/null +++ b/src/roles/blessed.py @@ -0,0 +1,91 @@ +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 debuglog, errlog, plog +from src.decorators import cmd, event_listener +from src.messages import messages +from src.events import Event + +# TODO: some additional stuff with blessed villager has not been split yet, +# notably the interactions with assassin and mad scientist, need to split those out too +# as part of splitting assassin/MS (new events will be required) + +@event_listener("transition_day", priority=4.3) +def on_transition_day(evt, cli, var): + pl = list_players() + vs = set(evt.data["victims"]) + for v in pl: + if v in vs: + if v in var.DYING: + continue + if v in var.ROLES["blessed villager"]: + evt.data["numkills"][v] -= 1 + if evt.data["numkills"][v] >= 0: + evt.data["killers"][v].pop(0) + if evt.data["numkills"][v] <= 0 and v not in evt.data["protected"]: + evt.data["protected"][v] = "blessing" + elif evt.data["numkills"][v] <= 0: + var.ACTIVE_PROTECTIONS[v].append("blessing") + elif v in var.ROLES["blessed villager"]: + var.ACTIVE_PROTECTIONS[v].append("blessing") + +@event_listener("transition_day_resolve", priority=2) +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"]: + return + # END checks to remove + + if evt.data["protected"].get(victim) == "blessing": + # don't play any special message for a blessed target, this means in a game with priest and monster it's not really possible + # for wolves to tell which is which. May want to change that in the future to be more obvious to wolves since there's not really + # any good reason to hide that info from them. In any case, we don't want to say the blessed person was attacked to the channel + evt.stop_propagation = True + evt.prevent_default = True + +@event_listener("transition_night_end", priority=5) +def on_transition_night_end(evt, cli, var): + if var.FIRST_NIGHT or var.ALWAYS_PM_ROLE: + for blessed in var.ROLES["blessed villager"]: + if blessed in var.PLAYERS and not is_user_simple(blessed): + pm(cli, blessed, messages["blessed_notify"]) + else: + pm(cli, blessed, messages["blessed_simple"]) + +@event_listener("desperation_totem") +def on_desperation(evt, cli, var, votee, target, prot): + if prot == "blessing": + var.ACTIVE_PROTECTIONS[target].remove("blessing") + evt.prevent_default = True + evt.stop_propagation = True + +@event_listener("retribution_totem") +def on_retribution(evt, cli, var, victim, loser, prot): + if prot == "blessing": + var.ACTIVE_PROTECTIONS[target].remove("blessing") + evt.prevent_default = True + evt.stop_propagation = True + +@event_listener("assassinate") +def on_assassinate(evt, cli, var, nick, target, prot): + if prot == "blessing": + var.ACTIVE_PROTECTIONS[target].remove("blessing") + evt.prevent_default = True + evt.stop_propagation = True + # don't message the channel whenever a blessing blocks a kill, but *do* let the killer know so they don't try to report it as a bug + pm(cli, nick, messages["assassin_fail_blessed"].format(target)) + +@event_listener("myrole") +def on_myrole(evt, cli, var, nick): + if nick in var.ROLES["blessed villager"]: + evt.data["messages"].append(messages["blessed_simple"]) + +# vim: set sw=4 expandtab: diff --git a/src/roles/cursed.py b/src/roles/cursed.py new file mode 100644 index 0000000..aa44f15 --- /dev/null +++ b/src/roles/cursed.py @@ -0,0 +1,25 @@ +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 debuglog, errlog, plog +from src.decorators import cmd, event_listener +from src.messages import messages +from src.events import Event + +@event_listener("see") +def on_see(evt, cli, var, nick, victim): + if nick in var.ROLES["cursed villager"]: + evt.data["role"] = "wolf" + +@event_listener("wolflist") +def on_wolflist(evt, cli, var, nick, wolf): + if nick in var.ROLES["cursed villager"]: + evt.data["tags"].add("cursed") + +# vim: set sw=4 expandtab: diff --git a/src/roles/dullahan.py b/src/roles/dullahan.py index 6404313..2bb2273 100644 --- a/src/roles/dullahan.py +++ b/src/roles/dullahan.py @@ -1,7 +1,7 @@ import math import re import random -from collections import defaultdict +from collections import defaultdict, deque import src.settings as var from src.utilities import * @@ -77,35 +77,30 @@ def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers): targets = TARGETS[nick] & set(pl) if targets: target = random.choice(list(targets)) - if "totem" in var.ACTIVE_PROTECTIONS[target]: - var.ACTIVE_PROTECTIONS[target].remove("totem") - cli.msg(botconfig.CHANNEL, messages["dullahan_die_totem"].format(nick, target)) - elif "angel" in var.ACTIVE_PROTECTIONS[target]: - var.ACTIVE_PROTECTIONS[target].remove("angel") - cli.msg(botconfig.CHANNEL, messages["dullahan_die_angel"].format(nick, target)) - elif "bodyguard" in var.ACTIVE_PROTECTIONS[target]: - var.ACTIVE_PROTECTIONS[target].remove("bodyguard") - for bg in var.ROLES["bodyguard"]: - if var.GUARDED.get(bg) == target: - cli.msg(botconfig.CHANNEL, messages["dullahan_die_bodyguard"].format(nick, target, bg)) - evt.params.del_player(cli, bg, True, end_game=False, killer_role=nickrole, deadlist=evt.params.deadlist, original=evt.params.original, ismain=False) - evt.data["pl"] = evt.params.refresh_pl(pl) - break - elif "blessing" in var.ACTIVE_PROTECTIONS[target] or (var.GAMEPHASE == "day" and target in var.ROLES["blessed villager"]): - if "blessing" in var.ACTIVE_PROTECTIONS[target]: - var.ACTIVE_PROTECTIONS[target].remove("blessing") - # don't message the channel whenever a blessing blocks a kill, but *do* let the dullahan know so they don't try to report it as a bug - pm(cli, nick, messages["assassin_fail_blessed"].format(target)) + prots = deque(var.ACTIVE_PROTECTIONS[target]) + aevt = Event("assassinate", {"pl": evt.data["pl"]}, + del_player=evt.params.del_player, + deadlist=evt.params.deadlist, + original=evt.params.original, + refresh_pl=evt.params.refresh_pl, + message_prefix="dullahan_die_") + while len(prots) > 0: + # an event can read the current active protection and cancel the totem + # if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS + # so that it cannot be used again (if the protection is meant to be usable once-only) + if not aevt.dispatch(cli, var, nick, target, prots[0]): + evt.data["pl"] = aevt.data["pl"] + return + prots.popleft() + if var.ROLE_REVEAL in ("on", "team"): + role = get_reveal_role(target) + an = "n" if role.startswith(("a", "e", "i", "o", "u")) else "" + cli.msg(botconfig.CHANNEL, messages["dullahan_die_success"].format(nick, target, an, role)) else: - if var.ROLE_REVEAL in ("on", "team"): - role = get_reveal_role(target) - an = "n" if role.startswith(("a", "e", "i", "o", "u")) else "" - cli.msg(botconfig.CHANNEL, messages["dullahan_die_success"].format(nick, target, an, role)) - else: - cli.msg(botconfig.CHANNEL, messages["dullahan_die_success_noreveal"].format(nick, target)) - debuglog("{0} ({1}) DULLAHAN ASSASSINATE: {2} ({3})".format(nick, nickrole, target, get_role(target))) - evt.params.del_player(cli, target, True, end_game=False, killer_role=nickrole, deadlist=evt.params.deadlist, original=evt.params.original, ismain=False) - evt.data["pl"] = evt.params.refresh_pl(pl) + cli.msg(botconfig.CHANNEL, messages["dullahan_die_success_noreveal"].format(nick, target)) + debuglog("{0} ({1}) DULLAHAN ASSASSINATE: {2} ({3})".format(nick, nickrole, target, get_role(target))) + evt.params.del_player(cli, target, True, end_game=False, killer_role=nickrole, deadlist=evt.params.deadlist, original=evt.params.original, ismain=False) + evt.data["pl"] = evt.params.refresh_pl(pl) @event_listener("rename_player") def on_rename(evt, cli, var, prefix, nick): diff --git a/src/roles/seer.py b/src/roles/seer.py index d58a3c3..3088ec9 100644 --- a/src/roles/seer.py +++ b/src/roles/seer.py @@ -33,7 +33,7 @@ def see(cli, nick, chan, rest): victimrole = get_role(victim) if role != "augur": - if (victimrole in var.SEEN_WOLF and victimrole not in var.SEEN_DEFAULT) or victim in var.ROLES["cursed villager"]: + if (victimrole in var.SEEN_WOLF and victimrole not in var.SEEN_DEFAULT): victimrole = "wolf" elif victimrole in var.SEEN_DEFAULT: victimrole = var.DEFAULT_ROLE diff --git a/src/roles/shaman.py b/src/roles/shaman.py index 4804241..f82ecee 100644 --- a/src/roles/shaman.py +++ b/src/roles/shaman.py @@ -1,7 +1,7 @@ import re import random import itertools -from collections import defaultdict +from collections import defaultdict, deque import botconfig import src.settings as var @@ -275,11 +275,16 @@ def on_chk_decision_lynch5(evt, cli, var, voters): if votee in DESPERATION: # Also kill the very last person to vote them, unless they voted themselves last in which case nobody else dies target = voters[-1] - # TODO: instead of desperation_totem event to accomodate blessed villager, base on var.ACTIVE_PROTECTIONS - # this means that prot totem, GA, and bodyguard would have a shot to block this too - # and so that blessed villager doesn't double-dip (like if they were targeted that past night) - desp_evt = Event("desperation_totem", {}) - if target != votee and desp_evt.dispatch(cli, var, votee, target) and target not in var.ROLES["blessed villager"]: + if target != votee: + prots = deque(var.ACTIVE_PROTECTIONS[target]) + while len(prots) > 0: + # an event can read the current active protection and cancel the totem + # if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS + # so that it cannot be used again (if the protection is meant to be usable once-only) + desp_evt = Event("desperation_totem", {}) + if not desp_evt.dispatch(cli, var, votee, target, prots[0]): + return + prots.popleft() if var.ROLE_REVEAL in ("on", "team"): r1 = get_reveal_role(target) an1 = "n" if r1.startswith(("a", "e", "i", "o", "u")) else "" @@ -464,8 +469,17 @@ def on_transition_day_resolve6(evt, cli, var, victim): evt.data["message"].extend(ret_evt.data["message"]) if loser in evt.data["dead"] or victim == loser: loser = None - # TODO: when blessed is split off, roll that check into retribution_kill - if loser is not None and loser not in var.ROLES["blessed villager"]: + if loser is not None: + prots = deque(var.ACTIVE_PROTECTIONS[loser]) + while len(prots) > 0: + # an event can read the current active protection and cancel the totem + # if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS + # so that it cannot be used again (if the protection is meant to be usable once-only) + ret_evt = Event("retribution_totem", {"message": []}) + if not ret_evt.dispatch(cli, var, victim, loser, prots[0]): + evt.data["message"].extend(ret_evt.data["message"]) + return + prots.popleft() evt.data["dead"].append(loser) if var.ROLE_REVEAL in ("on", "team"): role = get_reveal_role(loser) @@ -551,6 +565,14 @@ def on_lynch(evt, cli, var, nick): pm(cli, nick, messages["totem_narcolepsy"]) evt.prevent_default = True +@event_listener("assassinate") +def on_assassinate(evt, cli, var, nick, target, prot): + if prot == "totem": + var.ACTIVE_PROTECTIONS[target].remove("totem") + evt.prevent_default = True + evt.stop_propagation = True + cli.msg(botconfig.CHANNEL, messages[evt.params.message_prefix + "totem"].format(nick, target)) + @event_listener("myrole") def on_myrole(evt, cli, var, nick): role = evt.data["role"] diff --git a/src/roles/wolf.py b/src/roles/wolf.py index d684fe3..5c9657c 100644 --- a/src/roles/wolf.py +++ b/src/roles/wolf.py @@ -230,14 +230,16 @@ def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role): prole = get_role(player) if player == nick: prole = actor_role + wevt = Event("wolflist", {"tags": set()}) + wevt.dispatch(cli, var, player, actor) + tags = " ".join(wevt.data["tags"]) if prole in wcroles: - cursed = "" - if player in var.ROLES["cursed villager"]: - cursed = "cursed " - pl[i] = "\u0002{0}\u0002 ({1}{2})".format(player, cursed, prole) + if tags: + tags += " " + pl[i] = "\u0002{0}\u0002 ({1}{2})".format(player, tags, prole) notify.append(player) - elif player in var.ROLES["cursed villager"]: - pl[i] = player + " (cursed)" + elif tags: + pl[i] = "{0} ({1})".format(player, tags) mass_privmsg(cli, notify, messages["players_exchanged_roles"].format(nick, actor)) evt.data["actor_messages"].append("Players: " + ", ".join(pl)) @@ -256,14 +258,16 @@ def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role): prole = get_role(player) if player == actor: prole = nick_role + wevt = Event("wolflist", {"tags": set()}) + wevt.dispatch(cli, var, player, nick) + tags = " ".join(wevt.data["tags"]) if prole in wcroles: - cursed = "" - if player in var.ROLES["cursed villager"]: - cursed = "cursed " - pl[i] = "\u0002{0}\u0002 ({1}{2})".format(player, cursed, prole) + if tags: + tags += " " + pl[i] = "\u0002{0}\u0002 ({1}{2})".format(player, tags, prole) notify.append(player) - elif player in var.ROLES["cursed villager"]: - pl[i] = player + " (cursed)" + elif tags: + pl[i] = "{0} ({1})".format(player, tags) mass_privmsg(cli, notify, messages["players_exchanged_roles"].format(actor, nick)) evt.data["nick_messages"].append("Players: " + ", ".join(pl)) @@ -333,34 +337,36 @@ def on_transition_night_end(evt, cli, var): talkroles = var.WOLF_ROLES | {"traitor"} for wolf in wolves: - # should make the cursed information an event that cursedvillager can then add to - # (e.g. an event to change what prefixes are sent with the role message, and a - # 2nd event to change information in parens in player list) normal_notify = wolf in var.PLAYERS and not is_user_simple(wolf) role = get_role(wolf) - cursed = "cursed " if wolf in var.ROLES["cursed villager"] and role in wcroles else "" + wevt = Event("wolflist", {"tags": set()}) + tags = "" + if role in wcroles: + wevt.dispatch(cli, var, wolf, wolf) + tags = " ".join(wevt.data["tags"]) if normal_notify: msg = "{0}_notify".format(role.replace(" ", "_")) cmsg = "cursed_" + msg - try: - if cursed: - try: - pm(cli, wolf, messages[cmsg]) - except KeyError: - pm(cli, wolf, messages[msg].format(cursed)) - else: - pm(cli, wolf, messages[msg].format(cursed)) - except KeyError: - # catchall in case we forgot something above - an = 'n' if role.startswith(("a", "e", "i", "o", "u")) else "" - pm(cli, wolf, messages["undefined_role_notify"].format(an, role)) + if "cursed" in wevt.data["tags"]: + try: + tags2 = " ".join(wevt.data["tags"] - {"cursed"}) + pm(cli, wolf, messages[cmsg].format(tags2)) + except KeyError: + pm(cli, wolf, messages[msg].format(tags)) + else: + pm(cli, wolf, messages[msg].format(tags)) if len(wolves) > 1 and wccond is not None and role in talkroles: pm(cli, wolf, messages["wolfchat_notify"].format(wccond)) else: - an = "n" if cursed == "" and role.startswith(("a", "e", "i", "o", "u")) else "" - pm(cli, wolf, messages["wolf_simple"].format(an, cursed, role)) # !simple + an = "" + if tags: + if tags.startswith(("a", "e", "i", "o", "u")): + an = "n" + elif role.startswith(("a", "e", "i", "o", "u")): + an = "n" + pm(cli, wolf, messages["wolf_simple"].format(an, tags, role)) # !simple pl = ps[:] random.shuffle(pl) @@ -368,14 +374,17 @@ def on_transition_night_end(evt, cli, var): if role in wcroles: for i, player in enumerate(pl): prole = get_role(player) + wevt.data["tags"] = set() + wevt.dispatch(cli, var, player, wolf) + tags = " ".join(wevt.data["tags"]) if prole in wcroles: - cursed = "" - if player in var.ROLES["cursed villager"]: - cursed = "cursed " - pl[i] = "\u0002{0}\u0002 ({1}{2})".format(player, cursed, prole) - elif player in var.ROLES["cursed villager"]: - pl[i] = player + " (cursed)" + if tags: + tags += " " + pl[i] = "\u0002{0}\u0002 ({1}{2})".format(player, tags, prole) + elif tags: + pl[i] = "{0} ({1})".format(player, tags) elif role == "warlock": + # warlock specifically only sees cursed if they're not in wolfchat for i, player in enumerate(pl): if player in var.ROLES["cursed villager"]: pl[i] = player + " (cursed)" diff --git a/src/wolfgame.py b/src/wolfgame.py index 16e56f7..f153fe7 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -36,7 +36,7 @@ import threading import time import traceback import urllib.request -from collections import defaultdict +from collections import defaultdict, deque from datetime import datetime, timedelta from oyoyo.parse import parse_nick @@ -1420,14 +1420,17 @@ def stats(cli, nick, chan, rest): if role in badguys: for i, player in enumerate(ps): prole = get_role(player) + wevt = Event("wolflist", {"tags": set()}) + wevt.dispatch(cli, var, player, nick) + tags = " ".join(wevt.data["tags"]) if prole in badguys: - cursed = "" - if player in var.ROLES["cursed villager"]: - cursed = "cursed " - ps[i] = "\u0002{0}\u0002 ({1}{2})".format(player, cursed, prole) - elif player in var.ROLES["cursed villager"]: - ps[i] = player + " (cursed)" + if tags: + tags += " " + ps[i] = "\u0002{0}\u0002 ({1}{2})".format(player, tags, prole) + elif tags: + ps[i] = "{0} ({1})".format(player, tags) elif role == "warlock": + # warlock not in wolfchat explicitly only sees cursed for i, player in enumerate(pl): if player in var.ROLES["cursed villager"]: ps[i] = player + " (cursed)" @@ -2823,10 +2826,12 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death random.shuffle(wolves) for i, wolf in enumerate(wolves): wolfrole = get_role(wolf) - cursed = "" - if wolf in var.ROLES["cursed villager"]: - cursed = "cursed " - wolves[i] = "\u0002{0}\u0002 ({1}{2})".format(wolf, cursed, wolfrole) + wevt = Event("wolflist", {"tags": set()}) + wevt.dispatch(cli, var, wolf, clone) + tags = " ".join(wevt.data["tags"]) + if tags: + tags += " " + wolves[i] = "\u0002{0}\u0002 ({1}{2})".format(wolf, tags, wolfrole) if len(wolves): pm(cli, clone, "Wolves: " + ", ".join(wolves)) @@ -2862,32 +2867,28 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death if nick in var.TARGETED: target = var.TARGETED[nick] del var.TARGETED[nick] - if target != None and target in pl: - # TODO: split this off into an event so that individual roles can handle it as needed - if "totem" in var.ACTIVE_PROTECTIONS[target] and nickrole != "fallen angel": - var.ACTIVE_PROTECTIONS[target].remove("totem") - message = messages["assassin_fail_totem"].format(nick, target) - cli.msg(botconfig.CHANNEL, message) - elif "angel" in var.ACTIVE_PROTECTIONS[target] and nickrole != "fallen angel": - var.ACTIVE_PROTECTIONS[target].remove("angel") - message = messages["assassin_fail_angel"].format(nick, target) - cli.msg(botconfig.CHANNEL, message) - elif "bodyguard" in var.ACTIVE_PROTECTIONS[target] and nickrole != "fallen angel": - var.ACTIVE_PROTECTIONS[target].remove("bodyguard") - from src.roles import angel - for ga in var.ROLES["bodyguard"]: - if angel.GUARDED.get(ga) == target: - message = messages["assassin_fail_bodyguard"].format(nick, target, ga) - cli.msg(botconfig.CHANNEL, message) - del_player(cli, ga, True, end_game=False, killer_role=nickrole, deadlist=deadlist, original=original, ismain=False) - pl = refresh_pl(pl) - break - elif "blessing" in var.ACTIVE_PROTECTIONS[target] or (var.GAMEPHASE == "day" and target in var.ROLES["blessed villager"]): - if "blessing" in var.ACTIVE_PROTECTIONS[target]: - var.ACTIVE_PROTECTIONS[target].remove("blessing") - # don't message the channel whenever a blessing blocks a kill, but *do* let the assassin know so they don't try to report it as a bug - pm(cli, nick, messages["assassin_fail_blessed"].format(target)) - else: + if target is not None and target in pl: + prots = deque(var.ACTIVE_PROTECTIONS[target]) + aevt = Event("assassinate", {"pl": pl}, + del_player=del_player, + deadlist=deadlist, + original=original, + refresh_pl=refresh_pl, + message_prefix="assassin_fail_") + while len(prots) > 0: + # FA bypasses all protection (TODO: split off) + # when split instead of setting prots to [] will need to stop_propagation but NOT prevent_default + if nickrole == "fallen angel": + prots = [] + break + # an event can read the current active protection and cancel the totem + # if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS + # so that it cannot be used again (if the protection is meant to be usable once-only) + if not aevt.dispatch(cli, var, nick, target, prots[0]): + pl = aevt.data["pl"] + break + prots.popleft() + if len(prots) == 0: if var.ROLE_REVEAL in ("on", "team"): role = get_reveal_role(target) an = "n" if role.startswith(("a", "e", "i", "o", "u")) else "" @@ -3691,7 +3692,6 @@ def begin_day(cli): var.LUCKY = set() var.DISEASED = set() var.MISDIRECTED = set() - var.ACTIVE_PROTECTIONS = defaultdict(list) var.ENTRANCED_DYING = set() var.DYING = set() @@ -3862,6 +3862,7 @@ def transition_day(cli, gameid=0): # 2 = non-wolf kills # 3 = fixing killers dict to have correct priority (wolf-side VG kills -> non-wolf kills -> wolf kills) # 4 = protections/fallen angel + # 4.1 = shaman, 4.2 = bodyguard/GA, 4.3 = blessed villager, 4.8 = fallen angel # 5 = alpha wolf bite, other custom events that trigger after all protection stuff is resolved # 6 = rearranging victim list (ensure bodyguard/harlot messages plays), # fixing killers dict priority again (in case step 4 or 5 added to it) @@ -3898,25 +3899,14 @@ def transition_day(cli, gameid=0): # Logic out stacked kills and protections. If we get down to 1 kill remaining that is valid and the victim is in bywolves, # we re-add them to onlybywolves to indicate that the other kill attempts were guarded against (and the wolf kill is what went through) # If protections >= kills, we keep track of which protection message to show (prot totem > GA > bodyguard > blessing) + # TODO: split out adding people back to onlybywolves as part of splitting off FA pl = list_players() for v in pl: if v in victims_set: if v in var.DYING: - continue # bypass protections - if v in var.ROLES["blessed villager"]: - numkills[v] -= 1 - if numkills[v] >= 0: - killers[v].pop(0) - if numkills[v] <= 0 and v not in protected: - protected[v] = "blessing" - elif numkills[v] <= 0: - var.ACTIVE_PROTECTIONS[v].append("blessing") + continue # dying by themselves, not killed by wolves if numkills[v] == 1 and v in bywolves: onlybywolves.add(v) - else: - # player wasn't targeted, but apply protections on them - if v in var.ROLES["blessed villager"]: - var.ACTIVE_PROTECTIONS[v].append("blessing") fallenkills = set() brokentotem = set() @@ -4145,11 +4135,6 @@ def transition_day(cli, gameid=0): if victim not in revt.data["bitten"]: revt.data["message"].append(messages["target_not_home"]) revt.data["novictmsg"] = False - elif revt.data["protected"].get(victim) == "blessing": - # don't play any special message for a blessed target, this means in a game with priest and monster it's not really possible - # for wolves to tell which is which. May want to change that in the future to be more obvious to wolves since there's not really - # any good reason to hide that info from them. In any case, we don't want to say the blessed person was attacked to the channel - continue elif (victim in var.ROLES["lycan"] or victim in var.LYCANTHROPES) and victim in revt.data["onlybywolves"] and victim not in var.IMMUNIZED: vrole = get_role(victim) if vrole not in var.WOLFCHAT_ROLES: @@ -4166,10 +4151,12 @@ def transition_day(cli, gameid=0): for i, wolf in enumerate(wolves): pm(cli, wolf, messages["lycan_wc_notification"].format(victim)) role = get_role(wolf) - cursed = "" - if wolf in var.ROLES["cursed villager"]: - cursed = "cursed " - wolves[i] = "\u0002{0}\u0002 ({1}{2})".format(wolf, cursed, role) + wevt = Event("wolflist", {"tags": set()}) + wevt.dispatch(cli, var, wolf, victim) + tags = " ".join(wevt.data["tags"]) + if tags: + tags += " " + wolves[i] = "\u0002{0}\u0002 ({1}{2})".format(wolf, tags, role) pm(cli, victim, "Wolves: " + ", ".join(wolves)) revt.data["novictmsg"] = False @@ -6052,12 +6039,6 @@ def transition_night(cli): pm(cli, minion, messages["minion_simple"]) pm(cli, minion, "Wolves: " + ", ".join(wolves)) - for blessed in var.ROLES["blessed villager"]: - if blessed in var.PLAYERS and not is_user_simple(blessed): - pm(cli, blessed, messages["blessed_notify"]) - else: - pm(cli, blessed, messages["blessed_simple"]) - for g in var.GUNNERS.keys(): if g not in ps: continue @@ -7116,10 +7097,6 @@ def myrole(cli, nick, chan, rest): if nick in var.ROLES["assassin"] and nick not in var.ROLES["amnesiac"]: pm(cli, nick, messages["assassin_role_info"].format(messages["assassin_targeting"].format(var.TARGETED[nick]) if nick in var.TARGETED else "")) - # Remind blessed villager of their role - if nick in var.ROLES["blessed villager"]: - pm(cli, nick, messages["blessed_simple"]) - # Remind prophet of their role, in sleepy mode only where it is hacked into a template instead of a role if "prophet" in var.TEMPLATE_RESTRICTIONS and nick in var.ROLES["prophet"]: pm(cli, nick, messages["prophet_simple"])