Add 2 new roles: doctor and alpha wolf

Doctor is on the village team, and has a number of immunizations they
can give out during the day. An immunized lycan reverts a normal
villager, and anyone that is immunized will die instead of turning into
a wolf if they are bitten by the alpha wolf. Beware, however, because
attempting to immunize someone already bitten will simply speed up the
process of them becoming a wolf!

Alpha wolf has a once-per-game ability where they can bite the wolves'
target instead of killing them following the death of a werewolf during
the day. The bitten person will subsequently turn into a wolf in 3
nights.
This commit is contained in:
skizzerz 2014-12-13 17:31:34 -06:00
parent c611db5109
commit 9b61dde15e
2 changed files with 437 additions and 101 deletions

View File

@ -91,6 +91,9 @@ var.LOGGER = WolfgameLogger(var.LOG_FILENAME, var.BARE_LOG_FILENAME)
var.OPPED = False # Keeps track of whether the bot is opped
var.BITTEN = {}
var.BITTEN_ROLES = {}
if botconfig.DEBUG_MODE:
var.NIGHT_TIME_LIMIT = 0 # 120
var.NIGHT_TIME_WARN = 0 # 90
@ -1061,12 +1064,6 @@ def stats(cli, nick, chan, rest):
rs.append(var.DEFAULT_ROLE)
firstcount = len(var.ROLES[rs[0]])
if firstcount > 1 or not firstcount:
vb = "are"
else:
vb = "is"
amn_roles = {"amnesiac": 0}
for amn in var.ORIGINAL_ROLES["amnesiac"]:
if amn not in pl:
@ -1086,6 +1083,14 @@ def stats(cli, nick, chan, rest):
else:
amn_roles[amnrole] = -1
bitten_roles = {}
for bitten, role in var.BITTEN_ROLES.items():
if role in bitten_roles:
bitten_roles[role] += 1
else:
bitten_roles[role] = 1
vb = "are"
for role in rs:
# only show actual roles
if role in ("village elder", "time lord", "vengeful ghost") or role in var.TEMPLATE_RESTRICTIONS.keys():
@ -1093,18 +1098,43 @@ def stats(cli, nick, chan, rest):
count = len(var.ROLES[role])
if role == "traitor" and var.HIDDEN_TRAITOR:
continue
elif role == "lycan":
count += len([p for p in var.CURED_LYCANS if p in var.ROLES["villager"]])
count += bitten_roles["lycan"] if "lycan" in bitten_roles else 0
elif role == var.DEFAULT_ROLE:
if var.HIDDEN_TRAITOR:
count += len(var.ROLES["traitor"])
count += bitten_roles["traitor"] if "traitor" in bitten_roles else 0
if var.DEFAULT_ROLE == "villager":
count += len(var.ROLES["village elder"] + var.ROLES["time lord"] + var.ROLES["vengeful ghost"])
count -= len([p for p in var.CURED_LYCANS if p in var.ROLES["villager"]])
count += bitten_roles["village elder"] if "village elder" in bitten_roles else 0
count += bitten_roles["time lord"] if "time lord" in bitten_roles else 0
count += bitten_roles["vengeful ghost"] if "vengeful ghost" in bitten_roles else 0
else:
count += len(var.ROLES["vengeful ghost"])
count += bitten_roles["vengeful ghost"] if "vengeful ghost" in bitten_roles else 0
count += bitten_roles[var.DEFAULT_ROLE] if var.DEFAULT_ROLE in bitten_roles else 0
elif role == "villager":
count += len(var.ROLES["village elder"] + var.ROLES["time lord"])
count -= len([p for p in var.CURED_LYCANS if p in var.ROLES["villager"]])
count += bitten_roles["villager"] if "villager" in bitten_roles else 0
count += bitten_roles["village elder"] if "village elder" in bitten_roles else 0
count += bitten_roles["time lord"] if "time lord" in bitten_roles else 0
elif role == "wolf":
count -= sum(bitten_roles.values())
else:
count += bitten_roles[role] if role in bitten_roles else 0
if role in amn_roles:
count += amn_roles[role]
if role == rs[0]:
if count == 1:
vb = "is"
else:
vb = "are"
if count > 1 or count == 0:
if count == 0 and len(var.ORIGINAL_ROLES[role]) == 0:
continue
@ -1956,6 +1986,8 @@ def del_player(cli, nick, forced_death = False, devoice = True, end_game = True,
"to exact your revenge on the \u0002{0}\u0002 that killed you.").format(var.VENGEFUL_GHOSTS[nick]))
if nickrole == "wolf cub":
var.ANGRY_WOLVES = True
if nickrole in var.WOLF_ROLES and var.GAMEPHASE == "day":
var.ALPHA_ENABLED = True
if nickrole == "mad scientist":
# kills the 2 players adjacent to them in the original players listing (in order of !joining)
# if those players are already dead, nothing happens
@ -2374,9 +2406,7 @@ def on_nick(cli, oldnick, nick):
if prefix == k:
var.PLAYERS[nick] = var.PLAYERS[k]
del var.PLAYERS[k]
if prefix in var.GUNNERS.keys():
var.GUNNERS[nick] = var.GUNNERS.pop(prefix)
for dictvar in (var.HVISITED, var.OBSERVED, var.GUARDED, var.OTHER_KILLS, var.TARGETED, var.CLONED, var.LASTGUARDED, var.LASTGIVEN, var.LASTHEXED):
for dictvar in (var.HVISITED, var.OBSERVED, var.GUARDED, var.OTHER_KILLS, var.TARGETED, var.CLONED, var.LASTGUARDED, var.LASTGIVEN, var.LASTHEXED, var.BITE_PREFERENCES, var.BITTEN_ROLES):
kvp = []
for a,b in dictvar.items():
if a == prefix:
@ -2387,7 +2417,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):
for dictvar in (var.VENGEFUL_GHOSTS, var.TOTEMS, var.FINAL_ROLES, var.BITTEN, var.GUNNERS, var.DOCTORS):
if prefix in dictvar.keys():
dictvar[nick] = dictvar[prefix]
del dictvar[prefix]
@ -2498,6 +2528,15 @@ def on_nick(cli, oldnick, nick):
if prefix in var.TOBEEXCHANGED:
var.TOBEEXCHANGED.remove(prefix)
var.TOBEEXCHANGED.append(nick)
if prefix in var.IMMUNIZED:
var.IMMUNIZED.remove(prefix)
var.IMMUNIZED.add(nick)
if prefix in var.CURED_LYCANS:
var.CURED_LYCANS.remove(prefix)
var.CURED_LYCANS.append(nick)
if prefix in var.ALPHA_WOLVES:
var.ALPHA_WOLVES.remove(prefix)
var.ALPHA_WOLVES.append(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)
@ -2823,6 +2862,13 @@ def transition_day(cli, gameid=0):
var.NIGHT_TIMEDELTA += td
min, sec = td.seconds // 60, td.seconds % 60
# determine if we need to play the new wolf message due to bitten people
new_wolf = False
for (p, v) in var.BITTEN.items():
if v <= 0:
new_wolf = True
break
found = {}
for v in var.KILLS.values():
for p in v:
@ -2833,6 +2879,7 @@ def transition_day(cli, gameid=0):
maxc = 0
victims = []
bitten = []
killers = {} # dict of victim: list of killers (for retribution totem)
bywolves = set() # wolves targeted, others may have as well (needed for harlot visit and maybe other things)
onlybywolves = set() # wolves and nobody else targeted (needed for lycan)
@ -2875,6 +2922,37 @@ def transition_day(cli, gameid=0):
else:
killers[victim] = ["@wolves"]
if var.ALPHA_ENABLED: # check for bites
for (alpha, desired) in var.BITE_PREFERENCES.items():
if len(bywolves) == 0:
# nobody to bite, so let them do it again in the future
var.ALPHA_WOLVES.remove(alpha)
if len(bywolves) == 1:
target = tuple(bywolves)[0]
elif desired in bywolves:
target = desired
else:
target = random.choice(tuple(bywolves))
pm(cli, alpha, "You have bitten \u0002{0}\u0002.".format(target))
targetrole = var.get_role(target)
# 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)
# also if a lycan is bitten, just turn them into a wolf immediately
if (target not in var.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["lycan"]
and target not in var.LYCANTHROPES
and target not in var.IMMUNIZED):
var.BITTEN[target] = var.ALPHA_WOLF_NIGHTS
bitten.append(target)
victims.remove(target)
bywolves.remove(target)
onlybywolves.remove(target)
killers[target].remove("@wolves")
var.BITE_PREFERENCES = {}
for monster in var.ROLES["monster"]:
if monster in victims:
victims.remove(monster)
@ -2975,6 +3053,7 @@ def transition_day(cli, gameid=0):
# This needs to go down here since having them be their night value matters above
var.ANGRY_WOLVES = False
var.DISEASED_WOLVES = False
var.ALPHA_ENABLED = False
dead = []
for crow, target in iter(var.OBSERVED.items()):
@ -2990,6 +3069,10 @@ def transition_day(cli, gameid=0):
vlist = copy.copy(victims)
novictmsg = True
if new_wolf:
message.append("A chilling howl was heard last night. It appears there is another werewolf in our midst!")
novictmsg = False
for victim in vlist:
if victim in var.PROTECTED and victim not in var.DYING:
message.append(("\u0002{0}\u0002 was attacked last night, but their totem " +
@ -3012,7 +3095,7 @@ def transition_day(cli, gameid=0):
elif 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.")
novictmsg = False
elif (victim in var.ROLES["lycan"] or victim in var.LYCANTHROPES) and victim in onlybywolves:
elif (victim in var.ROLES["lycan"] or victim in var.LYCANTHROPES) and victim in onlybywolves and victim not in var.IMMUNIZED:
message.append("A chilling howl was heard last night. It appears there is another werewolf in our midst!")
pm(cli, victim, 'HOOOOOOOOOWL. You have become... a wolf!')
vrole = var.get_role(victim)
@ -3179,6 +3262,14 @@ def transition_day(cli, gameid=0):
for msg in message:
var.LOGGER.logMessage(msg.replace("\02", ""))
for chump in var.BITTEN.keys():
if chump not in dead and var.get_role(chump) not in var.WOLF_ROLES:
pm(cli, chump, get_bitten_message(chump))
for chump in bitten:
if chump not in dead and chump not in var.WOLF_ROLES:
pm(cli, chump, "You woke up today feeling light-headed, and you notice some odd bite marks on your neck...")
for deadperson in dead: # kill each player, but don't end the game if one group outnumbers another
# take a shortcut for killer_role here since vengeful ghost only cares about team and not particular roles
# this will have to be modified to track the actual killer if that behavior changes
@ -3208,7 +3299,7 @@ def chk_nightdone(cli):
list(var.TARGETED.keys()))
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["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"] +
var.ROLES["crazed shaman"] + var.ROLES["assassin"] + var.ROLES["augur"])
if var.FIRST_NIGHT:
@ -3216,7 +3307,7 @@ def chk_nightdone(cli):
nightroles += var.ROLES["matchmaker"] + var.ROLES["clone"]
if var.DISEASED_WOLVES:
nightroles = [p for p in nightroles if p not in var.ROLES["wolf"]]
nightroles = [p for p in nightroles if p not in var.ROLES["wolf"] and p not in var.ROLES["alpha wolf"]]
for p in var.HUNTERS:
# only remove one instance of their name if they have used hunter ability, in case they have templates
@ -3461,6 +3552,17 @@ def check_exchange(cli, actor, nick):
del var.LASTHEXED[actor]
if actor in var.HEXED:
var.HEXED.remove(actor)
elif actor_role == "doctor":
if nick_role == "doctor":
temp_immunizations = var.DOCTORS[actor]
var.DOCTORS[actor] = var.DOCTORS[nick]
var.DOCTORS[nick] = temp_immunizations
else:
var.DOCTORS[nick] = var.DOCTORS[actor]
del var.DOCTORS[actor]
elif actor_role == "alpha wolf":
if actor in var.ALPHA_WOLVES:
var.ALPHA_WOLVES.remove(actor)
if nick_role == "amnesiac":
nick_role = var.FINAL_ROLES[nick]
@ -3511,6 +3613,15 @@ def check_exchange(cli, actor, nick):
del var.LASTHEXED[nick]
if nick in var.HEXED:
var.HEXED.remove(nick)
elif nick_role == "doctor":
# Both being doctors is handled above
if actor_role != "doctor":
var.DOCTORS[actor] = var.DOCTORS[nick]
del var.DOCTORS[nick]
elif nick_role == "alpha wolf":
if nick in var.ALPHA_WOLVES:
var.ALPHA_WOLVES.remove(nick)
var.FINAL_ROLES[actor] = nick_role
var.FINAL_ROLES[nick] = actor_role
@ -3518,6 +3629,10 @@ def check_exchange(cli, actor, nick):
var.ROLES[actor_role].remove(actor)
var.ROLES[nick_role].append(actor)
var.ROLES[nick_role].remove(nick)
if actor in var.BITTEN_ROLES.keys():
var.BITTEN_ROLES[actor] = nick_role
if nick in var.BITTEN_ROLES.keys():
var.BITTEN_ROLES[nick] = actor_role
actor_rev_role = actor_role
if actor_role == "vengeful ghost":
@ -3558,10 +3673,15 @@ def check_exchange(cli, actor, nick):
pl[i] = player + " (cursed)"
pm(cli, actor, "Players: " + ", ".join(pl))
angry_alpha = ''
if var.DISEASED_WOLVES:
pm(cli, actor, 'You are feeling ill tonight, and are unable to kill anyone.')
elif var.ANGRY_WOLVES and actor_role in ("wolf", "werecrow"):
elif var.ANGRY_WOLVES and actor_role in ("wolf", "werecrow", "alpha wolf"):
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:
pm(cli, actor, ('You may use "bite{0}" 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 ''))
elif nick_role == "minion":
wolves = var.list_players(var.WOLF_ROLES)
random.shuffle(wolves)
@ -3589,10 +3709,15 @@ def check_exchange(cli, actor, nick):
pl[i] = player + " (cursed)"
pm(cli, nick, "Players: " + ", ".join(pl))
angry_alpha = ''
if var.DISEASED_WOLVES:
pm(cli, nick, 'You are feeling ill tonight, and are unable to kill anyone.')
elif var.ANGRY_WOLVES and nick_role in ("wolf", "werecrow"):
elif var.ANGRY_WOLVES and nick_role in ("wolf", "werecrow", "alpha wolf"):
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:
pm(cli, nick, ('You may use "bite{0}" 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 ''))
elif actor_role == "minion":
wolves = var.list_players(var.WOLF_ROLES)
random.shuffle(wolves)
@ -3651,7 +3776,7 @@ def wolfretract(cli, nick, rest):
return
role = var.get_role(nick)
if role not in ("wolf", "werecrow", "hunter") and nick not in var.VENGEFUL_GHOSTS.keys():
if role not in ("wolf", "werecrow", "alpha wolf", "hunter") and nick not in var.VENGEFUL_GHOSTS.keys():
return
if var.PHASE != "night":
pm(cli, nick, "You may only retract at night.")
@ -3664,12 +3789,23 @@ def wolfretract(cli, nick, rest):
elif role == "hunter" and nick in var.HUNTERS and nick not in var.OTHER_KILLS.keys():
return
if role in var.WOLF_ROLES and nick in var.KILLS.keys():
what = re.split(" +",rest)[0].strip().lower()
if not what or what not in ("kill", "bite"):
what = "kill"
if what == "kill" and role in var.WOLF_ROLES and nick in var.KILLS.keys():
del var.KILLS[nick]
elif role not in var.WOLF_ROLES and nick in var.OTHER_KILLS.keys():
pm(cli, nick, "You have retracted your kill.")
elif what == "kill" and role not in var.WOLF_ROLES and nick in var.OTHER_KILLS.keys():
del var.OTHER_KILLS[nick]
var.HUNTERS.remove(nick)
pm(cli, nick, "You have retracted your vote.")
pm(cli, nick, "You have retracted your kill.")
elif what == "bite" and role == "alpha wolf" and nick in var.BITE_PREFERENCES.keys():
del var.BITE_PREFERENCES[nick]
var.ALPHA_WOLVES.remove(nick)
pm(cli, nick, "You have decided to not bite anyone tonight.")
else:
pm(cli, nick, "You have not chosen to {0} anyone yet.".format(what))
#var.LOGGER.logBare(nick, "RETRACT", nick)
@cmd("shoot")
@ -3805,9 +3941,9 @@ def kill(cli, nick, rest):
role = var.get_role(nick)
except KeyError:
role = None
if role in var.WOLFCHAT_ROLES and role not in ("wolf", "werecrow"):
if role in var.WOLFCHAT_ROLES and role not in ("wolf", "werecrow", "alpha wolf"):
return # they do this a lot.
if role not in ("wolf", "werecrow", "hunter") and nick not in var.VENGEFUL_GHOSTS.keys():
if role not in ("wolf", "werecrow", "alpha wolf", "hunter") and nick not in var.VENGEFUL_GHOSTS.keys():
pm(cli, nick, "Only a wolf, hunter, or dead vengeful ghost may use this command.")
return
if var.PHASE != "night":
@ -3820,13 +3956,13 @@ def kill(cli, nick, rest):
if nick in var.SILENCED:
pm(cli, nick, "You have been silenced, and are unable to use any special powers.")
return
if role in ("wolf", "werecrow") and var.DISEASED_WOLVES:
if role in ("wolf", "werecrow", "alpha wolf") and var.DISEASED_WOLVES:
pm(cli, nick, "You are feeling ill, and are unable to kill anyone tonight.")
return
pieces = re.split(" +",rest)
victim = pieces[0]
victim2 = None
if role in ("wolf", "werecrow") and var.ANGRY_WOLVES:
if role in ("wolf", "werecrow", "alpha wolf") and var.ANGRY_WOLVES:
if len(pieces) > 1:
if len(pieces) > 2 and pieces[1].lower() == "and":
victim2 = pieces[2]
@ -3868,7 +4004,7 @@ def kill(cli, nick, rest):
pm(cli, nick, "You must target a villager.")
return
if role in ("wolf", "werecrow"):
if role in ("wolf", "werecrow", "alpha wolf"):
wolfchatwolves = var.list_players(var.WOLFCHAT_ROLES)
if victim in wolfchatwolves or victim2 in wolfchatwolves:
pm(cli, nick, "You may only kill villagers, not other wolves.")
@ -3909,7 +4045,7 @@ def kill(cli, nick, rest):
else:
pm(cli, nick, "You have selected \u0002{0}\u0002 to be killed.".format(victim))
var.LOGGER.logBare(nick, "SELECT", victim)
if var.ANGRY_WOLVES and role in ("wolf", "werecrow"):
if var.ANGRY_WOLVES and role in ("wolf", "werecrow", "alpha wolf"):
pm(cli, nick, "You are angry tonight and may kill a second target. Use kill <nick1> and <nick2> to select multiple targets.")
chk_nightdone(cli)
@ -4176,7 +4312,9 @@ def see(cli, nick, rest):
var.LOGGER.logBare(victim, "SEEN", nick)
chk_nightdone(cli)
@pmcmd("give", "totem")
# this is exactly why we should have classes; both shamans and doctors make sense to use "give" but
# a doctor could immunize someone with !totem and a shaman could totem someone with !immunize...
@pmcmd("give", "totem", "immunize", "immunise")
def give(cli, nick, rest):
if var.PHASE in ("none", "join"):
cli.notice(nick, "No game is currently running.")
@ -4185,82 +4323,207 @@ def give(cli, nick, rest):
cli.notice(nick, "You're not currently playing.")
return
role = var.get_role(nick)
if role not in ("shaman", "crazed shaman"):
pm(cli, nick, "Only a shaman or a crazed shaman may use this command.")
if role not in ("shaman", "crazed shaman", "doctor"):
pm(cli, nick, "Only a shaman, crazed shaman, or doctor may use this command.")
return
if var.PHASE != "night":
if var.PHASE != "night" and role in ("shaman", "crazed shaman"):
pm(cli, nick, "You may only give totems at night.")
return
elif var.PHASE != "day" and role == "doctor":
pm(cli, nick, "You may only immunize people during the day.")
return
if nick in var.SILENCED:
pm(cli, nick, "You have been silenced, and are unable to use any special powers.")
return
if nick in var.SHAMANS:
pm(cli, nick, "You have already given out your totem this round.")
return
elif nick in var.DOCTORS and var.DOCTORS[nick] == 0:
pm(cli, nick, "You have run out of immunizations.")
return
victim = get_victim(cli, nick, re.split(" +",rest)[0], True)
if not victim:
return
if role in ("shaman", "crazed shaman"):
if nick in var.LASTGIVEN and var.LASTGIVEN[nick] == victim:
pm(cli, nick, "You gave your totem to \u0002{0}\u0002 last time, you must choose someone else.".format(victim))
return
type = ""
if role == "shaman":
type = " of " + var.TOTEMS[nick]
victim = choose_target(nick, victim)
if check_exchange(cli, nick, victim):
return
pm(cli, nick, ("You have given a totem{0} to \u0002{1}\u0002.").format(type, victim))
totem = var.TOTEMS[nick]
if totem == "death":
if victim not in var.DYING:
var.DYING.append(victim)
elif totem == "protection":
if victim not in var.PROTECTED:
var.PROTECTED.append(victim)
elif totem == "revealing":
if victim not in var.REVEALED:
var.REVEALED.append(victim)
elif totem == "narcolepsy":
if victim not in var.ASLEEP:
var.ASLEEP.append(victim)
elif totem == "silence":
if victim not in var.TOBESILENCED:
var.TOBESILENCED.append(victim)
elif totem == "desperation":
if victim not in var.DESPERATE:
var.DESPERATE.append(victim)
elif totem == "impatience": # this totem stacks
var.IMPATIENT.append(victim)
elif totem == "pacifism": # this totem stacks
var.PACIFISTS.append(victim)
elif totem == "influence":
if victim not in var.INFLUENTIAL:
var.INFLUENTIAL.append(victim)
elif totem == "exchange":
if victim not in var.TOBEEXCHANGED:
var.TOBEEXCHANGED.append(victim)
elif totem == "lycanthropy":
if victim not in var.TOBELYCANTHROPES:
var.TOBELYCANTHROPES.append(victim)
elif totem == "luck":
if victim not in var.TOBELUCKY:
var.TOBELUCKY.append(victim)
elif totem == "pestilence":
if victim not in var.TOBEDISEASED:
var.TOBEDISEASED.append(victim)
elif totem == "retribution":
if victim not in var.RETRIBUTION:
var.RETRIBUTION.append(victim)
elif totem == "misdirection":
if victim not in var.TOBEMISDIRECTED:
var.TOBEMISDIRECTED.append(victim)
else:
pm(cli, nick, "I don't know what to do with a '{0}' totem. This is a bug, please report it to the admins.".format(totem))
var.LASTGIVEN[nick] = victim
var.SHAMANS.append(nick)
var.LOGGER.logBare(victim, "GIVEN TOTEM", nick)
chk_nightdone(cli)
elif role == "doctor":
victim = choose_target(nick, victim)
if check_exchange(cli, nick, victim):
return
pm(cli, nick, "You have given an immunization to \u0002{0}\u0002.".format(victim))
if var.get_role(victim) == "lycan":
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!")
var.ROLES["lycan"].remove(victim)
var.ROLES["villager"].append(victim)
var.FINAL_ROLES[victim] = "villager"
var.CURED_LYCANS.append(victim)
var.IMMUNIZED.add(victim)
elif victim in var.BITTEN:
# fun fact: immunizations in real life are done by injecting a small amount of (usually neutered) virus into the person
# so that their immune system can fight it off and build up antibodies. This doesn't work very well if that person is
# currently afflicted with the virus however, as you're just adding more virus to the mix...
# 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? :)
var.BITTEN[victim] -= 1
lycan_message = ("You have a brief flashback to your dream last night. " +
"The event quickly subsides, but a lingering thought remains in your mind...")
else:
lycan_message = "You don't feel any different..."
var.IMMUNIZED.add(victim)
pm(cli, victim, ("You feel a sharp prick in the back of your arm and temporarily black out. " +
"When you come to, you notice an empty syringe lying on the ground. {0}").format(lycan_message))
var.DOCTORS[nick] -= 1
if nick in var.LASTGIVEN and var.LASTGIVEN[nick] == victim:
pm(cli, nick, "You gave your totem to \u0002{0}\u0002 last time, you must choose someone else.".format(victim))
return
type = ""
if role == "shaman":
type = " of " + var.TOTEMS[nick]
victim = choose_target(nick, victim)
if check_exchange(cli, nick, victim):
return
pm(cli, nick, ("You have given a totem{0} to \u0002{1}\u0002.").format(type, victim))
totem = var.TOTEMS[nick]
if totem == "death":
if victim not in var.DYING:
var.DYING.append(victim)
elif totem == "protection":
if victim not in var.PROTECTED:
var.PROTECTED.append(victim)
elif totem == "revealing":
if victim not in var.REVEALED:
var.REVEALED.append(victim)
elif totem == "narcolepsy":
if victim not in var.ASLEEP:
var.ASLEEP.append(victim)
elif totem == "silence":
if victim not in var.TOBESILENCED:
var.TOBESILENCED.append(victim)
elif totem == "desperation":
if victim not in var.DESPERATE:
var.DESPERATE.append(victim)
elif totem == "impatience": # this totem stacks
var.IMPATIENT.append(victim)
elif totem == "pacifism": # this totem stacks
var.PACIFISTS.append(victim)
elif totem == "influence":
if victim not in var.INFLUENTIAL:
var.INFLUENTIAL.append(victim)
elif totem == "exchange":
if victim not in var.TOBEEXCHANGED:
var.TOBEEXCHANGED.append(victim)
elif totem == "lycanthropy":
if victim not in var.TOBELYCANTHROPES:
var.TOBELYCANTHROPES.append(victim)
elif totem == "luck":
if victim not in var.TOBELUCKY:
var.TOBELUCKY.append(victim)
elif totem == "pestilence":
if victim not in var.TOBEDISEASED:
var.TOBEDISEASED.append(victim)
elif totem == "retribution":
if victim not in var.RETRIBUTION:
var.RETRIBUTION.append(victim)
elif totem == "misdirection":
if victim not in var.TOBEMISDIRECTED:
var.TOBEMISDIRECTED.append(victim)
def get_bitten_message(nick):
time_left = var.BITTEN[nick]
message = ''
if time_left <= 1:
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. " +
"Suddenly your prey trips over a rock and falls down, allowing you to close in the remaining distance. " +
"You savor the fear in their eyes briefly before you raise your claw to deal a killing blow. " +
"Right before it connects, you wake up.")
elif time_left == 2:
message = ("You dreamt of running through the woods outside the village at night, wind blowing across your " +
"face as you weave between the pines. Suddenly you hear a rustling sound as a monstrous creature " +
"jumps out at you - a werewolf! You start running as fast as you can, you soon feel yourself falling " +
"down as you trip over a rock. You look up helplessly as the werewolf catches up to you, " +
"then wake up screaming.")
else:
pm(cli, nick, "I don't know what to do with a '{0}' totem. This is a bug, please report it to the admins.".format(totem))
var.LASTGIVEN[nick] = victim
var.SHAMANS.append(nick)
var.LOGGER.logBare(victim, "GIVEN TOTEM", nick)
chk_nightdone(cli)
message = ("You had a strange dream last night; a person was running away from something through a forest. " +
"They tripped and fell over a rock as a shadow descended upon them. Before you could actually see " +
"who or what the pursuer was, you woke with a start.")
return message
@pmcmd("bite")
def bite_cmd(cli, nick, rest):
if var.PHASE in ("none", "join"):
cli.notice(nick, "No game is currently running.")
return
elif nick not in var.list_players() or nick in var.DISCONNECTED.keys():
cli.notice(nick, "You're not currently playing.")
return
role = var.get_role(nick)
if role != "alpha wolf":
pm(cli, nick, "Only an alpha wolf may use this command.")
return
if var.PHASE != "night":
pm(cli, nick, "You may only bite at night.")
return
if nick in var.ALPHA_WOLVES and nick not in var.BITE_PREFERENCES:
pm(cli, nick, "You have already bitten someone this game.")
return
if nick in var.SILENCED:
pm(cli, nick, "You have been silenced, and are unable to use any special powers.")
return
if not var.ALPHA_ENABLED:
pm(cli, nick, "You may only bite someone after another wolf has died during the day.")
return
pieces = [p.strip().lower() for p in re.split(" +",rest)]
victim = pieces[0]
if var.ANGRY_WOLVES:
if not victim:
pm(cli, nick, "Please choose who to bite by specifying their nick.")
return
pl = var.list_players()
pll = [x.lower() for x in pl]
matches = 0
for player in pll:
if victim == player:
target = player
break
if player.startswith(victim):
target = player
matches += 1
else:
if matches != 1:
pm(cli, nick, "\u0002{0}\u0002 is currently not playing.".format(victim))
return
victim = pl[pll.index(target)]
vrole = var.get_role(victim)
if vrole in var.WOLFCHAT_ROLES:
pm(cli, nick, "You may not bite other wolves.")
return
if nick not in var.ALPHA_WOLVES:
var.ALPHA_WOLVES.append(nick)
# this means that if victim is chosen by wolves, they will get bitten
# if victim isn't chosen, then a target is chosen at random from the victims
# (really only matters if wolves are angry; since there's only one target otherwise)
var.BITE_PREFERENCES[nick] = victim
if victim:
pm(cli, nick, "You have chosen to bite \u0002{0}\u0002. If that player is not selected to be killed, you will bite one of the wolf targets at random instead.".format(victim))
else:
pm(cli, nick, "You have chosen to bite tonight. Whomever the wolves select to be killed tonight will be bitten instead.")
@pmcmd("pass")
def pass_cmd(cli, nick, rest):
@ -4670,6 +4933,33 @@ def transition_night(cli):
t2.daemon = True
t2.start()
# convert bitten people to wolves, and advance bite stage
bittencopy = copy.copy(var.BITTEN)
for chump in bittencopy:
var.BITTEN[chump] -= 1
# short-circuit if they are already a wolf
# this makes playing the day transition message easier since we can keep
# var.BITTEN around for a day after they turn
chumprole = var.get_role(chump)
if chumprole in var.WOLF_ROLES:
del var.BITTEN[chump]
continue
if var.BITTEN[chump] <= 0:
# now a wolf
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 " +
"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..."))
var.BITTEN_ROLES[chump] = chumprole
var.ROLES[chumprole].remove(chump)
var.ROLES["wolf"].append(chump)
var.FINAL_ROLES[chump] = "wolf"
for wolf in var.list_players(var.WOLFCHAT_ROLES):
if wolf != chump:
pm(cli, wolf, "\u0002{0}\u0002 is now a \u0002wolf\u0002!".format(chump))
# convert amnesiac and kill village elder if necessary
if var.NIGHT_COUNT == var.AMNESIAC_NIGHTS:
amns = copy.copy(var.ROLES["amnesiac"])
@ -4728,16 +5018,23 @@ def transition_night(cli):
pm(cli, wolf, ('You are a \u0002wolf cub\u0002. While you cannot kill anyone, ' +
'the other wolves will become enraged if you die and will get ' +
'two kills the following night.'))
elif role == "alpha wolf":
pm(cli, wolf, ('You are an \u0002alpha wolf\u0002. Once per game following the death of another wolf ' +
'during the day, you can choose to bite the wolves\' next target to turn ' +
'them into a wolf instead of killing them. Kill villagers by using '
'"kill <nick>" and "bite" to use your once-per-game bite power.'))
else:
# catchall in case we forgot something above
pm(cli, wolf, ('You are a \u0002{0}\u0002. There would normally be instructions ' +
an = 'n' if role[0] in ('a', 'e', 'i', 'o', 'u') else ''
pm(cli, wolf, ('You are a{0} \u0002{1}\u0002. There would normally be instructions ' +
'here, but someone forgot to add them in. Please report this to ' +
'the admins, you can PM me "admins" for a list of available ones.').format(role))
'the admins, you can PM me "admins" for a list of available ones.').format(an, role))
if len(wolves) > 1:
pm(cli, wolf, 'Also, if you PM me, your message will be relayed to other wolves.')
else:
pm(cli, wolf, "You are a \02{0}{1}\02.".format(cursed, role)) # !simple
an = 'n' if cursed == '' and role[0] in ('a', 'e', 'i', 'o', 'u') else ''
pm(cli, wolf, "You are a{0} \02{1}{2}\02.".format(an, cursed, role)) # !simple
pl = ps[:]
random.shuffle(pl)
@ -4755,10 +5052,15 @@ def transition_night(cli):
pm(cli, wolf, "Players: " + ", ".join(pl))
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 ""))
angry_alpha = ''
if var.DISEASED_WOLVES:
pm(cli, wolf, 'You are feeling ill tonight, and are unable to kill anyone.')
elif var.ANGRY_WOLVES and role in ("wolf", "werecrow"):
elif var.ANGRY_WOLVES and role in ("wolf", "werecrow", "alpha wolf"):
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:
pm(cli, wolf, ('You may use "bite{0}" 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 ''))
for seer in var.list_players(["seer", "oracle", "augur"]):
pl = ps[:]
@ -4938,6 +5240,19 @@ def transition_night(cli):
pm(cli, hunter, "You are a \u0002hunter\u0002.")
pm(cli, hunter, "Players: " + ", ".join(pl))
for doctor in var.ROLES["doctor"]:
if var.DOCTORS[doctor] > 0: # has immunizations remaining
pl = ps[:]
random.shuffle(pl)
if doctor in var.PLAYERS and not is_user_simple(doctor):
pm(cli, doctor, ('You are a \u0002doctor\u0002. You can give out immunizations to ' +
'villagers by using "give <nick>" in PM during the daytime. ' +
'An immunized villager will die instead of turning into a wolf due to the ' +
'alpha wolf\'s or lycan\'s power.'))
else:
pm(cli, doctor, "You are a \u0002doctor\u0002.")
pm(cli, doctor, 'You have \u0002{0}\u0002 immunization{1}.'.format(var.DOCTORS[doctor], 's' if var.DOCTORS[doctor] > 1 else ''))
for fool in var.ROLES["fool"]:
if fool in var.PLAYERS and not is_user_simple(fool):
pm(cli, fool, ('You are a \u0002fool\u0002. The game immediately ends with you ' +
@ -5107,7 +5422,7 @@ def transition_night(cli):
var.LOGGER.logBare("NIGHT", "BEGIN")
# cli.msg(chan, "DEBUG: "+str(var.ROLES))
if len(var.ROLES["wolf"] + var.ROLES["werecrow"]) == 0 or var.DISEASED_WOLVES: # Probably something interesting going on.
if len(var.list_players(var.WOLF_ROLES)) - len(var.ROLES["wolf cub"]) == 0 or var.DISEASED_WOLVES: # Probably something interesting going on.
chk_nightdone(cli)
chk_traitor(cli)
@ -5286,6 +5601,14 @@ def start(cli, nick, chan, forced = False):
var.OTHER_KILLS = {}
var.ACTED_EXTRA = 0
var.ABSTAINED = False
var.DOCTORS = {}
var.IMMUNIZED = set()
var.CURED_LYCANS = []
var.ALPHA_WOLVES = []
var.ALPHA_ENABLED = False
var.BITTEN = {}
var.BITE_PREFERENCES = {}
var.BITTEN_ROLES = {}
for role, count in addroles.items():
if role in var.TEMPLATE_RESTRICTIONS.keys():
@ -5363,6 +5686,10 @@ def start(cli, nick, chan, forced = False):
for amnesiac in var.ROLES["amnesiac"]:
var.FINAL_ROLES[amnesiac] = random.choice(amnroles)
# Handle doctor
for doctor in var.ROLES["doctor"]:
var.DOCTORS[doctor] = math.ceil(var.DOCTOR_IMMUNIZATION_MULTIPLIER * len(pl))
var.DAY_TIMEDELTA = timedelta(0)
var.NIGHT_TIMEDELTA = timedelta(0)
var.DAY_START_TIME = datetime.now()

View File

@ -90,6 +90,9 @@ DETECTIVE_REVEALED_CHANCE = 2/5
SHARPSHOOTER_CHANCE = 1/5 # if sharpshooter is enabled, chance that a gunner will become a sharpshooter instead
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)
DOCTOR_IMMUNIZATION_MULTIPLIER = 0.17 # ceil(num_players * multiplier) = number of immunizations
# SHAMAN CRAZED SHAMAN
TOTEM_CHANCES = { "death": ( 1/8 , 1/15 ),
@ -141,6 +144,7 @@ ROLE_GUIDE = {# village roles
"mad scientist" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"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 ),
"doctor" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
# wolf roles
"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 ),
@ -150,6 +154,7 @@ ROLE_GUIDE = {# village roles
"hag" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 ),
"wolf cub" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
"sorcerer" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ),
"alpha wolf" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
# neutral roles
"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 ),
@ -171,23 +176,23 @@ ROLE_GUIDE = {# village roles
# Harlot dies when visiting, gunner kills when shooting, GA and bodyguard have a chance at dying when guarding
# If every wolf role dies, the game ends and village wins and there are no remaining traitors, the game ends and villagers win
WOLF_ROLES = ["wolf", "werecrow", "wolf cub"]
WOLF_ROLES = ["wolf", "alpha wolf", "werecrow", "wolf cub"]
# Access to wolfchat, and counted towards the # of wolves vs villagers when determining if a side has won
WOLFCHAT_ROLES = ["wolf", "werecrow", "wolf cub", "traitor", "hag", "sorcerer"]
WOLFCHAT_ROLES = ["wolf", "alpha wolf", "werecrow", "wolf cub", "traitor", "hag", "sorcerer"]
# Wins with the wolves, even if the roles are not necessarily wolves themselves
WOLFTEAM_ROLES = ["wolf", "werecrow", "wolf cub", "traitor", "hag", "sorcerer", "minion", "cultist"]
WOLFTEAM_ROLES = ["wolf", "alpha wolf", "werecrow", "wolf cub", "traitor", "hag", "sorcerer", "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"]
# These are the roles that will NOT be used for when amnesiac turns, everything else is fair game!
AMNESIAC_BLACKLIST = ["monster", "amnesiac", "minion", "matchmaker", "clone", "villager", "cultist"]
AMNESIAC_BLACKLIST = ["monster", "amnesiac", "minion", "matchmaker", "clone", "doctor", "villager", "cultist"]
# 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
TEMPLATE_RESTRICTIONS = {"cursed villager" : ["wolf", "wolf cub", "werecrow", "seer", "oracle", "augur", "fool", "jester", "mad scientist"],
"gunner" : ["wolf", "traitor", "werecrow", "hag", "wolf cub", "sorcerer", "minion", "cultist", "fool", "lycan", "jester"],
"sharpshooter" : ["wolf", "traitor", "werecrow", "hag", "wolf cub", "sorcerer", "minion", "cultist", "fool", "lycan", "jester"],
TEMPLATE_RESTRICTIONS = {"cursed villager" : WOLF_ROLES + ["seer", "oracle", "augur", "fool", "jester", "mad scientist"],
"gunner" : WOLFTEAM_ROLES + ["fool", "lycan", "jester"],
"sharpshooter" : WOLFTEAM_ROLES + ["fool", "lycan", "jester"],
"mayor" : ["fool", "jester", "monster"],
"assassin" : ["seer", "augur", "oracle", "harlot", "detective", "bodyguard", "guardian angel", "village drunk", "hunter", "shaman", "crazed shaman", "fool", "mayor", "wolf", "werecrow", "wolf cub", "traitor", "lycan"],
"assassin" : WOLF_ROLES + ["traitor", "seer", "augur", "oracle", "harlot", "detective", "bodyguard", "guardian angel", "village drunk", "hunter", "shaman", "crazed shaman", "fool", "mayor", "lycan", "doctor"],
"bureaucrat" : [],
}
@ -299,6 +304,10 @@ def del_player(pname):
tpls = get_templates(pname)
for t in tpls:
ROLES[t].remove(pname)
if pname in BITTEN:
del BITTEN[pname]
if pname in BITTEN_ROLES:
del BITTEN_ROLES[pname]
def get_templates(nick):
tpl = []