652 lines
26 KiB
Python
652 lines
26 KiB
Python
import re
|
|
import random
|
|
import itertools
|
|
from collections import defaultdict, deque
|
|
|
|
import botconfig
|
|
import src.settings as var
|
|
from src.utilities import *
|
|
from src import debuglog, errlog, plog
|
|
from src.decorators import cmd, event_listener
|
|
from src.messages import messages
|
|
from src.events import Event
|
|
|
|
# To add new totem types in your custom roles/whatever.py file:
|
|
# 1. Add a key to var.TOTEM_CHANCES with the totem name
|
|
# 2. Add a message totemname_totem to your custom messages.json describing
|
|
# the totem (this is displayed at night if !simple is off)
|
|
# 3. Add events as necessary to implement the totem's functionality
|
|
#
|
|
# To add new shaman roles in your custom roles/whatever.py file:
|
|
# 1. Expand var.TOTEM_ORDER and upate var.TOTEM_CHANCES to account for the new width
|
|
# 2. Add the role to var.ROLE_GUIDE
|
|
# 3. Add the role to whatever other holding vars are necessary based on what it does
|
|
# 4. Implement custom events if the role does anything else beyond giving totems.
|
|
#
|
|
# Modifying this file to add new totems or new shaman roles is generally never required
|
|
|
|
TOTEMS = {} # type: Dict[str, str]
|
|
LASTGIVEN = {} # type: Dict[str, str]
|
|
SHAMANS = {} # type: Dict[str, Tuple[str, str]]
|
|
|
|
DEATH = {} # type: Dict[str, str]
|
|
PROTECTION = [] # type: List[str]
|
|
REVEALING = set() # type: Set[str]
|
|
NARCOLEPSY = set() # type: Set[str]
|
|
SILENCE = set() # type: Set[str]
|
|
DESPERATION = set() # type: Set[str]
|
|
IMPATIENCE = [] # type: List[str]
|
|
PACIFISM = [] # type: List[str]
|
|
INFLUENCE = set() # type: Set[str]
|
|
EXCHANGE = set() # type: Set[str]
|
|
LYCANTHROPY = set() # type: Set[str]
|
|
LUCK = set() # type: Set[str]
|
|
PESTILENCE = set() # type: Set[str]
|
|
RETRIBUTION = set() # type: Set[str]
|
|
MISDIRECTION = set() # type: Set[str]
|
|
DECEIT = set() # type: Set[str]
|
|
|
|
# holding vars that don't persist long enough to need special attention in
|
|
# reset/exchange/nickchange
|
|
havetotem = [] # type: List[str]
|
|
brokentotem = set() # type: Set[str]
|
|
|
|
@cmd("give", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=var.TOTEM_ORDER)
|
|
@cmd("totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=var.TOTEM_ORDER)
|
|
def totem(cli, nick, chan, rest, prefix="You"): # XXX: The transition_day_begin event needs updating alongside this
|
|
"""Give a totem to a player."""
|
|
victim = get_victim(cli, nick, re.split(" +",rest)[0], False, True)
|
|
if not victim:
|
|
return
|
|
if LASTGIVEN.get(nick) == victim:
|
|
pm(cli, nick, messages["shaman_no_target_twice"].format(victim))
|
|
return
|
|
|
|
original_victim = victim
|
|
role = get_role(nick)
|
|
totem = ""
|
|
if role != "crazed shaman":
|
|
totem = " of " + TOTEMS[nick]
|
|
|
|
tags = set()
|
|
if role != "crazed shaman" and TOTEMS[nick] in var.BENEFICIAL_TOTEMS:
|
|
tags.add("beneficial")
|
|
|
|
evt = Event("targeted_command", {"target": victim, "misdirection": True, "exchange": True},
|
|
action="give a totem{0} to".format(totem))
|
|
evt.dispatch(cli, var, "totem", nick, victim, frozenset(tags))
|
|
if evt.prevent_default:
|
|
return
|
|
victim = evt.data["target"]
|
|
victimrole = get_role(victim)
|
|
|
|
pm(cli, nick, messages["shaman_success"].format(prefix, totem, original_victim))
|
|
if role == "wolf shaman":
|
|
relay_wolfchat_command(cli, nick, messages["shaman_wolfchat"].format(nick, original_victim), ("wolf shaman",), is_wolf_command=True)
|
|
SHAMANS[nick] = (victim, original_victim)
|
|
debuglog("{0} ({1}) TOTEM: {2} ({3})".format(nick, role, victim, TOTEMS[nick]))
|
|
chk_nightdone(cli)
|
|
|
|
@event_listener("rename_player")
|
|
def on_rename(evt, cli, var, prefix, nick):
|
|
if prefix in TOTEMS:
|
|
TOTEMS[nick] = TOTEMS.pop(prefix)
|
|
|
|
for dictvar in (LASTGIVEN, DEATH):
|
|
kvp = {}
|
|
for a,b in dictvar.items():
|
|
s = nick if a == prefix else a
|
|
t = nick if b == prefix else b
|
|
kvp[s] = t
|
|
dictvar.update(kvp)
|
|
if prefix in dictvar:
|
|
del dictvar[prefix]
|
|
|
|
kvp = {}
|
|
for a,(b,c) in SHAMANS.items():
|
|
s = nick if a == prefix else a
|
|
t1 = nick if b == prefix else b
|
|
t2 = nick if c == prefix else c
|
|
kvp[s] = (t1, t2)
|
|
SHAMANS.update(kvp)
|
|
if prefix in SHAMANS:
|
|
del SHAMANS[prefix]
|
|
|
|
for listvar in (PROTECTION, IMPATIENCE, PACIFISM):
|
|
for i,a in enumerate(listvar):
|
|
if a == prefix:
|
|
listvar[i] = nick
|
|
|
|
for setvar in (REVEALING, NARCOLEPSY, SILENCE, DESPERATION,
|
|
INFLUENCE, EXCHANGE, LYCANTHROPY, LUCK, PESTILENCE,
|
|
RETRIBUTION, MISDIRECTION, DECEIT):
|
|
for a in list(setvar):
|
|
if a == prefix:
|
|
setvar.discard(a)
|
|
setvar.add(nick)
|
|
|
|
@event_listener("see", priority=10)
|
|
def on_see(evt, cli, var, nick, victim):
|
|
if (victim in DECEIT) ^ (nick in DECEIT):
|
|
if evt.data["role"] in var.SEEN_WOLF and evt.data["role"] not in var.SEEN_DEFAULT:
|
|
evt.data["role"] = "villager"
|
|
else:
|
|
evt.data["role"] = "wolf"
|
|
|
|
@event_listener("del_player")
|
|
def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers):
|
|
for a,(b,c) in list(SHAMANS.items()):
|
|
if nick in (a, b, c):
|
|
del SHAMANS[a]
|
|
|
|
@event_listener("night_acted")
|
|
def on_acted(evt, cli, var, nick, sender):
|
|
if nick in SHAMANS:
|
|
evt.data["acted"] = True
|
|
|
|
@event_listener("get_special")
|
|
def on_get_special(evt, cli, var):
|
|
evt.data["special"].update(list_players(("shaman", "crazed shaman", "wolf shaman")))
|
|
|
|
@event_listener("exchange_roles")
|
|
def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role):
|
|
actor_totem = None
|
|
nick_totem = None
|
|
if actor_role in var.TOTEM_ORDER:
|
|
actor_totem = TOTEMS.pop(actor)
|
|
if actor in SHAMANS:
|
|
del SHAMANS[actor]
|
|
if actor in LASTGIVEN:
|
|
del LASTGIVEN[actor]
|
|
if nick_role in var.TOTEM_ORDER:
|
|
nick_totem = TOTEMS.pop(nick)
|
|
if nick in SHAMANS:
|
|
del SHAMANS[nick]
|
|
if nick in LASTGIVEN:
|
|
del LASTGIVEN[nick]
|
|
if nick_totem:
|
|
if nick_role != "crazed shaman":
|
|
evt.data["actor_messages"].append(messages["shaman_totem"].format(nick_totem))
|
|
TOTEMS[actor] = nick_totem
|
|
if actor_totem:
|
|
if actor_role != "crazed shaman":
|
|
evt.data["nick_messages"].append(messages["shaman_totem"].format(actor_totem))
|
|
TOTEMS[nick] = actor_totem
|
|
|
|
@event_listener("chk_nightdone")
|
|
def on_chk_nightdone(evt, cli, var):
|
|
evt.data["actedcount"] += len(SHAMANS)
|
|
evt.data["nightroles"].extend(list_players(var.TOTEM_ORDER))
|
|
|
|
@event_listener("get_voters")
|
|
def on_get_voters(evt, cli, var):
|
|
evt.data["voters"] -= NARCOLEPSY
|
|
|
|
@event_listener("chk_decision", priority=1)
|
|
def on_chk_decision(evt, cli, var, force):
|
|
nl = []
|
|
for p in PACIFISM:
|
|
if p in evt.params.voters:
|
|
nl.append(p)
|
|
# .remove() will only remove the first instance, which means this plays nicely with pacifism countering this
|
|
for p in IMPATIENCE:
|
|
if p in nl:
|
|
nl.remove(p)
|
|
evt.data["not_lynching"] |= set(nl)
|
|
|
|
for votee, voters in evt.data["votelist"].items():
|
|
numvotes = 0
|
|
random.shuffle(IMPATIENCE)
|
|
for v in IMPATIENCE:
|
|
if v in evt.params.voters and v not in voters and v != votee:
|
|
# don't add them in if they have the same number or more of pacifism totems
|
|
# this matters for desperation totem on the votee
|
|
imp_count = IMPATIENCE.count(v)
|
|
pac_count = PACIFISM.count(v)
|
|
if pac_count >= imp_count:
|
|
continue
|
|
|
|
# yes, this means that one of the impatient people will get desperation totem'ed if they didn't
|
|
# already !vote earlier. sucks to suck. >:)
|
|
voters.append(v)
|
|
for v in voters:
|
|
weight = 1
|
|
imp_count = IMPATIENCE.count(v)
|
|
pac_count = PACIFISM.count(v)
|
|
if pac_count > imp_count:
|
|
weight = 0 # more pacifists than impatience totems
|
|
elif imp_count == pac_count and v not in var.VOTES[votee]:
|
|
weight = 0 # impatience and pacifist cancel each other out, so don't count impatience
|
|
if v in INFLUENCE:
|
|
weight *= 2
|
|
numvotes += weight
|
|
if votee not in evt.data["weights"]:
|
|
evt.data["weights"][votee] = {}
|
|
evt.data["weights"][votee][v] = weight
|
|
evt.data["numvotes"][votee] = numvotes
|
|
|
|
@event_listener("chk_decision", priority=1.1)
|
|
def on_hurry_up(evt, cli, var, force):
|
|
if evt.params.timeout:
|
|
evt.stop_processing = True
|
|
|
|
@event_listener("chk_decision_abstain")
|
|
def on_chk_decision_abstain(evt, cli, var, nl):
|
|
for p in nl:
|
|
if p in PACIFISM and p not in var.NO_LYNCH:
|
|
cli.msg(botconfig.CHANNEL, messages["player_meek_abstain"].format(p))
|
|
|
|
@event_listener("chk_decision_lynch", priority=1)
|
|
def on_chk_decision_lynch1(evt, cli, var, voters):
|
|
votee = evt.data["votee"]
|
|
for p in voters:
|
|
if p in IMPATIENCE and p not in var.VOTES[votee]:
|
|
cli.msg(botconfig.CHANNEL, messages["impatient_vote"].format(p, votee))
|
|
|
|
# mayor is at exactly 3, so we want that to always happen before revealing totem
|
|
@event_listener("chk_decision_lynch", priority=3.1)
|
|
def on_chk_decision_lynch3(evt, cli, var, voters):
|
|
votee = evt.data["votee"]
|
|
if votee in REVEALING:
|
|
role = get_role(votee)
|
|
rev_evt = Event("revealing_totem", {"role": role})
|
|
rev_evt.dispatch(cli, var, votee)
|
|
role = rev_evt.data["role"]
|
|
if role == "amnesiac":
|
|
var.ROLES["amnesiac"].remove(votee)
|
|
role = var.AMNESIAC_ROLES[votee]
|
|
var.ROLES[role].add(votee)
|
|
var.AMNESIACS.add(votee)
|
|
var.FINAL_ROLES[votee] = role
|
|
pm(cli, votee, messages["totem_amnesia_clear"])
|
|
# If wolfteam, don't bother giving list of wolves since night is about to start anyway
|
|
# Existing wolves also know that someone just joined their team because revealing totem says what they are
|
|
# If turncoat, set their initial starting side to "none" just in case game ends before they can set it themselves
|
|
if role == "turncoat":
|
|
var.TURNCOATS[votee] = ("none", -1)
|
|
|
|
an = "n" if role.startswith(("a", "e", "i", "o", "u")) else ""
|
|
cli.msg(botconfig.CHANNEL, messages["totem_reveal"].format(votee, an, role))
|
|
evt.data["votee"] = None
|
|
evt.prevent_default = True
|
|
evt.stop_processing = True
|
|
|
|
@event_listener("chk_decision_lynch", priority=5)
|
|
def on_chk_decision_lynch5(evt, cli, var, voters):
|
|
votee = evt.data["votee"]
|
|
if votee in DESPERATION:
|
|
# Also kill the very last person to vote them, unless they voted themselves last in which case nobody else dies
|
|
target = voters[-1]
|
|
if target != votee:
|
|
prots = deque(var.ACTIVE_PROTECTIONS[target])
|
|
while len(prots) > 0:
|
|
# an event can read the current active protection and cancel the totem
|
|
# if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS
|
|
# so that it cannot be used again (if the protection is meant to be usable once-only)
|
|
desp_evt = Event("desperation_totem", {})
|
|
if not desp_evt.dispatch(cli, var, votee, target, prots[0]):
|
|
return
|
|
prots.popleft()
|
|
if var.ROLE_REVEAL in ("on", "team"):
|
|
r1 = get_reveal_role(target)
|
|
an1 = "n" if r1.startswith(("a", "e", "i", "o", "u")) else ""
|
|
tmsg = messages["totem_desperation"].format(votee, target, an1, r1)
|
|
else:
|
|
tmsg = messages["totem_desperation_no_reveal"].format(votee, target)
|
|
cli.msg(botconfig.CHANNEL, tmsg)
|
|
# we lie to this function so it doesn't devoice the player yet. instead, we'll let the call further down do it
|
|
evt.data["deadlist"].append(target)
|
|
evt.params.del_player(cli, target, True, end_game=False, killer_role="shaman", deadlist=evt.data["deadlist"], original=target, ismain=False)
|
|
|
|
@event_listener("player_win")
|
|
def on_player_win(evt, var, user, rol, winner, survived):
|
|
if rol == "crazed shaman" and survived and not winner.startswith("@") and singular(winner) not in var.WIN_STEALER_ROLES:
|
|
evt.data["iwon"] = True
|
|
|
|
@event_listener("transition_day_begin", priority=4)
|
|
def on_transition_day_begin(evt, cli, var):
|
|
# Select random totem recipients if shamans didn't act
|
|
pl = list_players()
|
|
for shaman in list_players(var.TOTEM_ORDER):
|
|
if shaman not in SHAMANS and shaman not in var.SILENCED:
|
|
ps = pl[:]
|
|
if LASTGIVEN.get(shaman) in ps:
|
|
ps.remove(LASTGIVEN.get(shaman))
|
|
levt = Event("get_random_totem_targets", {"targets": ps})
|
|
levt.dispatch(cli, var, shaman)
|
|
ps = levt.data["targets"]
|
|
if ps:
|
|
target = random.choice(ps)
|
|
totem.func(cli, shaman, shaman, target, messages["random_totem_prefix"]) # XXX: Old API
|
|
else:
|
|
LASTGIVEN[shaman] = None
|
|
elif shaman not in SHAMANS:
|
|
LASTGIVEN[shaman] = None
|
|
|
|
@event_listener("transition_day_begin", priority=6)
|
|
def on_transition_day_begin2(evt, cli, var):
|
|
# Reset totem variables
|
|
DEATH.clear()
|
|
PROTECTION.clear()
|
|
REVEALING.clear()
|
|
NARCOLEPSY.clear()
|
|
SILENCE.clear()
|
|
DESPERATION.clear()
|
|
IMPATIENCE.clear()
|
|
PACIFISM.clear()
|
|
INFLUENCE.clear()
|
|
EXCHANGE.clear()
|
|
LYCANTHROPY.clear()
|
|
LUCK.clear()
|
|
PESTILENCE.clear()
|
|
RETRIBUTION.clear()
|
|
MISDIRECTION.clear()
|
|
DECEIT.clear()
|
|
|
|
# Give out totems here
|
|
for shaman, (victim, target) in SHAMANS.items():
|
|
totemname = TOTEMS[shaman]
|
|
if totemname == "death": # this totem stacks
|
|
DEATH[shaman] = victim
|
|
elif totemname == "protection": # this totem stacks
|
|
PROTECTION.append(victim)
|
|
elif totemname == "revealing":
|
|
REVEALING.add(victim)
|
|
elif totemname == "narcolepsy":
|
|
NARCOLEPSY.add(victim)
|
|
elif totemname == "silence":
|
|
SILENCE.add(victim)
|
|
elif totemname == "desperation":
|
|
DESPERATION.add(victim)
|
|
elif totemname == "impatience": # this totem stacks
|
|
IMPATIENCE.append(victim)
|
|
elif totemname == "pacifism": # this totem stacks
|
|
PACIFISM.append(victim)
|
|
elif totemname == "influence":
|
|
INFLUENCE.add(victim)
|
|
elif totemname == "exchange":
|
|
EXCHANGE.add(victim)
|
|
elif totemname == "lycanthropy":
|
|
LYCANTHROPY.add(victim)
|
|
elif totemname == "luck":
|
|
LUCK.add(victim)
|
|
elif totemname == "pestilence":
|
|
PESTILENCE.add(victim)
|
|
elif totemname == "retribution":
|
|
RETRIBUTION.add(victim)
|
|
elif totemname == "misdirection":
|
|
MISDIRECTION.add(victim)
|
|
elif totemname == "deceit":
|
|
DECEIT.add(victim)
|
|
# other totem types possibly handled in an earlier event,
|
|
# as such there is no else: clause here
|
|
if target != victim:
|
|
pm(cli, shaman, messages["totem_retarget"].format(victim))
|
|
LASTGIVEN[shaman] = victim
|
|
|
|
# In transition_day_end we report who was given totems based on havetotem.
|
|
# Fallen angel messes with this list, hence why it is separated from LASTGIVEN
|
|
# and calculated here.
|
|
brokentotem.clear()
|
|
havetotem.clear()
|
|
havetotem.extend(sorted(filter(None, LASTGIVEN.values())))
|
|
|
|
@event_listener("transition_day", priority=2)
|
|
def on_transition_day2(evt, cli, var):
|
|
for k, d in DEATH.items():
|
|
evt.data["victims"].append(d)
|
|
evt.data["onlybywolves"].discard(d)
|
|
evt.data["killers"][d].append(k)
|
|
|
|
@event_listener("transition_day", priority=4.1)
|
|
def on_transition_day3(evt, cli, var):
|
|
# protection totems are applied first in default logic, however
|
|
# we set priority=4.1 to allow other modes of protection
|
|
# to pre-empt us if desired
|
|
pl = list_players()
|
|
vs = set(evt.data["victims"])
|
|
for v in pl:
|
|
numtotems = PROTECTION.count(v)
|
|
if v in vs:
|
|
if v in var.DYING:
|
|
continue
|
|
numkills = evt.data["numkills"][v]
|
|
for i in range(0, numtotems):
|
|
numkills -= 1
|
|
if numkills >= 0:
|
|
evt.data["killers"][v].pop(0)
|
|
if numkills <= 0 and v not in evt.data["protected"]:
|
|
evt.data["protected"][v] = "totem"
|
|
elif numkills <= 0:
|
|
var.ACTIVE_PROTECTIONS[v].append("totem")
|
|
evt.data["numkills"][v] = numkills
|
|
else:
|
|
for i in range(0, numtotems):
|
|
var.ACTIVE_PROTECTIONS[v].append("totem")
|
|
|
|
@event_listener("fallen_angel_guard_break")
|
|
def on_fagb(evt, cli, var, victim, killer):
|
|
# we'll never end up killing a shaman who gave out protection, but delete the totem since
|
|
# story-wise it gets demolished at night by the FA
|
|
while victim in havetotem:
|
|
havetotem.remove(victim)
|
|
brokentotem.add(victim)
|
|
|
|
@event_listener("transition_day_resolve", priority=2)
|
|
def on_transition_day_resolve2(evt, cli, var, victim):
|
|
if evt.data["protected"].get(victim) == "totem":
|
|
evt.data["message"].append(messages["totem_protection"].format(victim))
|
|
evt.data["novictmsg"] = False
|
|
evt.stop_processing = True
|
|
evt.prevent_default = True
|
|
|
|
@event_listener("transition_day_resolve", priority=6)
|
|
def on_transition_day_resolve6(evt, cli, var, victim):
|
|
# TODO: remove these checks once everything is split
|
|
# right now they're needed because otherwise retribution may fire off when the target isn't actually dying
|
|
# that will not be an issue once everything is using the event
|
|
if evt.data["protected"].get(victim):
|
|
return
|
|
if victim in var.ROLES["lycan"] and victim in evt.data["onlybywolves"] and victim not in var.IMMUNIZED:
|
|
return
|
|
# END checks to remove
|
|
|
|
if victim in RETRIBUTION:
|
|
killers = list(evt.data["killers"].get(victim, []))
|
|
loser = None
|
|
while killers:
|
|
loser = random.choice(killers)
|
|
if loser in evt.data["dead"] or victim == loser:
|
|
killers.remove(loser)
|
|
continue
|
|
break
|
|
if loser in evt.data["dead"] or victim == loser:
|
|
loser = None
|
|
ret_evt = Event("retribution_kill", {"target": loser, "message": []})
|
|
ret_evt.dispatch(cli, var, victim, loser)
|
|
loser = ret_evt.data["target"]
|
|
evt.data["message"].extend(ret_evt.data["message"])
|
|
if loser in evt.data["dead"] or victim == loser:
|
|
loser = None
|
|
if loser is not None:
|
|
prots = deque(var.ACTIVE_PROTECTIONS[loser])
|
|
while len(prots) > 0:
|
|
# an event can read the current active protection and cancel the totem
|
|
# if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS
|
|
# so that it cannot be used again (if the protection is meant to be usable once-only)
|
|
ret_evt = Event("retribution_totem", {"message": []})
|
|
if not ret_evt.dispatch(cli, var, victim, loser, prots[0]):
|
|
evt.data["message"].extend(ret_evt.data["message"])
|
|
return
|
|
prots.popleft()
|
|
evt.data["dead"].append(loser)
|
|
if var.ROLE_REVEAL in ("on", "team"):
|
|
role = get_reveal_role(loser)
|
|
an = "n" if role.startswith(("a", "e", "i", "o", "u")) else ""
|
|
evt.data["message"].append(messages["totem_death"].format(victim, loser, an, role))
|
|
else:
|
|
evt.data["message"].append(messages["totem_death_no_reveal"].format(victim, loser))
|
|
|
|
@event_listener("transition_day_end", priority=1)
|
|
def on_transition_day_end(evt, cli, var):
|
|
message = []
|
|
for player, tlist in itertools.groupby(havetotem):
|
|
ntotems = len(list(tlist))
|
|
message.append(messages["totem_posession"].format(
|
|
player, "ed" if player not in list_players() else "s", "a" if ntotems == 1 else "\u0002{0}\u0002".format(ntotems), "s" if ntotems > 1 else ""))
|
|
for player in brokentotem:
|
|
message.append(messages["totem_broken"].format(player))
|
|
cli.msg(botconfig.CHANNEL, "\n".join(message))
|
|
|
|
@event_listener("transition_night_end", priority=2.01)
|
|
def on_transition_night_end(evt, cli, var):
|
|
max_totems = defaultdict(int)
|
|
ps = list_players()
|
|
shamans = list_players(var.TOTEM_ORDER)
|
|
for ix in range(len(var.TOTEM_ORDER)):
|
|
for c in var.TOTEM_CHANCES.values():
|
|
max_totems[var.TOTEM_ORDER[ix]] += c[ix]
|
|
for s in list(LASTGIVEN.keys()):
|
|
if s not in shamans:
|
|
del LASTGIVEN[s]
|
|
for shaman in list_players(var.TOTEM_ORDER):
|
|
pl = ps[:]
|
|
random.shuffle(pl)
|
|
if shaman in LASTGIVEN and LASTGIVEN[shaman] in pl:
|
|
pl.remove(LASTGIVEN[shaman])
|
|
role = get_role(shaman)
|
|
indx = var.TOTEM_ORDER.index(role)
|
|
target = 0
|
|
rand = random.random() * max_totems[var.TOTEM_ORDER[indx]]
|
|
for t in var.TOTEM_CHANCES.keys():
|
|
target += var.TOTEM_CHANCES[t][indx]
|
|
if rand <= target:
|
|
TOTEMS[shaman] = t
|
|
break
|
|
if shaman in var.PLAYERS and not is_user_simple(shaman):
|
|
if role not in var.WOLFCHAT_ROLES:
|
|
pm(cli, shaman, messages["shaman_notify"].format(role, "random " if shaman in var.ROLES["crazed shaman"] else ""))
|
|
if role != "crazed shaman":
|
|
totem = TOTEMS[shaman]
|
|
tmsg = messages["shaman_totem"].format(totem)
|
|
try:
|
|
tmsg += messages[totem + "_totem"]
|
|
except KeyError:
|
|
tmsg += messages["generic_bug_totem"]
|
|
pm(cli, shaman, tmsg)
|
|
else:
|
|
if role not in var.WOLFCHAT_ROLES:
|
|
pm(cli, shaman, messages["shaman_simple"].format(role))
|
|
if role != "crazed shaman":
|
|
pm(cli, shaman, messages["totem_simple"].format(TOTEMS[shaman]))
|
|
if role not in var.WOLFCHAT_ROLES:
|
|
pm(cli, shaman, "Players: " + ", ".join(pl))
|
|
|
|
@event_listener("begin_day")
|
|
def on_begin_day(evt, cli, var):
|
|
# Apply totem effects that need to begin on day proper
|
|
var.EXCHANGED.update(EXCHANGE)
|
|
var.SILENCED.update(SILENCE)
|
|
var.LYCANTHROPES.update(LYCANTHROPY)
|
|
# pestilence doesn't take effect on immunized players
|
|
var.DISEASED.update(PESTILENCE - var.IMMUNIZED)
|
|
var.LUCKY.update(LUCK)
|
|
var.MISDIRECTED.update(MISDIRECTION)
|
|
|
|
SHAMANS.clear()
|
|
|
|
@event_listener("abstain")
|
|
def on_abstain(evt, cli, var, nick):
|
|
if nick in NARCOLEPSY:
|
|
pm(cli, nick, messages["totem_narcolepsy"])
|
|
evt.prevent_default = True
|
|
|
|
@event_listener("lynch")
|
|
def on_lynch(evt, cli, var, nick):
|
|
if nick in NARCOLEPSY:
|
|
pm(cli, nick, messages["totem_narcolepsy"])
|
|
evt.prevent_default = True
|
|
|
|
@event_listener("assassinate")
|
|
def on_assassinate(evt, cli, var, nick, target, prot):
|
|
if prot == "totem":
|
|
var.ACTIVE_PROTECTIONS[target].remove("totem")
|
|
evt.prevent_default = True
|
|
evt.stop_processing = True
|
|
cli.msg(botconfig.CHANNEL, messages[evt.params.message_prefix + "totem"].format(nick, target))
|
|
|
|
@event_listener("succubus_visit")
|
|
def on_succubus_visit(evt, cli, var, nick, victim):
|
|
if (SHAMANS.get(victim, (None, None))[1] in var.ROLES["succubus"] and
|
|
(get_role(victim) == "crazed shaman" or TOTEMS[victim] not in var.BENEFICIAL_TOTEMS)):
|
|
pm(cli, victim, messages["retract_totem_succubus"].format(SHAMANS[victim]))
|
|
del SHAMANS[victim]
|
|
|
|
@event_listener("myrole")
|
|
def on_myrole(evt, cli, var, nick):
|
|
role = evt.data["role"]
|
|
if role in var.TOTEM_ORDER and role != "crazed shaman" and var.PHASE == "night" and nick not in SHAMANS:
|
|
evt.data["messages"].append(messages["totem_simple"].format(TOTEMS[nick]))
|
|
|
|
@event_listener("revealroles_role")
|
|
def on_revealroles(evt, var, wrapper, nickname, role):
|
|
if role in var.TOTEM_ORDER and nickname in TOTEMS:
|
|
if nickname in SHAMANS:
|
|
evt.data["special_case"].append("giving {0} totem to {1}".format(TOTEMS[nickname], SHAMANS[nickname][0]))
|
|
elif var.PHASE == "night":
|
|
evt.data["special_case"].append("has {0} totem".format(TOTEMS[nickname]))
|
|
elif nickname in LASTGIVEN and LASTGIVEN[nickname]:
|
|
evt.data["special_case"].append("gave {0} totem to {1}".format(TOTEMS[nickname], LASTGIVEN[nickname]))
|
|
|
|
@event_listener("reset")
|
|
def on_reset(evt, var):
|
|
TOTEMS.clear()
|
|
LASTGIVEN.clear()
|
|
SHAMANS.clear()
|
|
DEATH.clear()
|
|
PROTECTION.clear()
|
|
REVEALING.clear()
|
|
NARCOLEPSY.clear()
|
|
SILENCE.clear()
|
|
DESPERATION.clear()
|
|
IMPATIENCE.clear()
|
|
PACIFISM.clear()
|
|
INFLUENCE.clear()
|
|
EXCHANGE.clear()
|
|
LYCANTHROPY.clear()
|
|
LUCK.clear()
|
|
PESTILENCE.clear()
|
|
RETRIBUTION.clear()
|
|
MISDIRECTION.clear()
|
|
DECEIT.clear()
|
|
|
|
@event_listener("frole_role")
|
|
def on_frole_role(evt, cli, var, who, role, oldrole, args):
|
|
if role in var.TOTEM_ORDER:
|
|
if len(args) == 2:
|
|
TOTEMS[who] = args[1]
|
|
else:
|
|
max_totems = defaultdict(int)
|
|
for ix in range(len(var.TOTEM_ORDER)):
|
|
for c in var.TOTEM_CHANCES.values():
|
|
max_totems[var.TOTEM_ORDER[ix]] += c[ix]
|
|
for shaman in list_players(var.TOTEM_ORDER):
|
|
indx = var.TOTEM_ORDER.index(role)
|
|
target = 0
|
|
rand = random.random() * max_totems[var.TOTEM_ORDER[indx]]
|
|
for t in var.TOTEM_CHANCES.keys():
|
|
target += var.TOTEM_CHANCES[t][indx]
|
|
if rand <= target:
|
|
TOTEMS[shaman] = t
|
|
break
|
|
|
|
@event_listener("get_role_metadata")
|
|
def on_get_role_metadata(evt, cli, var, kind):
|
|
if kind == "night_kills":
|
|
# only add shamans here if they were given a death totem
|
|
# even though retribution kills, it is given a special kill message
|
|
# note that all shaman types (shaman/CS/wolf shaman) are lumped under the "shaman" key
|
|
evt.data["shaman"] = list(TOTEMS.values()).count("death")
|
|
|
|
# vim: set sw=4 expandtab:
|