<reponame>ManyRaptors/lykos<filename>src/roles/shaman.py<gh_stars>0
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, users, channels
from src.functions import get_players, get_main_role
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]

# FIXME: this needs to be split into shaman.py, wolfshaman.py, and crazedshaman.py
@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) # FIXME: this is bad, check if nick is in var.ROLES[thingy] instead once split
    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, var, user, mainrole, allroles, death_triggers):
    for a,(b,c) in list(SHAMANS.items()):
        if user.nick in (a, b, c):
            del SHAMANS[a]

@event_listener("night_acted")
def on_acted(evt, var, user, actor):
    if user.nick in SHAMANS:
        evt.data["acted"] = True

@event_listener("get_special")
def on_get_special(evt, var):
    evt.data["special"].update(get_players(("shaman", "crazed shaman", "wolf shaman")))

@event_listener("exchange_roles")
def on_exchange(evt, var, actor, target, actor_role, target_role):
    actor_totem = None
    target_totem = None
    if actor_role in var.TOTEM_ORDER:
        actor_totem = TOTEMS.pop(actor.nick)
        if actor.nick in SHAMANS:
            del SHAMANS[actor.nick]
        if actor.nick in LASTGIVEN:
            del LASTGIVEN[actor.nick]
    if target_role in var.TOTEM_ORDER:
        target_totem = TOTEMS.pop(target.nick)
        if target.nick in SHAMANS:
            del SHAMANS[target.nick]
        if target.nick in LASTGIVEN:
            del LASTGIVEN[target.nick]
    if target_totem:
        if target_role != "crazed shaman":
            evt.data["actor_messages"].append(messages["shaman_totem"].format(target_totem))
        TOTEMS[actor.nick] = target_totem
    if actor_totem:
        if actor_role != "crazed shaman":
            evt.data["target_messages"].append(messages["shaman_totem"].format(actor_totem))
        TOTEMS[target.nick] = actor_totem

@event_listener("chk_nightdone")
def on_chk_nightdone(evt, var):
    evt.data["actedcount"] += len(SHAMANS)
    evt.data["nightroles"].extend(get_players(var.TOTEM_ORDER))

@event_listener("get_voters")
def on_get_voters(evt, 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"]
        # TODO: once amnesiac is split, roll this into the revealing_totem event
        if role == "amnesiac":
            role = var.AMNESIAC_ROLES[votee]
            change_role(users._get(votee), "amnesiac", role) # FIXME
            var.AMNESIACS.add(votee)
            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)
            better_deadlist = [users._get(p) for p in evt.data["deadlist"]] # FIXME
            target_user = users._get(target) # FIXME
            evt.params.del_player(target_user, end_game=False, killer_role="shaman", deadlist=better_deadlist, 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, var):
    # Select random totem recipients if shamans didn't act
    pl = get_players()
    for shaman in get_players(var.TOTEM_ORDER):
        if shaman.nick not in SHAMANS and shaman.nick not in var.SILENCED:
            ps = pl[:]
            if shaman.nick in LASTGIVEN:
                user = users._get(LASTGIVEN[shaman.nick]) # FIXME
                if user in ps:
                    ps.remove(user)
            levt = Event("get_random_totem_targets", {"targets": ps})
            levt.dispatch(var, shaman)
            ps = levt.data["targets"]
            if ps:
                target = random.choice(ps)
                totem.func(target.client, shaman.nick, shaman.nick, target.nick, messages["random_totem_prefix"]) # XXX: Old API
            else:
                LASTGIVEN[shaman.nick] = None
        elif shaman not in SHAMANS:
            LASTGIVEN[shaman.nick] = None

