Convert chk_decision (#317)

Convert chk_decision, chk_nightdone, transition_day, transition_night, doomsayer, mayor, and convert+split shamans in three files with a shared helper. Fixes and updates for the User containers, and some other tweaks and fixes.
This commit is contained in:
Em Barry 2018-04-23 13:25:38 -04:00 committed by GitHub
parent 42424e49df
commit 745a1dc68a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1522 additions and 1287 deletions

View File

@ -502,7 +502,6 @@
"retribution_totem": "If the player who is given this totem will die tonight, they also kill anyone who killed them.",
"misdirection_totem": "If the player who is given this totem attempts to use a power the following day or night, they will target a player adjacent to their intended target instead of the player they targeted.",
"deceit_totem": "If the player who is given this totem is a seer or an oracle, or is seen by a seer or an oracle, the vision will be shifted. If the person would be seen as wolf, they are instead seen as a villager. Otherwise, they are seen as a wolf.",
"generic_bug_totem": "No description for this totem is available. This is a bug, so please report this to the admins.",
"shaman_simple": "You are a \u0002{0}\u0002.",
"totem_simple": "You have the \u0002{0}\u0002 totem.",
"hunter_notify": "You are a \u0002hunter\u0002. Once per game, you may kill another player with \"kill <nick>\". If you do not wish to kill anyone tonight, use \"pass\" instead.",
@ -562,6 +561,8 @@
"need_one_wolf": "There has to be at least one wolf!",
"too_many_wolves": "Too many wolves.",
"error_role_players_count": "Error: Not all roles have defined player counts.",
"error_frole_too_many": "There are too many users assigned to role {0}. Please try again.",
"too_many_roles": "There are not enough players for the number of preset roles. Please try again.",
"default_reset": "The default settings have been restored. Please {0}start again.",
"command_disabled_admin": "This command has been disabled by an admin.",
"not_enough_targets": "Not enough valid targets for the {0} template.",
@ -621,6 +622,7 @@
"invalid_target": "This can only be done on players in the channel or fake nicks.",
"admin_only_force": "Only full admins can force an admin-only command.",
"operation_successful": "Operation successful.",
"frole_incorrect": "Invalid arguments for {0}frole: {1}",
"not_owner": "You are not the owner.",
"invalid_permissions": "You do not have permission to use that command.",
"player_joined_deadchat": "\u0002{0}\u0002 has joined the deadchat.",

View File

@ -1,6 +1,8 @@
import copy
from src.users import User
__all__ = ["UserList", "UserSet", "UserDict"]
__all__ = ["UserList", "UserSet", "UserDict", "DefaultUserDict"]
""" * Important *
@ -45,12 +47,27 @@ class UserList(list):
def __exit__(self, exc_type, exc_value, tb):
self.clear()
def __eq__(self, other):
return self is other
def __copy__(self):
return type(self)(self)
def __deepcopy__(self, memo):
return type(self)(copy.deepcopy(x, memo) for x in self)
def __add__(self, other):
if not isinstance(other, list):
return NotImplemented
self.extend(other)
def __getitem__(self, item):
new = super().__getitem__(item)
if isinstance(item, slice):
new = type(self)(new)
return new
def __setitem__(self, index, value):
if not isinstance(value, User):
raise TypeError("UserList may only contain User instances")
@ -135,6 +152,12 @@ class UserSet(set):
def __exit__(self, exc_type, exc_value, tb):
self.clear()
def __copy__(self):
return type(self)(self)
def __deepcopy__(self, memo):
return type(self)(copy.deepcopy(x, memo) for x in self)
# Comparing UserSet instances for equality doesn't make much sense in our context
# However, if there are identical instances in a list, we only want to remove ourselves
@ -263,6 +286,18 @@ class UserDict(dict):
def __exit__(self, exc_type, exc_value, tb):
self.clear()
def __eq__(self, other):
return self is other
def __copy__(self):
return type(self)(self)
def __deepcopy__(self, memo):
new = type(self)()
for key, value in self.items():
new[key] = copy.deepcopy(value, memo)
return new
def __setitem__(self, item, value):
old = self.get(item)
super().__setitem__(item, value)
@ -288,6 +323,9 @@ class UserDict(dict):
if value not in self.values():
value.dict_values.remove(self)
if isinstance(value, (UserSet, UserList, UserDict)):
value.clear()
def clear(self):
for key, value in self.items():
if isinstance(key, User):
@ -339,3 +377,12 @@ class UserDict(dict):
iterable = iterable.items()
for key, value in iterable:
self[key] = value
class DefaultUserDict(UserDict):
def __init__(_self, _factory, _it=(), **kwargs):
_self.factory = _factory
super().__init__(_it, **kwargs)
def __missing__(self, key):
self[key] = self.factory()
return self[key]

View File

@ -248,6 +248,10 @@ class command:
self.aliases.append(name)
alias = True
if playing: # Don't restrict to owners or allow in alt channels
self.owner_only = False
self.alt_allowed = False
def __call__(self, func):
if isinstance(func, command):
func = func.func
@ -303,7 +307,7 @@ class command:
# Role commands might end the night if it's nighttime
if var.PHASE == "night":
from src.wolfgame import chk_nightdone
chk_nightdone(cli)
chk_nightdone()
return
if self.owner_only:
@ -461,7 +465,7 @@ class cmd:
# Role commands might end the night if it's nighttime
if var.PHASE == "night":
from src.wolfgame import chk_nightdone
chk_nightdone(cli)
chk_nightdone()
return
forced_owner_only = False

View File

@ -6,7 +6,8 @@ from src import users
__all__ = [
"get_players", "get_all_players", "get_participants",
"get_target",
"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):
@ -104,5 +105,17 @@ 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

@ -12,7 +12,7 @@ from src.utilities import *
from src.messages import messages
from src.functions import get_players, get_all_players, get_main_role
from src.decorators import handle_error, command
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src import events, channels, users
def game_mode(name, minp, maxp, likelihood = 0):
@ -172,6 +172,23 @@ class DefaultMode(GameMode):
self.ROLE_INDEX = role_index
self.ROLE_GUIDE = role_guide
def startup(self):
events.add_listener("chk_decision", self.chk_decision, priority=20)
def teardown(self):
events.remove_listener("chk_decision", self.chk_decision, priority=20)
def chk_decision(self, evt, var, force):
if len(var.ALL_PLAYERS) <= 9 and var.VILLAGERGAME_CHANCE > 0:
if users.Bot in evt.data["votelist"]:
if len(evt.data["votelist"][users.Bot]) == len(set(evt.params.voters) - evt.data["not_lynching"]):
channels.Main.send(messages["villagergame_nope"])
from src.wolfgame import stop_game
stop_game("wolves")
evt.prevent_default = True
else:
del evt.data["votelist"][users.Bot]
@game_mode("villagergame", minp = 4, maxp = 9, likelihood = 0)
class VillagergameMode(GameMode):
"""This mode definitely does not exist, now please go away."""
@ -194,12 +211,14 @@ class VillagergameMode(GameMode):
events.add_listener("chk_nightdone", self.chk_nightdone)
events.add_listener("transition_day_begin", self.transition_day)
events.add_listener("retribution_kill", self.on_retribution_kill, priority=4)
events.add_listener("chk_decision", self.chk_decision, priority=20)
def teardown(self):
events.remove_listener("chk_win", self.chk_win)
events.remove_listener("chk_nightdone", self.chk_nightdone)
events.remove_listener("transition_day_begin", self.transition_day)
events.remove_listener("retribution_kill", self.on_retribution_kill, priority=4)
events.remove_listener("chk_decision", self.chk_decision, priority=20)
def chk_win(self, evt, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
# village can only win via unanimous vote on the bot nick
@ -214,15 +233,15 @@ class VillagergameMode(GameMode):
def chk_nightdone(self, evt, var):
transition_day = evt.data["transition_day"]
evt.data["transition_day"] = lambda cli, gameid=0: self.prolong_night(cli, var, gameid, transition_day)
evt.data["transition_day"] = lambda gameid=0: self.prolong_night(var, gameid, transition_day)
def prolong_night(self, cli, var, gameid, transition_day):
def prolong_night(self, var, gameid, transition_day):
nspecials = len(get_all_players(("seer", "harlot", "shaman", "crazed shaman")))
rand = random.gauss(5, 1.5)
if rand <= 0 and nspecials > 0:
transition_day(cli, gameid=gameid)
transition_day(gameid=gameid)
else:
t = threading.Timer(abs(rand), transition_day, args=(cli,), kwargs={"gameid": gameid})
t = threading.Timer(abs(rand), transition_day, kwargs={"gameid": gameid})
t.start()
def transition_day(self, evt, var):
@ -265,6 +284,16 @@ class VillagergameMode(GameMode):
evt.data["target"] = None
evt.stop_processing = True
def chk_decision(self, evt, var, force):
if users.Bot in evt.data["votelist"]:
if len(evt.data["votelist"][users.Bot]) == len(set(evt.params.voters) - evt.data["not_lynching"]):
channels.Main.send(messages["villagergame_win"])
from src.wolfgame import stop_game
stop_game("everyone")
evt.prevent_default = True
else:
del evt.data["votelist"][users.Bot]
@game_mode("foolish", minp = 8, maxp = 24, likelihood = 8)
class FoolishMode(GameMode):
"""Contains the fool, be careful not to lynch them!"""
@ -1315,7 +1344,7 @@ class MudkipMode(GameMode):
events.remove_listener("daylight_warning", self.daylight_warning)
events.remove_listener("transition_night_begin", self.transition_night_begin)
def chk_decision(self, evt, cli, var, force):
def chk_decision(self, evt, var, force):
# If everyone is voting, end day here with the person with plurality being voted. If there's a tie,
# kill all tied players rather than hanging. The intent of this is to benefit village team in the event
# of a stalemate, as they could use the extra help (especially in 5p).
@ -1323,13 +1352,15 @@ class MudkipMode(GameMode):
# in here, this means we're in a child chk_decision event called from this one
# we need to ensure we don't turn into nighttime prematurely or try to vote
# anyone other than the person we're forcing the lynch on
evt.data["transition_night"] = lambda cli: None
evt.data["transition_night"] = lambda: None
if force:
evt.data["votelist"] = {force: set()}
evt.data["numvotes"] = {force: 0}
evt.data["votelist"].clear()
evt.data["votelist"][force] = set()
evt.data["numvotes"].clear()
evt.data["numvotes"][force] = 0
else:
evt.data["votelist"] = {}
evt.data["numvotes"] = {}
evt.data["votelist"].clear()
evt.data["numvotes"].clear()
return
avail = len(evt.params.voters)
@ -1357,12 +1388,12 @@ class MudkipMode(GameMode):
for p in tovote:
deadlist = tovote[:]
deadlist.remove(p)
chk_decision(cli, force=p, deadlist=deadlist, end_game=p is last)
chk_decision(force=p, deadlist=deadlist, end_game=p is last)
self.recursion_guard = False
# gameid changes if game stops due to us voting someone
if var.GAME_ID == gameid:
evt.data["transition_night"](cli)
evt.data["transition_night"]()
# make original chk_decision that called us no-op
evt.prevent_default = True

View File

@ -24,10 +24,12 @@ def on_privmsg(cli, rawnick, chan, msg, *, notice=False, force_role=None):
user = users._get(rawnick, allow_none=True) # FIXME
ch = chan.lstrip("".join(hooks.Features["PREFIX"]))
if users.equals(chan, users.Bot.nick): # PM
target = users.Bot
else:
target = channels.get(chan, allow_none=True)
target = channels.get(ch, allow_none=True)
if user is None or target is None:
return
@ -43,7 +45,7 @@ def on_privmsg(cli, rawnick, chan, msg, *, notice=False, force_role=None):
if force_role is None: # if force_role isn't None, that indicates recursion; don't fire these off twice
for fn in decorators.COMMANDS[""]:
fn.caller(cli, rawnick, chan, msg)
fn.caller(cli, rawnick, ch, msg)
parts = msg.split(sep=" ", maxsplit=1)
key = parts[0].lower()
@ -120,7 +122,7 @@ def on_privmsg(cli, rawnick, chan, msg, *, notice=False, force_role=None):
for fn in cmds:
if phase == var.PHASE:
# FIXME: pass in var, wrapper, message instead of cli, rawnick, chan, message
fn.caller(cli, rawnick, chan, message)
fn.caller(cli, rawnick, ch, message)
def unhandled(cli, prefix, cmd, *args):
for fn in decorators.HOOKS.get(cmd, []):

View File

@ -9,7 +9,7 @@ search = os.path.join(path, "*.py")
for f in glob.iglob(search):
f = os.path.basename(f)
n, _ = os.path.splitext(f)
if f == "__init__.py":
if f.startswith("_"):
continue
importlib.import_module("." + n, package="src.roles")

537
src/roles/_shaman_helper.py Normal file
View File

@ -0,0 +1,537 @@
import itertools
import random
import re
from collections import deque
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
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
DEATH = UserDict() # type: Dict[users.User, List[users.User]]
PROTECTION = UserList() # type: List[users.User]
REVEALING = UserSet() # type: Set[users.User]
NARCOLEPSY = UserSet() # type: Set[users.User]
SILENCE = UserSet() # type: Set[users.User]
DESPERATION = UserSet() # type: Set[users.User]
IMPATIENCE = UserList() # type: List[users.User]
PACIFISM = UserList() # type: List[users.User]
INFLUENCE = UserSet() # type: Set[users.User]
EXCHANGE = UserSet() # type: Set[users.User]
LYCANTHROPY = UserSet() # type: Set[users.User]
LUCK = UserSet() # type: Set[users.User]
PESTILENCE = UserSet() # type: Set[users.User]
RETRIBUTION = UserSet() # type: Set[users.User]
MISDIRECTION = UserSet() # type: Set[users.User]
DECEIT = UserSet() # type: Set[users.User]
# holding vars that don't persist long enough to need special attention in
# reset/exchange/nickchange
havetotem = [] # type: List[users.User]
brokentotem = set() # type: Set[users.User]
# To add new totem types in your custom roles/whatever.py file:
# 1. Add a key to var.TOTEM_CHANCES with the totem name
# 2. Add a message totemname_totem to your custom messages.json describing
# the totem (this is displayed at night if !simple is off)
# 3. Add events as necessary to implement the totem's functionality
#
# To add new shaman roles in your custom roles/whatever.py file:
# 1. Expand var.TOTEM_ORDER and upate var.TOTEM_CHANCES to account for the new width
# 2. Add the role to var.ROLE_GUIDE
# 3. Add the role to whatever other holding vars are necessary based on what it does
# 4. Setup initial variables and events with setup_variables(rolename, knows_totem, get_tags)
# knows_totem is a bool and keyword-only. get_tags is a function in the form get_tags(var, totem)
# and should return a set
# 5. Implement custom events if the role does anything else beyond giving totems.
#
# Modifying this file to add new totems or new shaman roles is generally never required
def setup_variables(rolename, *, knows_totem, get_tags):
"""Setup role variables and shared events."""
TOTEMS = UserDict() # type: Dict[users.User, str]
LASTGIVEN = UserDict() # type: Dict[users.User, users.User]
SHAMANS = UserDict() # type: Dict[users.User, List[users.User]]
@event_listener("reset")
def on_reset(evt, var):
TOTEMS.clear()
LASTGIVEN.clear()
SHAMANS.clear()
@event_listener("begin_day")
def on_begin_day(evt, var):
SHAMANS.clear()
@event_listener("revealroles_role")
def on_revealroles(evt, var, wrapper, user, role):
if role == rolename and user in TOTEMS:
if user in SHAMANS:
evt.data["special_case"].append("giving {0} totem to {1}".format(TOTEMS[user], SHAMANS[user][0]))
elif var.PHASE == "night":
evt.data["special_case"].append("has {0} totem".format(TOTEMS[user]))
elif user in LASTGIVEN and LASTGIVEN[user]:
evt.data["special_case"].append("gave {0} totem to {1}".format(TOTEMS[user], LASTGIVEN[user]))
@event_listener("transition_day_begin", priority=7)
def on_transition_day_begin2(evt, var):
for shaman, (victim, target) in SHAMANS.items():
totem = TOTEMS[shaman]
if totem == "death": # this totem stacks
if shaman not in DEATH:
DEATH[shaman] = UserList()
DEATH[shaman].append(victim)
elif totem == "protection": # this totem stacks
PROTECTION.append(victim)
elif totem == "revealing":
REVEALING.add(victim)
elif totem == "narcolepsy":
NARCOLEPSY.add(victim)
elif totem == "silence":
SILENCE.add(victim)
elif totem == "desperation":
DESPERATION.add(victim)
elif totem == "impatience": # this totem stacks
IMPATIENCE.append(victim)
elif totem == "pacifism": # this totem stacks
PACIFISM.append(victim)
elif totem == "influence":
INFLUENCE.add(victim)
elif totem == "exchange":
EXCHANGE.add(victim)
elif totem == "lycanthropy":
LYCANTHROPY.add(victim)
elif totem == "luck":
LUCK.add(victim)
elif totem == "pestilence":
PESTILENCE.add(victim)
elif totem == "retribution":
RETRIBUTION.add(victim)
elif totem == "misdirection":
MISDIRECTION.add(victim)
elif totem == "deceit":
DECEIT.add(victim)
# other totem types possibly handled in an earlier event,
# as such there is no else: clause here
if target is not victim:
shaman.send(messages["totem_retarget"].format(victim))
LASTGIVEN[shaman] = victim
havetotem.extend(sorted(filter(None, LASTGIVEN.values())))
@event_listener("del_player")
def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
for a,(b,c) in list(SHAMANS.items()):
if user in (a, b, c):
del SHAMANS[a]
@event_listener("night_acted")
def on_acted(evt, var, user, actor):
if user in SHAMANS:
evt.data["acted"] = True
@event_listener("get_special")
def on_get_special(evt, var):
evt.data["special"].update(get_players((rolename,)))
@event_listener("chk_nightdone")
def on_chk_nightdone(evt, var):
evt.data["actedcount"] += len(SHAMANS)
evt.data["nightroles"].extend(get_players((rolename,)))
@event_listener("get_role_metadata")
def on_get_role_metadata(evt, var, kind):
if kind == "night_kills":
# only add shamans here if they were given a death totem
# even though retribution kills, it is given a special kill message
evt.data[rolename] = list(TOTEMS.values()).count("death")
@event_listener("exchange_roles")
def on_exchange(evt, var, actor, target, actor_role, target_role):
actor_totem = None
target_totem = None
if actor_role == rolename:
actor_totem = TOTEMS.pop(actor)
if actor in SHAMANS:
del SHAMANS[actor]
if actor in LASTGIVEN:
del LASTGIVEN[actor]
if target_role == rolename:
target_totem = TOTEMS.pop(target)
if target in SHAMANS:
del SHAMANS[target]
if target in LASTGIVEN:
del LASTGIVEN[target]
if target_totem:
if knows_totem:
evt.data["actor_messages"].append(messages["shaman_totem"].format(target_totem))
TOTEMS[actor] = target_totem
if actor_totem:
if knows_totem:
evt.data["target_messages"].append(messages["shaman_totem"].format(actor_totem))
TOTEMS[target] = actor_totem
@event_listener("succubus_visit")
def on_succubus_visit(evt, var, succubus, target):
if target in SHAMANS and SHAMANS[target][1] in get_all_players(("succubus",)):
tags = get_tags(var, TOTEMS[target])
if "beneficial" not in tags:
target.send(messages["retract_totem_succubus"].format(SHAMANS[target][1]))
del SHAMANS[target]
if knows_totem:
@event_listener("myrole")
def on_myrole(evt, var, user):
if evt.data["role"] == rolename and var.PHASE == "night" and user not in SHAMANS:
evt.data["messages"].append(messages["totem_simple"].format(TOTEMS[user]))
return (TOTEMS, LASTGIVEN, SHAMANS)
def get_totem_target(var, wrapper, message, lastgiven):
"""Get the totem target."""
target = get_target(var, wrapper, re.split(" +", message)[0], allow_self=True)
if not target:
return
if lastgiven.get(wrapper.source) is target:
wrapper.send(messages["shaman_no_target_twice"].format(target))
return
return target
def give_totem(var, wrapper, target, prefix, tags, role, msg):
"""Give a totem to a player. Return the value of SHAMANS[user]."""
orig_target = target
orig_role = get_main_role(orig_target)
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True},
action="give a totem{0} to".format(msg))
if not evt.dispatch(var, "totem", wrapper.source, target, frozenset(tags)):
return
target = evt.data["target"]
targrole = get_main_role(target)
wrapper.send(messages["shaman_success"].format(prefix, msg, orig_target))
debuglog("{0} ({1}) TOTEM: {2} ({3}) as {4} ({5})".format(wrapper.source, role, target, targrole, orig_target, orig_role))
return UserList((target, orig_target))
@event_listener("see", priority=10)
def on_see(evt, var, nick, victim):
if (users._get(victim) in DECEIT) ^ (users._get(nick) in DECEIT): # FIXME
if evt.data["role"] in var.SEEN_WOLF and evt.data["role"] not in var.SEEN_DEFAULT:
evt.data["role"] = "villager"
else:
evt.data["role"] = "wolf"
@event_listener("get_voters")
def on_get_voters(evt, var):
evt.data["voters"] -= NARCOLEPSY
@event_listener("chk_decision", priority=1)
def on_chk_decision(evt, var, force):
nl = []
for p in PACIFISM:
if p in evt.params.voters:
nl.append(p)
# .remove() will only remove the first instance, which means this plays nicely with pacifism countering this
for p in IMPATIENCE:
if p in nl:
nl.remove(p)
evt.data["not_lynching"].update(nl)
for votee, voters in evt.data["votelist"].items():
numvotes = 0
random.shuffle(IMPATIENCE)
for v in IMPATIENCE:
if v in evt.params.voters and v not in voters and v is not votee:
# don't add them in if they have the same number or more of pacifism totems
# this matters for desperation totem on the votee
imp_count = IMPATIENCE.count(v)
pac_count = PACIFISM.count(v)
if pac_count >= imp_count:
continue
# yes, this means that one of the impatient people will get desperation totem'ed if they didn't
# already !vote earlier. sucks to suck. >:)
voters.append(v)
for v in voters:
weight = 1
imp_count = IMPATIENCE.count(v)
pac_count = PACIFISM.count(v)
if pac_count > imp_count:
weight = 0 # more pacifists than impatience totems
elif imp_count == pac_count and v not in var.VOTES[votee]:
weight = 0 # impatience and pacifist cancel each other out, so don't count impatience
if v in INFLUENCE:
weight *= 2
numvotes += weight
if votee not in evt.data["weights"]:
evt.data["weights"][votee] = {}
evt.data["weights"][votee][v] = weight
evt.data["numvotes"][votee] = numvotes
@event_listener("chk_decision_abstain")
def on_chk_decision_abstain(evt, var, not_lynching):
for p in not_lynching:
if p in PACIFISM and p not in var.NO_LYNCH:
channels.Main.send(messages["player_meek_abstain"].format(p))
@event_listener("chk_decision_lynch", priority=1)
def on_chk_decision_lynch1(evt, var, voters):
votee = evt.data["votee"]
for p in voters:
if p in IMPATIENCE and p not in var.VOTES[votee]:
channels.Main.send(messages["impatient_vote"].format(p, votee))
# mayor is at exactly 3, so we want that to always happen before revealing totem
@event_listener("chk_decision_lynch", priority=3.1)
def on_chk_decision_lynch3(evt, var, voters):
votee = evt.data["votee"]
if votee in REVEALING:
role = get_main_role(votee)
rev_evt = Event("revealing_totem", {"role": role})
rev_evt.dispatch(var, votee)
role = rev_evt.data["role"]
# TODO: once amnesiac is split, roll this into the revealing_totem event
if role == "amnesiac":
role = var.AMNESIAC_ROLES[votee.nick]
change_role(votee, "amnesiac", role)
var.AMNESIACS.add(votee.nick)
votee.send(messages["totem_amnesia_clear"])
# If wolfteam, don't bother giving list of wolves since night is about to start anyway
# Existing wolves also know that someone just joined their team because revealing totem says what they are
# If turncoat, set their initial starting side to "none" just in case game ends before they can set it themselves
if role == "turncoat":
var.TURNCOATS[votee.nick] = ("none", -1)
an = "n" if role.startswith(("a", "e", "i", "o", "u")) else ""
channels.Main.send(messages["totem_reveal"].format(votee, an, role))
evt.data["votee"] = None
evt.prevent_default = True
evt.stop_processing = True
@event_listener("chk_decision_lynch", priority=5)
def on_chk_decision_lynch5(evt, var, voters):
votee = evt.data["votee"]
if votee in DESPERATION:
# Also kill the very last person to vote them, unless they voted themselves last in which case nobody else dies
target = voters[-1]
if target is not votee:
prots = deque(var.ACTIVE_PROTECTIONS[target])
while len(prots) > 0:
# an event can read the current active protection and cancel the totem
# if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS
# so that it cannot be used again (if the protection is meant to be usable once-only)
desp_evt = Event("desperation_totem", {})
if not desp_evt.dispatch(var, votee, target, prots[0]):
return
prots.popleft()
if var.ROLE_REVEAL in ("on", "team"):
r1 = get_reveal_role(target)
an1 = "n" if r1.startswith(("a", "e", "i", "o", "u")) else ""
tmsg = messages["totem_desperation"].format(votee, target, an1, r1)
else:
tmsg = messages["totem_desperation_no_reveal"].format(votee, target)
channels.Main.send(tmsg)
# we lie to this function so it doesn't devoice the player yet. instead, we'll let the call further down do it
evt.data["deadlist"].append(target)
evt.params.del_player(target, end_game=False, killer_role="shaman", deadlist=evt.data["deadlist"], ismain=False)
@event_listener("transition_day", priority=2)
def on_transition_day2(evt, var):
for shaman, targets in DEATH.items():
for target in targets:
evt.data["victims"].append(target)
evt.data["onlybywolves"].discard(target)
evt.data["killers"][target].append(shaman)
@event_listener("transition_day", priority=4.1)
def on_transition_day3(evt, var):
# protection totems are applied first in default logic, however
# we set priority=4.1 to allow other modes of protection
# to pre-empt us if desired
pl = get_players()
vs = set(evt.data["victims"])
for v in pl:
numtotems = PROTECTION.count(v)
if v in vs:
if v in var.DYING:
continue
numkills = evt.data["numkills"][v]
for i in range(0, numtotems):
numkills -= 1
if numkills >= 0:
evt.data["killers"][v].pop(0)
if numkills <= 0 and v not in evt.data["protected"]:
evt.data["protected"][v] = "totem"
elif numkills <= 0:
var.ACTIVE_PROTECTIONS[v.nick].append("totem")
evt.data["numkills"][v] = numkills
else:
for i in range(0, numtotems):
var.ACTIVE_PROTECTIONS[v.nick].append("totem")
@event_listener("fallen_angel_guard_break")
def on_fagb(evt, var, victim, killer):
# we'll never end up killing a shaman who gave out protection, but delete the totem since
# story-wise it gets demolished at night by the FA
while victim in havetotem:
havetotem.remove(victim)
brokentotem.add(victim)
@event_listener("transition_day_begin", priority=6)
def on_transition_day_begin(evt, var):
# Reset totem variables
DEATH.clear()
PROTECTION.clear()
REVEALING.clear()
NARCOLEPSY.clear()
SILENCE.clear()
DESPERATION.clear()
IMPATIENCE.clear()
PACIFISM.clear()
INFLUENCE.clear()
EXCHANGE.clear()
LYCANTHROPY.clear()
LUCK.clear()
PESTILENCE.clear()
RETRIBUTION.clear()
MISDIRECTION.clear()
DECEIT.clear()
# In transition_day_end we report who was given totems based on havetotem.
# Fallen angel messes with this list, hence why it is separated from LASTGIVEN
# and calculated here (updated in the separate role files)
brokentotem.clear()
havetotem.clear()
@event_listener("transition_day_resolve", priority=2)
def on_transition_day_resolve2(evt, var, victim):
if evt.data["protected"].get(victim) == "totem":
evt.data["message"].append(messages["totem_protection"].format(victim))
evt.data["novictmsg"] = False
evt.stop_processing = True
evt.prevent_default = True
@event_listener("transition_day_resolve", priority=6)
def on_transition_day_resolve6(evt, var, victim):
# TODO: remove these checks once everything is split
# right now they're needed because otherwise retribution may fire off when the target isn't actually dying
# that will not be an issue once everything is using the event
if evt.data["protected"].get(victim):
return
if victim in var.ROLES["lycan"] and victim in evt.data["onlybywolves"] and victim.nick not in var.IMMUNIZED:
return
# END checks to remove
if victim in RETRIBUTION:
killers = list(evt.data["killers"].get(victim, []))
loser = None
while killers:
loser = random.choice(killers)
if loser in evt.data["dead"] or victim is loser:
killers.remove(loser)
continue
break
if loser in evt.data["dead"] or victim is loser:
loser = None
ret_evt = Event("retribution_kill", {"target": loser, "message": []})
ret_evt.dispatch(var, victim, loser)
loser = ret_evt.data["target"]
evt.data["message"].extend(ret_evt.data["message"])
if loser in evt.data["dead"] or victim is loser:
loser = None
if loser is not None:
prots = deque(var.ACTIVE_PROTECTIONS[loser.nick])
while len(prots) > 0:
# an event can read the current active protection and cancel the totem
# if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS
# so that it cannot be used again (if the protection is meant to be usable once-only)
ret_evt = Event("retribution_totem", {"message": []})
if not ret_evt.dispatch(var, victim, loser, prots[0]):
evt.data["message"].extend(ret_evt.data["message"])
return
prots.popleft()
evt.data["dead"].append(loser)
if var.ROLE_REVEAL in ("on", "team"):
role = get_reveal_role(loser)
an = "n" if role.startswith(("a", "e", "i", "o", "u")) else ""
evt.data["message"].append(messages["totem_death"].format(victim, loser, an, role))
else:
evt.data["message"].append(messages["totem_death_no_reveal"].format(victim, loser))
@event_listener("transition_day_end", priority=1)
def on_transition_day_end(evt, var):
message = []
for player, tlist in itertools.groupby(havetotem):
ntotems = len(list(tlist))
message.append(messages["totem_posession"].format(
player, "ed" if player not in get_players() else "s", "a" if ntotems == 1 else "\u0002{0}\u0002".format(ntotems), "s" if ntotems > 1 else ""))
for player in brokentotem:
message.append(messages["totem_broken"].format(player))
channels.Main.send("\n".join(message))
@event_listener("begin_day")
def on_begin_day(evt, var):
# Apply totem effects that need to begin on day proper
var.EXCHANGED.update(p.nick for p in EXCHANGE)
var.SILENCED.update(p.nick for p in SILENCE)
var.LYCANTHROPES.update(p.nick for p in LYCANTHROPY)
# pestilence doesn't take effect on immunized players
var.DISEASED.update({p.nick for p in PESTILENCE} - var.IMMUNIZED)
var.LUCKY.update(p.nick for p in LUCK)
var.MISDIRECTED.update(p.nick for p in MISDIRECTION)
@event_listener("abstain")
def on_abstain(evt, var, user):
if user in NARCOLEPSY:
user.send(messages["totem_narcolepsy"])
evt.prevent_default = True
@event_listener("lynch")
def on_lynch(evt, var, user):
if user in NARCOLEPSY:
user.send(messages["totem_narcolepsy"])
evt.prevent_default = True
@event_listener("assassinate")
def on_assassinate(evt, var, killer, target, prot):
if prot == "totem":
var.ACTIVE_PROTECTIONS[target.nick].remove("totem")
evt.prevent_default = True
evt.stop_processing = True
channels.Main.send(messages[evt.params.message_prefix + "totem"].format(killer, target))
@event_listener("reset")
def on_reset(evt, var):
DEATH.clear()
PROTECTION.clear()
REVEALING.clear()
NARCOLEPSY.clear()
SILENCE.clear()
DESPERATION.clear()
IMPATIENCE.clear()
PACIFISM.clear()
INFLUENCE.clear()
EXCHANGE.clear()
LYCANTHROPY.clear()
LUCK.clear()
PESTILENCE.clear()
RETRIBUTION.clear()
MISDIRECTION.clear()
DECEIT.clear()
brokentotem.clear()
havetotem.clear()
# vim: set sw=4 expandtab:

View File

@ -10,7 +10,7 @@ from src.utilities import *
from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -5,12 +5,11 @@ import math
from collections import defaultdict
import botconfig
import src.settings as var
from src.utilities import *
from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event
@ -54,7 +53,7 @@ def on_transition_night_end(evt, var):
@event_listener("desperation_totem")
def on_desperation(evt, var, votee, target, prot):
if prot == "blessing":
var.ACTIVE_PROTECTIONS[target].remove("blessing")
var.ACTIVE_PROTECTIONS[target.nick].remove("blessing")
evt.prevent_default = True
evt.stop_processing = True

View File

@ -0,0 +1,98 @@
import re
import random
import itertools
from collections import defaultdict, deque
import botconfig
from src.utilities import *
from src import debuglog, errlog, plog, users, channels
from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.dispatcher import MessageDispatcher
from src.messages import messages
from src.events import Event
from src.roles._shaman_helper import setup_variables, get_totem_target, give_totem
def get_tags(var, totem):
return set()
TOTEMS, LASTGIVEN, SHAMANS = setup_variables("crazed shaman", knows_totem=False, get_tags=get_tags)
@command("give", "totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("crazed shaman",))
def crazed_shaman_totem(var, wrapper, message):
"""Give a random totem to a player."""
target = get_totem_target(var, wrapper, message, LASTGIVEN)
if not target:
return
totem = TOTEMS[wrapper.source]
SHAMANS[wrapper.source] = give_totem(var, wrapper, target, prefix="You", tags=get_tags(var, totem), role="crazed shaman", msg="")
@event_listener("player_win")
def on_player_win(evt, var, user, role, winner, survived):
if role == "crazed shaman" and survived and not winner.startswith("@") and singular(winner) not in var.WIN_STEALER_ROLES:
evt.data["iwon"] = True
@event_listener("transition_day_begin", priority=4)
def on_transition_day_begin(evt, var):
# Select random totem recipients if shamans didn't act
pl = get_players()
for shaman in get_players(("crazed shaman",)):
if shaman not in SHAMANS and shaman.nick not in var.SILENCED:
ps = pl[:]
if shaman in LASTGIVEN:
if LASTGIVEN[shaman] in ps:
ps.remove(LASTGIVEN[shaman])
levt = Event("get_random_totem_targets", {"targets": ps})
levt.dispatch(var, shaman)
ps = levt.data["targets"]
if ps:
target = random.choice(ps)
dispatcher = MessageDispatcher(shaman, shaman)
tags = get_tags(var, TOTEMS[shaman])
SHAMANS[shaman] = give_totem(var, dispatcher, target, prefix=messages["random_totem_prefix"], tags=tags, role="crazed shaman", msg="")
else:
LASTGIVEN[shaman] = None
elif shaman not in SHAMANS:
LASTGIVEN[shaman] = None
@event_listener("transition_night_end", priority=2.01)
def on_transition_night_end(evt, var):
max_totems = 0
ps = get_players()
shamans = get_players(("crazed shaman",))
index = var.TOTEM_ORDER.index("crazed shaman")
for c in var.TOTEM_CHANCES.values():
max_totems += c[index]
for s in list(LASTGIVEN):
if s not in shamans:
del LASTGIVEN[s]
for shaman in shamans:
pl = ps[:]
random.shuffle(pl)
if LASTGIVEN.get(shaman):
if LASTGIVEN[shaman] in pl:
pl.remove(LASTGIVEN[shaman])
target = 0
rand = random.random() * max_totems
for t in var.TOTEM_CHANCES.keys():
target += var.TOTEM_CHANCES[t][index]
if rand <= target:
TOTEMS[shaman] = t
break
if shaman.prefers_simple():
shaman.send(messages["shaman_simple"].format("crazed shaman"))
else:
shaman.send(messages["shaman_notify"].format("crazed shaman", "random "))
shaman.send("Players: " + ", ".join(p.nick for p in pl))
# vim: set sw=4 expandtab:

View File

@ -5,11 +5,10 @@ import math
from collections import defaultdict
import botconfig
import src.settings as var
from src.utilities import *
from src import users, channels, debuglog, errlog, plog
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -7,7 +7,7 @@ from src.utilities import *
from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -1,107 +1,84 @@
import re
import random
import src.settings as var
from src.utilities import *
from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.functions import get_players, get_all_players, get_main_role, get_target, is_known_wolf_ally
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
SEEN = set()
KILLS = {}
SICK = {}
LYCANS = {}
SEEN = UserSet()
KILLS = UserDict()
SICK = UserDict()
LYCANS = UserDict()
_mappings = ("death", KILLS), ("lycan", LYCANS), ("sick", SICK)
@cmd("see", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("doomsayer",))
def see(cli, nick, chan, rest):
@command("see", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("doomsayer",))
def see(var, wrapper, message):
"""Use your paranormal senses to determine a player's doom."""
role = get_role(nick)
if nick in SEEN:
pm(cli, nick, messages["seer_fail"])
if wrapper.source in SEEN:
wrapper.send(messages["seer_fail"])
return
victim = get_victim(cli, nick, re.split(" +",rest)[0], False)
if not victim:
target = get_target(var, wrapper, re.split(" +", message)[0], not_self_message="no_see_self")
if not target:
return
if victim == nick:
pm(cli, nick, messages["no_see_self"])
if is_known_wolf_ally(wrapper.source, target):
wrapper.send(messages["no_see_wolf"])
return
if in_wolflist(nick, victim):
pm(cli, nick, messages["no_see_wolf"])
return
doomsayer = users._get(nick) # FIXME
target = users._get(victim) # FIXME
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True})
evt.dispatch(var, "see", doomsayer, target, frozenset({"detrimental", "immediate"}))
evt.dispatch(var, "see", wrapper.source, target, frozenset({"detrimental", "immediate"}))
if evt.prevent_default:
return
victim = evt.data["target"].nick
victimrole = get_role(victim)
target = evt.data["target"]
targrole = get_main_role(target)
mode, mapping = random.choice(_mappings)
pm(cli, nick, messages["doomsayer_{0}".format(mode)].format(victim))
if mode != "sick" or nick not in var.IMMUNIZED:
mapping[nick] = victim
wrapper.send(messages["doomsayer_{0}".format(mode)].format(target))
if mode != "sick" or wrapper.source.nick not in var.IMMUNIZED:
mapping[wrapper.source] = target
debuglog("{0} ({1}) SEE: {2} ({3}) - {4}".format(nick, role, victim, victimrole, mode.upper()))
relay_wolfchat_command(cli, nick, messages["doomsayer_wolfchat"].format(nick, victim), ("doomsayer",), is_wolf_command=True)
debuglog("{0} (doomsayer) SEE: {1} ({2}) - {3}".format(wrapper.source, target, targrole, mode.upper()))
relay_wolfchat_command(wrapper.client, wrapper.source.nick, messages["doomsayer_wolfchat"].format(wrapper.source, target), ("doomsayer",), is_wolf_command=True)
SEEN.add(nick)
@event_listener("rename_player")
def on_rename(evt, var, prefix, nick):
if prefix in SEEN:
SEEN.remove(prefix)
SEEN.add(nick)
for name, dictvar in _mappings:
kvp = []
for a, b in dictvar.items():
if a == prefix:
a = nick
if b == prefix:
b = nick
kvp.append((a, b))
dictvar.update(kvp)
if prefix in dictvar:
del dictvar[prefix]
SEEN.add(wrapper.source)
@event_listener("night_acted")
def on_acted(evt, var, user, actor):
if user.nick in SEEN:
if user in SEEN:
evt.data["acted"] = True
@event_listener("exchange_roles")
def on_exchange(evt, var, actor, target, actor_role, target_role):
if actor_role == "doomsayer" and target_role != "doomsayer":
SEEN.discard(actor.nick)
SEEN.discard(actor)
for name, mapping in _mappings:
mapping.pop(actor.nick, None)
mapping.pop(actor, None)
elif target_role == "doomsayer" and actor_role != "doomsayer":
SEEN.discard(target.nick)
SEEN.discard(target)
for name, mapping in _mappings:
mapping.pop(target.nick, None)
mapping.pop(target, None)
@event_listener("del_player")
def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
SEEN.discard(user.nick)
SEEN.discard(user)
for name, dictvar in _mappings:
for k, v in list(dictvar.items()):
if user.nick in (k, v):
if user in (k, v):
del dictvar[k]
@event_listener("doctor_immunize")
def on_doctor_immunize(evt, var, doctor, target):
if target in SICK.values():
user = users._get(target) # FIXME
if user in SICK.values():
for n, v in list(SICK.items()):
if v == target:
if v is user:
del SICK[n]
evt.data["message"] = "not_sick"
@ -115,15 +92,15 @@ def on_chk_nightdone(evt, var):
evt.data["nightroles"].extend(get_all_players(("doomsayer",)))
@event_listener("abstain")
def on_abstain(evt, cli, var, nick):
if nick in SICK.values():
pm(cli, nick, messages["illness_no_vote"])
def on_abstain(evt, var, user):
if user in SICK.values():
user.send(messages["illness_no_vote"])
evt.prevent_default = True
@event_listener("lynch")
def on_lynch(evt, cli, var, nick):
if nick in SICK.values():
pm(cli, nick, messages["illness_no_vote"])
def on_lynch(evt, var, target):
if target in SICK.values():
target.send(messages["illness_no_vote"])
evt.prevent_default = True
@event_listener("get_voters")
@ -132,17 +109,14 @@ def on_get_voters(evt, var):
@event_listener("transition_day_begin")
def on_transition_day_begin(evt, var):
for victim in SICK.values():
user = users._get(victim)
user.queue_message(messages["player_sick"])
for target in SICK.values():
target.queue_message(messages["player_sick"])
if SICK:
user.send_messages()
target.send_messages()
@event_listener("transition_day", priority=2)
def on_transition_day(evt, var):
for k, v in list(KILLS.items()):
killer = users._get(k) # FIXME
victim = users._get(v) # FIXME
for killer, victim in list(KILLS.items()):
evt.data["victims"].append(victim)
# even though doomsayer is a wolf, remove from onlybywolves since
# that particular item indicates that they were the target of a wolf !kill.

View File

@ -7,7 +7,7 @@ from src.utilities import *
from src.functions import get_players, get_all_players, get_target, get_main_role, get_reveal_role
from src import users, channels, debuglog, errlog, plog
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event
import botconfig
@ -199,9 +199,8 @@ def on_myrole(evt, var, user):
evt.data["messages"].append(messages["dullahan_targets_dead"])
@event_listener("revealroles_role")
def on_revealroles_role(evt, var, wrapper, nickname, role):
user = users._get(nickname) # FIXME
if role == "dullahan" and user in TARGETS: # FIXME
def on_revealroles_role(evt, var, wrapper, user, role):
if role == "dullahan" and user in TARGETS:
targets = set(TARGETS[user])
for target in TARGETS[user]:
if target.nick in var.DEAD:

View File

@ -5,11 +5,10 @@ import math
from collections import defaultdict
import botconfig
import src.settings as var
from src.utilities import *
from src import users, channels, debuglog, errlog, plog
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.functions import get_players, get_all_players
from src.messages import messages
from src.events import Event

View File

@ -5,12 +5,11 @@ import math
from collections import defaultdict
import botconfig
import src.settings as var
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
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -2,12 +2,11 @@ import re
import random
from collections import defaultdict
import src.settings as var
from src.utilities import *
from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players, get_target, get_main_role
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -5,12 +5,11 @@ import math
from collections import defaultdict
import botconfig
import src.settings as var
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
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -5,12 +5,11 @@ import math
from collections import defaultdict, deque
import botconfig
import src.settings as var
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
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event
@ -170,11 +169,10 @@ def on_myrole(evt, var, user):
evt.data["messages"].append(messages["mad_scientist_myrole_targets"].format(target1, target2))
@event_listener("revealroles_role")
def on_revealroles(evt, var, wrapper, nickname, role):
def on_revealroles(evt, var, wrapper, user, role):
if role == "mad scientist":
pl = get_players()
target1, target2 = _get_targets(var, pl, users._get(nickname)) # FIXME
target1, target2 = _get_targets(var, pl, user)
evt.data["special_case"].append(messages["mad_scientist_revealroles_targets"].format(target1, target2))
# vim: set sw=4 expandtab:

View File

@ -5,27 +5,20 @@ import math
from collections import defaultdict
import botconfig
import src.settings as var
from src.utilities import *
from src import users, channels, debuglog, errlog, plog
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event
REVEALED_MAYORS = set()
@event_listener("rename_player")
def on_rename_player(evt, var, prefix, nick):
if prefix in REVEALED_MAYORS:
REVEALED_MAYORS.remove(prefix)
REVEALED_MAYORS.add(nick)
REVEALED_MAYORS = UserSet()
@event_listener("chk_decision_lynch", priority=3)
def on_chk_decision_lynch(evt, cli, var, voters):
def on_chk_decision_lynch(evt, var, voters):
votee = evt.data["votee"]
if users._get(votee) in var.ROLES["mayor"] and votee not in REVEALED_MAYORS: # FIXME
cli.msg(botconfig.CHANNEL, messages["mayor_reveal"].format(votee))
if votee in var.ROLES["mayor"] and votee not in REVEALED_MAYORS:
channels.Main.send(messages["mayor_reveal"].format(votee))
REVEALED_MAYORS.add(votee)
evt.data["votee"] = None
evt.prevent_default = True

View File

@ -1,12 +1,11 @@
import re
import random
import src.settings as var
from src.utilities import *
from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -5,12 +5,11 @@ import math
from collections import defaultdict
import botconfig
import src.settings as var
from src.utilities import *
from src.functions import get_players, get_all_players, get_target, get_main_role
from src import channels, users, debuglog, errlog, plog
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -5,7 +5,7 @@ import src.settings as var
from src.utilities import *
from src import users, channels, debuglog, errlog, plog
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.functions import get_players, get_all_players, get_main_role
from src.messages import messages
from src.events import Event

View File

@ -4,637 +4,98 @@ import itertools
from collections import defaultdict, deque
import botconfig
import src.settings as var
from src.utilities import *
from src import debuglog, errlog, plog, users, channels
from src.functions import get_players, get_all_players, get_main_role, get_reveal_role
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.dispatcher import MessageDispatcher
from src.messages import messages
from src.events import Event
# To add new totem types in your custom roles/whatever.py file:
# 1. Add a key to var.TOTEM_CHANCES with the totem name
# 2. Add a message totemname_totem to your custom messages.json describing
# the totem (this is displayed at night if !simple is off)
# 3. Add events as necessary to implement the totem's functionality
#
# To add new shaman roles in your custom roles/whatever.py file:
# 1. Expand var.TOTEM_ORDER and upate var.TOTEM_CHANCES to account for the new width
# 2. Add the role to var.ROLE_GUIDE
# 3. Add the role to whatever other holding vars are necessary based on what it does
# 4. Implement custom events if the role does anything else beyond giving totems.
#
# Modifying this file to add new totems or new shaman roles is generally never required
TOTEMS = {} # type: Dict[str, str]
LASTGIVEN = {} # type: Dict[str, str]
SHAMANS = {} # type: Dict[str, Tuple[str, str]]
DEATH = {} # type: Dict[str, str]
PROTECTION = [] # type: List[str]
REVEALING = set() # type: Set[str]
NARCOLEPSY = set() # type: Set[str]
SILENCE = set() # type: Set[str]
DESPERATION = set() # type: Set[str]
IMPATIENCE = [] # type: List[str]
PACIFISM = [] # type: List[str]
INFLUENCE = set() # type: Set[str]
EXCHANGE = set() # type: Set[str]
LYCANTHROPY = set() # type: Set[str]
LUCK = set() # type: Set[str]
PESTILENCE = set() # type: Set[str]
RETRIBUTION = set() # type: Set[str]
MISDIRECTION = set() # type: Set[str]
DECEIT = set() # type: Set[str]
# holding vars that don't persist long enough to need special attention in
# reset/exchange/nickchange
havetotem = [] # type: List[str]
brokentotem = set() # type: Set[str]
# FIXME: this needs to be split into shaman.py, wolfshaman.py, and crazedshaman.py
@cmd("give", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=var.TOTEM_ORDER)
@cmd("totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=var.TOTEM_ORDER)
def totem(cli, nick, chan, rest, prefix="You"): # XXX: The transition_day_begin event needs updating alongside this
"""Give a totem to a player."""
victim = get_victim(cli, nick, re.split(" +",rest)[0], False, True)
if not victim:
return
if LASTGIVEN.get(nick) == victim:
pm(cli, nick, messages["shaman_no_target_twice"].format(victim))
return
original_victim = victim
role = get_role(nick) # FIXME: this is bad, check if user is in var.ROLES[thingy] instead once converted
totem = ""
if role != "crazed shaman":
totem = " of " + TOTEMS[nick]
from src.roles._shaman_helper import setup_variables, get_totem_target, give_totem
def get_tags(var, totem):
tags = set()
if role != "crazed shaman" and TOTEMS[nick] in var.BENEFICIAL_TOTEMS:
if totem in var.BENEFICIAL_TOTEMS:
tags.add("beneficial")
return tags
shaman = users._get(nick) # FIXME
target = users._get(victim) # FIXME
TOTEMS, LASTGIVEN, SHAMANS = setup_variables("shaman", knows_totem=True, get_tags=get_tags)
evt = Event("targeted_command", {"target": target, "misdirection": True, "exchange": True},
action="give a totem{0} to".format(totem))
evt.dispatch(var, "totem", shaman, target, frozenset(tags))
if evt.prevent_default:
@command("give", "totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("shaman",))
def shaman_totem(var, wrapper, message):
"""Give a totem to a player."""
target = get_totem_target(var, wrapper, message, LASTGIVEN)
if not target:
return
victim = evt.data["target"].nick
victimrole = get_role(victim)
pm(cli, nick, messages["shaman_success"].format(prefix, totem, original_victim))
if role == "wolf shaman":
relay_wolfchat_command(cli, nick, messages["shaman_wolfchat"].format(nick, original_victim), ("wolf shaman",), is_wolf_command=True)
SHAMANS[nick] = (victim, original_victim)
debuglog("{0} ({1}) TOTEM: {2} ({3})".format(nick, role, victim, TOTEMS[nick]))
totem = TOTEMS[wrapper.source]
@event_listener("rename_player")
def on_rename(evt, var, prefix, nick):
if prefix in TOTEMS:
TOTEMS[nick] = TOTEMS.pop(prefix)
for dictvar in (LASTGIVEN, DEATH):
kvp = {}
for a,b in dictvar.items():
s = nick if a == prefix else a
t = nick if b == prefix else b
kvp[s] = t
dictvar.update(kvp)
if prefix in dictvar:
del dictvar[prefix]
kvp = {}
for a,(b,c) in SHAMANS.items():
s = nick if a == prefix else a
t1 = nick if b == prefix else b
t2 = nick if c == prefix else c
kvp[s] = (t1, t2)
SHAMANS.update(kvp)
if prefix in SHAMANS:
del SHAMANS[prefix]
for listvar in (PROTECTION, IMPATIENCE, PACIFISM):
for i,a in enumerate(listvar):
if a == prefix:
listvar[i] = nick
for setvar in (REVEALING, NARCOLEPSY, SILENCE, DESPERATION,
INFLUENCE, EXCHANGE, LYCANTHROPY, LUCK, PESTILENCE,
RETRIBUTION, MISDIRECTION, DECEIT):
for a in list(setvar):
if a == prefix:
setvar.discard(a)
setvar.add(nick)
@event_listener("see", priority=10)
def on_see(evt, var, nick, victim):
if (victim in DECEIT) ^ (nick in DECEIT):
if evt.data["role"] in var.SEEN_WOLF and evt.data["role"] not in var.SEEN_DEFAULT:
evt.data["role"] = "villager"
else:
evt.data["role"] = "wolf"
@event_listener("del_player")
def on_del_player(evt, var, user, mainrole, allroles, death_triggers):
for a,(b,c) in list(SHAMANS.items()):
if user.nick in (a, b, c):
del SHAMANS[a]
@event_listener("night_acted")
def on_acted(evt, var, user, actor):
if user.nick in SHAMANS:
evt.data["acted"] = True
@event_listener("get_special")
def on_get_special(evt, var):
evt.data["special"].update(get_players(("shaman", "crazed shaman", "wolf shaman")))
@event_listener("exchange_roles")
def on_exchange(evt, var, actor, target, actor_role, target_role):
actor_totem = None
target_totem = None
if actor_role in var.TOTEM_ORDER:
actor_totem = TOTEMS.pop(actor.nick)
if actor.nick in SHAMANS:
del SHAMANS[actor.nick]
if actor.nick in LASTGIVEN:
del LASTGIVEN[actor.nick]
if target_role in var.TOTEM_ORDER:
target_totem = TOTEMS.pop(target.nick)
if target.nick in SHAMANS:
del SHAMANS[target.nick]
if target.nick in LASTGIVEN:
del LASTGIVEN[target.nick]
if target_totem:
if target_role != "crazed shaman":
evt.data["actor_messages"].append(messages["shaman_totem"].format(target_totem))
TOTEMS[actor.nick] = target_totem
if actor_totem:
if actor_role != "crazed shaman":
evt.data["target_messages"].append(messages["shaman_totem"].format(actor_totem))
TOTEMS[target.nick] = actor_totem
@event_listener("chk_nightdone")
def on_chk_nightdone(evt, var):
evt.data["actedcount"] += len(SHAMANS)
evt.data["nightroles"].extend(get_players(var.TOTEM_ORDER))
@event_listener("get_voters")
def on_get_voters(evt, var):
evt.data["voters"] -= NARCOLEPSY
@event_listener("chk_decision", priority=1)
def on_chk_decision(evt, cli, var, force):
nl = []
for p in PACIFISM:
if p in evt.params.voters:
nl.append(p)
# .remove() will only remove the first instance, which means this plays nicely with pacifism countering this
for p in IMPATIENCE:
if p in nl:
nl.remove(p)
evt.data["not_lynching"] |= set(nl)
for votee, voters in evt.data["votelist"].items():
numvotes = 0
random.shuffle(IMPATIENCE)
for v in IMPATIENCE:
if v in evt.params.voters and v not in voters and v != votee:
# don't add them in if they have the same number or more of pacifism totems
# this matters for desperation totem on the votee
imp_count = IMPATIENCE.count(v)
pac_count = PACIFISM.count(v)
if pac_count >= imp_count:
continue
# yes, this means that one of the impatient people will get desperation totem'ed if they didn't
# already !vote earlier. sucks to suck. >:)
voters.append(v)
for v in voters:
weight = 1
imp_count = IMPATIENCE.count(v)
pac_count = PACIFISM.count(v)
if pac_count > imp_count:
weight = 0 # more pacifists than impatience totems
elif imp_count == pac_count and v not in var.VOTES[votee]:
weight = 0 # impatience and pacifist cancel each other out, so don't count impatience
if v in INFLUENCE:
weight *= 2
numvotes += weight
if votee not in evt.data["weights"]:
evt.data["weights"][votee] = {}
evt.data["weights"][votee][v] = weight
evt.data["numvotes"][votee] = numvotes
@event_listener("chk_decision_abstain")
def on_chk_decision_abstain(evt, cli, var, nl):
for p in nl:
if p in PACIFISM and p not in var.NO_LYNCH:
cli.msg(botconfig.CHANNEL, messages["player_meek_abstain"].format(p))
@event_listener("chk_decision_lynch", priority=1)
def on_chk_decision_lynch1(evt, cli, var, voters):
votee = evt.data["votee"]
for p in voters:
if p in IMPATIENCE and p not in var.VOTES[votee]:
cli.msg(botconfig.CHANNEL, messages["impatient_vote"].format(p, votee))
# mayor is at exactly 3, so we want that to always happen before revealing totem
@event_listener("chk_decision_lynch", priority=3.1)
def on_chk_decision_lynch3(evt, cli, var, voters):
votee = evt.data["votee"]
if votee in REVEALING:
role = get_role(votee)
rev_evt = Event("revealing_totem", {"role": role})
rev_evt.dispatch(cli, var, votee)
role = rev_evt.data["role"]
# TODO: once amnesiac is split, roll this into the revealing_totem event
if role == "amnesiac":
role = var.AMNESIAC_ROLES[votee]
change_role(users._get(votee), "amnesiac", role) # FIXME
var.AMNESIACS.add(votee)
pm(cli, votee, messages["totem_amnesia_clear"])
# If wolfteam, don't bother giving list of wolves since night is about to start anyway
# Existing wolves also know that someone just joined their team because revealing totem says what they are
# If turncoat, set their initial starting side to "none" just in case game ends before they can set it themselves
if role == "turncoat":
var.TURNCOATS[votee] = ("none", -1)
an = "n" if role.startswith(("a", "e", "i", "o", "u")) else ""
cli.msg(botconfig.CHANNEL, messages["totem_reveal"].format(votee, an, role))
evt.data["votee"] = None
evt.prevent_default = True
evt.stop_processing = True
@event_listener("chk_decision_lynch", priority=5)
def on_chk_decision_lynch5(evt, cli, var, voters):
votee = evt.data["votee"]
if votee in DESPERATION:
# Also kill the very last person to vote them, unless they voted themselves last in which case nobody else dies
target = voters[-1]
if target != votee:
prots = deque(var.ACTIVE_PROTECTIONS[target])
while len(prots) > 0:
# an event can read the current active protection and cancel the totem
# if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS
# so that it cannot be used again (if the protection is meant to be usable once-only)
desp_evt = Event("desperation_totem", {})
if not desp_evt.dispatch(var, votee, target, prots[0]):
return
prots.popleft()
if var.ROLE_REVEAL in ("on", "team"):
r1 = get_reveal_role(users._get(target)) # FIXME
an1 = "n" if r1.startswith(("a", "e", "i", "o", "u")) else ""
tmsg = messages["totem_desperation"].format(votee, target, an1, r1)
else:
tmsg = messages["totem_desperation_no_reveal"].format(votee, target)
cli.msg(botconfig.CHANNEL, tmsg)
# we lie to this function so it doesn't devoice the player yet. instead, we'll let the call further down do it
evt.data["deadlist"].append(target)
better_deadlist = [users._get(p) for p in evt.data["deadlist"]] # FIXME
target_user = users._get(target) # FIXME
evt.params.del_player(target_user, end_game=False, killer_role="shaman", deadlist=better_deadlist, ismain=False)
@event_listener("player_win")
def on_player_win(evt, var, user, rol, winner, survived):
if rol == "crazed shaman" and survived and not winner.startswith("@") and singular(winner) not in var.WIN_STEALER_ROLES:
evt.data["iwon"] = True
SHAMANS[wrapper.source] = give_totem(var, wrapper, target, prefix="You", tags=get_tags(var, totem), role="shaman", msg=" of {0}".format(totem))
@event_listener("transition_day_begin", priority=4)
def on_transition_day_begin(evt, var):
# Select random totem recipients if shamans didn't act
pl = get_players()
for shaman in get_players(var.TOTEM_ORDER):
if shaman.nick not in SHAMANS and shaman.nick not in var.SILENCED:
for shaman in get_players(("shaman",)):
if shaman not in SHAMANS and shaman.nick not in var.SILENCED:
ps = pl[:]
if shaman.nick in LASTGIVEN:
user = users._get(LASTGIVEN[shaman.nick]) # FIXME
if user in ps:
ps.remove(user)
if shaman in LASTGIVEN:
if LASTGIVEN[shaman] in ps:
ps.remove(LASTGIVEN[shaman])
levt = Event("get_random_totem_targets", {"targets": ps})
levt.dispatch(var, shaman)
ps = levt.data["targets"]
if ps:
target = random.choice(ps)
totem.func(target.client, shaman.nick, shaman.nick, target.nick, messages["random_totem_prefix"]) # XXX: Old API
dispatcher = MessageDispatcher(shaman, shaman)
tags = get_tags(var, TOTEMS[shaman])
SHAMANS[shaman] = give_totem(var, dispatcher, target, prefix=messages["random_totem_prefix"], tags=tags, role="shaman", msg=" of {0}".format(TOTEMS[shaman]))
else:
LASTGIVEN[shaman.nick] = None
LASTGIVEN[shaman] = None
elif shaman not in SHAMANS:
LASTGIVEN[shaman.nick] = None
@event_listener("transition_day_begin", priority=6)
def on_transition_day_begin2(evt, var):
# Reset totem variables
DEATH.clear()
PROTECTION.clear()
REVEALING.clear()
NARCOLEPSY.clear()
SILENCE.clear()
DESPERATION.clear()
IMPATIENCE.clear()
PACIFISM.clear()
INFLUENCE.clear()
EXCHANGE.clear()
LYCANTHROPY.clear()
LUCK.clear()
PESTILENCE.clear()
RETRIBUTION.clear()
MISDIRECTION.clear()
DECEIT.clear()
# Give out totems here
for shaman, (victim, target) in SHAMANS.items():
totemname = TOTEMS[shaman]
if totemname == "death": # this totem stacks
DEATH[shaman] = victim
elif totemname == "protection": # this totem stacks
PROTECTION.append(victim)
elif totemname == "revealing":
REVEALING.add(victim)
elif totemname == "narcolepsy":
NARCOLEPSY.add(victim)
elif totemname == "silence":
SILENCE.add(victim)
elif totemname == "desperation":
DESPERATION.add(victim)
elif totemname == "impatience": # this totem stacks
IMPATIENCE.append(victim)
elif totemname == "pacifism": # this totem stacks
PACIFISM.append(victim)
elif totemname == "influence":
INFLUENCE.add(victim)
elif totemname == "exchange":
EXCHANGE.add(victim)
elif totemname == "lycanthropy":
LYCANTHROPY.add(victim)
elif totemname == "luck":
LUCK.add(victim)
elif totemname == "pestilence":
PESTILENCE.add(victim)
elif totemname == "retribution":
RETRIBUTION.add(victim)
elif totemname == "misdirection":
MISDIRECTION.add(victim)
elif totemname == "deceit":
DECEIT.add(victim)
# other totem types possibly handled in an earlier event,
# as such there is no else: clause here
if target != victim:
user = users._get(shaman) # FIXME
user.send(messages["totem_retarget"].format(victim))
LASTGIVEN[shaman] = victim
# In transition_day_end we report who was given totems based on havetotem.
# Fallen angel messes with this list, hence why it is separated from LASTGIVEN
# and calculated here.
brokentotem.clear()
havetotem.clear()
havetotem.extend(sorted(filter(None, LASTGIVEN.values())))
@event_listener("transition_day", priority=2)
def on_transition_day2(evt, var):
for k, d in DEATH.items():
shaman = users._get(k) # FIXME
target = users._get(d) # FIXME
evt.data["victims"].append(target)
evt.data["onlybywolves"].discard(target)
evt.data["killers"][target].append(shaman)
@event_listener("transition_day", priority=4.1)
def on_transition_day3(evt, var):
# protection totems are applied first in default logic, however
# we set priority=4.1 to allow other modes of protection
# to pre-empt us if desired
pl = get_players()
vs = set(evt.data["victims"])
for v in pl:
numtotems = PROTECTION.count(v.nick)
if v in vs:
if v in var.DYING:
continue
numkills = evt.data["numkills"][v]
for i in range(0, numtotems):
numkills -= 1
if numkills >= 0:
evt.data["killers"][v].pop(0)
if numkills <= 0 and v not in evt.data["protected"]:
evt.data["protected"][v] = "totem"
elif numkills <= 0:
var.ACTIVE_PROTECTIONS[v.nick].append("totem")
evt.data["numkills"][v] = numkills
else:
for i in range(0, numtotems):
var.ACTIVE_PROTECTIONS[v.nick].append("totem")
@event_listener("fallen_angel_guard_break")
def on_fagb(evt, var, victim, killer):
# we'll never end up killing a shaman who gave out protection, but delete the totem since
# story-wise it gets demolished at night by the FA
while victim.nick in havetotem:
havetotem.remove(victim.nick)
brokentotem.add(victim.nick)
@event_listener("transition_day_resolve", priority=2)
def on_transition_day_resolve2(evt, var, victim):
if evt.data["protected"].get(victim) == "totem":
evt.data["message"].append(messages["totem_protection"].format(victim))
evt.data["novictmsg"] = False
evt.stop_processing = True
evt.prevent_default = True
@event_listener("transition_day_resolve", priority=6)
def on_transition_day_resolve6(evt, var, victim):
# TODO: remove these checks once everything is split
# right now they're needed because otherwise retribution may fire off when the target isn't actually dying
# that will not be an issue once everything is using the event
if evt.data["protected"].get(victim):
return
if victim in var.ROLES["lycan"] and victim in evt.data["onlybywolves"] and victim.nick not in var.IMMUNIZED:
return
# END checks to remove
if victim.nick in RETRIBUTION:
killers = list(evt.data["killers"].get(victim, []))
loser = None
while killers:
loser = random.choice(killers)
if loser in evt.data["dead"] or victim is loser:
killers.remove(loser)
continue
break
if loser in evt.data["dead"] or victim is loser:
loser = None
ret_evt = Event("retribution_kill", {"target": loser, "message": []})
ret_evt.dispatch(var, victim, loser)
loser = ret_evt.data["target"]
evt.data["message"].extend(ret_evt.data["message"])
if loser in evt.data["dead"] or victim is loser:
loser = None
if loser is not None:
prots = deque(var.ACTIVE_PROTECTIONS[loser.nick])
while len(prots) > 0:
# an event can read the current active protection and cancel the totem
# if it cancels, it is responsible for removing the protection from var.ACTIVE_PROTECTIONS
# so that it cannot be used again (if the protection is meant to be usable once-only)
ret_evt = Event("retribution_totem", {"message": []})
if not ret_evt.dispatch(var, victim, loser, prots[0]):
evt.data["message"].extend(ret_evt.data["message"])
return
prots.popleft()
evt.data["dead"].append(loser)
if var.ROLE_REVEAL in ("on", "team"):
role = get_reveal_role(loser)
an = "n" if role.startswith(("a", "e", "i", "o", "u")) else ""
evt.data["message"].append(messages["totem_death"].format(victim, loser, an, role))
else:
evt.data["message"].append(messages["totem_death_no_reveal"].format(victim, loser))
@event_listener("transition_day_end", priority=1)
def on_transition_day_end(evt, var):
message = []
for player, tlist in itertools.groupby(havetotem):
ntotems = len(list(tlist))
message.append(messages["totem_posession"].format(
player, "ed" if player not in list_players() else "s", "a" if ntotems == 1 else "\u0002{0}\u0002".format(ntotems), "s" if ntotems > 1 else ""))
for player in brokentotem:
message.append(messages["totem_broken"].format(player))
channels.Main.send("\n".join(message))
LASTGIVEN[shaman] = None
@event_listener("transition_night_end", priority=2.01)
def on_transition_night_end(evt, var):
max_totems = defaultdict(int)
max_totems = 0
ps = get_players()
shamans = list_players(var.TOTEM_ORDER) # FIXME: Need to convert alongside the entire role
for ix in range(len(var.TOTEM_ORDER)):
for c in var.TOTEM_CHANCES.values():
max_totems[var.TOTEM_ORDER[ix]] += c[ix]
for s in list(LASTGIVEN.keys()):
shamans = get_players(("shaman",))
index = var.TOTEM_ORDER.index("shaman")
for c in var.TOTEM_CHANCES.values():
max_totems += c[index]
for s in list(LASTGIVEN):
if s not in shamans:
del LASTGIVEN[s]
for shaman in get_players(var.TOTEM_ORDER):
for shaman in shamans:
pl = ps[:]
random.shuffle(pl)
if LASTGIVEN.get(shaman.nick):
user = users._get(LASTGIVEN[shaman.nick]) # FIXME
if user in pl:
pl.remove(user)
role = get_main_role(shaman) # FIXME: don't use get_main_role here once split into one file per role
indx = var.TOTEM_ORDER.index(role)
if LASTGIVEN.get(shaman):
if LASTGIVEN[shaman] in pl:
pl.remove(LASTGIVEN[shaman])
target = 0
rand = random.random() * max_totems[var.TOTEM_ORDER[indx]]
rand = random.random() * max_totems
for t in var.TOTEM_CHANCES.keys():
target += var.TOTEM_CHANCES[t][indx]
target += var.TOTEM_CHANCES[t][index]
if rand <= target:
TOTEMS[shaman.nick] = t # FIXME: Fix once shaman is converted
TOTEMS[shaman] = t
break
if shaman.prefers_simple():
if role not in var.WOLFCHAT_ROLES:
shaman.send(messages["shaman_simple"].format(role))
if role != "crazed shaman":
shaman.send(messages["totem_simple"].format(TOTEMS[shaman.nick])) # FIXME
shaman.send(messages["shaman_simple"].format("shaman"))
shaman.send(messages["totem_simple"].format(TOTEMS[shaman]))
else:
if role not in var.WOLFCHAT_ROLES:
shaman.send(messages["shaman_notify"].format(role, "random " if shaman in var.ROLES["crazed shaman"] else ""))
if role != "crazed shaman":
totem = TOTEMS[shaman.nick] # FIXME
tmsg = messages["shaman_totem"].format(totem)
try:
tmsg += messages[totem + "_totem"]
except KeyError:
tmsg += messages["generic_bug_totem"]
shaman.send(tmsg)
if role not in var.WOLFCHAT_ROLES:
shaman.send("Players: " + ", ".join(p.nick for p in pl))
@event_listener("begin_day")
def on_begin_day(evt, var):
# Apply totem effects that need to begin on day proper
var.EXCHANGED.update(EXCHANGE)
var.SILENCED.update(SILENCE)
var.LYCANTHROPES.update(LYCANTHROPY)
# pestilence doesn't take effect on immunized players
var.DISEASED.update(PESTILENCE - var.IMMUNIZED)
var.LUCKY.update(LUCK)
var.MISDIRECTED.update(MISDIRECTION)
SHAMANS.clear()
@event_listener("abstain")
def on_abstain(evt, cli, var, nick):
if nick in NARCOLEPSY:
pm(cli, nick, messages["totem_narcolepsy"])
evt.prevent_default = True
@event_listener("lynch")
def on_lynch(evt, cli, var, nick):
if nick in NARCOLEPSY:
pm(cli, nick, messages["totem_narcolepsy"])
evt.prevent_default = True
@event_listener("assassinate")
def on_assassinate(evt, var, killer, target, prot):
if prot == "totem":
var.ACTIVE_PROTECTIONS[target.nick].remove("totem")
evt.prevent_default = True
evt.stop_processing = True
channels.Main.send(messages[evt.params.message_prefix + "totem"].format(killer, target))
@event_listener("succubus_visit")
def on_succubus_visit(evt, var, succubus, target):
if (users._get(SHAMANS.get(target.nick, (None, None))[1], allow_none=True) in get_all_players(("succubus",)) and # FIXME
(get_main_role(target) == "crazed shaman" or TOTEMS[target.nick] not in var.BENEFICIAL_TOTEMS)):
target.send(messages["retract_totem_succubus"].format(SHAMANS[target.nick][1]))
del SHAMANS[target.nick]
@event_listener("myrole")
def on_myrole(evt, var, user):
role = evt.data["role"]
if role in var.TOTEM_ORDER and role != "crazed shaman" and var.PHASE == "night" and user.nick not in SHAMANS:
evt.data["messages"].append(messages["totem_simple"].format(TOTEMS[user.nick]))
@event_listener("revealroles_role")
def on_revealroles(evt, var, wrapper, nickname, role):
if role in var.TOTEM_ORDER and nickname in TOTEMS:
if nickname in SHAMANS:
evt.data["special_case"].append("giving {0} totem to {1}".format(TOTEMS[nickname], SHAMANS[nickname][0]))
elif var.PHASE == "night":
evt.data["special_case"].append("has {0} totem".format(TOTEMS[nickname]))
elif nickname in LASTGIVEN and LASTGIVEN[nickname]:
evt.data["special_case"].append("gave {0} totem to {1}".format(TOTEMS[nickname], LASTGIVEN[nickname]))
@event_listener("reset")
def on_reset(evt, var):
TOTEMS.clear()
LASTGIVEN.clear()
SHAMANS.clear()
DEATH.clear()
PROTECTION.clear()
REVEALING.clear()
NARCOLEPSY.clear()
SILENCE.clear()
DESPERATION.clear()
IMPATIENCE.clear()
PACIFISM.clear()
INFLUENCE.clear()
EXCHANGE.clear()
LYCANTHROPY.clear()
LUCK.clear()
PESTILENCE.clear()
RETRIBUTION.clear()
MISDIRECTION.clear()
DECEIT.clear()
@event_listener("get_role_metadata")
def on_get_role_metadata(evt, var, kind):
if kind == "night_kills":
# only add shamans here if they were given a death totem
# even though retribution kills, it is given a special kill message
# note that all shaman types (shaman/CS/wolf shaman) are lumped under the "shaman" key (for now),
# this will change so they all get their own key in the future (once this is split into 3 files)
evt.data["shaman"] = list(TOTEMS.values()).count("death")
shaman.send(messages["shaman_notify"].format("shaman", ""))
totem = TOTEMS[shaman]
tmsg = messages["shaman_totem"].format(totem)
tmsg += messages[totem + "_totem"]
shaman.send(tmsg)
shaman.send("Players: " + ", ".join(p.nick for p in pl))
# vim: set sw=4 expandtab:

View File

@ -5,12 +5,11 @@ import math
from collections import defaultdict
import botconfig
import src.settings as var
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
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -5,12 +5,11 @@ import math
from collections import defaultdict
import botconfig
import src.settings as var
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
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event
@ -103,16 +102,17 @@ def on_get_random_totem_targets(evt, var, shaman):
evt.data["targets"].remove(succubus)
@event_listener("chk_decision")
def on_chk_decision(evt, cli, var, force):
def on_chk_decision(evt, var, force):
for votee, voters in evt.data["votelist"].items():
if users._get(votee) in get_all_players(("succubus",)): # FIXME
if votee in get_all_players(("succubus",)):
for vtr in ENTRANCED:
if vtr.nick in voters:
evt.data["numvotes"][votee] -= evt.data["weights"][votee][vtr.nick]
evt.data["weights"][votee][vtr.nick] = 0
if vtr in voters:
evt.data["numvotes"][votee] -= evt.data["weights"][votee][vtr]
evt.data["weights"][votee][vtr] = 0
def _kill_entranced_voters(var, votelist, not_lynching, votee):
if not {p.nick for p in get_all_players(("succubus",))} & (set(itertools.chain(*votelist.values())) | not_lynching): # FIXME
voters = set(itertools.chain(*votelist.values()))
if not get_all_players(("succubus",)) & (voters | not_lynching):
# none of the succubi voted (or there aren't any succubi), so short-circuit
return
# kill off everyone entranced that did not follow one of the succubi's votes or abstain
@ -122,32 +122,28 @@ def _kill_entranced_voters(var, votelist, not_lynching, votee):
ENTRANCED_DYING.add(x)
for other_votee, other_voters in votelist.items():
if {p.nick for p in get_all_players(("succubus",))} & set(other_voters): # FIXME
if votee == other_votee:
if get_all_players(("succubus",)) & set(other_voters):
if votee is other_votee:
ENTRANCED_DYING.clear()
return
for x in set(ENTRANCED_DYING):
if x.nick in other_voters:
ENTRANCED_DYING.remove(x)
ENTRANCED_DYING.difference_update(other_voters)
if {p.nick for p in get_all_players(("succubus",))} & not_lynching: # FIXME
if get_all_players(("succubus",)) & not_lynching:
if votee is None:
ENTRANCED_DYING.clear()
return
for x in set(ENTRANCED_DYING):
if x.nick in not_lynching:
ENTRANCED_DYING.remove(x)
ENTRANCED_DYING.difference_update(not_lynching)
@event_listener("chk_decision_lynch", priority=5)
def on_chk_decision_lynch(evt, cli, var, voters):
def on_chk_decision_lynch(evt, var, voters):
# a different event may override the original votee, but people voting along with succubus
# won't necessarily know that, so base whether or not they risk death on the person originally voted
_kill_entranced_voters(var, evt.params.votelist, evt.params.not_lynching, evt.params.original_votee)
@event_listener("chk_decision_abstain")
def on_chk_decision_abstain(evt, cli, var, not_lynching):
def on_chk_decision_abstain(evt, var, not_lynching):
_kill_entranced_voters(var, evt.params.votelist, not_lynching, None)
# entranced logic should run after team wins have already been determined (aka run last)

View File

@ -5,11 +5,10 @@ import math
from collections import defaultdict
import botconfig
import src.settings as var
from src.utilities import *
from src import debuglog, errlog, plog, users, channels
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -2,12 +2,11 @@ import re
import random
from collections import defaultdict
import src.settings as var
from src.utilities import *
from src import channels, users, debuglog, errlog, plog
from src.functions import get_players, get_target, get_main_role
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -2,12 +2,11 @@ import re
import random
from collections import defaultdict
import src.settings as var
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
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -1,9 +1,8 @@
import src.settings as var
from src.utilities import *
from src import users, channels, debuglog, errlog, plog
from src.functions import get_players
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

View File

@ -6,7 +6,7 @@ from src.utilities import *
from src import users, channels, debuglog, errlog, plog
from src.functions import get_players, get_all_players, get_main_role
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event
@ -148,10 +148,10 @@ def on_transition_night_end(evt, var):
child.send(messages[to_send])
@event_listener("revealroles_role")
def on_revealroles_role(evt, var, wrapper, nick, role):
def on_revealroles_role(evt, var, wrapper, user, role):
if role == "wild child":
if nick in IDOLS:
evt.data["special_case"].append("picked {0} as idol".format(IDOLS[nick]))
if user.nick in IDOLS:
evt.data["special_case"].append("picked {0} as idol".format(IDOLS[user.nick]))
else:
evt.data["special_case"].append("no idol picked yet")

View File

@ -7,7 +7,7 @@ from src.utilities import *
from src.functions import get_players, get_all_players, get_main_role, get_all_roles
from src import debuglog, errlog, plog, users, channels
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event

102
src/roles/wolf_shaman.py Normal file
View File

@ -0,0 +1,102 @@
import re
import random
import itertools
from collections import defaultdict, deque
import botconfig
from src.utilities import *
from src import debuglog, errlog, plog, users, channels
from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target
from src.decorators import command, event_listener
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.dispatcher import MessageDispatcher
from src.messages import messages
from src.events import Event
from src.roles._shaman_helper import setup_variables, get_totem_target, give_totem
def get_tags(var, totem):
tags = set()
if totem in var.BENEFICIAL_TOTEMS:
tags.add("beneficial")
return tags
TOTEMS, LASTGIVEN, SHAMANS = setup_variables("wolf shaman", knows_totem=True, get_tags=get_tags)
@command("give", "totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("wolf shaman",))
def wolf_shaman_totem(var, wrapper, message):
"""Give a totem to a player."""
target = get_totem_target(var, wrapper, message, LASTGIVEN)
if not target:
return
totem = TOTEMS[wrapper.source]
SHAMANS[wrapper.source] = give_totem(var, wrapper, target, prefix="You", tags=get_tags(var, totem), role="wolf shaman", msg=" of {0}".format(totem))
relay_wolfchat_command(wrapper.client, wrapper.source.nick, messages["shaman_wolfchat"].format(wrapper.source, target), ("wolf shaman",), is_wolf_command=True)
@event_listener("transition_day_begin", priority=4)
def on_transition_day_begin(evt, var):
# Select random totem recipients if shamans didn't act
pl = get_players()
for shaman in get_players(("wolf shaman",)):
if shaman not in SHAMANS and shaman.nick not in var.SILENCED:
ps = pl[:]
if shaman in LASTGIVEN:
if LASTGIVEN[shaman] in ps:
ps.remove(LASTGIVEN[shaman])
levt = Event("get_random_totem_targets", {"targets": ps})
levt.dispatch(var, shaman)
ps = levt.data["targets"]
if ps:
target = random.choice(ps)
dispatcher = MessageDispatcher(shaman, shaman)
tags = get_tags(var, TOTEMS[shaman])
SHAMANS[shaman] = give_totem(var, dispatcher, target, prefix=messages["random_totem_prefix"], tags=tags, role="wolf shaman", msg=" of {0}".format(TOTEMS[shaman]))
relay_wolfchat_command(shaman.client, shaman.nick, messages["shaman_wolfchat"].format(shaman, target), ("wolf shaman",), is_wolf_command=True)
else:
LASTGIVEN[shaman] = None
elif shaman not in SHAMANS:
LASTGIVEN[shaman] = None
@event_listener("transition_night_end", priority=2.01)
def on_transition_night_end(evt, var):
max_totems = 0
ps = get_players()
shamans = get_players(("wolf shaman",))
index = var.TOTEM_ORDER.index("wolf shaman")
for c in var.TOTEM_CHANCES.values():
max_totems += c[index]
for s in list(LASTGIVEN):
if s not in shamans:
del LASTGIVEN[s]
for shaman in shamans:
pl = ps[:]
random.shuffle(pl)
if LASTGIVEN.get(shaman):
if LASTGIVEN[shaman] in pl:
pl.remove(LASTGIVEN[shaman])
target = 0
rand = random.random() * max_totems
for t in var.TOTEM_CHANCES.keys():
target += var.TOTEM_CHANCES[t][index]
if rand <= target:
TOTEMS[shaman] = t
break
if shaman.prefers_simple():
# Message about role was sent with wolfchat
shaman.send(messages["totem_simple"].format(TOTEMS[shaman]))
else:
totem = TOTEMS[shaman]
tmsg = messages["shaman_totem"].format(totem)
tmsg += messages[totem + "_totem"]
shaman.send(tmsg)
# vim: set sw=4 expandtab:

View File

@ -2,12 +2,11 @@ import re
import random
from collections import defaultdict
import src.settings as var
from src.utilities import *
from src.functions import get_players
from src import debuglog, errlog, plog, users, channels
from src.decorators import cmd, event_listener
from src.containers import UserList, UserSet, UserDict
from src.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event
from src.roles import wolf

View File

@ -1,7 +1,7 @@
import fnmatch
import re
import threading
from collections import defaultdict, OrderedDict
from collections import OrderedDict
LANGUAGE = 'en'

File diff suppressed because it is too large Load Diff