Listener structure major change
Still need priority
This commit is contained in:
		
							parent
							
								
									1723dc381d
								
							
						
					
					
						commit
						8a3f45bdc1
					
				
							
								
								
									
										113
									
								
								werewolf/game.py
									
									
									
									
									
								
							
							
						
						
									
										113
									
								
								werewolf/game.py
									
									
									
									
									
								
							| @ -16,6 +16,7 @@ from .votegroup import VoteGroup | ||||
| 
 | ||||
| log = logging.getLogger("red.fox_v3.werewolf.game") | ||||
| 
 | ||||
| HALF_DAY_LENGTH = 24  # FixMe: to 120 later for 4 minute days | ||||
| 
 | ||||
| class Game: | ||||
|     """ | ||||
| @ -262,7 +263,7 @@ class Game: | ||||
|         await asyncio.sleep(1) | ||||
|         await asyncio.ensure_future(self._cycle())  # Start the loop | ||||
| 
 | ||||
|     ############START Notify structure############ | ||||
|     # ###########START Notify structure############ | ||||
|     async def _cycle(self): | ||||
|         """ | ||||
|         Each event enqueues the next event | ||||
| @ -323,13 +324,13 @@ class Game: | ||||
|             return | ||||
|         self.can_vote = True | ||||
| 
 | ||||
|         await asyncio.sleep(24)  # 4 minute days FixMe to 120 later | ||||
|         await asyncio.sleep(HALF_DAY_LENGTH)  # 4 minute days FixMe to 120 later | ||||
|         if check(): | ||||
|             return | ||||
|         await self.village_channel.send( | ||||
|             embed=discord.Embed(title="**Two minutes of daylight remain...**") | ||||
|         ) | ||||
|         await asyncio.sleep(24)  # 4 minute days FixMe to 120 later | ||||
|         await asyncio.sleep(HALF_DAY_LENGTH)  # 4 minute days FixMe to 120 later | ||||
| 
 | ||||
|         # Need a loop here to wait for trial to end (can_vote?) | ||||
|         while self.ongoing_vote: | ||||
| @ -500,7 +501,7 @@ class Game: | ||||
|             #     await asyncio.gather(*tasks) | ||||
|             # Run same-priority task simultaneously | ||||
| 
 | ||||
|     ############END Notify structure############ | ||||
|     # ###########END Notify structure############ | ||||
| 
 | ||||
|     async def generate_targets(self, channel, with_roles=False): | ||||
|         embed = discord.Embed(title="Remaining Players") | ||||
| @ -911,89 +912,7 @@ class Game: | ||||
| 
 | ||||
|         # Optional dynamic channels/categories | ||||
| 
 | ||||
|     @classmethod | ||||
|     def wolflistener(cls, name=None): | ||||
|         """A decorator that marks a function as a listener. | ||||
| 
 | ||||
|         This is the cog equivalent of :meth:`.Bot.listen`. | ||||
| 
 | ||||
|         Parameters | ||||
|         ------------ | ||||
|         name: :class:`str` | ||||
|             The name of the event being listened to. If not provided, it | ||||
|             defaults to the function's name. | ||||
| 
 | ||||
|         Raises | ||||
|         -------- | ||||
|         TypeError | ||||
|             The function is not a coroutine function or a string was not passed as | ||||
|             the name. | ||||
|         """ | ||||
| 
 | ||||
|         if name is not None and not isinstance(name, str): | ||||
|             raise TypeError( | ||||
|                 "Cog.listener expected str but received {0.__class__.__name__!r} instead.".format( | ||||
|                     name | ||||
|                 ) | ||||
|             ) | ||||
| 
 | ||||
|         def decorator(func): | ||||
|             actual = func | ||||
|             if isinstance(actual, staticmethod): | ||||
|                 actual = actual.__func__ | ||||
|             if not inspect.iscoroutinefunction(actual): | ||||
|                 raise TypeError("Listener function must be a coroutine function.") | ||||
|             actual.__werewolf_listener__ = True | ||||
|             to_assign = name or actual.__name__ | ||||
|             try: | ||||
|                 actual.__cog_listener_names__.append(to_assign) | ||||
|             except AttributeError: | ||||
|                 actual.__cog_listener_names__ = [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 | ||||
|             # thus the assignments need to be on the actual function | ||||
|             return func | ||||
| 
 | ||||
|         return decorator | ||||
| 
 | ||||
|     def wolflisten(self, name=None): | ||||
|         """A decorator that registers another function as an external | ||||
|         event listener. Basically this allows you to listen to multiple | ||||
|         events from different places e.g. such as :func:`.on_ready` | ||||
| 
 | ||||
|         The functions being listened to must be a :ref:`coroutine <coroutine>`. | ||||
| 
 | ||||
|         Example | ||||
|         -------- | ||||
| 
 | ||||
|         .. code-block:: python3 | ||||
| 
 | ||||
|             @bot.listen() | ||||
|             async def on_message(message): | ||||
|                 print('one') | ||||
| 
 | ||||
|             # in some other file... | ||||
| 
 | ||||
|             @bot.listen('on_message') | ||||
|             async def my_message(message): | ||||
|                 print('two') | ||||
| 
 | ||||
|         Would print one and two in an unspecified order. | ||||
| 
 | ||||
|         Raises | ||||
|         ------- | ||||
|         TypeError | ||||
|             The function being listened to is not a coroutine. | ||||
|         """ | ||||
| 
 | ||||
|         def decorator(func): | ||||
|             self.add_wolflistener(func, name) | ||||
|             return func | ||||
| 
 | ||||
|         return decorator | ||||
| 
 | ||||
|     def add_wolflistener(self, func, name=None): | ||||
|     def add_listener(self, func, name=None): | ||||
|         """The non decorator alternative to :meth:`.listen`. | ||||
| 
 | ||||
|         Parameters | ||||
| @ -1024,3 +943,23 @@ class Game: | ||||
|             self.listeners[name].append(func) | ||||
|         else: | ||||
|             self.listeners[name] = [func] | ||||
| 
 | ||||
|     def remove_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 | ||||
|  | ||||
							
								
								
									
										91
									
								
								werewolf/listener.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								werewolf/listener.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | ||||
| import inspect | ||||
| 
 | ||||
| 
 | ||||
| def wolflistener(name=None): | ||||
|     """A decorator that marks a function as a listener. | ||||
| 
 | ||||
|     This is the werewolf.Game equivalent of :meth:`.Cog.listener`. | ||||
| 
 | ||||
|     Parameters | ||||
|     ------------ | ||||
|     name: :class:`str` | ||||
|         The name of the event being listened to. If not provided, it | ||||
|         defaults to the function's name. | ||||
| 
 | ||||
|     Raises | ||||
|     -------- | ||||
|     TypeError | ||||
|         The function is not a coroutine function or a string was not passed as | ||||
|         the name. | ||||
|     """ | ||||
| 
 | ||||
|     if name is not None and not isinstance(name, str): | ||||
|         raise TypeError( | ||||
|             "Game.listener expected str but received {0.__class__.__name__!r} instead.".format( | ||||
|                 name | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|     def decorator(func): | ||||
|         actual = func | ||||
|         if isinstance(actual, staticmethod): | ||||
|             actual = actual.__func__ | ||||
|         if not inspect.iscoroutinefunction(actual): | ||||
|             raise TypeError("Listener function must be a coroutine function.") | ||||
|         actual.__wolf_listener__ = True | ||||
|         to_assign = name or actual.__name__ | ||||
|         try: | ||||
|             actual.__wolf_listener_names__.append(to_assign) | ||||
|         except AttributeError: | ||||
|             actual.__wolf_listener_names__ = [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 | ||||
|         # thus the assignments need to be on the actual function | ||||
|         return func | ||||
| 
 | ||||
|     return decorator | ||||
| 
 | ||||
| 
 | ||||
| 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})" | ||||
| 
 | ||||
|         new_cls = super().__new__(cls, name, bases, **kwargs) | ||||
|         for base in reversed(new_cls.__mro__): | ||||
|             for elem, value in base.__dict__.items(): | ||||
|                 if elem in listeners: | ||||
|                     del listeners[elem] | ||||
| 
 | ||||
|                 is_static_method = isinstance(value, staticmethod) | ||||
|                 if is_static_method: | ||||
|                     value = value.__func__ | ||||
|                 if inspect.iscoroutinefunction(value): | ||||
|                     try: | ||||
|                         is_listener = getattr(value, "__wolf_listener__") | ||||
|                     except AttributeError: | ||||
|                         continue | ||||
|                     else: | ||||
|                         if not elem.startswith("at_"): | ||||
|                             raise TypeError(need_at_msg.format(mcs, elem)) | ||||
|                         listeners[elem] = value | ||||
| 
 | ||||
|         listeners_as_list = [] | ||||
|         for listener in listeners.values(): | ||||
|             for 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__)) | ||||
| 
 | ||||
|         new_cls.__wolf_listeners__ = listeners_as_list | ||||
|         return new_cls | ||||
| 
 | ||||
| 
 | ||||
| class WolfListener(metaclass=WolfListenerMeta): | ||||
|     def __init__(self, game): | ||||
|         for name, method_name in self.__wolf_listeners__: | ||||
|             game.add_listener(getattr(self, method_name), name) | ||||
| @ -1,12 +1,12 @@ | ||||
| import inspect | ||||
| import logging | ||||
| 
 | ||||
| from werewolf import Werewolf | ||||
| from werewolf.listener import WolfListener, wolflistener | ||||
| 
 | ||||
| log = logging.getLogger("red.fox_v3.werewolf.role") | ||||
| 
 | ||||
| 
 | ||||
| class Role: | ||||
| class Role(WolfListener): | ||||
|     """ | ||||
|     Base Role class for werewolf game | ||||
| 
 | ||||
| @ -28,7 +28,7 @@ class Role: | ||||
|         category = [11, 16] Could be Werewolf Silencer | ||||
| 
 | ||||
| 
 | ||||
|     Action guide as follows (on_event function): | ||||
|     Action priority guide as follows (on_event function): | ||||
|         _at_night_start | ||||
|         0. No Action | ||||
|         1. Detain actions (Jailer/Kidnapper) | ||||
| @ -62,6 +62,7 @@ class Role: | ||||
|     icon_url = None  # Adding a URL here will enable a thumbnail of the role | ||||
| 
 | ||||
|     def __init__(self, game): | ||||
|         super().__init__(game) | ||||
|         self.game = game | ||||
|         self.player = None | ||||
|         self.blocked = False | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| from ..listener import wolflistener | ||||
| from ..night_powers import pick_target | ||||
| from ..role import Role | ||||
| 
 | ||||
| @ -59,6 +60,7 @@ class Seer(Role): | ||||
|         """ | ||||
|         return "Villager" | ||||
| 
 | ||||
|     @wolflistener("at_night_start") | ||||
|     async def _at_night_start(self, data=None): | ||||
|         if not self.player.alive: | ||||
|             return | ||||
| @ -66,6 +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") | ||||
|     async def _at_night_end(self, data=None): | ||||
|         if self.see_target is None: | ||||
|             if self.player.alive: | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| from ..listener import wolflistener | ||||
| from ..night_powers import pick_target | ||||
| from ..role import Role | ||||
| 
 | ||||
| @ -5,31 +6,31 @@ from ..role import Role | ||||
| class Shifter(Role): | ||||
|     """ | ||||
|     Base Role class for werewolf game | ||||
|      | ||||
| 
 | ||||
|     Category enrollment guide as follows (category property): | ||||
|         Town: | ||||
|         1: Random, 2: Investigative, 3: Protective, 4: Government, | ||||
|         5: Killing, 6: Power (Special night action) | ||||
|          | ||||
| 
 | ||||
|         Werewolf: | ||||
|         11: Random, 12: Deception, 15: Killing, 16: Support | ||||
|          | ||||
| 
 | ||||
|         Neutral: | ||||
|         21: Benign, 22: Evil, 23: Killing | ||||
|          | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|         Example category: | ||||
|         category = [1, 5, 6] Could be Veteran | ||||
|         category = [1, 5] Could be Bodyguard | ||||
|         category = [11, 16] Could be Werewolf Silencer | ||||
|          | ||||
|      | ||||
| 
 | ||||
| 
 | ||||
|     Action guide as follows (on_event function): | ||||
|         _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) | ||||
| @ -61,17 +62,17 @@ class Shifter(Role): | ||||
|         super().__init__(game) | ||||
| 
 | ||||
