Listener structure major change

Still need priority
pull/147/head
bobloy 4 years ago
parent 1723dc381d
commit 8a3f45bdc1

@ -16,6 +16,7 @@ from .votegroup import VoteGroup
log = logging.getLogger("red.fox_v3.werewolf.game") log = logging.getLogger("red.fox_v3.werewolf.game")
HALF_DAY_LENGTH = 24 # FixMe: to 120 later for 4 minute days
class Game: class Game:
""" """
@ -262,7 +263,7 @@ class Game:
await asyncio.sleep(1) await asyncio.sleep(1)
await asyncio.ensure_future(self._cycle()) # Start the loop await asyncio.ensure_future(self._cycle()) # Start the loop
############START Notify structure############ # ###########START Notify structure############
async def _cycle(self): async def _cycle(self):
""" """
Each event enqueues the next event Each event enqueues the next event
@ -323,13 +324,13 @@ class Game:
return return
self.can_vote = True 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(): if check():
return return
await self.village_channel.send( await self.village_channel.send(
embed=discord.Embed(title="**Two minutes of daylight remain...**") 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?) # Need a loop here to wait for trial to end (can_vote?)
while self.ongoing_vote: while self.ongoing_vote:
@ -500,7 +501,7 @@ class Game:
# await asyncio.gather(*tasks) # await asyncio.gather(*tasks)
# Run same-priority task simultaneously # Run same-priority task simultaneously
############END Notify structure############ # ###########END Notify structure############
async def generate_targets(self, channel, with_roles=False): async def generate_targets(self, channel, with_roles=False):
embed = discord.Embed(title="Remaining Players") embed = discord.Embed(title="Remaining Players")
@ -911,89 +912,7 @@ class Game:
# Optional dynamic channels/categories # Optional dynamic channels/categories
@classmethod def add_listener(self, func, name=None):
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):
"""The non decorator alternative to :meth:`.listen`. """The non decorator alternative to :meth:`.listen`.
Parameters Parameters
@ -1024,3 +943,23 @@ class Game:
self.listeners[name].append(func) self.listeners[name].append(func)
else: else:
self.listeners[name] = [func] 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

@ -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 inspect
import logging import logging
from werewolf import Werewolf from werewolf.listener import WolfListener, wolflistener
log = logging.getLogger("red.fox_v3.werewolf.role") log = logging.getLogger("red.fox_v3.werewolf.role")
class Role: class Role(WolfListener):
""" """
Base Role class for werewolf game Base Role class for werewolf game
@ -28,7 +28,7 @@ class Role:
category = [11, 16] Could be Werewolf Silencer 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 _at_night_start
0. No Action 0. No Action
1. Detain actions (Jailer/Kidnapper) 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 icon_url = None # Adding a URL here will enable a thumbnail of the role
def __init__(self, game): def __init__(self, game):
super().__init__(game)
self.game = game self.game = game
self.player = None self.player = None
self.blocked = False self.blocked = False

@ -1,3 +1,4 @@
from ..listener import wolflistener
from ..night_powers import pick_target from ..night_powers import pick_target
from ..role import Role from ..role import Role
@ -59,6 +60,7 @@ class Seer(Role):
""" """
return "Villager" return "Villager"
@wolflistener("at_night_start")
async def _at_night_start(self, data=None): async def _at_night_start(self, data=None):
if not self.player.alive: if not self.player.alive:
return return
@ -66,6 +68,7 @@ class Seer(Role):
await self.game.generate_targets(self.player.member) await self.game.generate_targets(self.player.member)
await self.player.send_dm("**Pick a target to see tonight**") await self.player.send_dm("**Pick a target to see tonight**")
@wolflistener("at_night_end")
async def _at_night_end(self, data=None): async def _at_night_end(self, data=None):
if self.see_target is None: if self.see_target is None:
if self.player.alive: if self.player.alive:

@ -1,3 +1,4 @@
from ..listener import wolflistener
from ..night_powers import pick_target from ..night_powers import pick_target
from ..role import Role from ..role import Role
@ -61,17 +62,17 @@ class Shifter(Role):
super().__init__(game) super().__init__(game)
self.shift_target = None self.shift_target = None
self.action_list = [ # self.action_list = [
(self._at_game_start, 1), # (Action, Priority) # (self._at_game_start, 1), # (Action, Priority)
(self._at_day_start, 0), # (self._at_day_start, 0),
(self._at_voted, 0), # (self._at_voted, 0),
(self._at_kill, 0), # (self._at_kill, 0),
(self._at_hang, 0), # (self._at_hang, 0),
(self._at_day_end, 0), # (self._at_day_end, 0),
(self._at_night_start, 2), # Chooses targets # (self._at_night_start, 2), # Chooses targets
(self._at_night_end, 6), # Role Swap # (self._at_night_end, 6), # Role Swap
(self._at_visit, 0) # (self._at_visit, 0),
] # ]
async def see_alignment(self, source=None): async def see_alignment(self, source=None):
""" """
@ -94,14 +95,14 @@ class Shifter(Role):
""" """
return "Shifter" return "Shifter"
@wolflistener("at_night_start")
async def _at_night_start(self, data=None): async def _at_night_start(self, data=None):
await super()._at_night_start(data)
self.shift_target = None self.shift_target = None
await self.game.generate_targets(self.player.member) await self.game.generate_targets(self.player.member)
await self.player.send_dm("**Pick a target to shift into**") await self.player.send_dm("**Pick a target to shift into**")
@wolflistener("at_night_end")
async def _at_night_end(self, data=None): async def _at_night_end(self, data=None):
await super()._at_night_end(data)
if self.shift_target is None: if self.shift_target is None:
if self.player.alive: if self.player.alive:
await self.player.send_dm("You will not use your powers tonight...") await self.player.send_dm("You will not use your powers tonight...")
@ -114,16 +115,22 @@ class Shifter(Role):
# Roles have now been swapped # Roles have now been swapped
await self.player.send_dm("Your role has been stolen...\n" await self.player.send_dm(
"You are now a **Shifter**.") "Your role has been stolen...\n" "You are now a **Shifter**."
)
await self.player.send_dm(self.game_start_message) await self.player.send_dm(self.game_start_message)
await target.send_dm(target.role.game_start_message) await target.send_dm(target.role.game_start_message)
else: else:
await self.player.send_dm("**Your shift failed...**") await self.player.send_dm("**Your shift failed...**")
async def choose(self, ctx, data): async def choose(self, ctx, data):
"""Handle night actions""" """Handle night actions"""
await super().choose(ctx, data) await super().choose(ctx, data)
self.shift_target, target = await pick_target(self, 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 ..role import Role
from ..votegroups.wolfvote import WolfVote from ..votegroups.wolfvote import WolfVote
@ -19,17 +20,17 @@ class VanillaWerewolf(Role):
def __init__(self, game): def __init__(self, game):
super().__init__(game) super().__init__(game)
self.action_list = [ # self.action_list = [
(self._at_game_start, 1), # (Action, Priority) # (self._at_game_start, 1), # (Action, Priority)
(self._at_day_start, 0), # (self._at_day_start, 0),
(self._at_voted, 0), # (self._at_voted, 0),
(self._at_kill, 0), # (self._at_kill, 0),
(self._at_hang, 0), # (self._at_hang, 0),
(self._at_day_end, 0), # (self._at_day_end, 0),
(self._at_night_start, 0), # (self._at_night_start, 0),
(self._at_night_end, 0), # (self._at_night_end, 0),
(self._at_visit, 0) # (self._at_visit, 0)
] # ]
async def see_alignment(self, source=None): async def see_alignment(self, source=None):
""" """
@ -52,6 +53,7 @@ class VanillaWerewolf(Role):
""" """
return "Werewolf" return "Werewolf"
@wolflistener("at_game_start")
async def _at_game_start(self, data=None): async def _at_game_start(self, data=None):
if self.channel_id: if self.channel_id:
print("Wolf has channel_id: " + self.channel_id) print("Wolf has channel_id: " + self.channel_id)

@ -1,9 +1,11 @@
import logging import logging
from werewolf.listener import WolfListener, wolflistener
log = logging.getLogger("red.fox_v3.werewolf.votegroup") log = logging.getLogger("red.fox_v3.werewolf.votegroup")
class VoteGroup: class VoteGroup(WolfListener):
""" """
Base VoteGroup class for werewolf game Base VoteGroup class for werewolf game
Handles secret channels and group decisions Handles secret channels and group decisions
@ -13,57 +15,55 @@ class VoteGroup:
channel_id = "" channel_id = ""
def __init__(self, game, channel): def __init__(self, game, channel):
super().__init__(game)
self.game = game self.game = game
self.channel = channel self.channel = channel
self.players = [] self.players = []
self.vote_results = {} self.vote_results = {}
self.properties = {} # Extra data for other options self.properties = {} # Extra data for other options
self.action_list = [ # self.action_list = [
(self._at_game_start, 1), # (Action, Priority) # (self._at_game_start, 1), # (Action, Priority)
(self._at_day_start, 0), # (self._at_day_start, 0),
(self._at_voted, 0), # (self._at_voted, 0),
(self._at_kill, 1), # (self._at_kill, 1),
(self._at_hang, 1), # (self._at_hang, 1),
(self._at_day_end, 0), # (self._at_day_end, 0),
(self._at_night_start, 2), # (self._at_night_start, 2),
(self._at_night_end, 0), # (self._at_night_end, 0),
(self._at_visit, 0), # (self._at_visit, 0),
] # ]
async def on_event(self, event, data): # async def on_event(self, event, data):
""" # """
See Game class for event guide # See Game class for event guide
""" # """
#
await self.action_list[event][0](data) # await self.action_list[event][0](data)
@wolflistener("at_game_start")
async def _at_game_start(self, data=None): async def _at_game_start(self, data=None):
await self.channel.send(" ".join(player.mention for player in self.players)) await self.channel.send(" ".join(player.mention for player in self.players))
async def _at_day_start(self, data=None): @wolflistener("at_kill")
pass
async def _at_voted(self, data=None):
pass
async def _at_kill(self, data=None): async def _at_kill(self, data=None):
if data["player"] in self.players: if data["player"] in self.players:
self.players.remove(data["player"]) self.players.remove(data["player"])
async def _at_hang(self, data=None): # Removed, only if they actually die
if data["player"] in self.players: # @wolflistener("at_hang")
self.players.remove(data["player"]) # async def _at_hang(self, data=None):
# if data["player"] in self.players:
async def _at_day_end(self, data=None): # self.players.remove(data["player"])
pass
@wolflistener("at_night_start")
async def _at_night_start(self, data=None): async def _at_night_start(self, data=None):
if self.channel is None: if self.channel is None:
return return
await self.game.generate_targets(self.channel) await self.game.generate_targets(self.channel)
@wolflistener("at_night_end")
async def _at_night_end(self, data=None): async def _at_night_end(self, data=None):
if self.channel is None: if self.channel is None:
return return
@ -78,9 +78,6 @@ class VoteGroup:
# Do what you voted on # Do what you voted on
pass pass
async def _at_visit(self, data=None):
pass
async def register_players(self, *players): async def register_players(self, *players):
""" """
Extend players by passed list Extend players by passed list

Loading…
Cancel
Save