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}.",
"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.",
"werecrow_transformed": "You have already transformed into a crow, and cannot turn back until day.",
"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.",
"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.",
"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.",
"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.",

View File

@ -7,7 +7,6 @@ __all__ = [
"get_players", "get_all_players", "get_participants",
"get_target", "change_role"
"get_main_role", "get_all_roles", "get_reveal_role",
"is_known_wolf_ally",
]
def get_players(roles=None, *, mainroles=None):
@ -125,17 +124,4 @@ def get_reveal_role(user):
else:
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:

View File

@ -244,7 +244,7 @@ class VillagergameMode(GameMode):
if not tgt:
tgt = random.choice(pl)
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):
# 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 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.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages

View File

@ -3,12 +3,14 @@ import random
from src.utilities import *
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.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event
from src.roles._wolf_helper import is_known_wolf_ally
SEEN = UserSet()
KILLS = UserDict()
SICK = UserDict()
@ -26,7 +28,7 @@ def see(var, wrapper, message):
if not target:
return
if is_known_wolf_ally(wrapper.source, target):
if is_known_wolf_ally(var, wrapper.source, target):
wrapper.send(messages["no_see_wolf"])
return

View File

@ -4,114 +4,94 @@ from collections import defaultdict
import src.settings as var
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.decorators import cmd, event_listener
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
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
# from src.roles import wolf
# wolf.CAN_KILL.add("wolf sphere") # or whatever the new wolf role is
# 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):
# 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)
@cmd("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=CAN_KILL)
def wolf_kill(cli, nick, chan, rest):
@command("kill", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=CAN_KILL)
def wolf_kill(var, wrapper, message):
"""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
if var.DISEASED_WOLVES:
pm(cli, nick, messages["ill_wolves"])
wrapper.pm(messages["ill_wolves"])
return
pieces = re.split(" +", rest)
victims = []
pieces = re.split(" +", message)
targets = []
orig = []
nevt = Event("wolf_numkills", {"numkills": 1})
nevt.dispatch(var)
num_kills = nevt.data["numkills"]
wolf = users._get(nick) # FIXME
i = 0
extra = 0
while i < num_kills + extra:
try:
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:
if len(pieces) < num_kills:
wrapper.pm(messages["wolf_must_target_multiple"])
return
if victim == nick:
pm(cli, nick, messages["no_suicide"])
targs = iter(pieces) # allow random words to follow the initial line without issue
for i in range(num_kills):
target = get_target(var, wrapper, next(targs, None), not_self_message="no_suicide")
if target is None:
return
if in_wolflist(nick, victim):
pm(cli, nick, messages["wolf_no_target_wolf"])
return
orig.append(victim)
target = users._get(victim) # FIXME
if is_known_wolf_ally(var, wrapper.source, target):
wrapper.pm(messages["wolf_no_target_wolf"])
return
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.dispatch(var, wolf, target)
if evt.prevent_default:
if not evt.dispatch(var, wrapper.source, target):
return
victim = evt.data["target"].nick
victims.append(victim)
i += 1
if len(set(victims)) < len(victims):
pm(cli, nick, messages["wolf_must_target_multiple"])
return
KILLS[nick] = victims
target = evt.data["target"]
targets.append(target)
KILLS[wrapper.source] = UserList(targets)
if len(orig) > 1:
# need to expand this eventually (only accomodates 2 kills, whereas we should ideally support arbitrarily many)
pm(cli, nick, messages["player_kill_multiple"].format(orig[0], orig[1]))
msg = messages["wolfchat_kill_multiple"].format(nick, orig[0], orig[1])
debuglog("{0} ({1}) KILL: {2} ({3}) and {4} ({5})".format(nick, role, victims[0], get_role(victims[0]), victims[1], get_role(victims[1])))
# TODO: Expand this so we can support arbitrarily many kills (instead of just one or two)
wrapper.pm(messages["player_kill_multiple"].format(*orig))
msg = messages["wolfchat_kill_multiple"].format(wrapper.source, *orig)
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:
pm(cli, nick, messages["player_kill"].format(orig[0]))
msg = messages["wolfchat_kill"].format(nick, orig[0])
if num_kills > 1:
pm(cli, nick, messages["wolf_target_second"])
debuglog("{0} ({1}) KILL: {2} ({3})".format(nick, role, victims[0], get_role(victims[0])))
wrapper.pm(messages["player_kill"].format(orig[0]))
msg = messages["wolfchat_kill"].format(wrapper.source, orig[0])
debuglog("{0} ({1}) KILL: {2} ({3})".format(wrapper.source, role, targets[0], get_main_role(targets[0])))
if in_wolflist(nick, nick):
relay_wolfchat_command(cli, nick, msg, var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True)
send_wolfchat_message(var, wrapper.source, msg, var.WOLF_ROLES, role=role, command="kill")
@cmd("retract", "r", chan=False, pm=True, playing=True, phases=("night",))
def wolf_retract(cli, nick, chan, rest):
@command("retract", "r", chan=False, pm=True, playing=True, phases=("night",))
def wolf_retract(var, wrapper, message):
"""Removes a wolf's kill selection."""
if nick in KILLS:
del KILLS[nick]
pm(cli, nick, 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)
if users._get(nick) in var.ROLES["alpha wolf"] and nick in var.BITE_PREFERENCES: # FIXME
del var.BITE_PREFERENCES[nick]
var.ALPHA_WOLVES.remove(nick)
pm(cli, nick, messages["no_bite"])
relay_wolfchat_command(cli, nick, messages["wolfchat_no_bite"].format(nick), ("alpha wolf",), is_wolf_command=True)
if wrapper.source in KILLS:
del KILLS[wrapper.source]
wrapper.pm(messages["retracted_kill"])
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 wrapper.source in var.ROLES["alpha wolf"] and wrapper.source.nick in var.BITE_PREFERENCES: # FIXME: Split into alpha wolf and convert to users
del var.BITE_PREFERENCES[wrapper.source.nick]
var.ALPHA_WOLVES.remove(wrapper.source.nick)
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")
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:
var.ALPHA_ENABLED = True
for a,b in list(KILLS.items()):
for n in b:
if n == user.nick:
KILLS[a].remove(user.nick)
if a == user.nick or len(KILLS[a]) == 0:
del KILLS[a]
@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]
for killer, targets in list(KILLS.items()):
for target in targets:
if user is target:
targets.remove(target)
if user is killer or not targets:
del KILLS[killer]
@event_listener("night_acted")
def on_acted(evt, var, user, actor):
if user.nick in KILLS:
if user in KILLS:
evt.data["acted"] = True
@event_listener("get_special")
@ -161,11 +125,6 @@ def on_transition_day(evt, var):
num_kills = nevt.data["numkills"]
for v in KILLS.values():
for p in v:
if p:
# 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):
maxc = 0
@ -177,19 +136,18 @@ def on_transition_day(evt, var):
elif c == maxc:
dups.append(v)
if maxc and dups:
victim = random.choice(dups)
user = users._get(victim) # FIXME
evt.data["victims"].append(user)
evt.data["bywolves"].add(user)
evt.data["onlybywolves"].add(user)
target = random.choice(dups)
evt.data["victims"].append(target)
evt.data["bywolves"].add(target)
evt.data["onlybywolves"].add(target)
# special key to let us know to randomly select a wolf in case of retribution totem
evt.data["killers"][user].append("@wolves")
del found[victim]
evt.data["killers"][target].append("@wolves")
del found[target]
# this should be moved to an event in kill, where monster prefixes their nick with !
# and fallen angel subsequently removes the ! prefix
# when monster is split, add protection to them if in onlybywolves
# fallen angel will then remove that protection
# 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",)):
if monster in evt.data["victims"]:
evt.data["victims"].remove(monster)
@ -248,8 +206,8 @@ def on_new_role(evt, var, player, old_role):
sayrole = var.DEFAULT_ROLE
an = "n" if sayrole.startswith(("a", "e", "i", "o", "u")) else ""
if player.nick in KILLS: # FIXME
del KILLS[player.nick]
if player in KILLS:
del KILLS[player]
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

View File

@ -9,9 +9,11 @@ from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
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
@event_listener("wolf_numkills")
@ -27,7 +29,7 @@ def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
@event_listener("new_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"])
@event_listener("transition_night_end", priority=3)
@ -35,8 +37,8 @@ def on_transition_night_end(evt, var):
if not ANGRY_WOLVES:
return
wolves = get_players(wolf.CAN_KILL)
if not wolves or not wolf.wolf_can_kill(var, wolves[0]):
wolves = get_players(CAN_KILL)
if not wolves or not wolf_can_kill(var, wolves[0]):
return
for wofl in wolves: