diff --git a/werewolf/game.py b/werewolf/game.py index df5d263..98475ba 100644 --- a/werewolf/game.py +++ b/werewolf/game.py @@ -9,10 +9,10 @@ import discord from redbot.core import commands from redbot.core.bot import Red -from .builder import parse_code -from .player import Player -from .role import Role -from .votegroup import VoteGroup +from werewolf.builder import parse_code +from werewolf.player import Player +from werewolf.role import Role +from werewolf.votegroup import VoteGroup log = logging.getLogger("red.fox_v3.werewolf.game") @@ -480,7 +480,7 @@ class Game: async def _notify(self, event, **kwargs): for i in range(1, 7): # action guide 1-6 (0 is no action) tasks = [] - for event in self.listeners.get(event, []): + for event in self.listeners.get(event, {}).get(i, []): tasks.append(asyncio.ensure_future(event(**kwargs), loop=self.loop)) await asyncio.gather(*tasks) @@ -912,26 +912,19 @@ class Game: # Optional dynamic channels/categories - def add_listener(self, func, name=None): - """The non decorator alternative to :meth:`.listen`. + def add_ww_listener(self, func, priority=0, name=None): + """Adds a listener from the pool of listeners. Parameters ----------- func: :ref:`coroutine ` The function to call. + priority: Optional[:class:`int`] + Priority of the listener. Defaults to 0 (no-action) name: Optional[:class:`str`] The name of the event to listen for. Defaults to ``func.__name__``. - - Example - -------- - - .. code-block:: python3 - - async def on_ready(): pass - async def my_message(message): pass - - bot.add_listener(on_ready) - bot.add_listener(my_message, 'on_message') + do_sort: Optional[:class:`bool`] + Whether or not to sort listeners after. Skip sorting during mass appending """ name = func.__name__ if name is None else name @@ -940,26 +933,32 @@ class Game: raise TypeError('Listeners must be coroutines') if name in self.listeners: - self.listeners[name].append(func) + if priority in self.listeners[name]: + self.listeners[name][priority].append(func) + else: + self.listeners[name][priority] = [func] else: - self.listeners[name] = [func] - - def remove_listener(self, func, name=None): - """Removes a listener from the pool of listeners. + self.listeners[name] = {priority: [func]} - Parameters - ----------- - func - The function that was used as a listener to remove. - name: :class:`str` - The name of the event we want to remove. Defaults to - ``func.__name__``. - """ + # self.listeners[name].sort(reverse=True) - name = func.__name__ if name is None else name - if name in self.listeners: - try: - self.listeners[name].remove(func) - except ValueError: - pass + # def remove_wolf_listener(self, func, name=None): + # """Removes a listener from the pool of listeners. + # + # Parameters + # ----------- + # func + # The function that was used as a listener to remove. + # name: :class:`str` + # The name of the event we want to remove. Defaults to + # ``func.__name__``. + # """ + # + # name = func.__name__ if name is None else name + # + # if name in self.listeners: + # try: + # self.listeners[name].remove(func) + # except ValueError: + # pass diff --git a/werewolf/listener.py b/werewolf/listener.py index 9c36400..e14994a 100644 --- a/werewolf/listener.py +++ b/werewolf/listener.py @@ -1,7 +1,7 @@ import inspect -def wolflistener(name=None): +def wolflistener(name=None, priority=0): """A decorator that marks a function as a listener. This is the werewolf.Game equivalent of :meth:`.Cog.listener`. @@ -11,6 +11,22 @@ def wolflistener(name=None): name: :class:`str` The name of the event being listened to. If not provided, it defaults to the function's name. + priority: :class:`int` + The priority of the listener. + Priority guide as follows: + _at_night_start + 0. No Action + 1. Detain actions (Jailer/Kidnapper) + 2. Group discussions and choose targets + + _at_night_end + 0. No Action + 1. Self actions (Veteran) + 2. Target switching and role blocks (bus driver, witch, escort) + 3. Protection / Preempt actions (bodyguard/framer) + 4. Non-disruptive actions (seer/silencer) + 5. Disruptive actions (Killing) + 6. Role altering actions (Cult / Mason / Shifter) Raises -------- @@ -32,12 +48,12 @@ def wolflistener(name=None): actual = actual.__func__ if not inspect.iscoroutinefunction(actual): raise TypeError("Listener function must be a coroutine function.") - actual.__wolf_listener__ = True + actual.__wolf_listener__ = priority to_assign = name or actual.__name__ try: - actual.__wolf_listener_names__.append(to_assign) + actual.__wolf_listener_names__.append((priority, to_assign)) except AttributeError: - actual.__wolf_listener_names__ = [to_assign] + actual.__wolf_listener_names__ = [(priority, to_assign)] # we have to return `func` instead of `actual` because # we need the type to be `staticmethod` for the metaclass # to pick it up but the metaclass unfurls the function and @@ -51,7 +67,6 @@ class WolfListenerMeta(type): def __new__(mcs, cls, *args, **kwargs): name, bases = args - commands = {} listeners = {} need_at_msg = "Listeners must start with at_ (in method {0.__name__}.{1})" @@ -76,10 +91,10 @@ class WolfListenerMeta(type): listeners_as_list = [] for listener in listeners.values(): - for listener_name in listener.__wolf_listener_names__: + for priority, listener_name in listener.__wolf_listener_names__: # I use __name__ instead of just storing the value so I can inject # the self attribute when the time comes to add them to the bot - listeners_as_list.append((listener_name, listener.__name__)) + listeners_as_list.append((priority, listener_name, listener.__name__)) new_cls.__wolf_listeners__ = listeners_as_list return new_cls @@ -87,5 +102,5 @@ class WolfListenerMeta(type): class WolfListener(metaclass=WolfListenerMeta): def __init__(self, game): - for name, method_name in self.__wolf_listeners__: - game.add_listener(getattr(self, method_name), name) + for priority, name, method_name in self.__wolf_listeners__: + game.add_ww_listener(getattr(self, method_name), priority, name) diff --git a/werewolf/night_powers.py b/werewolf/night_powers.py index a39ad26..ab82e87 100644 --- a/werewolf/night_powers.py +++ b/werewolf/night_powers.py @@ -1,6 +1,6 @@ import logging -from .role import Role +from werewolf.role import Role log = logging.getLogger("red.fox_v3.werewolf.night_powers") diff --git a/werewolf/roles/seer.py b/werewolf/roles/seer.py index 603d197..a7a9aa8 100644 --- a/werewolf/roles/seer.py +++ b/werewolf/roles/seer.py @@ -1,6 +1,6 @@ -from ..listener import wolflistener -from ..night_powers import pick_target -from ..role import Role +from werewolf.listener import wolflistener +from werewolf.night_powers import pick_target +from werewolf.role import Role class Seer(Role): @@ -27,17 +27,17 @@ class Seer(Role): # self.blocked = False # self.properties = {} # Extra data for other roles (i.e. arsonist) self.see_target = None - self.action_list = [ - (self._at_game_start, 1), # (Action, Priority) - (self._at_day_start, 0), - (self._at_voted, 0), - (self._at_kill, 0), - (self._at_hang, 0), - (self._at_day_end, 0), - (self._at_night_start, 2), - (self._at_night_end, 4), - (self._at_visit, 0), - ] + # self.action_list = [ + # (self._at_game_start, 1), # (Action, Priority) + # (self._at_day_start, 0), + # (self._at_voted, 0), + # (self._at_kill, 0), + # (self._at_hang, 0), + # (self._at_day_end, 0), + # (self._at_night_start, 2), + # (self._at_night_end, 4), + # (self._at_visit, 0), + # ] async def see_alignment(self, source=None): """ @@ -60,7 +60,7 @@ class Seer(Role): """ return "Villager" - @wolflistener("at_night_start") + @wolflistener("at_night_start", priority=2) async def _at_night_start(self, data=None): if not self.player.alive: return @@ -68,7 +68,7 @@ class Seer(Role): await self.game.generate_targets(self.player.member) await self.player.send_dm("**Pick a target to see tonight**") - @wolflistener("at_night_end") + @wolflistener("at_night_end", priority=4) async def _at_night_end(self, data=None): if self.see_target is None: if self.player.alive: diff --git a/werewolf/roles/vanillawerewolf.py b/werewolf/roles/vanillawerewolf.py index 5f7407b..e6938eb 100644 --- a/werewolf/roles/vanillawerewolf.py +++ b/werewolf/roles/vanillawerewolf.py @@ -1,7 +1,6 @@ -from ..listener import wolflistener -from ..role import Role - -from ..votegroups.wolfvote import WolfVote +from werewolf.listener import wolflistener +from werewolf.role import Role +from werewolf.votegroups.wolfvote import WolfVote class VanillaWerewolf(Role): diff --git a/werewolf/roles/villager.py b/werewolf/roles/villager.py index bda51d2..040e34d 100644 --- a/werewolf/roles/villager.py +++ b/werewolf/roles/villager.py @@ -1,4 +1,4 @@ -from ..role import Role +from werewolf.role import Role class Villager(Role): diff --git a/werewolf/votegroups/__init__.py b/werewolf/votegroups/__init__.py new file mode 100644 index 0000000..03abc1b --- /dev/null +++ b/werewolf/votegroups/__init__.py @@ -0,0 +1 @@ +from .wolfvote import WolfVote \ No newline at end of file diff --git a/werewolf/votegroups/wolfvote.py b/werewolf/votegroups/wolfvote.py index fb98b20..f990eef 100644 --- a/werewolf/votegroups/wolfvote.py +++ b/werewolf/votegroups/wolfvote.py @@ -1,6 +1,6 @@ import random -from ..votegroup import VoteGroup +from werewolf.votegroup import VoteGroup class WolfVote(VoteGroup): diff --git a/werewolf/werewolf.py b/werewolf/werewolf.py index 0c8374c..abed258 100644 --- a/werewolf/werewolf.py +++ b/werewolf/werewolf.py @@ -6,14 +6,14 @@ from redbot.core.bot import Red from redbot.core.commands import Cog from redbot.core.utils.menus import DEFAULT_CONTROLS, menu -from .builder import ( +from werewolf.builder import ( GameBuilder, role_from_alignment, role_from_category, role_from_id, role_from_name, ) -from .game import Game +from werewolf.game import Game log = logging.getLogger("red.fox_v3.werewolf") @@ -81,8 +81,8 @@ class Werewolf(Cog): """ Lists current guild settings """ - success, role, category, channel, log_channel = await self._get_settings(ctx) - if not success: + valid, role, category, channel, log_channel = await self._get_settings(ctx) + if not valid: await ctx.send("Failed to get settings") return None @@ -362,13 +362,15 @@ class Werewolf(Cog): return None if guild.id not in self.games or self.games[guild.id].game_over: await ctx.send("Starting a new game...") - success, role, category, channel, log_channel = await self._get_settings(ctx) + valid, role, category, channel, log_channel = await self._get_settings(ctx) - if not success: + if not valid: await ctx.send("Cannot start a new game") return None - self.games[guild.id] = Game(self.bot, guild, role, category, channel, log_channel, game_code) + self.games[guild.id] = Game( + self.bot, guild, role, category, channel, log_channel, game_code + ) return self.games[guild.id]