You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Fox-V3/werewolf/werewolf.py

426 lines
14 KiB

4 years ago
import logging
import discord
from redbot.core import Config, checks, commands
from redbot.core.bot import Red
from redbot.core.commands import Cog
from redbot.core.utils.menus import DEFAULT_CONTROLS, menu
from werewolf.builder import (
GameBuilder,
role_from_alignment,
role_from_category,
role_from_id,
role_from_name,
)
from werewolf.game import Game
4 years ago
log = logging.getLogger("red.fox_v3.werewolf")
class Werewolf(Cog):
"""
Base to host werewolf on a guild
"""
def __init__(self, bot: Red):
super().__init__()
self.bot = bot
self.config = Config.get_conf(
self, identifier=87101114101119111108102, force_registration=True
)
default_global = {}
default_guild = {
7 years ago
"role_id": None,
"category_id": None,
4 years ago
"channel_name": None,
"log_channel_id": None,
7 years ago
}
self.config.register_global(**default_global)
self.config.register_guild(**default_guild)
7 years ago
self.games = {} # Active games stored here, id is per guild
7 years ago
async def red_delete_data_for_user(self, **kwargs):
"""Nothing to delete"""
return
7 years ago
def __unload(self):
4 years ago
log.debug("Unload called")
7 years ago
for game in self.games.values():
del game
@commands.command()
async def buildgame(self, ctx: commands.Context):
"""
Create game codes to run custom games.
Pick the roles or randomized roles you want to include in a game
"""
gb = GameBuilder()
code = await gb.build_game(ctx)
7 years ago
if code != "":
await ctx.send(f"Your game code is **{code}**")
else:
await ctx.send("No code generated")
@checks.guildowner()
7 years ago
@commands.group()
async def wwset(self, ctx: commands.Context):
7 years ago
"""
Base command to adjust settings. Check help for command list.
"""
if ctx.invoked_subcommand is None:
7 years ago
pass
7 years ago
@commands.guild_only()
@wwset.command(name="list")
async def wwset_list(self, ctx: commands.Context):
"""
Lists current guild settings
"""
valid, role, category, channel, log_channel = await self._get_settings(ctx)
# if not valid:
# await ctx.send("Failed to get settings")
# return None
embed = discord.Embed(
title="Current Guild Settings",
description=f"Valid: {valid}",
color=0x008000 if valid else 0xFF0000,
)
embed.add_field(name="Role", value=str(role))
embed.add_field(name="Category", value=str(category))
embed.add_field(name="Channel", value=str(channel))
embed.add_field(name="Log Channel", value=str(log_channel))
await ctx.send(embed=embed)
7 years ago
@commands.guild_only()
@wwset.command(name="role")
async def wwset_role(self, ctx: commands.Context, role: discord.Role = None):
7 years ago
"""
Set the game role
7 years ago
This role should not be manually assigned
"""
if role is None:
await self.config.guild(ctx.guild).role_id.set(None)
await ctx.send("Cleared Game Role")
else:
await self.config.guild(ctx.guild).role_id.set(role.id)
await ctx.send("Game Role has been set to **{}**".format(role.name))
7 years ago
7 years ago
@commands.guild_only()
@wwset.command(name="category")
async def wwset_category(self, ctx: commands.Context, category_id: int = None):
7 years ago
"""
Assign the channel category
"""
if category_id is None:
await self.config.guild(ctx.guild).category_id.set(None)
await ctx.send("Cleared Game Channel Category")
else:
category = discord.utils.get(ctx.guild.categories, id=int(category_id))
if category is None:
await ctx.send("Category not found")
return
await self.config.guild(ctx.guild).category_id.set(category.id)
await ctx.send("Game Channel Category has been set to **{}**".format(category.name))
7 years ago
@commands.guild_only()
@wwset.command(name="channel")
async def wwset_channel(self, ctx: commands.Context, channel: discord.TextChannel = None):
7 years ago
"""
Assign the village channel
"""
if channel is None:
await self.config.guild(ctx.guild).channel_id.set(None)
await ctx.send("Cleared Game Channel")
else:
await self.config.guild(ctx.guild).channel_id.set(channel.id)
await ctx.send("Game Channel has been set to **{}**".format(channel.mention))
7 years ago
@commands.guild_only()
@wwset.command(name="logchannel")
async def wwset_log_channel(self, ctx: commands.Context, channel: discord.TextChannel = None):
7 years ago
"""
Assign the log channel
"""
if channel is None:
await self.config.guild(ctx.guild).log_channel_id.set(None)
await ctx.send("Cleared Game Log Channel")
else:
await self.config.guild(ctx.guild).log_channel_id.set(channel.id)
await ctx.send("Game Log Channel has been set to **{}**".format(channel.mention))
7 years ago
@commands.group()
async def ww(self, ctx: commands.Context):
"""
Base command for this cog. Check help for the commands list.
"""
if ctx.invoked_subcommand is None:
7 years ago
pass
7 years ago
@commands.guild_only()
7 years ago
@ww.command(name="new")
async def ww_new(self, ctx: commands.Context, game_code=None):
"""
7 years ago
Create and join a new game of Werewolf
"""
7 years ago
game = await self._get_game(ctx, game_code)
7 years ago
if not game:
await ctx.send("Failed to start a new game")
else:
await ctx.send("Game is ready to join! Use `[p]ww join`")
@commands.guild_only()
7 years ago
@ww.command(name="join")
async def ww_join(self, ctx: commands.Context):
7 years ago
"""
Joins a game of Werewolf
"""
7 years ago
4 years ago
game: Game = await self._get_game(ctx)
7 years ago
7 years ago
if not game:
await ctx.send("No game to join!\nCreate a new one with `[p]ww new`")
7 years ago
return
7 years ago
await game.join(ctx.author, ctx.channel)
7 years ago
@commands.guild_only()
7 years ago
@ww.command(name="code")
async def ww_code(self, ctx: commands.Context, code):
"""
Adjust game code
"""
game = await self._get_game(ctx)
if not game:
await ctx.send("No game to join!\nCreate a new one with `[p]ww new`")
return
await game.set_code(ctx, code)
@commands.guild_only()
7 years ago
@ww.command(name="quit")
async def ww_quit(self, ctx: commands.Context):
"""
Quit a game of Werewolf
"""
7 years ago
game = await self._get_game(ctx)
7 years ago
7 years ago
await game.quit(ctx.author, ctx.channel)
7 years ago
@commands.guild_only()
7 years ago
@ww.command(name="start")
async def ww_start(self, ctx: commands.Context):
"""
Checks number of players and attempts to start the game
"""
game = await self._get_game(ctx)
if not game:
await ctx.send("No game running, cannot start")
7 years ago
if not await game.setup(ctx):
pass # ToDo something?
7 years ago
@commands.guild_only()
7 years ago
@ww.command(name="stop")
async def ww_stop(self, ctx: commands.Context):
7 years ago
"""
Stops the current game
"""
# if ctx.guild is None:
# # Private message, can't get guild
# await ctx.send("Cannot stop game from PM!")
# return
7 years ago
if ctx.guild.id not in self.games or self.games[ctx.guild.id].game_over:
await ctx.send("No game to stop")
return
7 years ago
7 years ago
game = await self._get_game(ctx)
7 years ago
game.game_over = True
7 years ago
await ctx.send("Game has been stopped")
7 years ago
@commands.guild_only()
7 years ago
@ww.command(name="vote")
async def ww_vote(self, ctx: commands.Context, target_id: int):
7 years ago
"""
Vote for a player by ID
"""
7 years ago
try:
target_id = int(target_id)
except ValueError:
target_id = None
7 years ago
if target_id is None:
7 years ago
await ctx.send("`id` must be an integer")
return
7 years ago
7 years ago
# if ctx.guild is None:
7 years ago
# # DM nonsense, find their game
# # If multiple games, panic
# for game in self.games.values():
# if await game.get_player_by_member(ctx.author):
# break #game = game
# else:
# await ctx.send("You're not part of any werewolf game")
# return
7 years ago
# else:
7 years ago
game = await self._get_game(ctx)
7 years ago
7 years ago
if game is None:
await ctx.send("No game running, cannot vote")
return
7 years ago
# Game handles response now
channel = ctx.channel
7 years ago
if channel == game.village_channel:
await game.vote(ctx.author, target_id, channel)
7 years ago
elif channel in (c["channel"] for c in game.p_channels.values()):
await game.vote(ctx.author, target_id, channel)
7 years ago
else:
await ctx.send("Nothing to vote for in this channel")
7 years ago
7 years ago
@ww.command(name="choose")
async def ww_choose(self, ctx: commands.Context, data):
7 years ago
"""
Arbitrary decision making
Handled by game+role
Can be received by DM
"""
7 years ago
7 years ago
if ctx.guild is not None:
await ctx.send("This action is only available in DM's")
return
# DM nonsense, find their game
# If multiple games, panic
for game in self.games.values():
if await game.get_player_by_member(ctx.author):
break # game = game
7 years ago
else:
await ctx.send("You're not part of any werewolf game")
return
await game.choose(ctx, data)
7 years ago
7 years ago
@ww.group(name="search")
async def ww_search(self, ctx: commands.Context):
7 years ago
"""
Find custom roles by name, alignment, category, or ID
"""
if ctx.invoked_subcommand is None or ctx.invoked_subcommand == self.ww_search:
pass
7 years ago
@ww_search.command(name="name")
async def ww_search_name(self, ctx: commands.Context, *, name):
7 years ago
"""Search for a role by name"""
if name is not None:
from_name = role_from_name(name)
if from_name:
await menu(ctx, from_name, DEFAULT_CONTROLS)
else:
await ctx.send("No roles containing that name were found")
@ww_search.command(name="alignment")
async def ww_search_alignment(self, ctx: commands.Context, alignment: int):
7 years ago
"""Search for a role by alignment"""
if alignment is not None:
from_alignment = role_from_alignment(alignment)
if from_alignment:
await menu(ctx, from_alignment, DEFAULT_CONTROLS)
else:
await ctx.send("No roles with that alignment were found")
@ww_search.command(name="category")
async def ww_search_category(self, ctx: commands.Context, category: int):
7 years ago
"""Search for a role by category"""
if category is not None:
pages = role_from_category(category)
if pages:
await menu(ctx, pages, DEFAULT_CONTROLS)
else:
await ctx.send("No roles in that category were found")
@ww_search.command(name="index")
async def ww_search_index(self, ctx: commands.Context, idx: int):
7 years ago
"""Search for a role by ID"""
if idx is not None:
idx_embed = role_from_id(idx)
if idx_embed is not None:
await ctx.send(embed=idx_embed)
else:
await ctx.send("Role ID not found")
async def _get_game(self, ctx: commands.Context, game_code=None):
guild: discord.Guild = getattr(ctx, "guild", None)
7 years ago
if guild is None:
7 years ago
# Private message, can't get guild
await ctx.send("Cannot start game from DM!")
7 years ago
return None
7 years ago
if guild.id not in self.games or self.games[guild.id].game_over:
7 years ago
await ctx.send("Starting a new game...")
valid, role, category, channel, log_channel = await self._get_settings(ctx)
if not valid:
await ctx.send("Cannot start a new game")
return None
7 years ago
self.games[guild.id] = Game(
self.bot, guild, role, category, channel, log_channel, game_code
)
7 years ago
7 years ago
return self.games[guild.id]
async def _game_start(self, game):
await game.start()
async def _get_settings(self, ctx):
guild = ctx.guild
role = None
category = None
channel = None
log_channel = None
role_id = await self.config.guild(guild).role_id()
category_id = await self.config.guild(guild).category_id()
channel_id = await self.config.guild(guild).channel_id()
log_channel_id = await self.config.guild(guild).log_channel_id()
if role_id is not None:
role = discord.utils.get(guild.roles, id=role_id)
# if role is None:
# # await ctx.send("Game Role is invalid")
# return False, None, None, None, None
if category_id is not None:
category = discord.utils.get(guild.categories, id=category_id)
# if category is None:
# # await ctx.send("Game Category is invalid")
# return False, role, None, None, None
if channel_id is not None:
channel = discord.utils.get(guild.text_channels, id=channel_id)
# if channel is None:
# # await ctx.send("Village Channel is invalid")
# return False, role, category, None, None
if log_channel_id is not None:
log_channel = discord.utils.get(guild.text_channels, id=log_channel_id)
# if log_channel is None:
# # await ctx.send("Log Channel is invalid")
# return False, None, None, None, None
return (
role is not None and category is not None and channel is not None,
role,
category,
channel,
log_channel,
)