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/builder.py

387 lines
11 KiB

import bisect
4 years ago
import logging
from collections import defaultdict
4 years ago
from operator import attrgetter
from random import choice
import discord
# Import all roles here
from redbot.core import commands
# from .roles.seer import Seer
# from .roles.vanillawerewolf import VanillaWerewolf
# from .roles.villager import Villager
from werewolf import roles
from redbot.core.utils.menus import menu, prev_page, next_page, close_menu
4 years ago
from werewolf.constants import ROLE_CATEGORY_DESCRIPTIONS
from werewolf.role import Role
4 years ago
log = logging.getLogger("red.fox_v3.werewolf.builder")
# All roles in this list for iterating
ROLE_DICT = {name: cls for name, cls in roles.__dict__.items() if isinstance(cls, type)}
ROLE_LIST = sorted(
[cls for cls in ROLE_DICT.values()],
4 years ago
key=attrgetter("alignment"),
)
log.debug(f"{ROLE_DICT=}")
4 years ago
# Town, Werewolf, Neutral
4 years ago
ALIGNMENT_COLORS = [0x008000, 0xFF0000, 0xC0C0C0]
ROLE_PAGES = []
4 years ago
def role_embed(idx, role: Role, color):
4 years ago
embed = discord.Embed(
title=f"**{idx}** - {role.__name__}",
4 years ago
description=role.game_start_message,
color=color,
)
4 years ago
if role.icon_url is not None:
embed.set_thumbnail(url=role.icon_url)
4 years ago
embed.add_field(
4 years ago
name="Alignment", value=["Town", "Werewolf", "Neutral"][role.alignment - 1], inline=False
4 years ago
)
4 years ago
embed.add_field(name="Multiples Allowed", value=str(not role.unique), inline=False)
4 years ago
embed.add_field(
4 years ago
name="Role Types",
value=", ".join(ROLE_CATEGORY_DESCRIPTIONS[x] for x in role.category),
inline=False,
4 years ago
)
4 years ago
embed.add_field(name="Random Option", value=str(role.rand_choice), inline=False)
return embed
"""
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
4 years ago
which translates to
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 in ["T", "W", "N"]:
# Random Towns
category = built
built = ""
digits = 1
continue
elif built == "-":
built = ""
digits += 1
7 years ago
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]
if not options:
raise IndexError("No Match Found")
decode.append(choice(options)(game))
built = ""
return decode
4 years ago
async def encode(role_list, rand_roles):
"""Convert role list to code"""
4 years ago
digit_sort = sorted(role for role in role_list if role < 10)
out_code = "".join(str(role) for role in digit_sort)
4 years ago
digit_sort = sorted(role for role in role_list 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
7 years ago
def role_from_alignment(alignment):
4 years ago
return [
role_embed(idx, role, ALIGNMENT_COLORS[role.alignment - 1])
for idx, role in enumerate(ROLE_LIST)
if alignment == role.alignment
]
7 years ago
def role_from_category(category):
4 years ago
return [
role_embed(idx, role, ALIGNMENT_COLORS[role.alignment - 1])
for idx, role in enumerate(ROLE_LIST)
if category in role.category
]
7 years ago
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):
4 years ago
return [
role_embed(idx, role, ALIGNMENT_COLORS[role.alignment - 1])
for idx, role in enumerate(ROLE_LIST)
if name in role.__name__
]
7 years ago
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
7 years ago
for role in rand_roles:
if 0 < role <= 6:
4 years ago
role_dict[f"Town {ROLE_CATEGORY_DESCRIPTIONS[role]}"] += 1
7 years ago
if 10 < role <= 16:
4 years ago
role_dict[f"Werewolf {ROLE_CATEGORY_DESCRIPTIONS[role]}"] += 1
7 years ago
if 20 < role <= 26:
4 years ago
role_dict[f"Neutral {ROLE_CATEGORY_DESCRIPTIONS[role]}"] += 1
7 years ago
for k, v in role_dict.items():
embed.add_field(name=k, value=f"Count: {v}", inline=True)
return embed
class GameBuilder:
def __init__(self):
self.code = []
self.rand_roles = []
4 years ago
self.page_groups = [0]
self.category_count = []
self.setup()
def setup(self):
# 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 self.page_groups:
self.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 self.page_groups:
self.page_groups.append(len(ROLE_PAGES) - 1)
for k, v in ROLE_CATEGORY_DESCRIPTIONS.items():
if 0 < k <= 9:
ROLE_PAGES.append(
discord.Embed(
title="RANDOM:Town Role",
description=f"Town {v}",
color=ALIGNMENT_COLORS[0],
)
)
self.category_count.append(k)
# Random WW Roles
if len(ROLE_PAGES) - 1 not in self.page_groups:
self.page_groups.append(len(ROLE_PAGES) - 1)
for k, v in ROLE_CATEGORY_DESCRIPTIONS.items():
if 10 < k <= 19:
ROLE_PAGES.append(
discord.Embed(
title="RANDOM:Werewolf Role",
description=f"Werewolf {v}",
color=ALIGNMENT_COLORS[1],
)
)
self.category_count.append(k)
# Random Neutral Roles
if len(ROLE_PAGES) - 1 not in self.page_groups:
self.page_groups.append(len(ROLE_PAGES) - 1)
for k, v in ROLE_CATEGORY_DESCRIPTIONS.items():
if 20 < k <= 29:
ROLE_PAGES.append(
discord.Embed(
title=f"RANDOM:Neutral Role",
description=f"Neutral {v}",
color=ALIGNMENT_COLORS[2],
)
)
self.category_count.append(k)
async def build_game(self, ctx: commands.Context):
new_controls = {
4 years ago
"": self.prev_group,
"": prev_page,
4 years ago
"": self.select_page,
"": next_page,
4 years ago
"": self.next_group,
4 years ago
"📇": 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
4 years ago
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.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))
4 years ago
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.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):
4 years ago
self.rand_roles.append(self.category_count[page - len(ROLE_LIST)])
else:
self.code.append(page)
4 years ago
return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout)
4 years ago
async def next_group(
self,
ctx: commands.Context,
pages: list,
controls: dict,
message: discord.Message,
page: int,
timeout: float,
emoji: str,
):
perms = message.channel.permissions_for(ctx.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(self.page_groups, page)
if page == len(self.page_groups):
page = self.page_groups[0]
else:
page = self.page_groups[page]
return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout)
async def prev_group(
self,
ctx: commands.Context,
pages: list,
controls: dict,
message: discord.Message,
page: int,
timeout: float,
emoji: str,
):
perms = message.channel.permissions_for(ctx.me)
if perms.manage_messages: # Can manage messages, so remove react
try:
await message.remove_reaction(emoji, ctx.author)
except discord.NotFound:
pass
page = self.page_groups[bisect.bisect_left(self.page_groups, page) - 1]
return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout)