Convert wolf.py and friends to the new User API (#347)

Also add a _wolf_helper.py file under the src/roles subfolder, containing helpers for all wolf-related roles.
This commit is contained in:
Em Barry 2018-07-05 15:28:30 -04:00 committed by GitHub
parent 7adebc49fa
commit ca684ce2e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 160 additions and 139 deletions

View File

@ -334,7 +334,7 @@
"mystic_info" : "There {0} {1}{2} alive{3}.", "mystic_info" : "There {0} {1}{2} alive{3}.",
"ill_wolves": "You are feeling ill tonight, and are unable to kill anyone.", "ill_wolves": "You are feeling ill tonight, and are unable to kill anyone.",
"angry_wolves": "You are \u0002angry\u0002 tonight, and may kill two targets by using \"kill <nick1> and <nick2>\".", "angry_wolves": "You are \u0002angry\u0002 tonight, and may kill two targets by using \"kill <nick1> <nick2>\".",
"wolf_bite": "You may use \"bite <nick>\" tonight in order to turn that person into a wolf.", "wolf_bite": "You may use \"bite <nick>\" tonight in order to turn that person into a wolf.",
"werecrow_transformed": "You have already transformed into a crow, and cannot turn back until day.", "werecrow_transformed": "You have already transformed into a crow, and cannot turn back until day.",
"retracted_kill": "You have retracted your kill.", "retracted_kill": "You have retracted your kill.",
@ -369,7 +369,6 @@
"player_kill_multiple": "You have selected \u0002{0}\u0002 and \u0002{1}\u0002 to be killed.", "player_kill_multiple": "You have selected \u0002{0}\u0002 and \u0002{1}\u0002 to be killed.",
"wolfchat_kill": "\u0002{0}\u0002 has selected \u0002{1}\u0002 to be killed.", "wolfchat_kill": "\u0002{0}\u0002 has selected \u0002{1}\u0002 to be killed.",
"wolfchat_kill_multiple": "\u0002{0}\u0002 has selected \u0002{1}\u0002 and \u0002{2}\u0002 to be killed.", "wolfchat_kill_multiple": "\u0002{0}\u0002 has selected \u0002{1}\u0002 and \u0002{2}\u0002 to be killed.",
"wolf_target_second": "You are angry tonight and may kill a second target. Use \"kill <nick1> and <nick2>\" to select multiple targets.",
"already_protecting": "You are already protecting someone tonight.", "already_protecting": "You are already protecting someone tonight.",
"guardian_target_another": "You protected \u0002{0}\u0002 last night. You cannot protect the same person two nights in a row.", "guardian_target_another": "You protected \u0002{0}\u0002 last night. You cannot protect the same person two nights in a row.",
"cannot_guard_self": "You cannot guard yourself. Use pass if you do not wish to guard anyone tonight.", "cannot_guard_self": "You cannot guard yourself. Use pass if you do not wish to guard anyone tonight.",

View File

@ -7,7 +7,6 @@ __all__ = [
"get_players", "get_all_players", "get_participants", "get_players", "get_all_players", "get_participants",
"get_target", "change_role" "get_target", "change_role"
"get_main_role", "get_all_roles", "get_reveal_role", "get_main_role", "get_all_roles", "get_reveal_role",
"is_known_wolf_ally",
] ]
def get_players(roles=None, *, mainroles=None): def get_players(roles=None, *, mainroles=None):
@ -125,17 +124,4 @@ def get_reveal_role(user):
else: else:
return "village member" return "village member"
def is_known_wolf_ally(actor, target):
actor_role = get_main_role(actor)
target_role = get_main_role(target)
wolves = var.WOLFCHAT_ROLES
if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES:
if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF:
wolves = var.WOLF_ROLES
else:
wolves = var.WOLF_ROLES | {"traitor"}
return actor_role in wolves and target_role in wolves
# vim: set sw=4 expandtab: # vim: set sw=4 expandtab:

View File

@ -244,7 +244,7 @@ class VillagergameMode(GameMode):
if not tgt: if not tgt:
tgt = random.choice(pl) tgt = random.choice(pl)
from src.roles import wolf from src.roles import wolf
wolf.KILLS[users.Bot.nick] = [tgt.nick] wolf.KILLS[users.Bot] = [tgt]
def on_retribution_kill(self, evt, var, victim, orig_target): def on_retribution_kill(self, evt, var, victim, orig_target):
# There are no wolves for this totem to kill # There are no wolves for this totem to kill

