banned/src/roles/hunter.py
skizzerz 740d14ef72 Experimental stats: intelligently determine if traitor could have died
If it is impossible for other (non-wolf) roles to have killed at night,
we do not deduct from the traitor count. This logic isn't perfect yet,
but should cover the majority of cases.
2017-01-16 16:38:51 -06:00

172 lines
5.3 KiB
Python

import re
import random
from collections import defaultdict
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
KILLS = {} # type: Dict[str, str]
HUNTERS = set()
PASSED = set()
@cmd("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("hunter",))
def hunter_kill(cli, nick, chan, rest):
"""Kill someone once per game."""
if nick in HUNTERS and nick not in KILLS:
pm(cli, nick, messages["hunter_already_killed"])
return
victim = get_victim(cli, nick, re.split(" +",rest)[0], False)
if not victim:
return
if victim == nick:
pm(cli, nick, messages["no_suicide"])
return
orig = victim
evt = Event("targeted_command", {"target": victim, "misdirection": True, "exchange": True})
evt.dispatch(cli, var, "kill", nick, victim, frozenset({"detrimental"}))
if evt.prevent_default:
return
victim = evt.data["target"]
KILLS[nick] = victim
HUNTERS.add(nick)
PASSED.discard(nick)
msg = messages["wolf_target"].format(orig)
pm(cli, nick, messages["player"].format(msg))
debuglog("{0} ({1}) KILL: {2} ({3})".format(nick, get_role(nick), victim, get_role(victim)))
chk_nightdone(cli)
@cmd("retract", "r", chan=False, pm=True, playing=True, phases=("night",), roles=("hunter",))
def hunter_retract(cli, nick, chan, rest):
"""Removes a hunter's kill selection."""
if nick not in KILLS and nick not in PASSED:
return
if nick in KILLS:
del KILLS[nick]
HUNTERS.discard(nick)
PASSED.discard(nick)
pm(cli, nick, messages["retracted_kill"])
@cmd("pass", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("hunter",))
def hunter_pass(cli, nick, chan, rest):
"""Do not use hunter's once-per-game kill tonight."""
if nick in HUNTERS and nick not in KILLS:
pm(cli, nick, messages["hunter_already_killed"])
return
if nick in KILLS:
del KILLS[nick]
HUNTERS.discard(nick)
PASSED.add(nick)
pm(cli, nick, messages["hunter_pass"])
debuglog("{0} ({1}) PASS".format(nick, get_role(nick)))
chk_nightdone(cli)
@event_listener("del_player")
def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers):
for h,v in list(KILLS.items()):
if v == nick:
HUNTERS.discard(h)
PASSED.discard(h)
pm(cli, h, messages["hunter_discard"])
del KILLS[h]
elif h == nick:
del KILLS[h]
@event_listener("rename_player")
def on_rename(evt, cli, var, prefix, nick):
kvp = []
for a,b in KILLS.items():
if a == prefix:
a = nick
if b == prefix:
b = nick
kvp.append((a,b))
KILLS.update(kvp)
if prefix in KILLS:
del KILLS[prefix]
if prefix in HUNTERS:
HUNTERS.discard(prefix)
HUNTERS.add(nick)
if prefix in PASSED:
PASSED.discard(prefix)
PASSED.add(nick)
@event_listener("night_acted")
def on_acted(evt, cli, var, nick, sender):
if nick in KILLS:
evt.data["acted"] = True
@event_listener("get_special")
def on_get_special(evt, cli, var):
evt.data["special"].update(list_players(("hunter",)))
@event_listener("transition_day", priority=2)
def on_transition_day(evt, cli, var):
for k, d in list(KILLS.items()):
evt.data["victims"].append(d)
evt.data["onlybywolves"].discard(d)
evt.data["killers"][d].append(k)
# important, otherwise our del_player listener lets hunter kill again
del KILLS[k]
@event_listener("exchange_roles")
def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role):
if actor in KILLS:
del KILLS[actor]
if nick in KILLS:
del KILLS[nick]
HUNTERS.discard(actor)
HUNTERS.discard(nick)
PASSED.discard(actor)
PASSED.discard(nick)
@event_listener("chk_nightdone")
def on_chk_nightdone(evt, cli, var):
evt.data["actedcount"] += len(KILLS) + len(PASSED)
evt.data["nightroles"].extend([p for p in var.ROLES["hunter"] if p not in HUNTERS or p in KILLS])
@event_listener("transition_night_end", priority=2)
def on_transition_night_end(evt, cli, var):
ps = list_players()
for hunter in var.ROLES["hunter"]:
if hunter in HUNTERS:
continue #already killed
pl = ps[:]
random.shuffle(pl)
pl.remove(hunter)
if hunter in var.PLAYERS and not is_user_simple(hunter):
pm(cli, hunter, messages["hunter_notify"])
else:
pm(cli, hunter, messages["hunter_simple"])
pm(cli, hunter, "Players: " + ", ".join(pl))
@event_listener("begin_day")
def on_begin_day(evt, cli, var):
KILLS.clear()
PASSED.clear()
@event_listener("reset")
def on_reset(evt, var):
KILLS.clear()
PASSED.clear()
HUNTERS.clear()
@event_listener("get_role_metadata")
def on_get_role_metadata(evt, cli, var, kind):
if kind == "night_kills":
# hunters is the set of all hunters that have not killed in a *previous* night
# (if they're in both HUNTERS and KILLS, then they killed tonight and should be counted)
hunters = (set(var.ROLES["hunter"]) - HUNTERS) | set(KILLS.keys())
evt.data["hunter"] = len(hunters)
# vim: set sw=4 expandtab: