Add turncoat role

Turncoats initially start off village-aligned, but can change which team
they're on by using the !side command. Turncoats cannot change sides two
nights in a row, and can use !pass if they don't wish to change sides on
any given night.

Seers and detectives see turncoats as turncoats, augurs see them as
having a grey aura. I was initially toying with det and augur seeing
them as their currently-chosen side and for mystics of the opposite team
to detect them, but since they can flip-flop an unlimited number of
times a night that info would be useless at best and misleading at
worst.
This commit is contained in:
skizzerz 2015-07-14 21:59:40 -05:00
parent 0286a55e6b
commit c0575bd460
2 changed files with 93 additions and 6 deletions

View File

@ -188,6 +188,7 @@ ROLE_GUIDE = {# village roles
"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 ),
"turncoat" : ( 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 ),
@ -205,8 +206,8 @@ WOLF_ROLES = ["wolf", "alpha wolf", "werecrow", "wolf cub", "werekitten", "wolf
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", "piper"]
# These roles either steal away wins or can otherwise win with any team
TRUE_NEUTRAL_ROLES = ["crazed shaman", "fool", "jester", "monster", "clone", "piper", "turncoat"]
# 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"]
# These roles are seen as wolf by the seer/oracle

View File

@ -1794,11 +1794,13 @@ 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 and pipers), only individual wins
# most true neutral roles never have a team win, only individual wins
if winner == "monsters" and rol == "monster":
won = True
if winner == "pipers" and rol == "piper":
won = True
if rol == "turncoat" and var.TURNCOATS[splr][0] != "none":
won = (winner == var.TURNCOATS[splr][0])
elif rol in ("amnesiac", "vengeful ghost") and splr not in var.VENGEFUL_GHOSTS:
if var.DEFAULT_ROLE == "villager" and winner == "villagers":
won = True
@ -2657,7 +2659,7 @@ def on_nick(cli, oldnick, nick):
dictvar.update(kvp)
if prefix in dictvar.keys():
del dictvar[prefix]
for dictvar in (var.VENGEFUL_GHOSTS, var.TOTEMS, var.FINAL_ROLES, var.BITTEN, var.GUNNERS, var.DOCTORS):
for dictvar in (var.VENGEFUL_GHOSTS, var.TOTEMS, var.FINAL_ROLES, var.BITTEN, var.GUNNERS, var.DOCTORS, var.TURNCOATS):
if prefix in dictvar.keys():
dictvar[nick] = dictvar[prefix]
del dictvar[prefix]
@ -3709,6 +3711,7 @@ def chk_nightdone(cli):
return
# TODO: alphabetize and/or arrange sensibly
pl = var.list_players()
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 + list(var.SHAMANS.keys()) +
@ -3735,6 +3738,18 @@ def chk_nightdone(cli):
# but remove all instances of their name if they are silenced
nightroles = [p for p in nightroles if p not in var.SILENCED]
# add in turncoats who should be able to act -- if they passed they're already in var.PASSED
# but if they can act they're in var.TURNCOATS where the second tuple item is the current night
# (if said tuple item is the previous night, then they are not allowed to act tonight)
for tc, tu in var.TURNCOATS.items():
if tc not in pl:
continue
if tu[1] == var.NIGHT_COUNT:
nightroles.append(tc)
actedcount += 1
elif tu[1] < var.NIGHT_COUNT - 1:
nightroles.append(tc)
playercount = len(nightroles) + var.ACTED_EXTRA
if var.PHASE == "night" and actedcount >= playercount:
@ -3973,6 +3988,10 @@ def check_exchange(cli, actor, nick):
elif actor_role == "warlock":
if actor in var.CURSED:
var.CURSED.remove(actor)
elif actor_role == "turncoat":
if actor in var.PASSED:
var.PASSED.remove(actor)
del var.TURNCOATS[actor]
if nick_role == "amnesiac":
nick_role = var.FINAL_ROLES[nick]
@ -4036,6 +4055,10 @@ def check_exchange(cli, actor, nick):
elif nick_role == "warlock":
if nick in var.CURSED:
var.CURSED.remove(nick)
elif nick_role == "turncoat":
if nick in var.PASSED:
var.PASSED.remove(nick)
del var.TURNCOATS[nick]
var.FINAL_ROLES[actor] = nick_role
@ -4106,6 +4129,8 @@ def check_exchange(cli, actor, nick):
wolves = var.list_players(var.WOLF_ROLES)
random.shuffle(wolves)
pm(cli, actor, "Wolves: " + ", ".join(wolves))
elif nick_role == "turncoat":
var.TURNCOATS[actor] = ("none", -1)
if actor_role == "clone":
pm(cli, nick, "You are cloning \u0002{0}\u0002.".format(actor_target))
@ -4147,6 +4172,8 @@ def check_exchange(cli, actor, nick):
wolves = var.list_players(var.WOLF_ROLES)
random.shuffle(wolves)
pm(cli, nick, "Wolves: " + ", ".join(wolves))
elif actor_role == "turncoat":
var.TURNCOATS[nick] = ("none", -1)
return True
return False
@ -4780,10 +4807,19 @@ def bite_cmd(cli, nick, chan, rest):
pm(cli, nick, "You have chosen to bite tonight. Whomever the wolves select to be killed tonight will be bitten instead.")
debuglog("{0} ({1}) BITE: {2} ({3})".format(nick, var.get_role(nick), victim if victim else "wolves' target", vrole if vrole else "unknown"))
@cmd("pass", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("hunter","harlot","bodyguard","guardian angel","turncoat"))
@cmd("pass", chan=False, pm=True, playing=True, phases=("night",), roles=("hunter","harlot","bodyguard","guardian angel","turncoat"))
def pass_cmd(cli, nick, chan, rest):
"""Decline to use your special power for that night."""
nickrole = var.get_role(nick)
# turncoats can change roles and pass even if silenced
if nickrole != "turncoat" and nick in var.SILENCED:
if chan == nick:
pm(cli, nick, "You have been silenced, and are unable to use any special powers.")
else:
cli.notice(nick, "You have been silenced, and are unable to use any special powers.")
return
if nickrole == "hunter":
if nick in var.OTHER_KILLS.keys():
del var.OTHER_KILLS[nick]
@ -4804,11 +4840,43 @@ def pass_cmd(cli, nick, chan, rest):
pm(cli, nick, "You are already protecting someone tonight.")
return
var.GUARDED[nick] = None
pm(cli, nick, "you have chosen not to guard anyone tonight.")
pm(cli, nick, "You have chosen not to guard anyone tonight.")
elif nickrole == "turncoat":
if var.TURNCOATS[nick][1] == var.NIGHT_COUNT:
# theoretically passing would revert them to how they were before, but
# we aren't tracking that, so just tell them to change it back themselves.
pm(cli, nick, ("You have already changed sides tonight. Use" +
'"side villagers" or "side wolves" to modify your selection.'))
return
pm(cli, nick, "You have decided to not change sides tonight.")
if var.TURNCOATS[nick][1] == var.NIGHT_COUNT - 1:
# don't add to var.PASSED since we aren't counting them anyway for nightdone
# let them still use !pass though to make them feel better or something
return
if nick not in var.PASSED:
var.PASSED.append(nick)
debuglog("{0} ({1}) PASS".format(nick, var.get_role(nick)))
chk_nightdone(cli)
@cmd("side", chan=False, pm=True, playing=True, phases=("night",), roles=("turncoat",))
def change_sides(cli, nick, chan, rest, sendmsg=True):
if var.TURNCOATS[nick][1] == var.NIGHT_COUNT - 1:
pm(cli, nick, "You have changed sides yesterday night, and may not do so again tonight.")
return
team = re.split(" +", rest)[0]
team, _ = complete_match(team, ("villagers", "wolves"))
if not team:
pm(cli, nick, "Please specify which team you wish to side with, villagers or wolves.")
return
pm(cli, nick, "You are now siding with \u0002{0}\u0002.".format(team))
var.TURNCOATS[nick] = (team, var.NIGHT_COUNT)
debuglog("{0} ({1}) SIDE {2}".format(nick, var.get_role(nick), team))
chk_nightdone(cli)
@cmd("choose", "match", chan=False, pm=True, playing=True, phases=("night",), roles=("matchmaker",))
def choose(cli, nick, chan, rest, sendmsg=True):
"""Select two players to fall in love. You may select yourself as one of the lovers."""
@ -5786,6 +5854,19 @@ def transition_night(cli):
pm(cli, piper, "You are a \u0002piper\u0002.")
pm(cli, piper, "Players: " + ", ".join(pl))
for turncoat in var.ROLES["turncoat"]:
# they start out as unsided, but can change n1
if turncoat not in var.TURNCOATS:
var.TURNCOATS[turncoat] = ("none", -1)
if turncoat in var.PLAYERS and not is_user_simple(turncoat):
pm(cli, turncoat, ('You are a \u0002turncoat\u0002. You can change which ' +
'team you\'re siding with every other night. Use ' +
'"side villagers" or "side wolves" to select your team. ' +
'You are currently siding with \u0002{0}\u0002.').format(var.TURNCOATS[turncoat][0]))
else:
pm(cli, turncoat, 'You are a \u0002turncoat\u0002. Current side: \u0002{0}\u0002.'.format(var.TURNCOATS[turncoat][0]))
if var.FIRST_NIGHT:
for mm in var.ROLES["matchmaker"]:
pl = ps[:]
@ -6090,6 +6171,7 @@ def start(cli, nick, chan, forced = False, restart = ""):
var.CHARMERS = set()
var.CHARMED = set()
var.ACTIVE_PROTECTIONS = defaultdict(list)
var.TURNCOATS = {}
for role, count in addroles.items():
if role in var.TEMPLATE_RESTRICTIONS.keys():
@ -7126,6 +7208,10 @@ def myrole(cli, nick, chan, rest):
wolves.append(player)
pm(cli, nick, "Original wolves: " + ", ".join(wolves))
# Remind turncoats of their side
if role == "turncoat":
pm(cli, nick, "Current side: \u0002{0}\u0002.".format(var.TURNCOATS[nick]))
# Check for gun/bullets
if nick not in var.ROLES["amnesiac"] and nick in var.GUNNERS and var.GUNNERS[nick]:
role = "gunner"