Split fallen angel

Also fixes some bugs with using stop_propagation instead of
stop_processing in events (the former does absolutely nothing).

Added a skeleton file to assist with adding new roles, contains the
needed imports on top and vim modeline on the bottom.

Yes, these are all related and need to go in the same commit, stop
throwing things at me.
This commit is contained in:
skizzerz 2016-09-23 20:10:04 -05:00
parent 9f21eb65c3
commit bfc675e953
7 changed files with 128 additions and 110 deletions

View File

@ -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))

View File

@ -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))

54
src/roles/fallenangel.py Normal file
View File

@ -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:

View File

@ -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")

17
src/roles/skel.py Normal file
View File

@ -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:

View File

@ -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"]:

View File

@ -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)