Merge pull request #146 from lykoss/fallen

New roles, protection semantics, and alpha fixes
This commit is contained in:
Emanuel Barry 2015-06-08 20:26:16 -04:00
commit fdd1fc5c12
2 changed files with 343 additions and 138 deletions

View File

@ -103,6 +103,7 @@ GUARDIAN_ANGEL_DIES_CHANCE = 0
BODYGUARD_DIES_CHANCE = 0 BODYGUARD_DIES_CHANCE = 0
DETECTIVE_REVEALED_CHANCE = 2/5 DETECTIVE_REVEALED_CHANCE = 2/5
SHARPSHOOTER_CHANCE = 1/5 # if sharpshooter is enabled, chance that a gunner will become a sharpshooter instead SHARPSHOOTER_CHANCE = 1/5 # if sharpshooter is enabled, chance that a gunner will become a sharpshooter instead
FALLEN_ANGEL_KILLS_GUARDIAN_ANGEL_CHANCE = 1/2
AMNESIAC_NIGHTS = 3 # amnesiac gets to know their actual role on this night AMNESIAC_NIGHTS = 3 # amnesiac gets to know their actual role on this night
ALPHA_WOLF_NIGHTS = 3 # alpha wolf turns the target into a wolf after this many nights (note the night they are bitten is considered night 1) ALPHA_WOLF_NIGHTS = 3 # alpha wolf turns the target into a wolf after this many nights (note the night they are bitten is considered night 1)
@ -160,6 +161,7 @@ ROLE_GUIDE = {# village roles
"hunter" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), "hunter" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
"shaman" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), "shaman" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
"doctor" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), "doctor" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"mystic" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
# wolf roles # wolf roles
"wolf" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 3 , 3 , 3 ), "wolf" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 3 , 3 , 3 ),
"traitor" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), "traitor" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
@ -172,6 +174,8 @@ ROLE_GUIDE = {# village roles
"alpha wolf" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), "alpha wolf" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"werekitten" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), "werekitten" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"warlock" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), "warlock" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"wolf mystic" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"fallen angel" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
# neutral roles # neutral roles
"lycan" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), "lycan" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"vengeful ghost" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), "vengeful ghost" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
@ -194,7 +198,7 @@ ROLE_GUIDE = {# village roles
# Harlot dies when visiting, seer sees as wolf, gunner kills when shooting, GA and bodyguard have a chance at dying when guarding # Harlot dies when visiting, seer sees as wolf, gunner kills when shooting, GA and bodyguard have a chance at dying when guarding
# If every wolf role dies, and there are no remaining traitors, the game ends and villagers win (monster may steal win) # If every wolf role dies, and there are no remaining traitors, the game ends and villagers win (monster may steal win)
WOLF_ROLES = ["wolf", "alpha wolf", "werecrow", "wolf cub", "werekitten"] WOLF_ROLES = ["wolf", "alpha wolf", "werecrow", "wolf cub", "werekitten", "wolf mystic", "fallen angel"]
# Access to wolfchat, and counted towards the # of wolves vs villagers when determining if a side has won # Access to wolfchat, and counted towards the # of wolves vs villagers when determining if a side has won
WOLFCHAT_ROLES = WOLF_ROLES + ["traitor", "hag", "sorcerer", "warlock"] WOLFCHAT_ROLES = WOLF_ROLES + ["traitor", "hag", "sorcerer", "warlock"]
# Wins with the wolves, even if the roles are not necessarily wolves themselves # Wins with the wolves, even if the roles are not necessarily wolves themselves
@ -202,7 +206,7 @@ WOLFTEAM_ROLES = WOLFCHAT_ROLES + ["minion", "cultist"]
# These roles never win as a team, only ever individually (either instead of or in addition to the regular winners) # These roles never win as a team, only ever individually (either instead of or in addition to the regular winners)
TRUE_NEUTRAL_ROLES = ["crazed shaman", "fool", "jester", "monster", "clone", "piper"] TRUE_NEUTRAL_ROLES = ["crazed shaman", "fool", "jester", "monster", "clone", "piper"]
# These are the roles that will NOT be used for when amnesiac turns, everything else is fair game! (var.DEFAULT_ROLE is also appended if not in this list) # These are the roles that will NOT be used for when amnesiac turns, everything else is fair game! (var.DEFAULT_ROLE is also appended if not in this list)
AMNESIAC_BLACKLIST = ["monster", "minion", "matchmaker", "clone", "doctor", "villager", "cultist", "piper"] AMNESIAC_BLACKLIST = ["monster", "minion", "matchmaker", "clone", "doctor", "villager", "cultist", "piper", "village elder"]
# These roles are seen as wolf by the seer/oracle # These roles are seen as wolf by the seer/oracle
SEEN_WOLF = WOLF_ROLES + ["monster", "mad scientist"] SEEN_WOLF = WOLF_ROLES + ["monster", "mad scientist"]
# These are seen as the default role (or villager) when seen by seer (this overrides SEEN_WOLF) # These are seen as the default role (or villager) when seen by seer (this overrides SEEN_WOLF)
@ -218,6 +222,8 @@ TEMPLATE_RESTRICTIONS = {"cursed villager" : WOLF_ROLES + ["seer", "oracle", "fo
"assassin" : WOLF_ROLES + ["traitor", "seer", "augur", "oracle", "harlot", "detective", "bodyguard", "guardian angel", "lycan"], "assassin" : WOLF_ROLES + ["traitor", "seer", "augur", "oracle", "harlot", "detective", "bodyguard", "guardian angel", "lycan"],
"bureaucrat" : [], "bureaucrat" : [],
} }
# fallen angel can be assassin even though they are a wolf role
TEMPLATE_RESTRICTIONS["assassin"].remove("fallen angel")
# Roles listed here cannot be used in !fgame roles=blah. If they are defined in ROLE_GUIDE they may still be used. # Roles listed here cannot be used in !fgame roles=blah. If they are defined in ROLE_GUIDE they may still be used.
DISABLED_ROLES = [] DISABLED_ROLES = []
@ -690,15 +696,15 @@ class NoRevealMode(GameMode):
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX) self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
self.ROLE_GUIDE.update({# village roles self.ROLE_GUIDE.update({# village roles
"seer" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), "seer" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
"guardian angel" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ), "guardian angel" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
"shaman" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ), "shaman" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
"village elder" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ), "mystic" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
"detective" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ), "detective" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ),
"hunter" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ), "hunter" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
# wolf roles # wolf roles
"wolf" : ( 1 , 1 , 2 , 2 , 2 , 2 , 2 , 3 ), "wolf" : ( 1 , 1 , 1 , 1 , 2 , 2 , 2 , 3 ),
"wolf mystic" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
"traitor" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ), "traitor" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
"minion" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ),
"werecrow" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ), "werecrow" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
# neutral roles # neutral roles
"clone" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ), "clone" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),

View File

@ -1305,6 +1305,10 @@ def stats(cli, nick, chan, rest):
count += bitten_roles["time lord"] count += bitten_roles["time lord"]
elif role == "wolf": elif role == "wolf":
count -= sum(bitten_roles.values()) count -= sum(bitten_roles.values())
# GAs turn into FAs, not wolves
count += bitten_roles["guardian angel"]
elif role == "fallen angel":
count -= bitten_roles["guardian angel"]
else: else:
count += bitten_roles[role] count += bitten_roles[role]
@ -1370,8 +1374,8 @@ def hurry_up(cli, gameid, change):
voters.append(v) voters.append(v)
for v in voters: for v in voters:
weight = 1 weight = 1
imp_count = sum(1 if p == v else 0 for p in var.IMPATIENT) imp_count = var.IMPATIENT.count(v)
pac_count = sum(1 if p == v else 0 for p in var.PACIFISTS) pac_count = var.PACIFISTS.count(v)
if pac_count > imp_count: if pac_count > imp_count:
weight = 0 # more pacifists than impatience totems weight = 0 # more pacifists than impatience totems
elif imp_count == pac_count and v not in var.VOTES[votee]: elif imp_count == pac_count and v not in var.VOTES[votee]:
@ -1451,8 +1455,8 @@ def chk_decision(cli, force = ""):
if v in pl and v not in voters and v != votee and v not in var.WOUNDED and v not in var.ASLEEP: if v in pl and v not in voters and v != votee and v not in var.WOUNDED and v not in var.ASLEEP:
# don't add them in if they have the same number or more of pacifism totems # don't add them in if they have the same number or more of pacifism totems
# this matters for desperation totem on the votee # this matters for desperation totem on the votee
imp_count = sum(1 if p == v else 0 for p in var.IMPATIENT) imp_count = var.IMPATIENT.count(v)
pac_count = sum(1 if p == v else 0 for p in var.PACIFISTS) pac_count = var.PACIFISTS.count(v)
if pac_count >= imp_count: if pac_count >= imp_count:
continue continue
@ -1462,8 +1466,8 @@ def chk_decision(cli, force = ""):
impatient_voters.append(v) impatient_voters.append(v)
for v in voters[:]: for v in voters[:]:
weight = 1 weight = 1
imp_count = sum(1 if p == v else 0 for p in var.IMPATIENT) imp_count = var.IMPATIENT.count(v)
pac_count = sum(1 if p == v else 0 for p in var.PACIFISTS) pac_count = var.PACIFISTS.count(v)
if pac_count > imp_count: if pac_count > imp_count:
weight = 0 # more pacifists than impatience totems weight = 0 # more pacifists than impatience totems
elif imp_count == pac_count and v not in var.VOTES[votee]: elif imp_count == pac_count and v not in var.VOTES[votee]:
@ -2126,20 +2130,20 @@ def del_player(cli, nick, forced_death = False, devoice = True, end_game = True,
target = var.TARGETED[nick] target = var.TARGETED[nick]
del var.TARGETED[nick] del var.TARGETED[nick]
if target != None and target in pl: if target != None and target in pl:
if target in var.PROTECTED: if "totem" in var.ACTIVE_PROTECTIONS[target] and nickrole != "fallen angel":
var.ACTIVE_PROTECTIONS[target].remove("totem")
message = ("Before dying, \u0002{0}\u0002 quickly attempts to slit \u0002{1}\u0002's throat; " + message = ("Before dying, \u0002{0}\u0002 quickly attempts to slit \u0002{1}\u0002's throat; " +
"however, {1}'s totem emits a brilliant flash of light, causing the attempt to miss.").format(nick, target) "however, {1}'s totem emits a brilliant flash of light, causing the attempt to miss.").format(nick, target)
cli.msg(botconfig.CHANNEL, message) cli.msg(botconfig.CHANNEL, message)
elif target in var.GUARDED.values() and var.GAMEPHASE == "night": elif "angel" in var.ACTIVE_PROTECTIONS[target] and nickrole != "fallen angel":
for bg in var.ROLES["guardian angel"]: var.ACTIVE_PROTECTIONS[target].remove("angel")
if bg in var.GUARDED and var.GUARDED[bg] == target:
message = ("Before dying, \u0002{0}\u0002 quickly attempts to slit \u0002{1}\u0002's throat; " + message = ("Before dying, \u0002{0}\u0002 quickly attempts to slit \u0002{1}\u0002's throat; " +
"however, a guardian angel was on duty and able to foil the attempt.").format(nick, target) "however, a guardian angel was on duty and able to foil the attempt.").format(nick, target)
cli.msg(botconfig.CHANNEL, message) cli.msg(botconfig.CHANNEL, message)
break elif "bodyguard" in var.ACTIVE_PROTECTIONS[target] and nickrole != "fallen angel":
else: var.ACTIVE_PROTECTIONS[target].remove("bodyguard")
for ga in var.ROLES["bodyguard"]: for ga in var.ROLES["bodyguard"]:
if ga in var.GUARDED and var.GUARDED[ga] == target: if var.GUARDED.get(ga) == target:
message = ("Before dying, \u0002{0}\u0002 quickly attempts to slit \u0002{1}\u0002's throat; " + message = ("Before dying, \u0002{0}\u0002 quickly attempts to slit \u0002{1}\u0002's throat; " +
"however, \u0002{2}\u0002, a bodyguard, sacrificed their life to protect them.").format(nick, target, ga) "however, \u0002{2}\u0002, a bodyguard, sacrificed their life to protect them.").format(nick, target, ga)
cli.msg(botconfig.CHANNEL, message) cli.msg(botconfig.CHANNEL, message)
@ -2668,7 +2672,13 @@ def on_nick(cli, oldnick, nick):
if prefix in var.DESPERATE: if prefix in var.DESPERATE:
var.DESPERATE.remove(prefix) var.DESPERATE.remove(prefix)
var.DESPERATE.append(nick) var.DESPERATE.append(nick)
if prefix in var.PROTECTED: for k, d in list(var.DEATH_TOTEM):
if k == prefix or d == prefix:
var.DEATH_TOTEM.remove((k, d))
nk = nick if k == prefix else k
nd = nick if d == prefix else d
var.DEATH_TOTEM.append((nk, nd))
while prefix in var.PROTECTED:
var.PROTECTED.remove(prefix) var.PROTECTED.remove(prefix)
var.PROTECTED.append(nick) var.PROTECTED.append(nick)
if prefix in var.REVEALED: if prefix in var.REVEALED:
@ -2972,6 +2982,7 @@ def begin_day(cli):
var.DISEASED = copy.copy(var.TOBEDISEASED) var.DISEASED = copy.copy(var.TOBEDISEASED)
var.MISDIRECTED = copy.copy(var.TOBEMISDIRECTED) var.MISDIRECTED = copy.copy(var.TOBEMISDIRECTED)
var.EXCHANGED = copy.copy(var.TOBEEXCHANGED) var.EXCHANGED = copy.copy(var.TOBEEXCHANGED)
var.ACTIVE_PROTECTIONS = defaultdict(list)
msg = ('The villagers must now vote for whom to lynch. '+ msg = ('The villagers must now vote for whom to lynch. '+
'Use "{0}lynch <nick>" to cast your vote. {1} votes '+ 'Use "{0}lynch <nick>" to cast your vote. {1} votes '+
@ -3069,7 +3080,7 @@ def transition_day(cli, gameid=0):
var.NO_LYNCH = [] var.NO_LYNCH = []
var.DAY_COUNT += 1 var.DAY_COUNT += 1
var.FIRST_DAY = (var.DAY_COUNT == 1) var.FIRST_DAY = (var.DAY_COUNT == 1)
havetotem = copy.copy(var.LASTGIVEN) havetotem = list(var.LASTGIVEN.values())
if var.START_WITH_DAY and var.FIRST_DAY: if var.START_WITH_DAY and var.FIRST_DAY:
# TODO: need to message everyone their roles and give a short thing saying "it's daytime" # TODO: need to message everyone their roles and give a short thing saying "it's daytime"
@ -3134,6 +3145,112 @@ def transition_day(cli, gameid=0):
onlybywolves.add(victim) onlybywolves.add(victim)
killers[victim].append("@wolves") # special key to let us know to randomly select a wolf killers[victim].append("@wolves") # special key to let us know to randomly select a wolf
if len(var.ROLES["fallen angel"]) == 0:
for monster in var.ROLES["monster"]:
if monster in victims:
victims.remove(monster)
bywolves.discard(monster)
onlybywolves.discard(monster)
wolfghostvictims = []
for ghost, target in var.VENGEFUL_GHOSTS.items():
if target == "villagers":
victim = var.OTHER_KILLS[ghost]
killers[victim].append(ghost)
if victim not in var.DYING: # wolf ghost killing ghost will take precedence over everything except elder
wolfghostvictims.append(victim)
for k, d in var.OTHER_KILLS.items():
victims.append(d)
onlybywolves.discard(d)
killers[d].append(k)
for k, d in var.DEATH_TOTEM:
victims.append(d)
onlybywolves.discard(d)
killers[d].append(k)
# handle elder and other auto-deaths
for d in var.DYING:
victims.append(d)
onlybywolves.discard(d)
victims_set = set(victims) # remove duplicates
victims_set.discard(None) # in the event that ever happens
vappend = []
# this keeps track of the protections active on each nick, stored in var since del_player needs to access it for sake of assassin
protected = {}
var.ACTIVE_PROTECTIONS = defaultdict(list)
# 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)
for v in victims_set:
if v in var.DYING:
# this person is dying no matter what
continue
numkills = victims.count(v)
numtotems = var.PROTECTED.count(v)
if numtotems >= numkills:
protected[v] = "totem"
if numtotems > numkills:
for i in range(0, numtotems - numkills):
var.ACTIVE_PROTECTIONS[v].append("totem")
numkills -= numtotems
for g in var.ROLES["guardian angel"]:
if var.GUARDED.get(g) == v:
numkills -= 1
if numkills <= 0 and v not in protected:
protected[v] = "angel"
elif numkills <= 0:
var.ACTIVE_PROTECTIONS[v].append("angel")
for g in var.ROLES["bodyguard"]:
if var.GUARDED.get(g) == v:
numkills -= 1
if numkills <= 0 and v not in protected:
protected[v] = "bodyguard"
elif numkills <= 0:
var.ACTIVE_PROTECTIONS[v].append("bodyguard")
numkills -= 1
if numkills == 1 and v in bywolves:
onlybywolves.add(v)
fallenkills = set()
brokentotem = set()
if len(var.ROLES["fallen angel"]) > 0:
for p, t in list(protected.items()):
if p in bywolves:
if t == "angel":
for g in var.ROLES["guardian angel"]:
if var.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)
elif t == "bodyguard":
for g in var.ROLES["bodyguard"]:
if var.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)
elif t == "totem":
# 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 var.ALPHA_ENABLED: # check for bites if var.ALPHA_ENABLED: # check for bites
for (alpha, desired) in var.BITE_PREFERENCES.items(): for (alpha, desired) in var.BITE_PREFERENCES.items():
if len(bywolves) == 0: if len(bywolves) == 0:
@ -3146,57 +3263,38 @@ def transition_day(cli, gameid=0):
target = desired target = desired
else: else:
target = random.choice(tuple(bywolves)) target = random.choice(tuple(bywolves))
pm(cli, alpha, "You have bitten \u0002{0}\u0002.".format(target))
targetrole = var.get_role(target) targetrole = var.get_role(target)
# do the usual checks; we can only bite those that would otherwise die # do the usual checks; we can only bite those that would otherwise die
# (e.g. block it on visiting harlot, GA/bodyguard, and protection totem) # (e.g. block it on visiting harlot, GA/bodyguard, and protection totem)
# also if a lycan is bitten, just turn them into a wolf immediately # also if a lycan is bitten, just turn them into a wolf immediately
if (target not in var.PROTECTED if (target not in protected
and target not in var.GUARDED.values()
and (target not in var.ROLES["harlot"] or not var.HVISITED.get(target)) and (target not in var.ROLES["harlot"] or not var.HVISITED.get(target))
and target not in var.ROLES["lycan"] and target not in var.ROLES["lycan"]
and target not in var.LYCANTHROPES and target not in var.LYCANTHROPES
and target not in var.IMMUNIZED): and target not in var.IMMUNIZED):
var.BITTEN[target] = var.ALPHA_WOLF_NIGHTS
bitten.append(target)
victims.remove(target) victims.remove(target)
bywolves.remove(target) bywolves.remove(target)
onlybywolves.remove(target) onlybywolves.remove(target)
killers[target].remove("@wolves") killers[target].remove("@wolves")
if target not in victims:
victims_set.discard(target)
var.BITTEN[target] = var.ALPHA_WOLF_NIGHTS
bitten.append(target)
else:
# someone else also killed them, mark bite unsuccessful
var.ALPHA_WOLVES.remove(alpha)
else: else:
# bite was unsuccessful, let them try again # bite was unsuccessful, let them try again
var.ALPHA_WOLVES.remove(alpha) var.ALPHA_WOLVES.remove(alpha)
if alpha in var.ALPHA_WOLVES:
pm(cli, alpha, "You have bitten \u0002{0}\u0002.".format(target))
else:
pm(cli, alpha, "You tried to bite \u0002{0}\u0002, but it didn't work. Better luck next time!".format(target))
var.BITE_PREFERENCES = {} var.BITE_PREFERENCES = {}
for monster in var.ROLES["monster"]:
if monster in victims:
victims.remove(monster)
bywolves.discard(monster)
onlybywolves.discard(monster)
wolfghostvictims = []
for ghost, target in var.VENGEFUL_GHOSTS.items():
if target == "villagers":
victim = var.OTHER_KILLS[ghost]
killers[victim].append(ghost)
if victim not in var.DYING: # wolf ghost killing ghost will take precedence over everything except death totem and elder
wolfghostvictims.append(victim)
for k, d in var.OTHER_KILLS.items():
victims.append(d)
onlybywolves.discard(d)
killers[d].append(k)
for d in var.DYING:
victims.append(d)
onlybywolves.discard(d)
for s, v in var.LASTGIVEN.items():
if v == d and var.TOTEMS[s] == "death":
killers[d].append(s)
victims_set = set(victims) # remove duplicates
victims_set.discard(None) # in the event that ever happens
victims = [] victims = []
vappend = []
# Ensures that special events play for bodyguard and harlot-visiting-victim so that kill can # Ensures that special events play for bodyguard and harlot-visiting-victim so that kill can
# be correctly attributed to wolves (for vengeful ghost lover), and that any gunner events # be correctly attributed to wolves (for vengeful ghost lover), and that any gunner events
# can play. Harlot visiting wolf doesn't play special events if they die via other means since # can play. Harlot visiting wolf doesn't play special events if they die via other means since
@ -3226,6 +3324,37 @@ def transition_day(cli, gameid=0):
vappend.remove(v) vappend.remove(v)
victims.append(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 = var.GUARDED.get(v)
if v not in fallenmsg:
fallenmsg.add(v)
if v != t:
pm(cli, v, ("A fell wind starts blowing through the village and you catch the flurry of blackened wings out of the corner of your eye. " +
"No longer caring for \u0002{0}\u0002's safety, you attempt to get away before your own life is taken...").format(t))
else:
pm(cli, v, "A fell wind blows through you and chills you to the bone. You no longer feel safe or protected...")
if v != t and t not in fallenmsg:
fallenmsg.add(t)
pm(cli, t, "A fell wind blows through you and chills you to the bone. You no longer feel safe or protected...")
# Also message GAs that don't die and their victims
for g in var.ROLES["guardian angel"]:
v = var.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, ("A fell wind starts blowing through the village and you catch the flurry of blackened wings out of the corner of your eye. " +
"No longer caring for \u0002{0}\u0002's safety, you attempt to get away before your own life is taken...").format(v))
else:
pm(cli, g, "A fell wind blows through you and chills you to the bone. You no longer feel safe or protected...")
if g != v and v not in fallenmsg:
fallenmsg.add(g)
pm(cli, v, "A fell wind blows through you and chills you to the bone. You no longer feel safe or protected...")
# Select a random target for assassin that isn't already going to die if they didn't target # Select a random target for assassin that isn't already going to die if they didn't target
pl = var.list_players() pl = var.list_players()
for ass in var.ROLES["assassin"]: for ass in var.ROLES["assassin"]:
@ -3284,18 +3413,15 @@ def transition_day(cli, gameid=0):
if victim in var.ROLES["harlot"] and var.HVISITED.get(victim) and victim not in var.DYING and victim not in dead and victim in onlybywolves: if victim in var.ROLES["harlot"] and var.HVISITED.get(victim) and victim not in var.DYING and victim not in dead and victim in onlybywolves:
message.append("The wolves' selected victim was a harlot, who was not at home last night.") message.append("The wolves' selected victim was a harlot, who was not at home last night.")
novictmsg = False novictmsg = False
elif victim in var.PROTECTED and victim not in var.DYING: elif protected.get(victim) == "totem":
message.append(("\u0002{0}\u0002 was attacked last night, but their totem " + message.append(("\u0002{0}\u0002 was attacked last night, but their totem " +
"emitted a brilliant flash of light, blinding the attacker and " + "emitted a brilliant flash of light, blinding the attacker and " +
"allowing them to escape.").format(victim)) "allowing them to escape.").format(victim))
novictmsg = False novictmsg = False
elif victim in var.GUARDED.values() and victim not in var.DYING: elif protected.get(victim) == "angel":
for gangel in var.ROLES["guardian angel"]:
if var.GUARDED.get(gangel) == victim:
message.append(("\u0002{0}\u0002 was attacked last night, but luckily, the guardian angel was on duty.").format(victim)) message.append(("\u0002{0}\u0002 was attacked last night, but luckily, the guardian angel was on duty.").format(victim))
novictmsg = False novictmsg = False
break elif protected.get(victim) == "bodyguard":
else:
for bodyguard in var.ROLES["bodyguard"]: for bodyguard in var.ROLES["bodyguard"]:
if var.GUARDED.get(bodyguard) == victim: if var.GUARDED.get(bodyguard) == victim:
dead.append(bodyguard) dead.append(bodyguard)
@ -3455,10 +3581,14 @@ def transition_day(cli, gameid=0):
if var.WOLF_STEALS_GUN and victim in bywolves and victim in var.GUNNERS.keys() and var.GUNNERS[victim] > 0: if var.WOLF_STEALS_GUN and victim in bywolves and victim in var.GUNNERS.keys() and var.GUNNERS[victim] > 0:
# victim has bullets # victim has bullets
try: try:
while True: looters = var.list_players(var.WOLFCHAT_ROLES)
guntaker = random.choice(var.list_players(var.WOLFCHAT_ROLES)) # random looter while len(looters) > 0:
guntaker = random.choice(looters) # random looter
if guntaker not in dead: if guntaker not in dead:
break break
else:
looters.remove(guntaker)
if guntaker not in dead:
numbullets = var.GUNNERS[victim] numbullets = var.GUNNERS[victim]
if guntaker not in var.WOLF_GUNNERS: if guntaker not in var.WOLF_GUNNERS:
var.WOLF_GUNNERS[guntaker] = 0 var.WOLF_GUNNERS[guntaker] = 0
@ -3493,9 +3623,11 @@ def transition_day(cli, gameid=0):
del_player(cli, deadperson, end_game = False, killer_role = "wolf" if deadperson in onlybywolves or deadperson in wolfghostvictims else "villager", deadlist = dead, original = deadperson) del_player(cli, deadperson, end_game = False, killer_role = "wolf" if deadperson in onlybywolves or deadperson in wolfghostvictims else "villager", deadlist = dead, original = deadperson)
message = [] message = []
for havetotem in havetotem.values(): for havetotem in havetotem:
if havetotem: if havetotem:
message.append("\u0002{0}\u0002 seem{1} to be in possession of a mysterious totem...".format(havetotem, "ed" if havetotem in dead else "s")) message.append("\u0002{0}\u0002 seem{1} to be in possession of a mysterious totem...".format(havetotem, "ed" if havetotem in dead else "s"))
for brokentotem in brokentotem:
message.append("Broken totem pieces were found next to \u0002{0}\u0002's body...".format(brokentotem))
cli.msg(chan, "\n".join(message)) cli.msg(chan, "\n".join(message))
if chk_win(cli): # if after the last person is killed, one side wins, then actually end the game here if chk_win(cli): # if after the last person is killed, one side wins, then actually end the game here
@ -3514,13 +3646,13 @@ def chk_nightdone(cli):
var.ROLES["werecrow"] + var.ROLES["alpha wolf"] + var.ROLES["sorcerer"] + var.ROLES["hunter"] + var.ROLES["werecrow"] + var.ROLES["alpha wolf"] + var.ROLES["sorcerer"] + var.ROLES["hunter"] +
list(var.VENGEFUL_GHOSTS.keys()) + var.ROLES["hag"] + var.ROLES["shaman"] + list(var.VENGEFUL_GHOSTS.keys()) + var.ROLES["hag"] + var.ROLES["shaman"] +
var.ROLES["crazed shaman"] + var.ROLES["augur"] + var.ROLES["werekitten"] + var.ROLES["crazed shaman"] + var.ROLES["augur"] + var.ROLES["werekitten"] +
var.ROLES["warlock"] + var.ROLES["piper"]) var.ROLES["warlock"] + var.ROLES["piper"] + var.ROLES["wolf mystic"] + var.ROLES["fallen angel"])
if var.FIRST_NIGHT: if var.FIRST_NIGHT:
actedcount += len(var.MATCHMAKERS + list(var.CLONED.keys())) actedcount += len(var.MATCHMAKERS + list(var.CLONED.keys()))
nightroles += var.ROLES["matchmaker"] + var.ROLES["clone"] nightroles += var.ROLES["matchmaker"] + var.ROLES["clone"]
if var.DISEASED_WOLVES: if var.DISEASED_WOLVES:
nightroles = [p for p in nightroles if p not in (var.ROLES["wolf"] + var.ROLES["alpha wolf"] + var.ROLES["werekitten"])] nightroles = [p for p in nightroles if p not in var.list_players(("wolf", "alpha wolf", "werekitten", "wolf mystic", "fallen angel"))]
for p in var.HUNTERS: for p in var.HUNTERS:
# only remove one instance of their name if they have used hunter ability, in case they have templates # only remove one instance of their name if they have used hunter ability, in case they have templates
@ -3718,7 +3850,7 @@ def check_exchange(cli, actor, nick):
var.SHAMANS.remove(actor) var.SHAMANS.remove(actor)
if actor in var.LASTGIVEN: if actor in var.LASTGIVEN:
del var.LASTGIVEN[actor] del var.LASTGIVEN[actor]
elif actor_role == "wolf" or actor_role == "werekitten": elif actor_role in ("wolf", "werekitten", "wolf mystic", "fallen angel"):
if actor in var.KILLS: if actor in var.KILLS:
del var.KILLS[actor] del var.KILLS[actor]
elif actor_role == "hunter": elif actor_role == "hunter":
@ -3785,7 +3917,7 @@ def check_exchange(cli, actor, nick):
var.SHAMANS.remove(nick) var.SHAMANS.remove(nick)
if nick in var.LASTGIVEN: if nick in var.LASTGIVEN:
del var.LASTGIVEN[nick] del var.LASTGIVEN[nick]
elif nick_role == "wolf" or nick_role == "werekitten": elif nick_role in ("wolf", "werekitten", "wolf mystic", "fallen angel"):
if nick in var.KILLS: if nick in var.KILLS:
del var.KILLS[nick] del var.KILLS[nick]
elif nick_role == "hunter": elif nick_role == "hunter":
@ -3870,6 +4002,9 @@ def check_exchange(cli, actor, nick):
if nick_role == "shaman": if nick_role == "shaman":
pm(cli, actor, "You have a \u0002{0}\u0002 totem.".format(nick_totem)) pm(cli, actor, "You have a \u0002{0}\u0002 totem.".format(nick_totem))
var.TOTEMS[actor] = nick_totem var.TOTEMS[actor] = nick_totem
elif nick_role == "mystic":
numevil = len(var.list_players(var.WOLFTEAM_ROLES))
pm(cli, actor, "There are \u0002{0}\u0002 evil villager{1} still alive.".format(numevil, "s" if numevil != 1 else ""))
elif nick_role in var.WOLFCHAT_ROLES and actor_role not in var.WOLFCHAT_ROLES: elif nick_role in var.WOLFCHAT_ROLES and actor_role not in var.WOLFCHAT_ROLES:
pl = var.list_players() pl = var.list_players()
random.shuffle(pl) random.shuffle(pl)
@ -3886,15 +4021,17 @@ def check_exchange(cli, actor, nick):
pl[i] = player + " (cursed)" pl[i] = player + " (cursed)"
pm(cli, actor, "Players: " + ", ".join(pl)) pm(cli, actor, "Players: " + ", ".join(pl))
angry_alpha = '' if actor_role == "wolf mystic":
# # of special villagers = # of players - # of villagers - # of wolves - # of neutrals
numvills = len(ps) - len(var.list_players(var.WOLFTEAM_ROLES)) - len(var.list_players(("villager", "vengeful ghost", "time lord", "amnesiac", "village elder", "lycan"))) - len(var.list_players(var.TRUE_NEUTRAL_ROLES))
pm(cli, actor, "There are \u0002{0}\u0002 special villager{1} still alive.".format(numvills, "s" if numvills != 1 else ""))
if var.DISEASED_WOLVES: if var.DISEASED_WOLVES:
pm(cli, actor, 'You are feeling ill tonight, and are unable to kill anyone.') pm(cli, actor, 'You are feeling ill tonight, and are unable to kill anyone.')
elif var.ANGRY_WOLVES and actor_role in ("wolf", "werecrow", "alpha wolf", "werekitten"): elif var.ANGRY_WOLVES and actor_role in var.WOLF_ROLES and actor_role != "wolf cub":
pm(cli, actor, 'You are \u0002angry\u0002 tonight, and may kill two targets by using "kill <nick1> and <nick2>".') pm(cli, actor, 'You are \u0002angry\u0002 tonight, and may kill two targets by using "kill <nick1> and <nick2>".')
angry_alpha = ' <nick>'
if var.ALPHA_ENABLED and actor_role == "alpha wolf" and actor not in var.ALPHA_WOLVES: if var.ALPHA_ENABLED and actor_role == "alpha wolf" and actor not in var.ALPHA_WOLVES:
pm(cli, actor, ('You may use "bite{0}" tonight in order to turn the wolves\' target into a wolf instead of killing them. ' + pm(cli, actor, ('You may use "bite <nick>" tonight in order to turn the wolves\' target into a wolf instead of killing them. ' +
'They will turn into a wolf in {1} night{2}.').format(angry_alpha, var.ALPHA_WOLF_NIGHTS, 's' if var.ALPHA_WOLF_NIGHTS > 1 else '')) 'They will turn into a wolf in {0} night{1}.').format(var.ALPHA_WOLF_NIGHTS, 's' if var.ALPHA_WOLF_NIGHTS > 1 else ''))
elif nick_role == "minion": elif nick_role == "minion":
wolves = var.list_players(var.WOLF_ROLES) wolves = var.list_players(var.WOLF_ROLES)
random.shuffle(wolves) random.shuffle(wolves)
@ -3906,6 +4043,9 @@ def check_exchange(cli, actor, nick):
if actor_role == "shaman": if actor_role == "shaman":
pm(cli, nick, "You have a \u0002{0}\u0002 totem.".format(actor_totem)) pm(cli, nick, "You have a \u0002{0}\u0002 totem.".format(actor_totem))
var.TOTEMS[nick] = actor_totem var.TOTEMS[nick] = actor_totem
elif actor_role == "mystic":
numevil = len(var.list_players(var.WOLFTEAM_ROLES))
pm(cli, nick, "There are \u0002{0}\u0002 evil villager{1} still alive.".format(numevil, "s" if numevil != 1 else ""))
elif actor_role in var.WOLFCHAT_ROLES and nick_role not in var.WOLFCHAT_ROLES: elif actor_role in var.WOLFCHAT_ROLES and nick_role not in var.WOLFCHAT_ROLES:
pl = var.list_players() pl = var.list_players()
random.shuffle(pl) random.shuffle(pl)
@ -3922,15 +4062,17 @@ def check_exchange(cli, actor, nick):
pl[i] = player + " (cursed)" pl[i] = player + " (cursed)"
pm(cli, nick, "Players: " + ", ".join(pl)) pm(cli, nick, "Players: " + ", ".join(pl))
angry_alpha = '' if nick_role == "wolf mystic":
# # of special villagers = # of players - # of villagers - # of wolves - # of neutrals
numvills = len(ps) - len(var.list_players(var.WOLFTEAM_ROLES)) - len(var.list_players(("villager", "vengeful ghost", "time lord", "amnesiac", "village elder", "lycan"))) - len(var.list_players(var.TRUE_NEUTRAL_ROLES))
pm(cli, nick, "There are \u0002{0}\u0002 special villager{1} still alive.".format(numvills, "s" if numvills != 1 else ""))
if var.DISEASED_WOLVES: if var.DISEASED_WOLVES:
pm(cli, nick, 'You are feeling ill tonight, and are unable to kill anyone.') pm(cli, nick, 'You are feeling ill tonight, and are unable to kill anyone.')
elif var.ANGRY_WOLVES and nick_role in ("wolf", "werecrow", "alpha wolf", "werekitten"): elif var.ANGRY_WOLVES and nick_role in ("wolf", "werecrow", "alpha wolf", "werekitten"):
pm(cli, nick, 'You are \u0002angry\u0002 tonight, and may kill two targets by using "kill <nick1> and <nick2>".') pm(cli, nick, 'You are \u0002angry\u0002 tonight, and may kill two targets by using "kill <nick1> and <nick2>".')
angry_alpha = ' <nick>'
if var.ALPHA_ENABLED and nick_role == "alpha wolf" and nick not in var.ALPHA_WOLVES: if var.ALPHA_ENABLED and nick_role == "alpha wolf" and nick not in var.ALPHA_WOLVES:
pm(cli, nick, ('You may use "bite{0}" tonight in order to turn the wolves\' target into a wolf instead of killing them. ' + pm(cli, nick, ('You may use "bite <nick>" tonight in order to turn the wolves\' target into a wolf instead of killing them. ' +
'They will turn into a wolf in {1} night{2}.').format(angry_alpha, var.ALPHA_WOLF_NIGHTS, 's' if var.ALPHA_WOLF_NIGHTS > 1 else '')) 'They will turn into a wolf in {0} night{1}.').format(var.ALPHA_WOLF_NIGHTS, 's' if var.ALPHA_WOLF_NIGHTS > 1 else ''))
elif actor_role == "minion": elif actor_role == "minion":
wolves = var.list_players(var.WOLF_ROLES) wolves = var.list_players(var.WOLF_ROLES)
random.shuffle(wolves) random.shuffle(wolves)
@ -3948,7 +4090,9 @@ def retract(cli, nick, chan, rest):
if chan == nick: # PM, use different code if chan == nick: # PM, use different code
role = var.get_role(nick) role = var.get_role(nick)
if role not in ("wolf", "werecrow", "alpha wolf", "werekitten", "hunter") and nick not in var.VENGEFUL_GHOSTS.keys(): if role not in var.WOLF_ROLES + ["hunter"] and nick not in var.VENGEFUL_GHOSTS.keys():
return
if role == "wolf cub":
return return
if var.PHASE != "night": if var.PHASE != "night":
return return
@ -4446,11 +4590,9 @@ def totem(cli, nick, chan, rest):
return return
pm(cli, nick, ("You have given a totem{0} to \u0002{1}\u0002.").format(type, victim)) pm(cli, nick, ("You have given a totem{0} to \u0002{1}\u0002.").format(type, victim))
totem = var.TOTEMS[nick] totem = var.TOTEMS[nick]
if totem == "death": if totem == "death": # this totem stacks
if victim not in var.DYING: var.DEATH_TOTEM.append((nick, victim))
var.DYING.append(victim) elif totem == "protection": # this totem stacks
elif totem == "protection":
if victim not in var.PROTECTED:
var.PROTECTED.append(victim) var.PROTECTED.append(victim)
elif totem == "revealing": elif totem == "revealing":
if victim not in var.REVEALED: if victim not in var.REVEALED:
@ -4508,11 +4650,12 @@ def immunize(cli, nick, chan, rest):
if not victim: if not victim:
return return
victim = choose_target(nick, victim) victim = choose_target(nick, victim)
vrole = var.get_role(victim)
if check_exchange(cli, nick, victim): if check_exchange(cli, nick, victim):
return return
pm(cli, nick, "You have given an immunization to \u0002{0}\u0002.".format(victim)) pm(cli, nick, "You have given an immunization to \u0002{0}\u0002.".format(victim))
lycan = False lycan = False
if var.get_role(victim) == "lycan": if vrole == "lycan":
lycan = True lycan = True
lycan_message = ("You feel as if a curse has been lifted from you... It seems that your lycanthropy is cured " + lycan_message = ("You feel as if a curse has been lifted from you... It seems that your lycanthropy is cured " +
"and you will no longer become a werewolf if targeted by the wolves!") "and you will no longer become a werewolf if targeted by the wolves!")
@ -4528,8 +4671,9 @@ def immunize(cli, nick, chan, rest):
# naturally, we would want to mimic that behavior here, and what better way of indicating that things got worse than # naturally, we would want to mimic that behavior here, and what better way of indicating that things got worse than
# by making the turning happen a night earlier? :) # by making the turning happen a night earlier? :)
var.BITTEN[victim] -= 1 var.BITTEN[victim] -= 1
lycan_message = ("You have a brief flashback to your dream last night. " + lycan_message = ("You have a brief flashback to {0} last night. " +
"The event quickly subsides, but a lingering thought remains in your mind...") "The event quickly subsides, but a lingering thought remains in your mind...").format(
"the events of" if vrole == "guardian angel" else "your dream")
else: else:
lycan_message = "You don't feel any different..." lycan_message = "You don't feel any different..."
var.IMMUNIZED.add(victim) var.IMMUNIZED.add(victim)
@ -4540,7 +4684,25 @@ def immunize(cli, nick, chan, rest):
def get_bitten_message(nick): def get_bitten_message(nick):
time_left = var.BITTEN[nick] time_left = var.BITTEN[nick]
role = var.get_role(nick)
message = "" message = ""
if role == "guardian angel":
if time_left <= 1:
message = ("After returning from last night's activities, you felt another wave of pain, this time on your back. " +
"Your wings grew larger and you can now fly faster and farther than ever before. Along with " +
"the size change, their color shifted from pure white to a midnight black. You didn't spend much " +
"time thinking on what happened, as you were tired and went to sleep shortly thereafter.")
elif time_left == 2:
message = ("Despite the gloves, it seems that the villagers have been keeping their distance from you as of late. " +
"None of them seem to know about your changes, so the change of behavior greatly angers you. You're " +
"doing just as good a job as ever, and if anything the changes make you MORE effective and powerful. " +
"These thoughts lingered for the rest of last night until you finally drifted off to an uneasy sleep.")
else:
message = ("As you were out last night, you felt a painful sensation as your hands grew very sharp claws. " +
"You figure they are now sharp enough to cut through most anything, but to avoid alarming the village " +
"you decide to fashion some gloves and wear them around from now on in an attempt to show nothing is " +
"happening.")
else:
if time_left <= 1: if time_left <= 1:
message = ("You had the same dream again, but this time YOU were the pursuer. You smell fear from your quarry " + message = ("You had the same dream again, but this time YOU were the pursuer. You smell fear from your quarry " +
"as you give an exhilerating chase, going only half your speed in order to draw out the fun. " + "as you give an exhilerating chase, going only half your speed in order to draw out the fun. " +
@ -5030,6 +5192,7 @@ def transition_night(cli):
var.REVEALED = [] var.REVEALED = []
var.TOBESILENCED = [] var.TOBESILENCED = []
var.IMPATIENT = [] var.IMPATIENT = []
var.DEATH_TOTEM = []
var.PACIFISTS = [] var.PACIFISTS = []
var.INFLUENTIAL = [] var.INFLUENTIAL = []
var.TOBELYCANTHROPES = [] var.TOBELYCANTHROPES = []
@ -5083,18 +5246,33 @@ def transition_night(cli):
if var.BITTEN[chump] <= 0: if var.BITTEN[chump] <= 0:
# now a wolf # now a wolf
newrole = "wolf"
if chumprole == "guardian angel":
pm(cli, chump, ("As the moonlight filters through your window, you think back on the past few days. " +
"Your power has been growing, but the villagers you protect subconsciously detected " +
"your shift and have been keeping more distant from you. Grinning with wicked resolve, " +
"you vow to show them what fools they have been as you take to the skies once more " +
"with an unholy vengeance. Soon they will know true fear."))
# fallen angels also automatically gain the assassin template if they don't already have it
# by default GA can never be assassin, but this guards against non-default cases
newrole = "fallen angel"
if chump not in var.ROLES["assassin"]:
var.ROLES["assassin"].append(chump)
debuglog("{0} ({1}) TURNED FALLEN ANGEL".format(chump, chumprole))
else:
pm(cli, chump, ("As you prepare for bed, you watch in horror as your body starts growing a coat of fur! " + pm(cli, chump, ("As you prepare for bed, you watch in horror as your body starts growing a coat of fur! " +
"Sudden realization hits you as you grin with your now muzzled face; that mysterious bite " + "Sudden realization hits you as you grin with your now muzzled face; that mysterious bite " +
"earlier slowly changed you into a werewolf! You feel bigger, stronger, faster, and ready to " + "earlier slowly changed you into a werewolf! You feel bigger, stronger, faster, and ready to " +
"seize the night as you stealthily exit your home and search for the rest of your pack...")) "seize the night as you stealthily exit your home and search for the rest of your pack..."))
debuglog("{0} ({1}) TURNED WOLF".format(chump, chumprole))
var.BITTEN_ROLES[chump] = chumprole var.BITTEN_ROLES[chump] = chumprole
var.ROLES[chumprole].remove(chump) var.ROLES[chumprole].remove(chump)
var.ROLES["wolf"].append(chump) var.ROLES[newrole].append(chump)
var.FINAL_ROLES[chump] = "wolf" var.FINAL_ROLES[chump] = newrole
for wolf in var.list_players(var.WOLFCHAT_ROLES): for wolf in var.list_players(var.WOLFCHAT_ROLES):
if wolf != chump: if wolf != chump:
pm(cli, wolf, "\u0002{0}\u0002 is now a \u0002wolf\u0002!".format(chump)) # no need for a/an since newrole is either wolf or fallen angel
debuglog("{0} ({1}) TURNED WOLF".format(chump, chumprole)) pm(cli, wolf, "\u0002{0}\u0002 is now a \u0002{1}\u0002!".format(chump, newrole))
# convert amnesiac and kill village elder if necessary # convert amnesiac and kill village elder if necessary
if var.NIGHT_COUNT == var.AMNESIAC_NIGHTS: if var.NIGHT_COUNT == var.AMNESIAC_NIGHTS:
@ -5185,6 +5363,12 @@ def transition_night(cli):
'to turn them into a cursed villager, so the seer sees them as wolf. Act quickly, as ' + 'to turn them into a cursed villager, so the seer sees them as wolf. Act quickly, as ' +
'your curse applies as soon as you cast it! Only detectives can reveal your true identity, ' + 'your curse applies as soon as you cast it! Only detectives can reveal your true identity, ' +
'seers will see you as a regular villager.').format(cursed)) 'seers will see you as a regular villager.').format(cursed))
elif role == "wolf mystic":
pm(cli, wolf, ('You are a \u0002wolf mystic\u0002. Each night you divine the number of alive good villagers ' +
'who have a special role. You may also use "kill <nick>" to kill a villager.'))
elif role == "fallen angel":
pm(cli, wolf, ('You are a \u0002fallen angel\u0002. Your sharp claws will rend any protection the villagers ' +
'may have, and will likely kill living guardians as well. Use "kill <nick>" to kill a villager.'))
else: else:
# catchall in case we forgot something above # catchall in case we forgot something above
an = 'n' if role.startswith(("a", "e", "i", "o", "u")) else "" an = 'n' if role.startswith(("a", "e", "i", "o", "u")) else ""
@ -5212,17 +5396,20 @@ def transition_night(cli):
pl[i] = player + " (cursed)" pl[i] = player + " (cursed)"
pm(cli, wolf, "Players: " + ", ".join(pl)) pm(cli, wolf, "Players: " + ", ".join(pl))
if role == "wolf mystic":
# if adding this info to !myrole, you will need to save off this count so that they can't get updated info until the next night
# # of special villagers = # of players - # of villagers - # of wolves - # of neutrals
numvills = len(ps) - len(var.list_players(var.WOLFTEAM_ROLES)) - len(var.list_players(("villager", "vengeful ghost", "time lord", "amnesiac", "village elder", "lycan"))) - len(var.list_players(var.TRUE_NEUTRAL_ROLES))
pm(cli, wolf, "There are \u0002{0}\u0002 special villager{1} still alive.".format(numvills, "s" if numvills != 1 else ""))
if wolf in var.WOLF_GUNNERS.keys() and var.WOLF_GUNNERS[wolf] > 0: if wolf in var.WOLF_GUNNERS.keys() and var.WOLF_GUNNERS[wolf] > 0:
pm(cli, wolf, "You have a \u0002gun\u0002 with {0} bullet{1}.".format(var.WOLF_GUNNERS[wolf], "s" if var.WOLF_GUNNERS[wolf] > 1 else "")) pm(cli, wolf, "You have a \u0002gun\u0002 with {0} bullet{1}.".format(var.WOLF_GUNNERS[wolf], "s" if var.WOLF_GUNNERS[wolf] > 1 else ""))
angry_alpha = ''
if var.DISEASED_WOLVES: if var.DISEASED_WOLVES:
pm(cli, wolf, 'You are feeling ill tonight, and are unable to kill anyone.') pm(cli, wolf, 'You are feeling ill tonight, and are unable to kill anyone.')
elif var.ANGRY_WOLVES and role in ("wolf", "werecrow", "alpha wolf", "werekitten"): elif var.ANGRY_WOLVES and role in var.WOLF_ROLES and role != "wolf cub":
pm(cli, wolf, 'You are \u0002angry\u0002 tonight, and may kill two targets by using "kill <nick1> and <nick2>".') pm(cli, wolf, 'You are \u0002angry\u0002 tonight, and may kill two targets by using "kill <nick1> and <nick2>".')
angry_alpha = ' <nick>'
if var.ALPHA_ENABLED and role == "alpha wolf" and wolf not in var.ALPHA_WOLVES: if var.ALPHA_ENABLED and role == "alpha wolf" and wolf not in var.ALPHA_WOLVES:
pm(cli, wolf, ('You may use "bite{0}" tonight in order to turn the wolves\' target into a wolf instead of killing them. ' + pm(cli, wolf, ('You may use "bite <nick>" tonight in order to turn the wolves\' target into a wolf instead of killing them. ' +
'They will turn into a wolf in {1} night{2}.').format(angry_alpha, var.ALPHA_WOLF_NIGHTS, 's' if var.ALPHA_WOLF_NIGHTS > 1 else '')) 'They will turn into a wolf in {0} night{1}.').format(var.ALPHA_WOLF_NIGHTS, 's' if var.ALPHA_WOLF_NIGHTS > 1 else ''))
for seer in var.list_players(("seer", "oracle", "augur")): for seer in var.list_players(("seer", "oracle", "augur")):
pl = ps[:] pl = ps[:]
@ -5335,6 +5522,16 @@ def transition_night(cli):
else: else:
pm(cli, drunk, "You are the \u0002village drunk\u0002.") pm(cli, drunk, "You are the \u0002village drunk\u0002.")
for mystic in var.ROLES["mystic"]:
if mystic in var.PLAYERS and not is_user_simple(mystic):
pm(cli, mystic, ("You are the \u0002mystic\u0002. Each night you divine the number of evil " +
"villagers (including wolves) that are still alive."))
else:
pm(cli, mystic, "You are the \u0002mystic\u0002.")
# if adding this info to !myrole, you will need to save off this count so that they can't get updated info until the next night
numevil = len(var.list_players(var.WOLFTEAM_ROLES))
pm(cli, mystic, "There are \u0002{0}\u0002 evil villager{1} still alive.".format(numevil, "s" if numevil != 1 else ""))
max_totems = {} max_totems = {}
for sham in var.TOTEM_ORDER: for sham in var.TOTEM_ORDER:
max_totems[sham] = 0 max_totems[sham] = 0
@ -5821,6 +6018,7 @@ def start(cli, nick, chan, forced = False, restart = ""):
var.FINAL_ROLES = {} var.FINAL_ROLES = {}
var.ORIGINAL_LOVERS = {} var.ORIGINAL_LOVERS = {}
var.IMPATIENT = [] var.IMPATIENT = []
var.DEATH_TOTEM = []
var.PACIFISTS = [] var.PACIFISTS = []
var.INFLUENTIAL = [] var.INFLUENTIAL = []
var.LYCANTHROPES = [] var.LYCANTHROPES = []
@ -5849,6 +6047,7 @@ def start(cli, nick, chan, forced = False, restart = ""):
var.BITTEN_ROLES = {} var.BITTEN_ROLES = {}
var.CHARMERS = set() var.CHARMERS = set()
var.CHARMED = set() var.CHARMED = set()
var.ACTIVE_PROTECTIONS = defaultdict(list)
for role, count in addroles.items(): for role, count in addroles.items():
if role in var.TEMPLATE_RESTRICTIONS.keys(): if role in var.TEMPLATE_RESTRICTIONS.keys():