|         self.shift_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),  # Chooses targets | ||||
|             (self._at_night_end, 6),  # Role Swap | ||||
|             (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),  # Chooses targets | ||||
|         #     (self._at_night_end, 6),  # Role Swap | ||||
|         #     (self._at_visit, 0), | ||||
|         # ] | ||||
| 
 | ||||
|     async def see_alignment(self, source=None): | ||||
|         """ | ||||
| @ -94,14 +95,14 @@ class Shifter(Role): | ||||
|         """ | ||||
|         return "Shifter" | ||||
| 
 | ||||
|     @wolflistener("at_night_start") | ||||
|     async def _at_night_start(self, data=None): | ||||
|         await super()._at_night_start(data) | ||||
|         self.shift_target = None | ||||
|         await self.game.generate_targets(self.player.member) | ||||
|         await self.player.send_dm("**Pick a target to shift into**") | ||||
| 
 | ||||
|     @wolflistener("at_night_end") | ||||
|     async def _at_night_end(self, data=None): | ||||
|         await super()._at_night_end(data) | ||||
|         if self.shift_target is None: | ||||
|             if self.player.alive: | ||||
|                 await self.player.send_dm("You will not use your powers tonight...") | ||||
| @ -114,16 +115,22 @@ class Shifter(Role): | ||||
| 
 | ||||
|             # Roles have now been swapped | ||||
| 
 | ||||
|             await self.player.send_dm("Your role has been stolen...\n" | ||||
|                                       "You are now a **Shifter**.") | ||||
|             await self.player.send_dm( | ||||
|                 "Your role has been stolen...\n" "You are now a **Shifter**." | ||||
|             ) | ||||
|             await self.player.send_dm(self.game_start_message) | ||||
| 
 | ||||
|             await target.send_dm(target.role.game_start_message) | ||||
|         else: | ||||
|             await self.player.send_dm("**Your shift failed...**") | ||||
| 
 | ||||
|     async def choose(self, ctx, data): | ||||
|         """Handle night actions""" | ||||
|         await super().choose(ctx, data) | ||||
| 
 | ||||
|         self.shift_target, target = await pick_target(self, ctx, data) | ||||
|         await ctx.send("**You will attempt to see the role of {} tonight...**".format(target.member.display_name)) | ||||
|         await ctx.send( | ||||
|             "**You will attempt to see the role of {} tonight...**".format( | ||||
|                 target.member.display_name | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| from ..listener import wolflistener | ||||
| from ..role import Role | ||||
| 
 | ||||
| from ..votegroups.wolfvote import WolfVote | ||||
| @ -19,17 +20,17 @@ class VanillaWerewolf(Role): | ||||
|     def __init__(self, game): | ||||
|         super().__init__(game) | ||||
| 
 | ||||
|         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, 0), | ||||
|             (self._at_night_end, 0), | ||||
|             (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, 0), | ||||
|         #     (self._at_night_end, 0), | ||||
|         #     (self._at_visit, 0) | ||||
|         # ] | ||||
| 
 | ||||
|     async def see_alignment(self, source=None): | ||||
|         """ | ||||
| @ -52,6 +53,7 @@ class VanillaWerewolf(Role): | ||||
|         """ | ||||
|         return "Werewolf" | ||||
| 
 | ||||
|     @wolflistener("at_game_start") | ||||
|     async def _at_game_start(self, data=None): | ||||
|         if self.channel_id: | ||||
|             print("Wolf has channel_id: " + self.channel_id) | ||||
|  | ||||
| @ -1,9 +1,11 @@ | ||||
| import logging | ||||
| 
 | ||||
| from werewolf.listener import WolfListener, wolflistener | ||||
| 
 | ||||
| log = logging.getLogger("red.fox_v3.werewolf.votegroup") | ||||
| 
 | ||||
| 
 | ||||
| class VoteGroup: | ||||
| class VoteGroup(WolfListener): | ||||
|     """ | ||||
|     Base VoteGroup class for werewolf game | ||||
|     Handles secret channels and group decisions | ||||
| @ -13,57 +15,55 @@ class VoteGroup: | ||||
|     channel_id = "" | ||||
| 
 | ||||
|     def __init__(self, game, channel): | ||||
|         super().__init__(game) | ||||
|         self.game = game | ||||
|         self.channel = channel | ||||
|         self.players = [] | ||||
|         self.vote_results = {} | ||||
|         self.properties = {}  # Extra data for other options | ||||
| 
 | ||||
|         self.action_list = [ | ||||
|             (self._at_game_start, 1),  # (Action, Priority) | ||||
|             (self._at_day_start, 0), | ||||
|             (self._at_voted, 0), | ||||
|             (self._at_kill, 1), | ||||
|             (self._at_hang, 1), | ||||
|             (self._at_day_end, 0), | ||||
|             (self._at_night_start, 2), | ||||
|             (self._at_night_end, 0), | ||||
|             (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, 1), | ||||
|         #     (self._at_hang, 1), | ||||
|         #     (self._at_day_end, 0), | ||||
|         #     (self._at_night_start, 2), | ||||
|         #     (self._at_night_end, 0), | ||||
|         #     (self._at_visit, 0), | ||||
|         # ] | ||||
| 
 | ||||
|     async def on_event(self, event, data): | ||||
|         """ | ||||
|         See Game class for event guide | ||||
|         """ | ||||
| 
 | ||||
|         await self.action_list[event][0](data) | ||||
|     # async def on_event(self, event, data): | ||||
|     #     """ | ||||
|     #     See Game class for event guide | ||||
|     #     """ | ||||
|     # | ||||
|     #     await self.action_list[event][0](data) | ||||
| 
 | ||||
|     @wolflistener("at_game_start") | ||||
|     async def _at_game_start(self, data=None): | ||||
|         await self.channel.send(" ".join(player.mention for player in self.players)) | ||||
| 
 | ||||
|     async def _at_day_start(self, data=None): | ||||
|         pass | ||||
| 
 | ||||
|     async def _at_voted(self, data=None): | ||||
|         pass | ||||
| 
 | ||||
|     @wolflistener("at_kill") | ||||
|     async def _at_kill(self, data=None): | ||||
|         if data["player"] in self.players: | ||||
|             self.players.remove(data["player"]) | ||||
| 
 | ||||
|     async def _at_hang(self, data=None): | ||||
|         if data["player"] in self.players: | ||||
|             self.players.remove(data["player"]) | ||||
| 
 | ||||
|     async def _at_day_end(self, data=None): | ||||
|         pass | ||||
|     # Removed, only if they actually die | ||||
|     # @wolflistener("at_hang") | ||||
|     # async def _at_hang(self, data=None): | ||||
|     #     if data["player"] in self.players: | ||||
|     #         self.players.remove(data["player"]) | ||||
| 
 | ||||
|     @wolflistener("at_night_start") | ||||
|     async def _at_night_start(self, data=None): | ||||
|         if self.channel is None: | ||||
|             return | ||||
| 
 | ||||
|         await self.game.generate_targets(self.channel) | ||||
| 
 | ||||
|     @wolflistener("at_night_end") | ||||
|     async def _at_night_end(self, data=None): | ||||
|         if self.channel is None: | ||||
|             return | ||||
| @ -78,9 +78,6 @@ class VoteGroup: | ||||
|             # Do what you voted on | ||||
|             pass | ||||
| 
 | ||||
|     async def _at_visit(self, data=None): | ||||
|         pass | ||||
| 
 | ||||
|     async def register_players(self, *players): | ||||
|         """ | ||||
|         Extend players by passed list | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 bobloy
						bobloy