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