diff --git a/src/roles/angel.py b/src/roles/angel.py index 6da32db..2b4c877 100644 --- a/src/roles/angel.py +++ b/src/roles/angel.py @@ -147,6 +147,32 @@ def on_transition_day(evt, cli, var): if GUARDED.get(g) == v: var.ACTIVE_PROTECTIONS[v].append("bodyguard") +@event_listener("fallen_angel_guard_break") +def on_fagb(evt, cli, var, victim, killer): + for g in var.ROLES["guardian angel"]: + if GUARDED.get(g) == victim: + if random.random() < var.FALLEN_ANGEL_KILLS_GUARDIAN_ANGEL_CHANCE: + if g in evt.data["protected"]: + del evt.data["protected"][g] + evt.data["bywolves"].add(g) + if g not in evt.data["victims"]: + evt.data["onlybywolves"].add(g) + evt.data["victims"].append(g) + evt.data["killers"][g].append(killer) + if g != victim: + pm(cli, g, messages["fallen_angel_success"].format(victim)) + for g in var.ROLES["bodyguard"]: + if GUARDED.get(g) == victim: + if g in evt.data["protected"]: + del evt.data["protected"][g] + evt.data["bywolves"].add(g) + if g not in evt.data["victims"]: + evt.data["onlybywolves"].add(g) + evt.data["victims"].append(g) + evt.data["killers"][g].append(killer) + if g != victim: + pm(cli, g, messages["fallen_angel_success"].format(victim)) + @event_listener("transition_day_resolve", priority=2) def on_transition_day_resolve(evt, cli, var, victim): # TODO: remove these checks once everything is split @@ -159,7 +185,7 @@ def on_transition_day_resolve(evt, cli, var, victim): if evt.data["protected"].get(victim) == "angel": evt.data["message"].append(messages["angel_protection"].format(victim)) evt.data["novictmsg"] = False - evt.stop_propagation = True + evt.stop_processing = True evt.prevent_default = True elif evt.data["protected"].get(victim) == "bodyguard": for bodyguard in var.ROLES["bodyguard"]: @@ -167,7 +193,7 @@ def on_transition_day_resolve(evt, cli, var, victim): evt.data["dead"].append(bodyguard) evt.data["message"].append(messages["bodyguard_protection"].format(bodyguard)) evt.data["novictmsg"] = False - evt.stop_propagation = True + evt.stop_processing = True evt.prevent_default = True break @@ -246,12 +272,12 @@ 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 + evt.stop_processing = 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 + evt.stop_processing = 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)) diff --git a/src/roles/blessed.py b/src/roles/blessed.py index 111e512..03f8845 100644 --- a/src/roles/blessed.py +++ b/src/roles/blessed.py @@ -48,7 +48,7 @@ def on_transition_day_resolve(evt, cli, var, victim): # 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.stop_processing = True evt.prevent_default = True @event_listener("transition_night_end", priority=5) @@ -65,21 +65,21 @@ 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 + evt.stop_processing = 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 + evt.stop_processing = 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 + evt.stop_processing = 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)) diff --git a/src/roles/fallenangel.py b/src/roles/fallenangel.py new file mode 100644 index 0000000..9693d91 --- /dev/null +++ b/src/roles/fallenangel.py @@ -0,0 +1,54 @@ +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("transition_day", priority=4.8) +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 + for v in victims: + if evt.data["numkills"][v] == 1 and v in evt.data["bywolves"]: + evt.data["onlybywolves"].add(v) + + if len(var.ROLES["fallen angel"]) > 0: + for p, t in list(evt.data["protected"].items()): + if p in evt.data["bywolves"]: + if p in evt.data["protected"]: + pm(cli, p, messages["fallen_angel_deprotect"]) + + # let other roles do special things when we bypass their guards + killer = random.choice(list(var.ROLES["fallen angel"])) + fevt = Event("fallen_angel_guard_break", evt.data) + fevt.dispatch(cli, var, p, killer) + + if p in evt.data["protected"]: + del evt.data["protected"][p] + if p in var.ACTIVE_PROTECTIONS: + del var.ACTIVE_PROTECTIONS[p] + # mark kill as performed by a random FA + # this is important as there may otherwise be no killers if every kill was blocked + evt.data["killers"][p].append(killer) + +@event_listener("assassinate", priority=1) +def on_assassinate(evt, cli, var, nick, target, prot): + # bypass all protection if FA is doing the killing + # we do this by stopping propagation, meaning future events won't fire + if evt.params.nickrole == "fallen angel": + evt.params.prots.clear() + evt.stop_processing = True + evt.prevent_default = True + +# vim: set sw=4 expandtab: diff --git a/src/roles/shaman.py b/src/roles/shaman.py index f82ecee..2f9b0e0 100644 --- a/src/roles/shaman.py +++ b/src/roles/shaman.py @@ -49,6 +49,7 @@ DECEIT = set() # type: Set[str] # holding vars that don't persist long enough to need special attention in # reset/exchange/nickchange havetotem = [] # type: List[str] +brokentotem = set() # type: Set[str] @cmd("give", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=var.TOTEM_ORDER) @cmd("totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=var.TOTEM_ORDER) @@ -227,7 +228,7 @@ def on_chk_decision(evt, cli, var, force): @event_listener("chk_decision", priority=1.1) def on_hurry_up(evt, cli, var, force): if evt.params.timeout: - evt.stop_propagation = True + evt.stop_processing = True @event_listener("chk_decision_abstain") def on_chk_decision_abstain(evt, cli, var, nl): @@ -267,7 +268,7 @@ def on_chk_decision_lynch3(evt, cli, var, voters): cli.msg(botconfig.CHANNEL, messages["totem_reveal"].format(votee, an, role)) evt.data["votee"] = None evt.prevent_default = True - evt.stop_propagation = True + evt.stop_processing = True @event_listener("chk_decision_lynch", priority=5) def on_chk_decision_lynch5(evt, cli, var, voters): @@ -388,6 +389,7 @@ def on_transition_day_begin2(evt, cli, var): # In transition_day_end we report who was given totems based on havetotem. # Fallen angel messes with this list, hence why it is separated from LASTGIVEN # and calculated here. + brokentotem.clear() havetotem.clear() havetotem.extend(sorted(filter(None, LASTGIVEN.values()))) @@ -424,6 +426,14 @@ def on_transition_day3(evt, cli, var): for i in range(0, numtotems): var.ACTIVE_PROTECTIONS[v].append("totem") +@event_listener("fallen_angel_guard_break") +def on_fagb(evt, cli, var, victim, killer): + # we'll never end up killing a shaman who gave out protection, but delete the totem since + # story-wise it gets demolished at night by the FA + while victim in havetotem: + havetotem.remove(victim) + brokentotem.add(victim) + @event_listener("transition_day_resolve", priority=2) def on_transition_day_resolve2(evt, cli, var, victim): # TODO: remove these checks once everything is split @@ -436,7 +446,7 @@ def on_transition_day_resolve2(evt, cli, var, victim): if evt.data["protected"].get(victim) == "totem": evt.data["message"].append(messages["totem_protection"].format(victim)) evt.data["novictmsg"] = False - evt.stop_propagation = True + evt.stop_processing = True evt.prevent_default = True @event_listener("transition_day_resolve", priority=6) @@ -495,6 +505,8 @@ def on_transition_day_end(evt, cli, var): ntotems = len(list(tlist)) message.append(messages["totem_posession"].format( player, "ed" if player not in list_players() else "s", "a" if ntotems == 1 else "\u0002{0}\u0002".format(ntotems), "s" if ntotems > 1 else "")) + for player in brokentotem: + message.append(messages["totem_broken"].format(player)) cli.msg(botconfig.CHANNEL, "\n".join(message)) @event_listener("transition_night_end", priority=2.01) @@ -570,7 +582,7 @@ 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 + evt.stop_processing = True cli.msg(botconfig.CHANNEL, messages[evt.params.message_prefix + "totem"].format(nick, target)) @event_listener("myrole") diff --git a/src/roles/skel.py b/src/roles/skel.py new file mode 100644 index 0000000..a7cbc15 --- /dev/null +++ b/src/roles/skel.py @@ -0,0 +1,17 @@ +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 + +# Skeleton file for new roles, basically to get all the imports right and stuff + +# vim: set sw=4 expandtab: diff --git a/src/roles/wolf.py b/src/roles/wolf.py index 5c9657c..6124e91 100644 --- a/src/roles/wolf.py +++ b/src/roles/wolf.py @@ -171,6 +171,7 @@ def on_transition_day(evt, cli, var): # this should be moved to an event in kill, where monster prefixes their nick with ! # and fallen angel subsequently removes the ! prefix + # TODO: when monster is split off if len(var.ROLES["fallen angel"]) == 0: for monster in var.ROLES["monster"]: if monster in evt.data["victims"]: diff --git a/src/wolfgame.py b/src/wolfgame.py index f153fe7..c792318 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -2874,14 +2874,12 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death deadlist=deadlist, original=original, refresh_pl=refresh_pl, - message_prefix="assassin_fail_") + message_prefix="assassin_fail_", + nickrole=nickrole, + nicktpls=nicktpls, + prots=prots) 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 + # an event can read the current active protection and cancel the assassination # 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]): @@ -3896,58 +3894,6 @@ def transition_day(cli, gameid=0): victims_set = set(victims) vappend = [] - # 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 # dying by themselves, not killed by wolves - if numkills[v] == 1 and v in bywolves: - onlybywolves.add(v) - - fallenkills = set() - brokentotem = set() - from src.roles.shaman import havetotem - from src.roles import angel - if len(var.ROLES["fallen angel"]) > 0: - for p, t in list(protected.items()): - if p in bywolves: - for g in var.ROLES["guardian angel"]: - if angel.GUARDED.get(g) == p and random.random() < var.FALLEN_ANGEL_KILLS_GUARDIAN_ANGEL_CHANCE: - if g in protected: - del protected[g] - bywolves.add(g) - victims.append(g) - fallenkills.add(g) - if g not in victims_set: - victims_set.add(g) - onlybywolves.add(g) - for g in var.ROLES["bodyguard"]: - if angel.GUARDED.get(g) == p: - if g in protected: - del protected[g] - bywolves.add(g) - victims.append(g) - fallenkills.add(g) - if g not in victims_set: - victims_set.add(g) - onlybywolves.add(g) - # we'll never end up killing a shaman who gave out protection, but delete the totem since - # story-wise it gets demolished at night by the FA - while p in havetotem: - havetotem.remove(p) - brokentotem.add(p) - if p in protected: - del protected[p] - if p in var.ACTIVE_PROTECTIONS: - del var.ACTIVE_PROTECTIONS[p] - # mark kill as performed by a random FA - # this is important as there may otherwise be no killers if every kill was blocked - killers[p].append(random.choice(list(var.ROLES["fallen angel"]))) - # set to True if we play chilling howl message due to a bitten person turning new_wolf = False if var.ALPHA_ENABLED: # check for bites @@ -4019,6 +3965,7 @@ def transition_day(cli, gameid=0): # that assumes they die en route to the wolves (and thus don't shoot/give out gun/etc.) # TODO: this needs to be split off into angel.py, but all the stuff above it needs to be split off first # so even though angel.py exists we can't exactly do this now + from src.roles import angel for v in victims_set: if v in var.DYING: victims.append(v) @@ -4046,40 +3993,6 @@ def transition_day(cli, gameid=0): vappend.remove(v) victims.append(v) - # If FA is killing through a guard, let them as well as the victim know so they don't - # try to report the extra kills as a bug - fallenmsg = set() - if len(var.ROLES["fallen angel"]) > 0: - for v in fallenkills: - t = angel.GUARDED.get(v) - if v not in fallenmsg: - fallenmsg.add(v) - if v != t: - pm(cli, v, (messages["fallen_angel_success"]).format(t)) - else: - pm(cli, v, messages["fallen_angel_deprotect"]) - if v != t and t not in fallenmsg: - fallenmsg.add(t) - pm(cli, t, messages["fallen_angel_deprotect"]) - # Also message GAs that don't die and their victims - for g in var.ROLES["guardian angel"]: - v = angel.GUARDED.get(g) - if v in bywolves and g not in fallenkills: - if g not in fallenmsg: - fallenmsg.add(g) - if g != v: - pm(cli, g, messages["fallen_angel_success"].format(v)) - else: - pm(cli, g, messages["fallen_angel_deprotect"]) - if g != v and v not in fallenmsg: - fallenmsg.add(v) - pm(cli, v, messages["fallen_angel_deprotect"]) - # Finally, message blessed people that aren't otherwise being guarded by a GA or bodyguard - for v in bywolves: - if v not in fallenmsg and v in var.ROLES["blessed villager"]: - fallenmsg.add(v) - pm(cli, v, messages["fallen_angel_deprotect"]) - # Select a random target for assassin that isn't already going to die if they didn't target pl = list_players() for ass in var.ROLES["assassin"]: @@ -4329,11 +4242,6 @@ def transition_day(cli, gameid=0): if deadperson in list_players(): del_player(cli, deadperson, end_game=False, killer_role=killer_role[deadperson], deadlist=dead, original=deadperson) - message = [] - for brokentotem in brokentotem: - message.append(messages["totem_broken"].format(brokentotem)) - cli.msg(chan, "\n".join(message)) - event_end = Event("transition_day_end", {"begin_day": begin_day}) event_end.dispatch(cli, var)