@event_listener("transition_day_begin", priority=6)
def on_transition_day_begin2(evt, 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:
            user = users._get(shaman) # FIXME
            user.send(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, var):
    for k, d in DEATH.items():
        shaman = users._get(k) # FIXME
        target = users._get(d) # FIXME
        evt.data["victims"].append(target)
        evt.data["onlybywolves"].discard(target)
        evt.data["killers"][target].append(shaman)

@event_listener("transition_day", priority=4.1)
def on_transition_day3(evt, 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 = get_players()
    vs = set(evt.data["victims"])
    for v in pl:
        numtotems = PROTECTION.count(v.nick)
        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.nick].append("totem")
            evt.data["numkills"][v] = numkills
        else:
            for i in range(0, numtotems):
                var.ACTIVE_PROTECTIONS[v.nick].append("totem")

@event_listener("fallen_angel_guard_break")
def on_fagb(evt, 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.nick in havetotem:
        havetotem.remove(victim.nick)
        brokentotem.add(victim.nick)

@event_listener("transition_day_resolve", priority=2)
def on_transition_day_resolve2(evt, 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, 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.nick in var.ROLES["lycan"] and victim in evt.data["onlybywolves"] and victim.nick not in var.IMMUNIZED:
        return
    # END checks to remove

    if victim.nick 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 is loser:
                killers.remove(loser)
                continue
            break
        if loser in evt.data["dead"] or victim is loser:
            loser = None
        ret_evt = Event("retribution_kill", {"target": loser, "message": []})
        ret_evt.dispatch(var, victim, loser)
        loser = ret_evt.data["target"]
        evt.data["message"].extend(ret_evt.data["message"])
        if loser in evt.data["dead"] or victim is loser:
            loser = None
        if loser is not None:
            prots = deque(var.ACTIVE_PROTECTIONS[loser.nick])
            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(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.nick)
                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, 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))
    channels.Main.send("\n".join(message))

@event_listener("transition_night_end", priority=2.01)
def on_transition_night_end(evt, var):
    max_totems = defaultdict(int)
    ps = get_players()
    shamans = list_players(var.TOTEM_ORDER) # FIXME: Need to convert alongside the entire role
    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 get_players(var.TOTEM_ORDER):
        pl = ps[:]
        random.shuffle(pl)
        if LASTGIVEN.get(shaman.nick):
            user = users._get(LASTGIVEN[shaman.nick]) # FIXME
            if user in pl:
                pl.remove(user)
        role = get_main_role(shaman) # FIXME: don't use get_main_role here once split into one file per role
        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.nick] = t # FIXME: Fix once shaman is converted
                break
        if shaman.prefers_simple():
            if role not in var.WOLFCHAT_ROLES:
                shaman.send(messages["shaman_simple"].format(role))
            if role != "crazed shaman":
                shaman.send(messages["totem_simple"].format(TOTEMS[shaman.nick])) # FIXME
        else:
            if role not in var.WOLFCHAT_ROLES:
                shaman.send(messages["shaman_notify"].format(role, "random " if shaman.nick in var.ROLES["crazed shaman"] else "")) # FIXME
            if role != "crazed shaman":
                totem = TOTEMS[shaman.nick] # FIXME
                tmsg = messages["shaman_totem"].format(totem)
                try:
                    tmsg += messages[totem + "_totem"]
                except KeyError:
                    tmsg += messages["generic_bug_totem"]
                shaman.send(tmsg)
        if role not in var.WOLFCHAT_ROLES:
            shaman.send("Players: " + ", ".join(p.nick for p in pl))

@event_listener("begin_day")
def on_begin_day(evt, 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, var, killer, target, prot):
    if prot == "totem":
        var.ACTIVE_PROTECTIONS[target.nick].remove("totem")
        evt.prevent_default = True
        evt.stop_processing = True
        channels.Main.send(messages[evt.params.message_prefix + "totem"].format(killer, 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, var, user):
    role = evt.data["role"]
    if role in var.TOTEM_ORDER and role != "crazed shaman" and var.PHASE == "night" and user.nick not in SHAMANS:
        evt.data["messages"].append(messages["totem_simple"].format(TOTEMS[user.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, 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 (for now),
        # this will change so they all get their own key in the future (once this is split into 3 files)
        evt.data["shaman"] = list(TOTEMS.values()).count("death")

# vim: set sw=4 expandtab:
