add cards command and update card search arguments
This commit is contained in:
parent
3cc08a0be1
commit
84ff7e74b6
@ -1,16 +1,23 @@
|
||||
import asyncio
|
||||
import enum
|
||||
import logging
|
||||
|
||||
import discord
|
||||
from d4dj_utils.master.card_master import CardMaster
|
||||
from d4dj_utils.master.event_master import EventMaster
|
||||
from d4dj_utils.master.event_specific_bonus_master import EventSpecificBonusMaster
|
||||
from d4dj_utils.master.skill_master import SkillMaster
|
||||
from discord.ext import commands
|
||||
|
||||
from main import masters
|
||||
from main import masters, asset_manager
|
||||
from miyu_bot.commands.common.argument_parsing import ParsedArguments, parse_arguments, ArgumentError
|
||||
from miyu_bot.commands.common.emoji import rarity_emoji_ids, attribute_emoji_ids_by_attribute_id, \
|
||||
unit_emoji_ids_by_unit_id, parameter_bonus_emoji_ids_by_parameter_id
|
||||
from miyu_bot.commands.common.event import get_latest_event
|
||||
from miyu_bot.commands.common.formatting import format_info
|
||||
from miyu_bot.commands.common.master_asset_manager import hash_master
|
||||
from miyu_bot.commands.common.reaction_message import run_tabbed_message
|
||||
from miyu_bot.commands.common.name_aliases import characters_by_name, attributes_by_name, units_by_name
|
||||
from miyu_bot.commands.common.reaction_message import run_tabbed_message, run_reaction_message, run_paged_message
|
||||
|
||||
|
||||
class Card(commands.Cog):
|
||||
@ -28,14 +35,129 @@ class Card(commands.Cog):
|
||||
help='!card secretcage')
|
||||
async def card(self, ctx: commands.Context, *, arg: commands.clean_content):
|
||||
self.logger.info(f'Searching for card "{arg}".')
|
||||
card = masters.cards.get(arg, ctx)
|
||||
|
||||
if card.rarity_id >= 3:
|
||||
embeds = [self.get_card_embed(card, 0), self.get_card_embed(card, 1)]
|
||||
try:
|
||||
arguments = parse_arguments(arg)
|
||||
cards = self.get_cards(ctx, arguments)
|
||||
except ArgumentError as e:
|
||||
await ctx.send(str(e))
|
||||
return
|
||||
|
||||
if not cards:
|
||||
await ctx.send(f'No results for card "{arg}"')
|
||||
return
|
||||
|
||||
if len(cards) == 1 or arguments.text():
|
||||
embeds = self.get_card_embeds(cards[0])
|
||||
asyncio.ensure_future(run_tabbed_message(ctx, self.rarity_emoji, embeds, starting_index=1))
|
||||
else:
|
||||
embeds = [self.get_card_embed(card, 0)]
|
||||
asyncio.ensure_future(run_tabbed_message(ctx, self.rarity_emoji[:1], embeds, starting_index=0))
|
||||
message = await ctx.send(embed=self.get_card_embeds(cards[0])[1])
|
||||
|
||||
emojis = self.rarity_emoji + ['◀', '▶']
|
||||
|
||||
index = 0
|
||||
limit_break = 1
|
||||
|
||||
async def callback(emoji, _ctx, _message):
|
||||
nonlocal index
|
||||
nonlocal limit_break
|
||||
try:
|
||||
emoji_index = emojis.index(emoji)
|
||||
if emoji_index == 0:
|
||||
limit_break = 0
|
||||
elif emoji_index == 1:
|
||||
limit_break = 1
|
||||
elif emoji_index == 2:
|
||||
index -= 1
|
||||
else:
|
||||
index += 1
|
||||
|
||||
index = min(len(cards) - 1, max(0, index))
|
||||
|
||||
await message.edit(embed=self.get_card_embeds(cards[index])[limit_break])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
asyncio.ensure_future(run_reaction_message(ctx, message, emojis, callback))
|
||||
|
||||
def get_card_embeds(self, card):
|
||||
if card.rarity_id >= 3:
|
||||
return [self.get_card_embed(card, 0), self.get_card_embed(card, 1)]
|
||||
else:
|
||||
return [self.get_card_embed(card, 0)] * 2 # no actual awakened art for 1/2* cards
|
||||
|
||||
@commands.command(name='cards',
|
||||
aliases=[],
|
||||
description='Lists cards matching the given search terms.',
|
||||
help='!cards')
|
||||
async def cards(self, ctx: commands.Context, *, arg: commands.clean_content = ''):
|
||||
self.logger.info(f'Searching for cards "{arg}".')
|
||||
|
||||
try:
|
||||
arguments = parse_arguments(arg)
|
||||
cards = self.get_cards(ctx, arguments)
|
||||
sort, sort_op = arguments.single('sort', None,
|
||||
allowed_operators=['<', '>', '='], converter=card_attribute_aliases)
|
||||
display, _ = arguments.single(['display', 'disp'], sort or CardAttribute.Power, allowed_operators=['='],
|
||||
converter=card_attribute_aliases)
|
||||
except ArgumentError as e:
|
||||
await ctx.send(str(e))
|
||||
return
|
||||
|
||||
listing = []
|
||||
for card in cards:
|
||||
display_prefix = display.get_formatted_from_card(card)
|
||||
if display_prefix:
|
||||
listing.append(
|
||||
f'{display_prefix} : {self.format_card_name(card)}')
|
||||
else:
|
||||
listing.append(self.format_card_name(card))
|
||||
|
||||
embed = discord.Embed(title=f'Card Search "{arg}"' if arg else 'Cards')
|
||||
asyncio.ensure_future(run_paged_message(ctx, embed, listing))
|
||||
|
||||
def get_cards(self, ctx, arguments: ParsedArguments):
|
||||
sort, sort_op = arguments.single('sort', None,
|
||||
allowed_operators=['<', '>', '='], converter=card_attribute_aliases)
|
||||
reverse_sort = sort_op == '<' or arguments.tag('reverse')
|
||||
# Not used, but here because it's a valid argument before running require_all_arguments_used.
|
||||
display, _ = arguments.single(['display', 'disp'], sort, allowed_operators=['='],
|
||||
converter=card_attribute_aliases)
|
||||
character = {characters_by_name[c].id for c in arguments.words(characters_by_name.keys())}
|
||||
unit = {units_by_name[a].id for a in arguments.words(units_by_name.keys())}
|
||||
rarity = {int(r[0]) for r in arguments.words(['4*', '3*', '2*', '1*', r'4\*', r'3\*', r'2\*', r'1\*'])}
|
||||
attribute = {attributes_by_name[a].id for a in arguments.words(attributes_by_name.keys())}
|
||||
|
||||
event_bonus = bool(arguments.tags(['event', 'eventbonus', 'event_bonus']))
|
||||
|
||||
if event_bonus:
|
||||
latest_event = get_latest_event(ctx)
|
||||
bonus: EventSpecificBonusMaster = latest_event.bonus
|
||||
character.update(bonus.character_ids)
|
||||
attribute.add(bonus.attribute_id)
|
||||
if not arguments.has_named('sort'):
|
||||
sort = CardAttribute.Date
|
||||
|
||||
arguments.require_all_arguments_used()
|
||||
|
||||
cards = masters.cards.get_sorted(arguments.text(), ctx)
|
||||
if not (arguments.text() and sort is None):
|
||||
sort = sort or CardAttribute.Power
|
||||
cards = sorted(cards, key=lambda c: (sort.get_sort_key_from_card(c), c.max_power_with_limit_break))
|
||||
if sort in [CardAttribute.Power, CardAttribute.Date]:
|
||||
cards = cards[::-1]
|
||||
if reverse_sort:
|
||||
cards = cards[::-1]
|
||||
if character:
|
||||
cards = [card for card in cards if card.character.id in character]
|
||||
if unit:
|
||||
cards = [card for card in cards if card.character.unit.id in unit]
|
||||
if rarity:
|
||||
cards = [card for card in cards if card.rarity_id in rarity]
|
||||
if attribute:
|
||||
cards = [card for card in cards if card.attribute.id in attribute]
|
||||
|
||||
return cards
|
||||
|
||||
def get_card_embed(self, card: CardMaster, limit_break):
|
||||
embed = discord.Embed(title=self.format_card_name(card))
|
||||
@ -53,7 +175,7 @@ class Card(commands.Cog):
|
||||
value=format_info({
|
||||
'Rarity': f'{card.rarity_id}★',
|
||||
'Character': f'{card.character.full_name_english}',
|
||||
'Attribute': f'{self.bot.get_emoji(attribute_emoji_ids_by_attribute_id[card.attribute_id])} {card.attribute.en_name}',
|
||||
'Attribute': f'{self.bot.get_emoji(attribute_emoji_ids_by_attribute_id[card.attribute_id])} {card.attribute.en_name.capitalize()}',
|
||||
'Unit': f'{self.bot.get_emoji(unit_emoji_ids_by_unit_id[card.character.unit_id])} {card.character.unit.name}',
|
||||
'Release Date': f'{card.start_datetime}',
|
||||
}),
|
||||
@ -65,7 +187,18 @@ class Card(commands.Cog):
|
||||
f'{self.bot.get_emoji(parameter_bonus_emoji_ids_by_parameter_id[2])} Technique': f'{"{:,}".format(card.max_parameters_with_limit_break[1])}',
|
||||
f'{self.bot.get_emoji(parameter_bonus_emoji_ids_by_parameter_id[3])} Physical': f'{"{:,}".format(card.max_parameters_with_limit_break[2])}',
|
||||
}),
|
||||
inline=False)
|
||||
inline=True)
|
||||
skill: SkillMaster = card.skill
|
||||
embed.add_field(name='Skill',
|
||||
value=format_info({
|
||||
'Name': card.skill_name,
|
||||
'Duration': f'{skill.min_seconds}-{skill.max_seconds}s',
|
||||
'Score Up': f'{skill.score_up_rate}%',
|
||||
'Heal': (f'{skill.min_recovery_value}-{skill.max_recovery_value}'
|
||||
if skill.min_recovery_value != skill.max_recovery_value
|
||||
else str(skill.min_recovery_value))
|
||||
}),
|
||||
inline=True)
|
||||
|
||||
return embed
|
||||
|
||||
@ -73,5 +206,37 @@ class Card(commands.Cog):
|
||||
return f'{card.rarity_id}★ {card.name} {card.character.full_name_english}'
|
||||
|
||||
|
||||
class CardAttribute(enum.Enum):
|
||||
Name = enum.auto()
|
||||
Id = enum.auto()
|
||||
Power = enum.auto()
|
||||
Date = enum.auto()
|
||||
|
||||
def get_sort_key_from_card(self, card: CardMaster):
|
||||
return {
|
||||
self.Name: card.name,
|
||||
self.Id: card.id,
|
||||
self.Power: card.max_power_with_limit_break,
|
||||
self.Date: card.start_datetime,
|
||||
}[self]
|
||||
|
||||
def get_formatted_from_card(self, card: CardMaster):
|
||||
return {
|
||||
self.Name: None,
|
||||
self.Id: str(card.id).zfill(9),
|
||||
self.Power: str(card.max_power_with_limit_break).rjust(5),
|
||||
self.Date: str(card.start_datetime.date()),
|
||||
}[self]
|
||||
|
||||
|
||||
card_attribute_aliases = {
|
||||
'name': CardAttribute.Name,
|
||||
'id': CardAttribute.Id,
|
||||
'power': CardAttribute.Power,
|
||||
'stats': CardAttribute.Power,
|
||||
'date': CardAttribute.Date,
|
||||
}
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Card(bot))
|
||||
|
@ -14,6 +14,7 @@ from main import asset_manager, masters
|
||||
from miyu_bot.commands.common.emoji import attribute_emoji_ids_by_attribute_id, unit_emoji_ids_by_unit_id, \
|
||||
parameter_bonus_emoji_ids_by_parameter_id, \
|
||||
event_point_emoji_id
|
||||
from miyu_bot.commands.common.event import get_latest_event
|
||||
from miyu_bot.commands.common.formatting import format_info
|
||||
from miyu_bot.commands.common.fuzzy_matching import romanize
|
||||
from miyu_bot.commands.common.master_asset_manager import MasterFilter, hash_master
|
||||
@ -37,14 +38,14 @@ class Event(commands.Cog):
|
||||
# Allows relative id searches like `!event +1` for next event or `!event -2` for the event before last event
|
||||
if arg[0] in ['-', '+']:
|
||||
try:
|
||||
latest = self.get_latest_event(ctx)
|
||||
latest = get_latest_event(ctx)
|
||||
event = masters.events.get(str(latest.id + int(arg)), ctx)
|
||||
except ValueError:
|
||||
event = masters.events.get(arg, ctx)
|
||||
else:
|
||||
event = masters.events.get(arg, ctx)
|
||||
else:
|
||||
event = self.get_latest_event(ctx)
|
||||
event = get_latest_event(ctx)
|
||||
|
||||
if not event:
|
||||
msg = f'Failed to find event "{arg}".'
|
||||
@ -150,7 +151,7 @@ class Event(commands.Cog):
|
||||
description='Displays the time left in the current event',
|
||||
help='!timeleft')
|
||||
async def time_left(self, ctx: commands.Context):
|
||||
latest = self.get_latest_event(ctx)
|
||||
latest = get_latest_event(ctx)
|
||||
|
||||
state = latest.state()
|
||||
|
||||
@ -204,19 +205,6 @@ class Event(commands.Cog):
|
||||
|
||||
await ctx.send(files=[logo], embed=embed)
|
||||
|
||||
def get_latest_event(self, ctx: commands.Context) -> EventMaster:
|
||||
"""Returns the oldest event that has not ended or the newest event otherwise."""
|
||||
try:
|
||||
# NY event overlapped with previous event
|
||||
return min((v for v in masters.events.values(ctx) if v.state() == EventState.Open),
|
||||
key=lambda e: e.start_datetime)
|
||||
except ValueError:
|
||||
try:
|
||||
return min((v for v in masters.events.values(ctx) if v.state() < EventState.Ended),
|
||||
key=lambda e: e.start_datetime)
|
||||
except ValueError:
|
||||
return max(masters.events.values(ctx), key=lambda v: v.start_datetime)
|
||||
|
||||
@commands.command(name='t20',
|
||||
aliases=['top20', 'top_20'],
|
||||
description='Displays the top 20 in the main leaderboard',
|
||||
@ -226,7 +214,7 @@ class Event(commands.Cog):
|
||||
async with session.get('http://www.projectdivar.com/eventdata/t20') as resp:
|
||||
leaderboard = await resp.json(encoding='utf-8')
|
||||
|
||||
latest = self.get_latest_event(ctx)
|
||||
latest = get_latest_event(ctx)
|
||||
logo = discord.File(latest.logo_path, filename='logo.png')
|
||||
embed = discord.Embed(title=f'{latest.name} t20').set_thumbnail(url=f'attachment://logo.png')
|
||||
max_points_digits = len(str(leaderboard[0]['points']))
|
||||
|
@ -56,7 +56,7 @@ class Music(commands.Cog):
|
||||
song = masters.music.get(arg, ctx)
|
||||
|
||||
if not song:
|
||||
msg = f'Failed to find song "{arg}".'
|
||||
msg = f'No results for song "{arg}".'
|
||||
await ctx.send(msg)
|
||||
self.logger.info(msg)
|
||||
return
|
||||
@ -171,7 +171,6 @@ class Music(commands.Cog):
|
||||
async def songs(self, ctx: commands.Context, *, arg: commands.clean_content = ''):
|
||||
self.logger.info(f'Searching for songs "{arg}".' if arg else 'Listing songs.')
|
||||
arguments = parse_arguments(arg)
|
||||
songs = masters.music.get_sorted(arguments.text_argument, ctx)
|
||||
|
||||
try:
|
||||
sort, sort_op = arguments.single('sort', MusicAttribute.DefaultOrder,
|
||||
@ -203,6 +202,9 @@ class Music(commands.Cog):
|
||||
|
||||
difficulty = arguments.repeatable(['difficulty', 'diff', 'level'], is_list=True,
|
||||
converter=difficulty_converter)
|
||||
|
||||
songs = masters.music.get_sorted(arguments.text(), ctx)
|
||||
|
||||
arguments.require_all_arguments_used()
|
||||
except ArgumentError as e:
|
||||
await ctx.send(str(e))
|
||||
@ -231,11 +233,13 @@ class Music(commands.Cog):
|
||||
songs = [song for song in songs if song.unit.id in allowed_unit_ids]
|
||||
|
||||
if not (arguments.text_argument and sort == MusicAttribute.DefaultOrder):
|
||||
songs = sorted(songs, key=lambda s: sort.get_from_music(s))
|
||||
songs = sorted(songs, key=lambda s: sort.get_sort_key_from_music(s))
|
||||
if sort == MusicAttribute.DefaultOrder and songs and songs[0].id == 1:
|
||||
songs = [*songs[1:], songs[0]]
|
||||
if sort in [MusicAttribute.Level, MusicAttribute.Date]:
|
||||
songs = songs[::-1]
|
||||
if reverse_sort:
|
||||
songs = reversed(songs)
|
||||
songs = songs[::-1]
|
||||
|
||||
listing = []
|
||||
for song in songs:
|
||||
@ -394,7 +398,7 @@ class MusicAttribute(enum.Enum):
|
||||
Duration = enum.auto()
|
||||
Date = enum.auto()
|
||||
|
||||
def get_from_music(self, music: MusicMaster):
|
||||
def get_sort_key_from_music(self, music: MusicMaster):
|
||||
return {
|
||||
self.DefaultOrder: -music.default_order,
|
||||
self.Name: music.name,
|
||||
|
@ -107,6 +107,9 @@ class ParsedArguments:
|
||||
self.used_tags.add(alias)
|
||||
return results
|
||||
|
||||
def has_named(self, name: str):
|
||||
return name in self.named_arguments
|
||||
|
||||
def single(self, names: Union[List[str], str], default: Any = None, allowed_operators: Optional[Container] = None,
|
||||
is_list=False, numeric=False, converter: Union[dict, Callable] = lambda n: n):
|
||||
if allowed_operators is None:
|
||||
|
19
miyu_bot/commands/common/event.py
Normal file
19
miyu_bot/commands/common/event.py
Normal file
@ -0,0 +1,19 @@
|
||||
from d4dj_utils.master.event_master import EventMaster, EventState
|
||||
from discord.ext import commands
|
||||
|
||||
from main import masters
|
||||
|
||||
|
||||
def get_latest_event(ctx: commands.Context) -> EventMaster:
|
||||
"""Returns the oldest event that has not ended or the newest event otherwise."""
|
||||
try:
|
||||
# NY event overlapped with previous event
|
||||
return min((v for v in masters.events.values(ctx) if v.state() == EventState.Open),
|
||||
key=lambda e: e.start_datetime)
|
||||
except ValueError:
|
||||
try:
|
||||
return min((v for v in masters.events.values(ctx) if v.state() < EventState.Ended),
|
||||
key=lambda e: e.start_datetime)
|
||||
except ValueError:
|
||||
return max(masters.events.values(ctx), key=lambda v: v.start_datetime)
|
||||
|
@ -30,7 +30,7 @@ class MasterFilterManager:
|
||||
)
|
||||
self.cards = MasterFilter(
|
||||
self.manager.card_master,
|
||||
naming_function=lambda c: c.name,
|
||||
naming_function=lambda c: f'{c.name} {c.character.first_name_english}',
|
||||
filter_function=lambda c: c.is_released,
|
||||
)
|
||||
|
||||
|
11
miyu_bot/commands/common/name_aliases.py
Normal file
11
miyu_bot/commands/common/name_aliases.py
Normal file
@ -0,0 +1,11 @@
|
||||
from main import asset_manager
|
||||
|
||||
characters_by_name = {}
|
||||
for character in asset_manager.character_master.values():
|
||||
for name in character.full_name_english.split():
|
||||
characters_by_name[name.lower()] = character
|
||||
|
||||
attributes_by_name = {attribute.en_name: attribute for attribute in asset_manager.attribute_master.values()}
|
||||
|
||||
units_by_name = {unit.name: unit for unit in asset_manager.unit_master.values()}
|
||||
units_by_name['rondo'] = units_by_name['燐舞曲']
|
Loading…
x
Reference in New Issue
Block a user