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:
parent
7adebc49fa
commit
ca684ce2e4
@ -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.",
|
||||
|
@ -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:
|
||||
|
@ -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
74
src/roles/_wolf_helper.py
Normal 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()
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user