74
src/roles/_wolf_helper.py Normal file
View File

@ -0,0 +1,74 @@
import re
import random
import itertools
import math
from collections import defaultdict
from src.utilities import *
from src.functions import get_main_role, get_players, get_all_roles
from src.decorators import event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event
CAN_KILL = set() # type: Set[str]
_kill_cmds = ("kill", "retract")
def wolf_can_kill(var, wolf):
# a wolf can kill if wolves in general can kill, and the wolf belongs to a role in CAN_KILL
# this is a utility function meant to be used by other wolf role modules
nevt = Event("wolf_numkills", {"numkills": 1})
nevt.dispatch(var)
num_kills = nevt.data["numkills"]
if num_kills == 0:
return False
wolfroles = get_all_roles(wolf)
return bool(CAN_KILL & wolfroles)
def is_known_wolf_ally(var, actor, target):
actor_role = get_main_role(actor)
target_role = get_main_role(target)
wolves = var.WOLFCHAT_ROLES
if var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES:
if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF:
wolves = var.WOLF_ROLES
else:
wolves = var.WOLF_ROLES | {"traitor"}
return actor_role in wolves and target_role in wolves
def send_wolfchat_message(var, user, message, roles, *, role=None, command=None):
if role not in CAN_KILL and var.RESTRICT_WOLFCHAT & var.RW_NO_INTERACTION:
return
if command not in _kill_cmds and var.RESTRICT_WOLFCHAT & var.RW_ONLY_KILL_CMD:
if var.PHASE == "night" and var.RESTRICT_WOLFCHAT & var.RW_DISABLE_NIGHT:
return
if var.PHASE == "day" and var.RESTRICT_WOLFCHAT & var.RW_DISABLE_DAY:
return
if not is_known_wolf_ally(var, user, user):
return
wcroles = var.WOLFCHAT_ROLES
if var.RESTRICT_WOLFCHAT & var.RW_ONLY_SAME_CMD:
if var.PHASE == "night" and var.RESTRICT_WOLFCHAT & var.RW_DISABLE_NIGHT:
wcroles = roles
if var.PHASE == "day" and var.RESTRICT_WOLFCHAT & var.RW_DISABLE_DAY:
wcroles = roles
elif var.RESTRICT_WOLFCHAT & var.RW_REM_NON_WOLVES:
if var.RESTRICT_WOLFCHAT & var.RW_TRAITOR_NON_WOLF:
wcroles = var.WOLF_ROLES
else:
wcroles = var.WOLF_ROLES | {"traitor"}
wcwolves = get_players(wcroles)
wcwolves.remove(user)
player = None
for player in wcwolves:
player.queue_message(message)
for player in var.SPECTATING_WOLFCHAT:
player.queue_message("[wolfchat] " + message)
if player is not None:
player.send_messages()

View File

@ -6,7 +6,7 @@ from collections import defaultdict
from src.utilities import * from src.utilities import *
from src import channels, users, debuglog, errlog, plog from src import channels, users, debuglog, errlog, plog
from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target, is_known_wolf_ally, change_role from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target, change_role
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages from src.messages import messages

View File

@ -3,12 +3,14 @@ import random
from src.utilities import * from src.utilities import *
from src import users, channels, debuglog, errlog, plog from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players, get_main_role, get_target, is_known_wolf_ally from src.functions import get_players, get_all_players, get_main_role, get_target
from src.decorators import command, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
from src.roles._wolf_helper import is_known_wolf_ally
SEEN = UserSet() SEEN = UserSet()
KILLS = UserDict() KILLS = UserDict()
SICK = UserDict() SICK = UserDict()
@ -26,7 +28,7 @@ def see(var, wrapper, message):
if not target: if not target:
return return
if is_known_wolf_ally(wrapper.source, target): if is_known_wolf_ally(var, wrapper.source, target):
wrapper.send(messages["no_see_wolf"]) wrapper.send(messages["no_see_wolf"])
return return

View File

