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 = 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 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-1112T11W112N2 0,0,0,1,11,12,E1,R1,R1,R1,R2,P2 pre-letter = exact role position double digit position preempted by `-` """ async def parse_code(code, game): """Do the magic described above""" decode = [] digits = 1 built = "" category = "" for c in code: if len(built) < digits: built += c if built == "T" or built == "W" or built == "N": # Random Towns category = built built = "" digits = 1 continue elif built == "-": built = "" digits += 1 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)) built = "" return decode async def encode(roles, rand_roles): """Convert role list to code""" out_code = "" digit_sort = sorted(role for role in roles if role < 10) for role in digit_sort: out_code += str(role) 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)