♪ come, children, come out to the forest ♫

This adds the new piper role, whose goal is to charm all the other
players to win. The charmed players are told who else is charmed, every
night, and must find out the piper and lynch him to win. The piper is a
win stealer, and takes precedence over monster. They can select either
one or two targets, but unless there is exactly one person left to be
charmed, they must pick two targets.
This commit is contained in:
Vgr E.Barry 2015-05-27 00:06:40 -04:00
parent c49c2647d8
commit 04c814ea16
2 changed files with 140 additions and 6 deletions

View File

@ -185,6 +185,7 @@ ROLE_GUIDE = {# village roles
"jester" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"monster" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
"amnesiac" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 ),
"piper" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
# templates
"cursed villager" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 ),
"gunner" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 ),
@ -203,13 +204,14 @@ WOLFCHAT_ROLES = WOLF_ROLES + ["traitor", "hag", "sorcerer", "warlock"]
# Wins with the wolves, even if the roles are not necessarily wolves themselves
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)
TRUE_NEUTRAL_ROLES = ["crazed shaman", "fool", "jester", "monster", "clone"]
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)
AMNESIAC_BLACKLIST = ["monster", "minion", "matchmaker", "clone", "doctor", "villager", "cultist"]
AMNESIAC_BLACKLIST = ["monster", "minion", "matchmaker", "clone", "doctor", "villager", "cultist", "piper"]
# These roles are seen as wolf by the seer/oracle
SEEN_WOLF = WOLF_ROLES + ["monster", "mad scientist"]
# These are seen as the default role (or villager) when seen by seer (this overrides SEEN_WOLF)
SEEN_DEFAULT = ["traitor", "hag", "sorcerer", "village elder", "time lord", "villager", "cultist", "minion", "vengeful ghost", "lycan", "clone", "fool", "jester", "werekitten", "warlock"]
SEEN_DEFAULT = ["traitor", "hag", "sorcerer", "village elder", "time lord", "villager", "cultist", "minion",
"vengeful ghost", "lycan", "clone", "fool", "jester", "werekitten", "warlock", "piper"]
# The roles in here are considered templates and will be applied on TOP of other roles. The restrictions are a list of roles that they CANNOT be applied to
# NB: if you want a template to apply to everyone, list it here but make the restrictions an empty list. Templates not listed here are considered full roles instead
@ -367,6 +369,8 @@ def del_player(pname):
del BITTEN[pname]
if pname in BITTEN_ROLES:
del BITTEN_ROLES[pname]
if pname in CHARMED:
CHARMED.remove(pname)
def get_templates(nick):
tpl = []

View File

