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.
172 lines
5.3 KiB
Python
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:
|