Merge branch 'werewolf_develop'
This commit is contained in:
commit
d62e01b785
@ -1,21 +1,94 @@
|
||||
import bisect
|
||||
from collections import defaultdict
|
||||
from random import choice
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
# Import all roles here
|
||||
from werewolf.roles.seer import Seer
|
||||
from werewolf.roles.vanillawerewolf import VanillaWerewolf
|
||||
from werewolf.roles.villager import Villager
|
||||
from redbot.core.utils.menus import menu, prev_page, next_page, close_menu
|
||||
|
||||
# All roles in this list for iterating
|
||||
role_list = [Villager, VanillaWerewolf]
|
||||
|
||||
ROLE_LIST = sorted([Villager, Seer, VanillaWerewolf], key=lambda x: x.alignment)
|
||||
|
||||
ALIGNMENT_COLORS = [0x008000, 0xff0000, 0xc0c0c0]
|
||||
TOWN_ROLES = [(idx, role) for idx, role in enumerate(ROLE_LIST) if role.alignment == 1]
|
||||
WW_ROLES = [(idx, role) for idx, role in enumerate(ROLE_LIST) if role.alignment == 2]
|
||||
OTHER_ROLES = [(idx, role) for idx, role in enumerate(ROLE_LIST) if role.alignment not in [0, 1]]
|
||||
|
||||
ROLE_PAGES = []
|
||||
PAGE_GROUPS = [0]
|
||||
|
||||
ROLE_CATEGORIES = {
|
||||
1: "Random", 2: "Investigative", 3: "Protective", 4: "Government",
|
||||
5: "Killing", 6: "Power (Special night action)",
|
||||
11: "Random", 12: "Deception", 15: "Killing", 16: "Support",
|
||||
21: "Benign", 22: "Evil", 23: "Killing"}
|
||||
|
||||
CATEGORY_COUNT = []
|
||||
|
||||
|
||||
def role_embed(idx, role, color):
|
||||
embed = discord.Embed(title="**{}** - {}".format(idx, str(role.__name__)), description=role.game_start_message,
|
||||
color=color)
|
||||
embed.add_field(name='Alignment', value=['Town', 'Werewolf', 'Neutral'][role.alignment - 1], inline=True)
|
||||
embed.add_field(name='Multiples Allowed', value=str(not role.unique), inline=True)
|
||||
embed.add_field(name='Role Type', value=", ".join(ROLE_CATEGORIES[x] for x in role.category), inline=True)
|
||||
embed.add_field(name='Random Option', value=str(role.rand_choice), inline=True)
|
||||
|
||||
return embed
|
||||
|
||||
|
||||
def setup():
|
||||
# Roles
|
||||
last_alignment = ROLE_LIST[0].alignment
|
||||
for idx, role in enumerate(ROLE_LIST):
|
||||
if role.alignment != last_alignment and len(ROLE_PAGES) - 1 not in PAGE_GROUPS:
|
||||
PAGE_GROUPS.append(len(ROLE_PAGES) - 1)
|
||||
last_alignment = role.alignment
|
||||
|
||||
ROLE_PAGES.append(role_embed(idx, role, ALIGNMENT_COLORS[role.alignment - 1]))
|
||||
|
||||
# Random Town Roles
|
||||
if len(ROLE_PAGES) - 1 not in PAGE_GROUPS:
|
||||
PAGE_GROUPS.append(len(ROLE_PAGES) - 1)
|
||||
for k, v in ROLE_CATEGORIES.items():
|
||||
if 0 < k <= 6:
|
||||
ROLE_PAGES.append(discord.Embed(title="RANDOM:Town Role", description="Town {}".format(v), color=0x008000))
|
||||
CATEGORY_COUNT.append(k)
|
||||
|
||||
# Random WW Roles
|
||||
if len(ROLE_PAGES) - 1 not in PAGE_GROUPS:
|
||||
PAGE_GROUPS.append(len(ROLE_PAGES) - 1)
|
||||
for k, v in ROLE_CATEGORIES.items():
|
||||
if 10 < k <= 16:
|
||||
ROLE_PAGES.append(
|
||||
discord.Embed(title="RANDOM:Werewolf Role", description="Werewolf {}".format(v), color=0xff0000))
|
||||
CATEGORY_COUNT.append(k)
|
||||
# Random Neutral Roles
|
||||
if len(ROLE_PAGES) - 1 not in PAGE_GROUPS:
|
||||
PAGE_GROUPS.append(len(ROLE_PAGES) - 1)
|
||||
for k, v in ROLE_CATEGORIES.items():
|
||||
if 20 < k <= 26:
|
||||
ROLE_PAGES.append(
|
||||
discord.Embed(title="RANDOM:Neutral Role", description="Neutral {}".format(v), color=0xc0c0c0))
|
||||
CATEGORY_COUNT.append(k)
|
||||
|
||||
|
||||
"""
|
||||
Example code:
|
||||
0 = Villager
|
||||
1 = VanillaWerewolf
|
||||
E1 = Random Town
|
||||
R1 = Random Werewolf
|
||||
J1 = Benign Neutral
|
||||
T1 - T6 = Random Town (1: Random, 2: Investigative, 3: Protective, 4: Government,
|
||||
5: Killing, 6: Power (Special night action))
|
||||
W1, W2, W5, W6 = Random Werewolf
|
||||
N1 = Benign Neutral
|
||||
|
||||
0001-1112E11R112P2
|
||||
0001-1112T11W112N2
|
||||
0,0,0,1,11,12,E1,R1,R1,R1,R2,P2
|
||||
|
||||
pre-letter = exact role position
|
||||
@ -23,29 +96,225 @@ double digit position preempted by `-`
|
||||
"""
|
||||
|
||||
|
||||
async def parse_code(code):
|
||||
async def parse_code(code, game):
|
||||
"""Do the magic described above"""
|
||||
out = []
|
||||
decode = code.copy() # for now, pass exact names
|
||||
for role_id in decode:
|
||||
print(role_id)
|
||||
if role_id == "Villager":
|
||||
role = Villager
|
||||
elif role_id == "VanillaWerewolf":
|
||||
role = VanillaWerewolf
|
||||
elif role_id == "Seer":
|
||||
role = Seer
|
||||
else: # Fail to parse
|
||||
return None
|
||||
out.append(role)
|
||||
decode = []
|
||||
|
||||
return out
|
||||
digits = 1
|
||||
built = ""
|
||||
category = ""
|
||||
for c in code:
|
||||
if built == "T" or built == "W" or built == "N":
|
||||
# Random Towns
|
||||
category = built
|
||||
built = ""
|
||||
digits = 1
|
||||
elif built == "-":
|
||||
digits += 1
|
||||
|
||||
if len(built) < digits:
|
||||
built += c
|
||||
continue
|
||||
|
||||
try:
|
||||
idx = int(built)
|
||||
except ValueError:
|
||||
raise ValueError("Invalid code")
|
||||
|
||||
if category == "": # no randomness yet
|
||||
decode.append(ROLE_LIST[idx](game))
|
||||
else:
|
||||
options = []
|
||||
if category == "T":
|
||||
options = [role for role in ROLE_LIST if idx in role.category]
|
||||
elif category == "W":
|
||||
options = [role for role in ROLE_LIST if 10 + idx in role.category]
|
||||
elif category == "N":
|
||||
options = [role for role in ROLE_LIST if 20 + idx in role.category]
|
||||
pass
|
||||
|
||||
if not options:
|
||||
raise IndexError("No Match Found")
|
||||
|
||||
decode.append(choice(options)(game))
|
||||
|
||||
return decode
|
||||
|
||||
|
||||
async def build_game(channel: discord.TextChannel):
|
||||
await channel.send("Not currently available")
|
||||
async def encode(roles, rand_roles):
|
||||
"""Convert role list to code"""
|
||||
out_code = ""
|
||||
|
||||
code = 12345678
|
||||
digit_sort = sorted(role for role in roles if role < 10)
|
||||
for role in digit_sort:
|
||||
out_code += str(role)
|
||||
|
||||
await channel.send("Your game code is **`{}`**".format(code))
|
||||
# Make this embeds
|
||||
digit_sort = sorted(role for role in roles if 10 <= role < 100)
|
||||
if digit_sort:
|
||||
out_code += "-"
|
||||
for role in digit_sort:
|
||||
out_code += str(role)
|
||||
# That covers up to 99 roles, add another set here if we breach 100
|
||||
|
||||
if rand_roles:
|
||||
# town sort
|
||||
digit_sort = sorted(role for role in rand_roles if role <= 6)
|
||||
if digit_sort:
|
||||
out_code += "T"
|
||||
for role in digit_sort:
|
||||
out_code += str(role)
|
||||
|
||||
# werewolf sort
|
||||
digit_sort = sorted(role for role in rand_roles if 10 < role <= 20)
|
||||
if digit_sort:
|
||||
out_code += "W"
|
||||
for role in digit_sort:
|
||||
out_code += str(role)
|
||||
|
||||
# neutral sort
|
||||
digit_sort = sorted(role for role in rand_roles if 20 < role <= 30)
|
||||
if digit_sort:
|
||||
out_code += "N"
|
||||
for role in digit_sort:
|
||||
out_code += str(role)
|
||||
|
||||
return out_code
|
||||
|
||||
|
||||
async def next_group(ctx: commands.Context, pages: list,
|
||||
controls: dict, message: discord.Message, page: int,
|
||||
timeout: float, emoji: str):
|
||||
perms = message.channel.permissions_for(ctx.guild.me)
|
||||
if perms.manage_messages: # Can manage messages, so remove react
|
||||
try:
|
||||
await message.remove_reaction(emoji, ctx.author)
|
||||
except discord.NotFound:
|
||||
pass
|
||||
page = bisect.bisect_right(PAGE_GROUPS, page)
|
||||
|
||||
if page == len(PAGE_GROUPS):
|
||||
page = PAGE_GROUPS[0]
|
||||
else:
|
||||
page = PAGE_GROUPS[page]
|
||||
|
||||
return await menu(ctx, pages, controls, message=message,
|
||||
page=page, timeout=timeout)
|
||||
|
||||
|
||||
async def prev_group(ctx: commands.Context, pages: list,
|
||||
controls: dict, message: discord.Message, page: int,
|
||||
timeout: float, emoji: str):
|
||||
perms = message.channel.permissions_for(ctx.guild.me)
|
||||
if perms.manage_messages: # Can manage messages, so remove react
|
||||
try:
|
||||
await message.remove_reaction(emoji, ctx.author)
|
||||
except discord.NotFound:
|
||||
pass
|
||||
page = PAGE_GROUPS[bisect.bisect_left(PAGE_GROUPS, page) - 1]
|
||||
|
||||
return await menu(ctx, pages, controls, message=message,
|
||||
page=page, timeout=timeout)
|
||||
|
||||
|
||||
def role_from_alignment(alignment):
|
||||
return [role_embed(idx, role, ALIGNMENT_COLORS[role.alignment - 1])
|
||||
for idx, role in enumerate(ROLE_LIST) if alignment == role.alignment]
|
||||
|
||||
|
||||
def role_from_category(category):
|
||||
return [role_embed(idx, role, ALIGNMENT_COLORS[role.alignment - 1])
|
||||
for idx, role in enumerate(ROLE_LIST) if category in role.category]
|
||||
|
||||
|
||||
def role_from_id(idx):
|
||||
try:
|
||||
role = ROLE_LIST[idx]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
return role_embed(idx, role, ALIGNMENT_COLORS[role.alignment - 1])
|
||||
|
||||
|
||||
def role_from_name(name: str):
|
||||
return [role_embed(idx, role, ALIGNMENT_COLORS[role.alignment - 1])
|
||||
for idx, role in enumerate(ROLE_LIST) if name in role.__name__]
|
||||
|
||||
|
||||
def say_role_list(code_list, rand_roles):
|
||||
roles = [ROLE_LIST[idx] for idx in code_list]
|
||||
embed = discord.Embed(title="Currently selected roles")
|
||||
role_dict = defaultdict(int)
|
||||
for role in roles:
|
||||
role_dict[str(role.__name__)] += 1
|
||||
|
||||
for role in rand_roles:
|
||||
if 0 < role <= 6:
|
||||
role_dict["Town {}".format(ROLE_CATEGORIES[role])] += 1
|
||||
if 10 < role <= 16:
|
||||
role_dict["Werewolf {}".format(ROLE_CATEGORIES[role])] += 1
|
||||
if 20 < role <= 26:
|
||||
role_dict["Neutral {}".format(ROLE_CATEGORIES[role])] += 1
|
||||
|
||||
for k, v in role_dict.items():
|
||||
embed.add_field(name=k, value="Count: {}".format(v), inline=True)
|
||||
|
||||
return embed
|
||||
|
||||
|
||||
class GameBuilder:
|
||||
|
||||
def __init__(self):
|
||||
self.code = []
|
||||
self.rand_roles = []
|
||||
setup()
|
||||
|
||||
async def build_game(self, ctx: commands.Context):
|
||||
new_controls = {
|
||||
'⏪': prev_group,
|
||||
"⬅": prev_page,
|
||||
'☑': self.select_page,
|
||||
"➡": next_page,
|
||||
'⏩': next_group,
|
||||
'📇': self.list_roles,
|
||||
"❌": close_menu
|
||||
}
|
||||
|
||||
await ctx.send("Browse through roles and add the ones you want using the check mark")
|
||||
|
||||
await menu(ctx, ROLE_PAGES, new_controls, timeout=60)
|
||||
|
||||
out = await encode(self.code, self.rand_roles)
|
||||
return out
|
||||
|
||||
async def list_roles(self, ctx: commands.Context, pages: list,
|
||||
controls: dict, message: discord.Message, page: int,
|
||||
timeout: float, emoji: str):
|
||||
perms = message.channel.permissions_for(ctx.guild.me)
|
||||
if perms.manage_messages: # Can manage messages, so remove react
|
||||
try:
|
||||
await message.remove_reaction(emoji, ctx.author)
|
||||
except discord.NotFound:
|
||||
pass
|
||||
|
||||
await ctx.send(embed=say_role_list(self.code, self.rand_roles))
|
||||
|
||||
return await menu(ctx, pages, controls, message=message,
|
||||
page=page, timeout=timeout)
|
||||
|
||||
async def select_page(self, ctx: commands.Context, pages: list,
|
||||
controls: dict, message: discord.Message, page: int,
|
||||
timeout: float, emoji: str):
|
||||
perms = message.channel.permissions_for(ctx.guild.me)
|
||||
if perms.manage_messages: # Can manage messages, so remove react
|
||||
try:
|
||||
await message.remove_reaction(emoji, ctx.author)
|
||||
except discord.NotFound:
|
||||
pass
|
||||
|
||||
if page >= len(ROLE_LIST):
|
||||
self.rand_roles.append(CATEGORY_COUNT[page-len(ROLE_LIST)])
|
||||
else:
|
||||
self.code.append(page)
|
||||
|
||||
return await menu(ctx, pages, controls, message=message,
|
||||
page=page, timeout=timeout)
|
||||
|
299
werewolf/game.py
299
werewolf/game.py
@ -2,6 +2,7 @@ import asyncio
|
||||
import random
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from werewolf.builder import parse_code
|
||||
from werewolf.player import Player
|
||||
@ -25,19 +26,14 @@ class Game:
|
||||
|
||||
day_vote_count = 3
|
||||
|
||||
# def __new__(cls, guild, game_code):
|
||||
# game_code = ["VanillaWerewolf", "Villager", "Villager"]
|
||||
#
|
||||
# return super().__new__(cls, guild, game_code)
|
||||
|
||||
def __init__(self, guild, role, game_code):
|
||||
def __init__(self, guild: discord.Guild, role: discord.Role = None,
|
||||
category: discord.CategoryChannel = None, village: discord.TextChannel = None,
|
||||
log_channel: discord.TextChannel = None, game_code=None):
|
||||
self.guild = guild
|
||||
self.game_code = ["VanillaWerewolf"]
|
||||
self.game_role = role
|
||||
self.game_code = game_code
|
||||
|
||||
self.roles = []
|
||||
|
||||
self.players = []
|
||||
self.roles = [] # List[Role]
|
||||
self.players = [] # List[Player]
|
||||
|
||||
self.day_vote = {} # author: target
|
||||
self.vote_totals = {} # id: total_votes
|
||||
@ -49,9 +45,15 @@ class Game:
|
||||
|
||||
self.day_time = False
|
||||
self.day_count = 0
|
||||
self.ongoing_vote = False
|
||||
|
||||
self.channel_category = None
|
||||
self.village_channel = None
|
||||
self.game_role = role # discord.Role
|
||||
self.channel_category = category # discord.CategoryChannel
|
||||
self.village_channel = village # discord.TextChannel
|
||||
self.log_channel = log_channel
|
||||
|
||||
self.to_delete = set()
|
||||
self.save_perms = {}
|
||||
|
||||
self.p_channels = {} # uses default_secret_channel
|
||||
self.vote_groups = {} # ID : VoteGroup()
|
||||
@ -60,22 +62,22 @@ class Game:
|
||||
|
||||
self.loop = asyncio.get_event_loop()
|
||||
|
||||
def __del__(self):
|
||||
"""
|
||||
Cleanup channels as necessary
|
||||
:return:
|
||||
"""
|
||||
# def __del__(self):
|
||||
# """
|
||||
# Cleanup channels as necessary
|
||||
# :return:
|
||||
# """
|
||||
#
|
||||
# print("Delete is called")
|
||||
#
|
||||
# self.game_over = True
|
||||
# if self.village_channel:
|
||||
# asyncio.ensure_future(self.village_channel.delete("Werewolf game-over"))
|
||||
#
|
||||
# for c_data in self.p_channels.values():
|
||||
# asyncio.ensure_future(c_data["channel"].delete("Werewolf game-over"))
|
||||
|
||||
print("Delete is called")
|
||||
|
||||
self.game_over = True
|
||||
if self.village_channel:
|
||||
asyncio.ensure_future(self.village_channel.delete("Werewolf game-over"))
|
||||
|
||||
for c_data in self.p_channels.values():
|
||||
asyncio.ensure_future(c_data["channel"].delete("Werewolf game-over"))
|
||||
|
||||
async def setup(self, ctx):
|
||||
async def setup(self, ctx: commands.Context):
|
||||
"""
|
||||
Runs the initial setup
|
||||
|
||||
@ -86,17 +88,34 @@ class Game:
|
||||
4. Start game
|
||||
"""
|
||||
if self.game_code:
|
||||
await self.get_roles()
|
||||
await self.get_roles(ctx)
|
||||
|
||||
if len(self.players) != len(self.roles):
|
||||
await ctx.send("Player count does not match role count, cannot start")
|
||||
await ctx.send("Player count does not match role count, cannot start\n"
|
||||
"Currently **{} / {}**\n"
|
||||
"Use `{}ww code` to pick a new game"
|
||||
"".format(len(self.players), len(self.roles), ctx.prefix))
|
||||
self.roles = []
|
||||
return False
|
||||
|
||||
if self.game_role is None:
|
||||
await ctx.send("Game role not configured, cannot start")
|
||||
self.roles = []
|
||||
return False
|
||||
try:
|
||||
self.game_role = await ctx.guild.create_role(name="WW Players",
|
||||
hoist=True,
|
||||
mentionable=True,
|
||||
reason="(BOT) Werewolf game role")
|
||||
self.to_delete.add(self.game_role)
|
||||
except (discord.Forbidden, discord.HTTPException):
|
||||
await ctx.send("Game role not configured and unable to generate one, cannot start")
|
||||
self.roles = []
|
||||
return False
|
||||
try:
|
||||
for player in self.players:
|
||||
await player.member.add_roles(*[self.game_role])
|
||||
except discord.Forbidden:
|
||||
await ctx.send(
|
||||
"Unable to add role **{}**\nBot is missing `manage_roles` permissions".format(self.game_role.name))
|
||||
return False
|
||||
|
||||
await self.assign_roles()
|
||||
|
||||
@ -104,21 +123,55 @@ class Game:
|
||||
overwrite = {
|
||||
self.guild.default_role: discord.PermissionOverwrite(read_messages=True, send_messages=False,
|
||||
add_reactions=False),
|
||||
self.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True, add_reactions=True),
|
||||
self.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True, add_reactions=True,
|
||||
manage_messages=True, manage_channels=True,
|
||||
manage_roles=True),
|
||||
self.game_role: discord.PermissionOverwrite(read_messages=True, send_messages=True)
|
||||
}
|
||||
if self.channel_category is None:
|
||||
self.channel_category = await self.guild.create_category("Werewolf Game",
|
||||
overwrites=overwrite,
|
||||
reason="(BOT) New game of werewolf")
|
||||
else: # No need to modify categories
|
||||
pass
|
||||
# await self.channel_category.edit(name="🔴 Werewolf Game (ACTIVE)", reason="(BOT) New game of werewolf")
|
||||
# for target, ow in overwrite.items():
|
||||
# await self.channel_category.set_permissions(target=target,
|
||||
# overwrite=ow,
|
||||
# reason="(BOT) New game of werewolf")
|
||||
if self.village_channel is None:
|
||||
try:
|
||||
self.village_channel = await self.guild.create_text_channel("🔵Werewolf",
|
||||
overwrites=overwrite,
|
||||
reason="(BOT) New game of werewolf",
|
||||
category=self.channel_category)
|
||||
except discord.Forbidden:
|
||||
await ctx.send("Unable to create Game Channel and none was provided\n"
|
||||
"Grant Bot appropriate permissions or assign a game_channel")
|
||||
return False
|
||||
else:
|
||||
self.save_perms[self.village_channel] = self.village_channel.overwrites
|
||||
try:
|
||||
await self.village_channel.edit(name="🔵Werewolf",
|
||||
category=self.channel_category,
|
||||
reason="(BOT) New game of werewolf")
|
||||
except discord.Forbidden as e:
|
||||
print("Unable to rename Game Channel")
|
||||
print(e)
|
||||
await ctx.send("Unable to rename Game Channel, ignoring")
|
||||
|
||||
self.channel_category = await self.guild.create_category("ww-game", overwrites=overwrite, reason="New game of "
|
||||
"werewolf")
|
||||
|
||||
# for player in self.players:
|
||||
# overwrite[player.member] = discord.PermissionOverwrite(read_messages=True)
|
||||
|
||||
self.village_channel = await self.guild.create_text_channel("Village Square",
|
||||
overwrites=overwrite,
|
||||
reason="New game of werewolf",
|
||||
category=self.channel_category)
|
||||
|
||||
try:
|
||||
for target, ow in overwrite.items():
|
||||
curr = self.village_channel.overwrites_for(target)
|
||||
curr.update(**{perm: value for perm, value in ow})
|
||||
await self.village_channel.set_permissions(target=target,
|
||||
overwrite=curr,
|
||||
reason="(BOT) New game of werewolf")
|
||||
except discord.Forbidden:
|
||||
await ctx.send("Unable to edit Game Channel permissions\n"
|
||||
"Grant Bot appropriate permissions to manage permissions")
|
||||
return
|
||||
self.started = True
|
||||
# Assuming everything worked so far
|
||||
print("Pre at_game_start")
|
||||
await self._at_game_start() # This will queue channels and votegroups to be made
|
||||
@ -127,7 +180,9 @@ class Game:
|
||||
print("Channel id: " + channel_id)
|
||||
overwrite = {
|
||||
self.guild.default_role: discord.PermissionOverwrite(read_messages=False),
|
||||
self.guild.me: discord.PermissionOverwrite(read_messages=True)
|
||||
self.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True, add_reactions=True,
|
||||
manage_messages=True, manage_channels=True,
|
||||
manage_roles=True)
|
||||
}
|
||||
|
||||
for player in self.p_channels[channel_id]["players"]:
|
||||
@ -135,7 +190,7 @@ class Game:
|
||||
|
||||
channel = await self.guild.create_text_channel(channel_id,
|
||||
overwrites=overwrite,
|
||||
reason="Ww game secret channel",
|
||||
reason="(BOT) WW game secret channel",
|
||||
category=self.channel_category)
|
||||
|
||||
self.p_channels[channel_id]["channel"] = channel
|
||||
@ -207,13 +262,15 @@ class Game:
|
||||
return
|
||||
self.can_vote = True
|
||||
|
||||
await asyncio.sleep(12) # 4 minute days FixMe to 120 later
|
||||
await asyncio.sleep(24) # 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(12) # 4 minute days FixMe to 120 later
|
||||
await asyncio.sleep(24) # 4 minute days FixMe to 120 later
|
||||
|
||||
# Need a loop here to wait for trial to end (can_vote?)
|
||||
while self.ongoing_vote:
|
||||
asyncio.sleep(5)
|
||||
|
||||
if check():
|
||||
return
|
||||
@ -226,16 +283,17 @@ class Game:
|
||||
data = {"player": target}
|
||||
await self._notify(2, data)
|
||||
|
||||
self.ongoing_vote = True
|
||||
|
||||
self.used_votes += 1
|
||||
|
||||
self.can_vote = False
|
||||
await self.speech_perms(self.village_channel, target.member)
|
||||
await self.speech_perms(self.village_channel, target.member) # Only target can talk
|
||||
await self.village_channel.send(
|
||||
"**{} will be put to trial and has 30 seconds to defend themselves**".format(target.mention))
|
||||
|
||||
await asyncio.sleep(30)
|
||||
|
||||
await self.speech_perms(self.village_channel, target.member, undo=True)
|
||||
await self.speech_perms(self.village_channel, target.member, undo=True) # No one can talk
|
||||
|
||||
message = await self.village_channel.send(
|
||||
"Everyone will now vote whether to lynch {}\n"
|
||||
@ -243,42 +301,46 @@ class Game:
|
||||
"*Majority rules, no-lynch on ties, "
|
||||
"vote both or neither to abstain, 15 seconds to vote*".format(target.mention))
|
||||
|
||||
await self.village_channel.add_reaction("👍")
|
||||
await self.village_channel.add_reaction("👎")
|
||||
await message.add_reaction("👍")
|
||||
await message.add_reaction("👎")
|
||||
|
||||
await asyncio.sleep(15)
|
||||
|
||||
reaction_list = message.reactions
|
||||
|
||||
up_votes = sum(p.emoji == "👍" and not p.me for p in reaction_list)
|
||||
down_votes = sum(p.emoji == "👎" and not p.me for p in reaction_list)
|
||||
up_votes = sum(p for p in reaction_list if p.emoji == "👍" and not p.me)
|
||||
down_votes = sum(p for p in reaction_list if p.emoji == "👎" and not p.me)
|
||||
|
||||
if len(down_votes) > len(up_votes):
|
||||
if down_votes > up_votes:
|
||||
embed = discord.Embed(title="Vote Results", color=0xff0000)
|
||||
else:
|
||||
embed = discord.Embed(title="Vote Results", color=0x80ff80)
|
||||
|
||||
embed.add_field(name="👎", value="**{}**".format(len(up_votes)), inline=True)
|
||||
embed.add_field(name="👍", value="**{}**".format(len(down_votes)), inline=True)
|
||||
embed.add_field(name="👎", value="**{}**".format(up_votes), inline=True)
|
||||
embed.add_field(name="👍", value="**{}**".format(down_votes), inline=True)
|
||||
|
||||
await self.village_channel.send(embed=embed)
|
||||
|
||||
if len(down_votes) > len(up_votes):
|
||||
if down_votes > up_votes:
|
||||
await self.village_channel.send("**Voted to lynch {}!**".format(target.mention))
|
||||
await self.lynch(target)
|
||||
self.can_vote = False
|
||||
else:
|
||||
await self.village_channel.send("**{} has been spared!**".format(target.mention))
|
||||
|
||||
if self.used_votes >= self.day_vote_count:
|
||||
await self.village_channel.send("**All votes have been used! Day is now over!**")
|
||||
self.can_vote = False
|
||||
else:
|
||||
await self.village_channel.send(
|
||||
"**{}**/**{}** of today's votes have been used!\n"
|
||||
"Nominate carefully..".format(self.used_votes, self.day_vote_count))
|
||||
self.can_vote = True # Only re-enable voting if more votes remain
|
||||
|
||||
self.ongoing_vote = False
|
||||
|
||||
if not self.can_vote:
|
||||
await self._at_day_end()
|
||||
else:
|
||||
await self.normal_perms(self.village_channel) # No point if about to be night
|
||||
|
||||
async def _at_kill(self, target): # ID 3
|
||||
if self.game_over:
|
||||
@ -329,7 +391,7 @@ class Game:
|
||||
return
|
||||
await self._notify(7)
|
||||
|
||||
await asyncio.sleep(15)
|
||||
await asyncio.sleep(10)
|
||||
await self._at_day_start()
|
||||
|
||||
async def _at_visit(self, target, source): # ID 8
|
||||
@ -355,16 +417,22 @@ class Game:
|
||||
|
||||
############END Notify structure############
|
||||
|
||||
async def generate_targets(self, channel):
|
||||
async def generate_targets(self, channel, with_roles=False):
|
||||
embed = discord.Embed(title="Remaining Players")
|
||||
for i in range(len(self.players)):
|
||||
player = self.players[i]
|
||||
if player.alive:
|
||||
status = ""
|
||||
else:
|
||||
status = "*Dead*"
|
||||
embed.add_field(name="ID# **{}**".format(i),
|
||||
value="{} {}".format(status, player.member.display_name), inline=True)
|
||||
status = "*[Dead]*-"
|
||||
if with_roles or not player.alive:
|
||||
embed.add_field(name="ID# **{}**".format(i),
|
||||
value="{}{}-{}".format(status, player.member.display_name, str(player.role)),
|
||||
inline=True)
|
||||
else:
|
||||
embed.add_field(name="ID# **{}**".format(i),
|
||||
value="{}{}".format(status, player.member.display_name),
|
||||
inline=True)
|
||||
|
||||
return await channel.send(embed=embed)
|
||||
|
||||
@ -400,6 +468,13 @@ class Game:
|
||||
|
||||
self.players.append(Player(member))
|
||||
|
||||
if self.game_role is not None:
|
||||
try:
|
||||
await member.add_roles(*[self.game_role])
|
||||
except discord.Forbidden:
|
||||
await channel.send(
|
||||
"Unable to add role **{}**\nBot is missing `manage_roles` permissions".format(self.game_role.name))
|
||||
|
||||
await channel.send("{} has been added to the game, "
|
||||
"total players is **{}**".format(member.mention, len(self.players)))
|
||||
|
||||
@ -417,6 +492,7 @@ class Game:
|
||||
await channel.send("{} has left the game".format(member.mention))
|
||||
else:
|
||||
self.players = [player for player in self.players if player.member != member]
|
||||
await member.remove_roles(*[self.game_role])
|
||||
await channel.send("{} chickened out, player count is now **{}**".format(member.mention, len(self.players)))
|
||||
|
||||
async def choose(self, ctx, data):
|
||||
@ -431,7 +507,7 @@ class Game:
|
||||
return
|
||||
|
||||
if not player.alive:
|
||||
await ctx.send("**Corpses** can't vote...")
|
||||
await ctx.send("**Corpses** can't participate...")
|
||||
return
|
||||
|
||||
if player.role.blocked:
|
||||
@ -441,7 +517,7 @@ class Game:
|
||||
# Let role do target validation, might be alternate targets
|
||||
# I.E. Go on alert? y/n
|
||||
|
||||
await player.choose(ctx, data)
|
||||
await player.role.choose(ctx, data)
|
||||
|
||||
async def _visit(self, target, source):
|
||||
await target.role.visit(source)
|
||||
@ -471,7 +547,7 @@ class Game:
|
||||
return
|
||||
|
||||
if not player.alive:
|
||||
await channel.send("Corpses can't vote")
|
||||
await channel.send("Corpses can't vote...")
|
||||
return
|
||||
|
||||
if channel == self.village_channel:
|
||||
@ -531,7 +607,9 @@ class Game:
|
||||
out = "**{ID}** - " + method
|
||||
return out.format(ID=target.id, target=target.member.display_name)
|
||||
else:
|
||||
return "**{ID}** - {target} was found dead".format(ID=target.id, target=target.member.display_name)
|
||||
return "**{ID}** - {target} the {role} was found dead".format(ID=target.id,
|
||||
target=target.member.display_name,
|
||||
role=await target.role.get_role())
|
||||
|
||||
async def _quit(self, player):
|
||||
"""
|
||||
@ -595,14 +673,25 @@ class Game:
|
||||
async def get_day_target(self, target_id, source=None):
|
||||
return self.players[target_id] # ToDo check source
|
||||
|
||||
async def get_roles(self, game_code=None):
|
||||
async def set_code(self, ctx: commands.Context, game_code):
|
||||
if game_code is not None:
|
||||
self.game_code = game_code
|
||||
await ctx.send("Code has been set")
|
||||
|
||||
async def get_roles(self, ctx, game_code=None):
|
||||
if game_code is not None:
|
||||
self.game_code = game_code
|
||||
|
||||
if self.game_code is None:
|
||||
return False
|
||||
|
||||
self.roles = await parse_code(self.game_code)
|
||||
try:
|
||||
self.roles = await parse_code(self.game_code, self)
|
||||
except ValueError as e:
|
||||
await ctx.send("Invalid Code: Code contains unknown character\n{}".format(e))
|
||||
return False
|
||||
except IndexError as e:
|
||||
await ctx.send("Invalid Code: Code references unknown role\n{}".format(e))
|
||||
|
||||
if not self.roles:
|
||||
return False
|
||||
@ -613,11 +702,10 @@ class Game:
|
||||
self.players.sort(key=lambda pl: pl.member.display_name.lower())
|
||||
|
||||
if len(self.roles) != len(self.players):
|
||||
await self.village_channel("Unhandled error - roles!=players")
|
||||
await self.village_channel.send("Unhandled error - roles!=players")
|
||||
return False
|
||||
|
||||
for idx, role in enumerate(self.roles):
|
||||
self.roles[idx] = role(self)
|
||||
await self.roles[idx].assign_player(self.players[idx])
|
||||
# Sorted players, now assign id's
|
||||
await self.players[idx].assign_id(idx)
|
||||
@ -645,28 +733,67 @@ class Game:
|
||||
await channel.set_permissions(self.game_role, read_messages=True, send_messages=False)
|
||||
await channel.set_permissions(member, send_messages=True)
|
||||
|
||||
async def normal_perms(self, channel, member_list):
|
||||
async def normal_perms(self, channel):
|
||||
await channel.set_permissions(self.game_role, read_messages=True, send_messages=True)
|
||||
# for member in member_list:
|
||||
# await channel.set_permissions(member, read_messages=True)
|
||||
|
||||
async def _check_game_over(self):
|
||||
alive_players = [player for player self.players if player.alive]
|
||||
# return # ToDo: re-enable game-over checking
|
||||
alive_players = [player for player in self.players if player.alive]
|
||||
|
||||
if len(alive_players)<=2:
|
||||
if len(alive_players) <= 0:
|
||||
await self.village_channel.send(embed=discord.Embed(title="**Everyone is dead! Game Over!**"))
|
||||
self.game_over = True
|
||||
elif len(alive_players) == 1:
|
||||
self.game_over = True
|
||||
await self._announce_winners(alive_players)
|
||||
elif len(alive_players) == 2:
|
||||
# Check 1v1 victory conditions ToDo
|
||||
pass
|
||||
self.game_over = True
|
||||
alignment1 = alive_players[0].role.alignment
|
||||
alignment2 = alive_players[1].role.alignment
|
||||
if alignment1 == alignment2: # Same team
|
||||
winners = alive_players
|
||||
else:
|
||||
winners = [max(alive_players, key=lambda p: p.role.alignment)]
|
||||
|
||||
await self._announce_winners(winners)
|
||||
else:
|
||||
#Check if everyone is on the same team
|
||||
alignment = alive_players[0].role.alignment
|
||||
# Check if everyone is on the same team
|
||||
alignment = alive_players[0].role.alignment # Get first allignment and compare to rest
|
||||
for player in alive_players:
|
||||
if player.role.alignment != alignment:
|
||||
return False
|
||||
return
|
||||
|
||||
# Only remaining team wins
|
||||
self.game_over = True
|
||||
await self._announce_winners(alive_players)
|
||||
|
||||
# If no return, cleanup and end game
|
||||
await self._end_game()
|
||||
|
||||
async def _announce_winners(self, winnerlist):
|
||||
await self.village_channel.send(self.game_role.mention)
|
||||
embed = discord.Embed(title='Game Over', description='The Following Players have won!')
|
||||
for player in winnerlist:
|
||||
embed.add_field(name=player.member.display_name, value=str(player.role), inline=True)
|
||||
embed.set_thumbnail(url='https://emojipedia-us.s3.amazonaws.com/thumbs/160/twitter/134/trophy_1f3c6.png')
|
||||
await self.village_channel.send(embed=embed)
|
||||
|
||||
await self.generate_targets(self.village_channel, True)
|
||||
|
||||
async def _end_game(self):
|
||||
# ToDo
|
||||
pass
|
||||
# Remove game_role access for potential archiving for now
|
||||
reason = '(BOT) End of WW game'
|
||||
for obj in self.to_delete:
|
||||
print(obj)
|
||||
await obj.delete(reason=reason)
|
||||
|
||||
try:
|
||||
await self.village_channel.edit(reason=reason, name="Werewolf")
|
||||
for target, overwrites in self.save_perms[self.village_channel]:
|
||||
await self.village_channel.set_permissions(target, overwrite=overwrites, reason=reason)
|
||||
await self.village_channel.set_permissions(self.game_role, overwrite=None, reason=reason)
|
||||
except (discord.HTTPException, discord.NotFound, discord.errors.NotFound):
|
||||
pass
|
||||
|
||||
# Optional dynamic channels/categories
|
||||
|
@ -46,6 +46,12 @@ class Role:
|
||||
"You win by testing the game\n"
|
||||
"Lynch players during the day with `[p]ww vote <ID>`"
|
||||
)
|
||||
description = (
|
||||
"This is the basic role\n"
|
||||
"All roles are based on this Class"
|
||||
"Has no special significance"
|
||||
)
|
||||
icon_url = None # Adding a URL here will enable a thumbnail of the role
|
||||
|
||||
def __init__(self, game):
|
||||
self.game = game
|
||||
@ -65,6 +71,9 @@ class Role:
|
||||
(self._at_visit, 0)
|
||||
]
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
async def on_event(self, event, data):
|
||||
"""
|
||||
See Game class for event guide
|
||||
|
@ -2,7 +2,7 @@ from werewolf.role import Role
|
||||
|
||||
|
||||
class Seer(Role):
|
||||
rand_choice = False # Determines if it can be picked as a random role (False for unusually disruptive roles)
|
||||
rand_choice = True # Determines if it can be picked as a random role (False for unusually disruptive roles)
|
||||
category = [1, 2] # List of enrolled categories (listed above)
|
||||
alignment = 1 # 1: Town, 2: Werewolf, 3: Neutral
|
||||
channel_id = "" # Empty for no private channel
|
||||
@ -29,7 +29,8 @@ class Seer(Role):
|
||||
(self._at_hang, 0),
|
||||
(self._at_day_end, 0),
|
||||
(self._at_night_start, 2),
|
||||
(self._at_night_end, 4)
|
||||
(self._at_night_end, 4),
|
||||
(self._at_visit, 0)
|
||||
]
|
||||
|
||||
# async def on_event(self, event, data):
|
||||
@ -96,15 +97,22 @@ class Seer(Role):
|
||||
# pass
|
||||
|
||||
async def _at_night_start(self, data=None):
|
||||
if not self.player.alive:
|
||||
return
|
||||
self.see_target = None
|
||||
await self.game.generate_targets(self.player.member)
|
||||
await self.player.send_dm("{}\n**Pick a target to see tonight**\n")
|
||||
await self.player.send_dm("**Pick a target to see tonight**\n")
|
||||
|
||||
async def _at_night_end(self, data=None):
|
||||
target = await self.game.visit(self.see_target)
|
||||
if self.see_target is None:
|
||||
if self.player.alive:
|
||||
await self.player.send_dm("You will not use your powers tonight...")
|
||||
return
|
||||
target = await self.game.visit(self.see_target, self.player)
|
||||
|
||||
alignment = None
|
||||
if target:
|
||||
alignment = await target.see_alignment(self.player)
|
||||
alignment = await target.role.see_alignment(self.player)
|
||||
|
||||
if alignment == "Werewolf":
|
||||
out = "Your insight reveals this player to be a **Werewolf!**"
|
||||
@ -133,6 +141,10 @@ class Seer(Role):
|
||||
|
||||
async def choose(self, ctx, data):
|
||||
"""Handle night actions"""
|
||||
if not self.player.alive: # FixMe: Game handles this?
|
||||
await self.player.send_dm("You're already dead!")
|
||||
return
|
||||
|
||||
target_id = int(data)
|
||||
try:
|
||||
target = self.game.players[target_id]
|
||||
|
@ -2,7 +2,7 @@ from werewolf.role import Role
|
||||
|
||||
|
||||
class Villager(Role):
|
||||
rand_choice = False # Determines if it can be picked as a random role (False for unusually disruptive roles)
|
||||
rand_choice = True # Determines if it can be picked as a random role (False for unusually disruptive roles)
|
||||
category = [1] # List of enrolled categories (listed above)
|
||||
alignment = 1 # 1: Town, 2: Werewolf, 3: Neutral
|
||||
channel_id = "" # Empty for no private channel
|
||||
|
@ -1,9 +1,12 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from redbot.core import Config
|
||||
from redbot.core import RedContext
|
||||
from redbot.core import Config, checks
|
||||
|
||||
from redbot.core.bot import Red
|
||||
|
||||
from werewolf.builder import GameBuilder, role_from_name, role_from_alignment, role_from_category, role_from_id
|
||||
from werewolf.game import Game
|
||||
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
||||
|
||||
|
||||
class Werewolf:
|
||||
@ -11,12 +14,15 @@ class Werewolf:
|
||||
Base to host werewolf on a guild
|
||||
"""
|
||||
|
||||
def __init__(self, bot):
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
self.config = Config.get_conf(self, identifier=87101114101119111108102, force_registration=True)
|
||||
default_global = {}
|
||||
default_guild = {
|
||||
"role": None
|
||||
"role_id": None,
|
||||
"category_id": None,
|
||||
"channel_id": None,
|
||||
"log_channel_id": None
|
||||
}
|
||||
|
||||
self.config.register_global(**default_global)
|
||||
@ -29,26 +35,102 @@ class Werewolf:
|
||||
for game in self.games.values():
|
||||
del game
|
||||
|
||||
@commands.command()
|
||||
async def buildgame(self, ctx: commands.Context):
|
||||
gb = GameBuilder()
|
||||
code = await gb.build_game(ctx)
|
||||
|
||||
if code != "":
|
||||
await ctx.send("Your game code is **{}**".format(code))
|
||||
else:
|
||||
await ctx.send("No code generated")
|
||||
|
||||
@checks.guildowner()
|
||||
@commands.group()
|
||||
async def wwset(self, ctx: RedContext):
|
||||
async def wwset(self, ctx: commands.Context):
|
||||
"""
|
||||
Base command to adjust settings. Check help for command list.
|
||||
"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help()
|
||||
|
||||
@commands.guild_only()
|
||||
@wwset.command(name="list")
|
||||
async def wwset_list(self, ctx: commands.Context):
|
||||
"""
|
||||
Lists current guild settings
|
||||
"""
|
||||
success, role, category, channel, log_channel = await self._get_settings(ctx)
|
||||
if not success:
|
||||
await ctx.send("Failed to get settings")
|
||||
return None
|
||||
|
||||
embed = discord.Embed(title="Current Guild Settings")
|
||||
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)
|
||||
|
||||
@commands.guild_only()
|
||||
@wwset.command(name="role")
|
||||
async def wwset_role(self, ctx, role: discord.Role):
|
||||
async def wwset_role(self, ctx: commands.Context, role: discord.Role=None):
|
||||
"""
|
||||
Assign the game role
|
||||
This role should not be manually assigned
|
||||
"""
|
||||
await self.config.guild(ctx.guild).role.set(role.id)
|
||||
await ctx.send("Game role has been set to **{}**".format(role.name))
|
||||
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))
|
||||
|
||||
@commands.guild_only()
|
||||
@wwset.command(name="category")
|
||||
async def wwset_category(self, ctx: commands.Context, category_id=None):
|
||||
"""
|
||||
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))
|
||||
|
||||
@commands.guild_only()
|
||||
@wwset.command(name="channel")
|
||||
async def wwset_channel(self, ctx: commands.Context, channel: discord.TextChannel=None):
|
||||
"""
|
||||
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))
|
||||
|
||||
@commands.guild_only()
|
||||
@wwset.command(name="logchannel")
|
||||
async def wwset_log_channel(self, ctx: commands.Context, channel: discord.TextChannel=None):
|
||||
"""
|
||||
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))
|
||||
|
||||
@commands.group()
|
||||
async def ww(self, ctx: RedContext):
|
||||
async def ww(self, ctx: commands.Context):
|
||||
"""
|
||||
Base command for this cog. Check help for the commands list.
|
||||
"""
|
||||
@ -56,27 +138,25 @@ class Werewolf:
|
||||
await ctx.send_help()
|
||||
|
||||
@commands.guild_only()
|
||||
@ww.command()
|
||||
async def new(self, ctx, game_code):
|
||||
@ww.command(name="new")
|
||||
async def ww_new(self, ctx: commands.Context, game_code=None):
|
||||
"""
|
||||
Create and join a new game of Werewolf
|
||||
"""
|
||||
|
||||
game = await self._get_game(ctx.guild, game_code)
|
||||
|
||||
game = await self._get_game(ctx, game_code)
|
||||
if not game:
|
||||
await ctx.send("Failed to start a new game")
|
||||
else:
|
||||
await ctx.send("New game has started")
|
||||
await ctx.send("Game is ready to join! Use `[p]ww join`")
|
||||
|
||||
@commands.guild_only()
|
||||
@ww.command()
|
||||
async def join(self, ctx):
|
||||
@ww.command(name="join")
|
||||
async def ww_join(self, ctx: commands.Context):
|
||||
"""
|
||||
Joins a game of Werewolf
|
||||
"""
|
||||
|
||||
game = await self._get_game(ctx.guild)
|
||||
game = await self._get_game(ctx)
|
||||
|
||||
if not game:
|
||||
await ctx.send("No game to join!\nCreate a new one with `[p]ww new`")
|
||||
@ -85,49 +165,71 @@ class Werewolf:
|
||||
await game.join(ctx.author, ctx.channel)
|
||||
|
||||
@commands.guild_only()
|
||||
@ww.command()
|
||||
async def quit(self, ctx):
|
||||
@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()
|
||||
@ww.command(name="quit")
|
||||
async def ww_quit(self, ctx: commands.Context):
|
||||
"""
|
||||
Quit a game of Werewolf
|
||||
"""
|
||||
|
||||
game = await self._get_game(ctx.guild)
|
||||
game = await self._get_game(ctx)
|
||||
|
||||
await game.quit(ctx.author, ctx.channel)
|
||||
|
||||
@commands.guild_only()
|
||||
@ww.command()
|
||||
async def start(self, ctx):
|
||||
@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.guild)
|
||||
game = await self._get_game(ctx)
|
||||
if not game:
|
||||
await ctx.send("No game running, cannot start")
|
||||
|
||||
await game.setup(ctx)
|
||||
if not await game.setup(ctx):
|
||||
pass # Do something?
|
||||
|
||||
@commands.guild_only()
|
||||
@ww.command()
|
||||
async def stop(self, ctx):
|
||||
@ww.command(name="stop")
|
||||
async def ww_stop(self, ctx: commands.Context):
|
||||
"""
|
||||
Stops the current game
|
||||
"""
|
||||
game = await self._get_game(ctx.guild)
|
||||
if not game:
|
||||
await ctx.send("No game running, cannot stop")
|
||||
if ctx.guild is None:
|
||||
# Private message, can't get guild
|
||||
await ctx.send("Cannot start game from PM!")
|
||||
return
|
||||
if ctx.guild.id not in self.games or self.games[ctx.guild.id].game_over:
|
||||
await ctx.send("No game to stop")
|
||||
return
|
||||
|
||||
game = await self._get_game(ctx)
|
||||
game.game_over = True
|
||||
await ctx.send("Game has been stopped")
|
||||
|
||||
@commands.guild_only()
|
||||
@ww.command()
|
||||
async def vote(self, ctx, target_id: int):
|
||||
@ww.command(name="vote")
|
||||
async def ww_vote(self, ctx: commands.Context, target_id: int):
|
||||
"""
|
||||
Vote for a player by ID
|
||||
"""
|
||||
try:
|
||||
target_id = int(target_id)
|
||||
except:
|
||||
except ValueError:
|
||||
target_id = None
|
||||
|
||||
if target_id is None:
|
||||
@ -145,7 +247,7 @@ class Werewolf:
|
||||
# return
|
||||
# else:
|
||||
|
||||
game = await self._get_game(ctx.guild)
|
||||
game = await self._get_game(ctx)
|
||||
|
||||
if game is None:
|
||||
await ctx.send("No game running, cannot vote")
|
||||
@ -160,8 +262,8 @@ class Werewolf:
|
||||
else:
|
||||
await ctx.send("Nothing to vote for in this channel")
|
||||
|
||||
@ww.command()
|
||||
async def choose(self, ctx, data):
|
||||
@ww.command(name="choose")
|
||||
async def ww_choose(self, ctx: commands.Context, data):
|
||||
"""
|
||||
Arbitrary decision making
|
||||
Handled by game+role
|
||||
@ -171,7 +273,6 @@ class Werewolf:
|
||||
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():
|
||||
@ -183,20 +284,108 @@ class Werewolf:
|
||||
|
||||
await game.choose(ctx, data)
|
||||
|
||||
async def _get_game(self, guild, game_code=None):
|
||||
@ww.group(name="search")
|
||||
async def ww_search(self, ctx: commands.Context):
|
||||
"""
|
||||
Find custom roles by name, alignment, category, or ID
|
||||
"""
|
||||
if ctx.invoked_subcommand is None or ctx.invoked_subcommand == self.ww_search:
|
||||
await ctx.send_help()
|
||||
|
||||
@ww_search.command(name="name")
|
||||
async def ww_search_name(self, ctx: commands.Context, *, name):
|
||||
"""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):
|
||||
"""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):
|
||||
"""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):
|
||||
"""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 = ctx.guild
|
||||
|
||||
if guild is None:
|
||||
# Private message, can't get guild
|
||||
await ctx.send("Cannot start game from PM!")
|
||||
return None
|
||||
if guild.id not in self.games:
|
||||
if not game_code:
|
||||
if guild.id not in self.games or self.games[guild.id].game_over:
|
||||
await ctx.send("Starting a new game...")
|
||||
success, role, category, channel, log_channel = await self._get_settings(ctx)
|
||||
|
||||
if not success:
|
||||
await ctx.send("Cannot start a new game")
|
||||
return None
|
||||
role = await self.config.guild(guild).role()
|
||||
role = discord.utils.get(guild.roles, id=role)
|
||||
if role is None:
|
||||
return None
|
||||
self.games[guild.id] = Game(guild, role, game_code)
|
||||
|
||||
self.games[guild.id] = Game(guild, role, category, channel, log_channel, game_code)
|
||||
|
||||
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, None, 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, None, None, 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 True, role, category, channel, log_channel
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user