@ -2090,9 +2090,11 @@ def stop_game(cli, winner = "", abort = False):
if winner == "wolves":
won = True
elif rol in var.TRUE_NEUTRAL_ROLES:
# true neutral roles never have a team win (with exception of monsters), only individual wins
# true neutral roles never have a team win (with exception of monsters and pipers), only individual wins
if winner == "monsters" and rol == "monster":
won = True
if winner == "pipers" and rol == "piper":
won = True
elif rol in ("amnesiac", "vengeful ghost") and splr not in var.VENGEFUL_GHOSTS:
if var.DEFAULT_ROLE == "villager" and winner == "villagers":
won = True
@ -2227,6 +2229,7 @@ def chk_win(cli, end_game = True):
lrealwolves = len(var.list_players(var.WOLF_ROLES)) - cubs
monsters = len(var.ROLES["monster"]) if "monster" in var.ROLES else 0
traitors = len(var.ROLES["traitor"]) if "traitor" in var.ROLES else 0
lpipers = len(var.ROLES["piper"]) if "piper" in var.ROLES else 0
if var.PHASE == "day":
for p in var.WOUNDED:
try:
@ -2252,6 +2255,11 @@ def chk_win(cli, end_game = True):
if lpl < 1:
message = "Game over! There are no players remaining."
winner = "none"
elif lpipers and lpl - lpipers == len(var.CHARMED - set(var.ROLES["piper"])):
winner = "pipers"
message = ("Game over! Everyone has fallen victim to the charms of the " +
"piper{0}. The piper{0} lead{1} the villagers away from the village, " +
"never to return...").format("s" if lpipers > 1 else "", "s" if lpipers == 1 else "")
elif lwolves == lpl / 2:
if monsters > 0:
plural = "s" if monsters > 1 else ""
@ -2309,6 +2317,9 @@ def chk_win(cli, end_game = True):
vroles = [role for role in var.ROLES.keys() if var.ROLES[role] and role not in (var.WOLFTEAM_ROLES + var.TRUE_NEUTRAL_ROLES + list(var.TEMPLATE_RESTRICTIONS.keys()))]
for plr in var.list_players(vroles):
players.append("{0} ({1})".format(plr, var.get_role(plr)))
elif winner == "pipers":
for plr in var.ROLES["piper"]:
players.append("{0} ({1})".format(plr, var.get_role(plr)))
debuglog("WIN:", winner)
debuglog("PLAYERS:", ", ".join(players))
cli.msg(chan, message)
@ -3068,6 +3079,12 @@ def on_nick(cli, oldnick, nick):
if prefix in var.CURSED:
var.CURSED.remove(prefix)
var.CURSED.append(nick)
if prefix in var.CHARMERS:
var.CHARMERS.remove(prefix)
var.CHARMERS.add(nick)
if prefix in var.CHARMED:
var.CHARMED.remove(prefix)
var.CHARMED.add(nick)
with var.GRAVEYARD_LOCK: # to be safe
if prefix in var.LAST_SAID_TIME.keys():
var.LAST_SAID_TIME[nick] = var.LAST_SAID_TIME.pop(prefix)
@ -3828,12 +3845,14 @@ def chk_nightdone(cli):
# TODO: alphabetize and/or arrange sensibly
actedcount = len(var.SEEN + list(var.HVISITED.keys()) + list(var.GUARDED.keys()) +
list(var.KILLS.keys()) + list(var.OTHER_KILLS.keys()) +
list(var.OBSERVED.keys()) + var.PASSED + var.HEXED + var.SHAMANS + var.CURSED)
list(var.OBSERVED.keys()) + var.PASSED + var.HEXED + var.SHAMANS +
var.CURSED + list(var.CHARMERS))
nightroles = (var.ROLES["seer"] + var.ROLES["oracle"] + var.ROLES["harlot"] +
var.ROLES["bodyguard"] + var.ROLES["guardian angel"] + var.ROLES["wolf"] +
var.ROLES["werecrow"] + var.ROLES["alpha wolf"] + var.ROLES["sorcerer"] + var.ROLES["hunter"] +
list(var.VENGEFUL_GHOSTS.keys()) + var.ROLES["hag"] + var.ROLES["shaman"] +
var.ROLES["crazed shaman"] + var.ROLES["augur"] + var.ROLES["werekitten"] + var.ROLES["warlock"])
var.ROLES["crazed shaman"] + var.ROLES["augur"] + var.ROLES["werekitten"] +
var.ROLES["warlock"] + var.ROLES["piper"])
if var.FIRST_NIGHT:
actedcount += len(var.MATCHMAKERS + list(var.CLONED.keys()))
nightroles += var.ROLES["matchmaker"] + var.ROLES["clone"]
@ -5214,6 +5233,94 @@ def clone(cli, nick, chan, rest):
debuglog("{0} ({1}) CLONE: {2} ({3})".format(nick, var.get_role(nick), victim, var.get_role(victim)))
chk_nightdone(cli)
@cmd("charm", chan=False, pm=True, game=True, playing=True, roles=("piper",))
def charm(cli, nick, chan, rest):
"""Charm a player, slowly leading to your win!"""
if var.PHASE != "night":
pm(cli, nick, "You may only charm other players during the night.")
return
if nick in var.CHARMERS:
pm(cli, nick, "You have already charmed players for tonight.")
return
if nick in var.SILENCED:
pm(cli, nick, "You are silenced, and are unable to use any special powers.")
return
pieces = re.split(" +",rest)
victim = pieces[0]
if len(pieces) > 1:
if len(pieces) > 2 and pieces[1].lower() == "and":
victim2 = pieces[2]
else:
victim2 = pieces[1]
else:
victim2 = None
victim = get_victim(cli, nick, victim, False, True)
if not victim:
return
if victim2 is not None:
victim2 = get_victim(cli, nick, victim2, False, True)
if victim == victim2:
pm(cli, nick, "You must choose two different people.")
return
if nick in (victim, victim2):
pm(cli, nick, "You may not charm yourself.")
return
if victim in var.CHARMED or victim2 in var.CHARMED:
if victim in var.CHARMED and victim2 and victim2 in var.CHARMED:
pm(cli, nick, "\u0002{0}\u0002 and \u0002{1}\u0002 are already charmed!".format(victim, victim2))
return
if (len(var.list_players()) - len(var.ROLES["piper"]) - len(var.CHARMED) - 2 >= 0 or
victim in var.CHARMED and not victim2):
pm(cli, nick, "\u0002{0}\u0002 is already charmed!".format(victim in var.CHARMED and victim or victim2))
return
elif not victim2 and len(var.list_players()) - len(var.ROLES["piper"]) - len(var.CHARMED) - 2 >= 0:
pm(cli, nick, "Not enough parameters.")
return
var.CHARMERS.add(nick)
var.CHARMED.add(victim)
if victim2:
var.CHARMED.add(victim2)
message = ("You hear the sweet tones of a flute coming from outside your window... " +
"You inexorably walk outside and find yourself stranded away from " +
"the village. You find out that \u0002{0}\u0002 {1} also charmed!")
simple_message = "You are now charmed. Other charmed players: \u0002{0}\u0002"
pm(cli, nick, "You have charmed \u0002{0}\u0002{1}.".format(victim, victim2 and " and \u0002{0}\u0002".format(victim2) or ""))
for vict in (victim, victim2):
if vict and vict in var.PLAYERS:
msg = is_user_simple(vict) and simple_message or message
pm(cli, vict, msg.format("\u0002, \u0002".join(var.CHARMED - {vict}),
"is" if len(var.CHARMED) == 2 else "are"))
for vict in var.CHARMED:
if vict in (victim, victim2):
continue
message = victim2 and "\u0002{0}\u0002 and \u0002{1}\u0002 are" or "\u0002{0}\u0002 is"
pm(cli, vict, (message + " now charmed! All charmed players: " +
"\u0002{2}\u0002").format(victim, victim2,
"\u0002, \u0002".join(var.CHARMED - {vict})))
if victim2:
debuglog("{0} ({1}) CHARM {2} ({3}) && {4} ({5})".format(nick, var.get_role(nick),
victim, var.get_role(victim),
victim2, var.get_role(victim2)))
else:
debuglog("{0} ({1}) CHARM {2} ({3})".format(nick, var.get_role(nick),
victim, var.get_role(victim)))
chk_nightdone(cli)
@hook("featurelist") # For multiple targets with PRIVMSG
def getfeatures(cli, nick, *rest):
for r in rest:
@ -5355,6 +5462,7 @@ def transition_night(cli):
var.SHAMANS = []
var.PASSED = [] # list of hunters that have chosen not to kill
var.OBSERVED = {} # those whom werecrows have observed
var.CHARMERS = set() # pipers who have charmed
var.HVISITED = {}
var.ASLEEP = []
var.DYING = []
@ -5873,6 +5981,22 @@ def transition_night(cli):
pm(cli, ass, "You are an \u0002assassin\u0002.")
pm(cli, ass, "Players: " + ", ".join(pl))
for piper in var.ROLES["piper"]:
pl = ps[:]
random.shuffle(pl)
pl.remove(piper)
for charmed in var.CHARMED:
pl.remove(charmed)
if piper in var.PLAYERS and not is_user_simple(piper):
pm(cli, piper, ('You are a \u0002piper\u0002. You must select two players ' +
'to charm each night. The charmed players will know each ' +
'other, but not who charmed them. You win when all other ' +
'players are charmed. Use "charm <nick1> and <nick2>" to ' +
'select the players to charm.'))
else:
pm(cli, piper, "You are a \u0002piper\u0002.")
pm(cli, piper, "Players: " + ", ".join(pl))
if var.FIRST_NIGHT:
for mm in var.ROLES["matchmaker"]:
pl = ps[:]
@ -6168,6 +6292,8 @@ def start(cli, nick, chan, forced = False, restart = ""):
var.BITTEN = {}
var.BITE_PREFERENCES = {}
var.BITTEN_ROLES = {}
var.CHARMERS = set()
var.CHARMED = set()
for role, count in addroles.items():
if role in var.TEMPLATE_RESTRICTIONS.keys():
@ -7565,6 +7691,10 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS:
if var.IMMUNIZED:
output.append("\u0002immunized:\u0002 {0}".format(', '.join(var.IMMUNIZED)))
# get charmed players
if var.CHARMED:
output.append("\u0002charmed players\u0002: {0}".format(', '.join(var.CHARMED)))
if chan == nick:
pm(cli, nick, var.break_long_message(output, ' | '))
else: