More WIP progress

pull/147/head
bobloy 4 years ago
parent 8a42b87bd6
commit 3a6d3df374

@ -19,6 +19,14 @@ log = logging.getLogger("red.fox_v3.werewolf.game")
HALF_DAY_LENGTH = 24 # FixMe: to 120 later for 4 minute days HALF_DAY_LENGTH = 24 # FixMe: to 120 later for 4 minute days
async def anyone_has_role(
member_list: List[discord.Member], role: discord.Role
) -> Union[None, discord.Member]:
return await AsyncIter(member_list).find(
lambda m: AsyncIter(m.roles).find(lambda r: r.id == role.id)
)
class Game: class Game:
""" """
Base class to run a single game of Werewolf Base class to run a single game of Werewolf
@ -129,6 +137,7 @@ class Game:
self.roles = [] self.roles = []
return False return False
# If there's no game role, make the role and delete it later in `self.to_delete`
if self.game_role is None: if self.game_role is None:
try: try:
self.game_role = await ctx.guild.create_role( self.game_role = await ctx.guild.create_role(
@ -144,14 +153,25 @@ class Game:
) )
self.roles = [] self.roles = []
return False return False
try:
for player in self.players: anyone_with_role = await anyone_has_role(self.guild.members, self.game_role)
await player.member.add_roles(*[self.game_role]) if anyone_with_role is not None:
except discord.Forbidden: await ctx.maybe_send_embed(
await ctx.send( f"{anyone_with_role.display_name} has the game role, "
f"Unable to add role **{self.game_role.name}**\nBot is missing `manage_roles` permissions" f"can't continue until no one has the role"
) )
return False return False
try:
for player in self.players:
await player.member.add_roles(*[self.game_role])
except discord.Forbidden:
log.exception(f"Unable to add role **{self.game_role.name}**")
await ctx.send(
f"Unable to add role **{self.game_role.name}**\n"
f"Bot is missing `manage_roles` permissions"
)
return False
await self.assign_roles() await self.assign_roles()
@ -223,9 +243,10 @@ class Game:
self.started = True self.started = True
# Assuming everything worked so far # Assuming everything worked so far
log.debug("Pre at_game_start") log.debug("Pre at_game_start")
await self._at_game_start() # This will queue channels and votegroups to be made await self._at_game_start() # This will add votegroups to self.p_channels
log.debug("Post at_game_start") log.debug("Post at_game_start")
for channel_id in self.p_channels: log.debug(f"Private channels: {self.p_channels}")
for channel_id in self.p_channels.keys():
log.debug("Setup Channel id: " + channel_id) log.debug("Setup Channel id: " + channel_id)
overwrite = { overwrite = {
self.guild.default_role: discord.PermissionOverwrite(read_messages=False), self.guild.default_role: discord.PermissionOverwrite(read_messages=False),
@ -251,6 +272,8 @@ class Game:
self.p_channels[channel_id]["channel"] = channel self.p_channels[channel_id]["channel"] = channel
self.to_delete.add(channel)
if self.p_channels[channel_id]["votegroup"] is not None: if self.p_channels[channel_id]["votegroup"] is not None:
vote_group = self.p_channels[channel_id]["votegroup"](self, channel) vote_group = self.p_channels[channel_id]["votegroup"](self, channel)
@ -259,8 +282,10 @@ class Game:
self.vote_groups[channel_id] = vote_group self.vote_groups[channel_id] = vote_group
log.debug("Pre-cycle") log.debug("Pre-cycle")
await asyncio.sleep(1) await asyncio.sleep(0)
await asyncio.ensure_future(self._cycle()) # Start the loop
asyncio.create_task(self._cycle()) # Start the loop
return True
# ###########START Notify structure############ # ###########START Notify structure############
async def _cycle(self): async def _cycle(self):
@ -553,13 +578,14 @@ class Game:
try: try:
await asyncio.sleep(1) # This will have multiple calls await asyncio.sleep(1) # This will have multiple calls
self.p_channels[channel_id]["players"].append(role.player) self.p_channels[channel_id]["players"].append(role.player)
if votegroup is not None:
self.p_channels[channel_id]["votegroup"] = votegroup
except AttributeError: except AttributeError:
continue continue
else: else:
break break
if votegroup is not None:
self.p_channels[channel_id]["votegroup"] = votegroup
async def join(self, member: discord.Member, channel: discord.TextChannel): async def join(self, member: discord.Member, channel: discord.TextChannel):
""" """
Have a member join a game Have a member join a game
@ -574,14 +600,15 @@ class Game:
self.players.append(Player(member)) self.players.append(Player(member))
if self.game_role is not None: # Add the role during setup, not before
try: # if self.game_role is not None:
await member.add_roles(*[self.game_role]) # try:
except discord.Forbidden: # await member.add_roles(*[self.game_role])
await channel.send( # except discord.Forbidden:
f"Unable to add role **{self.game_role.name}**\n" # await channel.send(
f"Bot is missing `manage_roles` permissions" # f"Unable to add role **{self.game_role.name}**\n"
) # f"Bot is missing `manage_roles` permissions"
# )
await channel.send( await channel.send(
f"{member.display_name} has been added to the game, " f"{member.display_name} has been added to the game, "
@ -908,7 +935,7 @@ class Game:
# Remove game_role access for potential archiving for now # Remove game_role access for potential archiving for now
reason = "(BOT) End of WW game" reason = "(BOT) End of WW game"
for obj in self.to_delete: for obj in self.to_delete:
log.debug(f"End_game: Deleting object {obj}") log.debug(f"End_game: Deleting object {obj.__repr__()}")
await obj.delete(reason=reason) await obj.delete(reason=reason)
try: try:
@ -926,6 +953,17 @@ class Game:
except (discord.HTTPException, discord.NotFound, discord.errors.NotFound): except (discord.HTTPException, discord.NotFound, discord.errors.NotFound):
pass pass
for player in self.players:
try:
await player.member.remove_roles(*[self.game_role])
except discord.Forbidden:
log.exception(f"Unable to add remove **{self.game_role.name}**")
# await ctx.send(
# f"Unable to add role **{self.game_role.name}**\n"
# f"Bot is missing `manage_roles` permissions"
# )
pass
# Optional dynamic channels/categories # Optional dynamic channels/categories
def add_ww_listener(self, func, priority=0, name=None): def add_ww_listener(self, func, priority=0, name=None):

@ -20,6 +20,9 @@ class Player:
self.muted = False self.muted = False
self.protected = False self.protected = False
def __repr__(self):
return f"{self.__class__.__name__}({self.member})"
async def assign_role(self, role): async def assign_role(self, role):
""" """
Give this player a role Give this player a role

@ -73,7 +73,7 @@ class Role(WolfListener):
self.properties = {} # Extra data for other roles (i.e. arsonist) self.properties = {} # Extra data for other roles (i.e. arsonist)
def __repr__(self): def __repr__(self):
return self.__class__.__name__ return f"{self.__class__.__name__}({self.player.__repr__()})"
async def assign_player(self, player): async def assign_player(self, player):
""" """
@ -84,6 +84,8 @@ class Role(WolfListener):
player.role = self player.role = self
self.player = player self.player = player
log.debug(f"Assigned {self} to {player}")
async def get_alignment(self, source=None): async def get_alignment(self, source=None):
""" """
Interaction for powerful access of alignment Interaction for powerful access of alignment

@ -15,6 +15,7 @@ log = logging.getLogger("red.fox_v3.werewolf.role.seer")
class Seer(Role): class Seer(Role):
rand_choice = True rand_choice = True
town_balance = 4
category = [ category = [
CATEGORY_TOWN_RANDOM, CATEGORY_TOWN_RANDOM,
CATEGORY_TOWN_INVESTIGATIVE, CATEGORY_TOWN_INVESTIGATIVE,

@ -22,21 +22,6 @@ class VanillaWerewolf(Role):
"Vote to kill players at night with `[p]ww vote <ID>`" "Vote to kill players at night with `[p]ww vote <ID>`"
) )
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)
# ]
async def see_alignment(self, source=None): async def see_alignment(self, source=None):
""" """
Interaction for investigative roles attempting Interaction for investigative roles attempting

@ -7,12 +7,9 @@ log = logging.getLogger("red.fox_v3.werewolf.role.villager")
class Villager(Role): class Villager(Role):
# Determines if it can be picked as a random role (False for unusually disruptive roles) # Determines if it can be picked as a random role (False for unusually disruptive roles)
rand_choice = True rand_choice = True
town_balance = 1 town_balance = 1
category = [CATEGORY_TOWN_RANDOM] # List of enrolled categories (listed above) category = [CATEGORY_TOWN_RANDOM] # List of enrolled categories (listed above)
alignment = ALIGNMENT_TOWN # 1: Town, 2: Werewolf, 3: Neutral alignment = ALIGNMENT_TOWN # 1: Town, 2: Werewolf, 3: Neutral
channel_id = "" # Empty for no private channel channel_id = "" # Empty for no private channel
@ -23,9 +20,6 @@ class Villager(Role):
"Lynch players during the day with `[p]ww vote <ID>`" "Lynch players during the day with `[p]ww vote <ID>`"
) )
def __init__(self, game):
super().__init__(game)
async def see_alignment(self, source=None): async def see_alignment(self, source=None):
""" """
Interaction for investigative roles attempting Interaction for investigative roles attempting

@ -75,7 +75,6 @@ class VoteGroup(WolfListener):
if not self.players: if not self.players:
# TODO: Confirm deletion # TODO: Confirm deletion
self.game.to_delete.add(self)
pass pass
async def vote(self, target, author, target_id): async def vote(self, target, author, target_id):

@ -1,9 +1,11 @@
import logging import logging
from typing import List, Union
import discord import discord
from redbot.core import Config, checks, commands from redbot.core import Config, checks, commands
from redbot.core.bot import Red from redbot.core.bot import Red
from redbot.core.commands import Cog from redbot.core.commands import Cog
from redbot.core.utils import AsyncIter
from redbot.core.utils.menus import DEFAULT_CONTROLS, menu from redbot.core.utils.menus import DEFAULT_CONTROLS, menu
from werewolf.builder import ( from werewolf.builder import (
@ -18,6 +20,14 @@ from werewolf.game import Game
log = logging.getLogger("red.fox_v3.werewolf") log = logging.getLogger("red.fox_v3.werewolf")
async def anyone_has_role(
member_list: List[discord.Member], role: discord.Role
) -> Union[None, discord.Member]:
return await AsyncIter(member_list).find(
lambda m: AsyncIter(m.roles).find(lambda r: r.id == role.id)
)
class Werewolf(Cog): class Werewolf(Cog):
""" """
Base to host werewolf on a guild Base to host werewolf on a guild
@ -189,12 +199,15 @@ class Werewolf(Cog):
return return
await game.join(ctx.author, ctx.channel) await game.join(ctx.author, ctx.channel)
await ctx.tick()
@commands.guild_only() @commands.guild_only()
@ww.command(name="code") @ww.command(name="code")
async def ww_code(self, ctx: commands.Context, code): async def ww_code(self, ctx: commands.Context, code):
""" """
Adjust game code Adjusts the game code.
See `[p]buildgame` to generate a new code
""" """
game = await self._get_game(ctx) game = await self._get_game(ctx)
@ -204,6 +217,7 @@ class Werewolf(Cog):
return return
await game.set_code(ctx, code) await game.set_code(ctx, code)
await ctx.tick()
@commands.guild_only() @commands.guild_only()
@ww.command(name="quit") @ww.command(name="quit")
@ -215,6 +229,7 @@ class Werewolf(Cog):
game = await self._get_game(ctx) game = await self._get_game(ctx)
await game.quit(ctx.author, ctx.channel) await game.quit(ctx.author, ctx.channel)
await ctx.tick()
@commands.guild_only() @commands.guild_only()
@ww.command(name="start") @ww.command(name="start")
@ -229,6 +244,8 @@ class Werewolf(Cog):
if not await game.setup(ctx): if not await game.setup(ctx):
pass # ToDo something? pass # ToDo something?
await ctx.tick()
@commands.guild_only() @commands.guild_only()
@ww.command(name="stop") @ww.command(name="stop")
async def ww_stop(self, ctx: commands.Context): async def ww_stop(self, ctx: commands.Context):
@ -245,6 +262,7 @@ class Werewolf(Cog):
game = await self._get_game(ctx) game = await self._get_game(ctx)
game.game_over = True game.game_over = True
await game.current_action.cancel()
await ctx.send("Game has been stopped") await ctx.send("Game has been stopped")
@commands.guild_only() @commands.guild_only()
@ -358,7 +376,7 @@ class Werewolf(Cog):
else: else:
await ctx.send("Role ID not found") await ctx.send("Role ID not found")
async def _get_game(self, ctx: commands.Context, game_code=None): async def _get_game(self, ctx: commands.Context, game_code=None) -> Union[Game, None]:
guild: discord.Guild = getattr(ctx, "guild", None) guild: discord.Guild = getattr(ctx, "guild", None)
if guild is None: if guild is None:

Loading…
Cancel
Save