@ -4,114 +4,94 @@ from collections import defaultdict
import src.settings as var import src.settings as var
from src.utilities import * from src.utilities import *
from src.functions import get_players, get_all_players, get_main_role, get_all_roles from src.functions import get_players, get_all_players, get_main_role, get_all_roles, get_target
from src import debuglog, errlog, plog, users, channels from src import debuglog, errlog, plog, users, channels
from src.decorators import cmd, event_listener from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
KILLS = {} # type: Dict[str, List[str]] from src.roles._wolf_helper import CAN_KILL, is_known_wolf_ally, send_wolfchat_message
KILLS = UserDict() # type: Dict[users.User, List[users.User]]
# wolves able to use the !kill command, roles should add to this in their own files via # wolves able to use the !kill command, roles should add to this in their own files via
# from src.roles import wolf # from src.roles import wolf
# wolf.CAN_KILL.add("wolf sphere") # or whatever the new wolf role is # wolf.CAN_KILL.add("wolf sphere") # or whatever the new wolf role is
# simply modifying var.WOLF_ROLES will *not* update this! # simply modifying var.WOLF_ROLES will *not* update this!
CAN_KILL = set(var.WOLF_ROLES) # type: Set[str] # TODO: Move this into something else
CAN_KILL.update(var.WOLF_ROLES)
def wolf_can_kill(var, wolf): @command("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=CAN_KILL)
# a wolf can kill if wolves in general can kill, and the wolf belongs to a role in CAN_KILL def wolf_kill(var, wrapper, message):
# this is a utility function meant to be used by other wolf role modules
nevt = Event("wolf_numkills", {"numkills": 1})
nevt.dispatch(var)
num_kills = nevt.data["numkills"]
if num_kills == 0:
return False
wolfroles = get_all_roles(wolf)
return bool(CAN_KILL & wolfroles)
@cmd("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=CAN_KILL)
def wolf_kill(cli, nick, chan, rest):
"""Kills one or more players as a wolf.""" """Kills one or more players as a wolf."""
role = get_role(nick) role = get_main_role(wrapper.source)
# eventually cub will listen on targeted_command and block kills that way # eventually cub will listen on targeted_command and block kills that way
if var.DISEASED_WOLVES: if var.DISEASED_WOLVES:
pm(cli, nick, messages["ill_wolves"]) wrapper.pm(messages["ill_wolves"])
return return
pieces = re.split(" +", rest) pieces = re.split(" +", message)
victims = [] targets = []
orig = [] orig = []
nevt = Event("wolf_numkills", {"numkills": 1}) nevt = Event("wolf_numkills", {"numkills": 1})
nevt.dispatch(var) nevt.dispatch(var)
num_kills = nevt.data["numkills"] num_kills = nevt.data["numkills"]
wolf = users._get(nick) # FIXME if len(pieces) < num_kills:
wrapper.pm(messages["wolf_must_target_multiple"])
return
i = 0 targs = iter(pieces) # allow random words to follow the initial line without issue
extra = 0 for i in range(num_kills):
while i < num_kills + extra: target = get_target(var, wrapper, next(targs, None), not_self_message="no_suicide")
try: if target is None:
victim = pieces[i]
except IndexError:
break
if i > 0 and victim.lower() == "and" and i+1 < len(pieces):
extra += 1
i += 1
victim = pieces[i]
victim = get_victim(cli, nick, victim, False)
if not victim:
return return
if victim == nick: if is_known_wolf_ally(var, wrapper.source, target):
pm(cli, nick, messages["no_suicide"]) wrapper.pm(messages["wolf_no_target_wolf"])
return return
if in_wolflist(nick, victim):
pm(cli, nick, messages["wolf_no_target_wolf"])
return
orig.append(victim)
target = users._get(victim) # FIXME if target in orig:
wrapper.pm(messages["wolf_must_target_multiple"])
return
orig.append(target)
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True}) evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
evt.dispatch(var, wolf, target) if not evt.dispatch(var, wrapper.source, target):
if evt.prevent_default:
return return
victim = evt.data["target"].nick
victims.append(victim)
i += 1
if len(set(victims)) < len(victims): target = evt.data["target"]
pm(cli, nick, messages["wolf_must_target_multiple"]) targets.append(target)
return
KILLS[nick] = victims KILLS[wrapper.source] = UserList(targets)
if len(orig) > 1: if len(orig) > 1:
# need to expand this eventually (only accomodates 2 kills, whereas we should ideally support arbitrarily many) # TODO: Expand this so we can support arbitrarily many kills (instead of just one or two)
pm(cli, nick, messages["player_kill_multiple"].format(orig[0], orig[1])) wrapper.pm(messages["player_kill_multiple"].format(*orig))
msg = messages["wolfchat_kill_multiple"].format(nick, orig[0], orig[1]) msg = messages["wolfchat_kill_multiple"].format(wrapper.source, *orig)
debuglog("{0} ({1}) KILL: {2} ({3}) and {4} ({5})".format(nick, role, victims[0], get_role(victims[0]), victims[1], get_role(victims[1]))) debuglog("{0} ({1}) KILL: {2} ({4}) and {3} ({5})".format(wrapper.source, role, *targets, get_main_role(targets[0]), get_main_role(targets[1])))
else: else:
pm(cli, nick, messages["player_kill"].format(orig[0])) wrapper.pm(messages["player_kill"].format(orig[0]))
msg = messages["wolfchat_kill"].format(nick, orig[0]) msg = messages["wolfchat_kill"].format(wrapper.source, orig[0])
if num_kills > 1: debuglog("{0} ({1}) KILL: {2} ({3})".format(wrapper.source, role, targets[0], get_main_role(targets[0])))
pm(cli, nick, messages["wolf_target_second"])
debuglog("{0} ({1}) KILL: {2} ({3})".format(nick, role, victims[0], get_role(victims[0])))
if in_wolflist(nick, nick): send_wolfchat_message(var, wrapper.source, msg, var.WOLF_ROLES, role=role, command="kill")
relay_wolfchat_command(cli, nick, msg, var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True)
@cmd("retract", "r", chan=False, pm=True, playing=True, phases=("night",)) @command("retract", "r", chan=False, pm=True, playing=True, phases=("night",))
def wolf_retract(cli, nick, chan, rest): def wolf_retract(var, wrapper, message):
"""Removes a wolf's kill selection.""" """Removes a wolf's kill selection."""
if nick in KILLS: if wrapper.source in KILLS:
del KILLS[nick] del KILLS[wrapper.source]
pm(cli, nick, messages["retracted_kill"]) wrapper.pm(messages["retracted_kill"])
relay_wolfchat_command(cli, nick, messages["wolfchat_retracted_kill"].format(nick), var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True) send_wolfchat_message(var, wrapper.source, messages["wolfchat_retracted_kill"].format(wrapper.source), var.WOLF_ROLES, role=get_main_role(wrapper.source), command="retract")
if users._get(nick) in var.ROLES["alpha wolf"] and nick in var.BITE_PREFERENCES: # FIXME
del var.BITE_PREFERENCES[nick] if wrapper.source in var.ROLES["alpha wolf"] and wrapper.source.nick in var.BITE_PREFERENCES: # FIXME: Split into alpha wolf and convert to users
var.ALPHA_WOLVES.remove(nick) del var.BITE_PREFERENCES[wrapper.source.nick]
pm(cli, nick, messages["no_bite"]) var.ALPHA_WOLVES.remove(wrapper.source.nick)
relay_wolfchat_command(cli, nick, messages["wolfchat_no_bite"].format(nick), ("alpha wolf",), is_wolf_command=True) wrapper.pm(messages["no_bite"])
send_wolfchat_message(var, wrapper.source, messages["wolfchat_no_bite"].format(wrapper.source), ("alpha wolf",), role="alpha wolf", command="retract")
@event_listener("del_player") @event_listener("del_player")
def on_del_player(evt, var, user, mainrole, allroles, death_triggers): def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
@ -120,32 +100,16 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
if allroles & var.WOLF_ROLES: if allroles & var.WOLF_ROLES:
var.ALPHA_ENABLED = True var.ALPHA_ENABLED = True
for a,b in list(KILLS.items()): for killer, targets in list(KILLS.items()):
for n in b: for target in targets:
if n == user.nick: if user is target:
KILLS[a].remove(user.nick) targets.remove(target)
if a == user.nick or len(KILLS[a]) == 0: if user is killer or not targets:
del KILLS[a] del KILLS[killer]
@event_listener("rename_player")
def on_rename(evt, var, prefix, nick):
kvp = []
for a,b in KILLS.items():
nl = []
for n in b:
if n == prefix:
n = nick
nl.append(n)
if a == prefix:
a = nick
kvp.append((a,nl))
KILLS.update(kvp)
if prefix in KILLS:
del KILLS[prefix]
@event_listener("night_acted") @event_listener("night_acted")
def on_acted(evt, var, user, actor): def on_acted(evt, var, user, actor):
if user.nick in KILLS: if user in KILLS:
evt.data["acted"] = True evt.data["acted"] = True
@event_listener("get_special") @event_listener("get_special")
@ -161,12 +125,7 @@ def on_transition_day(evt, var):
num_kills = nevt.data["numkills"] num_kills = nevt.data["numkills"]
for v in KILLS.values(): for v in KILLS.values():
for p in v: for p in v:
if p: found[p] += 1
# kill target starting with ! is invalid
# right now nothing does this, but monster eventually will
if p[0] == "!":
continue
found[p] += 1
for i in range(num_kills): for i in range(num_kills):
maxc = 0 maxc = 0
dups = [] dups = []
@ -177,19 +136,18 @@ def on_transition_day(evt, var):
elif c == maxc: elif c == maxc:
dups.append(v) dups.append(v)
if maxc and dups: if maxc and dups:
victim = random.choice(dups) target = random.choice(dups)
user = users._get(victim) # FIXME evt.data["victims"].append(target)
evt.data["victims"].append(user) evt.data["bywolves"].add(target)
evt.data["bywolves"].add(user) evt.data["onlybywolves"].add(target)
evt.data["onlybywolves"].add(user)
# special key to let us know to randomly select a wolf in case of retribution totem # special key to let us know to randomly select a wolf in case of retribution totem
evt.data["killers"][user].append("@wolves") evt.data["killers"][target].append("@wolves")
del found[victim] del found[target]
# this should be moved to an event in kill, where monster prefixes their nick with ! # when monster is split, add protection to them if in onlybywolves
# and fallen angel subsequently removes the ! prefix # fallen angel will then remove that protection
# TODO: when monster is split off # TODO: when monster is split off
if len(var.ROLES["fallen angel"]) == 0: if var.ROLES["fallen angel"]:
for monster in get_all_players(("monster",)): for monster in get_all_players(("monster",)):
if monster in evt.data["victims"]: if monster in evt.data["victims"]:
evt.data["victims"].remove(monster) evt.data["victims"].remove(monster)
@ -248,8 +206,8 @@ def on_new_role(evt, var, player, old_role):
sayrole = var.DEFAULT_ROLE sayrole = var.DEFAULT_ROLE
an = "n" if sayrole.startswith(("a", "e", "i", "o", "u")) else "" an = "n" if sayrole.startswith(("a", "e", "i", "o", "u")) else ""
if player.nick in KILLS: # FIXME if player in KILLS:
del KILLS[player.nick] del KILLS[player]
if old_role not in wcroles and evt.data["role"] in wcroles: if old_role not in wcroles and evt.data["role"] in wcroles:
# a new wofl has joined the party, give them tummy rubs and the wolf list # a new wofl has joined the party, give them tummy rubs and the wolf list

View File

@ -9,9 +9,11 @@ from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
from src.roles import wolf
wolf.CAN_KILL.remove("wolf cub") from src.roles._wolf_helper import wolf_can_kill, CAN_KILL
from src.roles import wolf # ensure that CAN_KILL is populated before trying to remove from it ...
CAN_KILL.remove("wolf cub")
ANGRY_WOLVES = False ANGRY_WOLVES = False
@event_listener("wolf_numkills") @event_listener("wolf_numkills")
@ -27,7 +29,7 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
@event_listener("new_role") @event_listener("new_role")
def on_new_role(evt, var, player, old_role): def on_new_role(evt, var, player, old_role):
if ANGRY_WOLVES and evt.data["in_wolfchat"] and wolf.wolf_can_kill(var, player): if ANGRY_WOLVES and evt.data["in_wolfchat"] and wolf_can_kill(var, player):
evt.data["messages"].append(messages["angry_wolves"]) evt.data["messages"].append(messages["angry_wolves"])
@event_listener("transition_night_end", priority=3) @event_listener("transition_night_end", priority=3)
@ -35,8 +37,8 @@ def on_transition_night_end(evt, var):
if not ANGRY_WOLVES: if not ANGRY_WOLVES:
return return
wolves = get_players(wolf.CAN_KILL) wolves = get_players(CAN_KILL)
if not wolves or not wolf.wolf_can_kill(var, wolves[0]): if not wolves or not wolf_can_kill(var, wolves[0]):
return return
for wofl in wolves: for wofl in wolves: