def get_card_embed(self, ctx, card: CardMaster, limit_break): l10n = self.l10n[ctx] if card.rarity_id <= 2: limit_break = 0 color_code = card.character.color_code color = discord.Colour.from_rgb(*ImageColor.getcolor(color_code, 'RGB')) if color_code else discord.Embed.Empty embed = discord.Embed(title=self.format_card_name(card), color=color) thumb_url = ctx.bot.asset_url + get_asset_filename(card.icon_path(limit_break)) art_url = ctx.bot.asset_url + get_asset_filename(card.art_path(limit_break)) embed.set_thumbnail(url=thumb_url) embed.set_image(url=art_url) embed.add_field(name=l10n.format_value('info'), value=l10n.format_value('info-desc', { 'rarity': f'{card.rarity_id}★', 'character': f'{card.character.full_name_english}', 'attribute': f'{ctx.bot.get_emoji(attribute_emoji_ids_by_attribute_id[card.attribute_id])} {card.attribute.en_name.capitalize()}', 'unit': f'{ctx.bot.get_emoji(unit_emoji_ids_by_unit_id[card.character.unit_id])} {card.character.unit.name}', 'release-date': fluent_date(ctx.convert_tz(card.start_datetime), dateStyle='medium', timeStyle='medium'), 'event': f'{card.event.name if card.event else "None"}', 'gacha': f'{card.gacha.name if card.gacha else "None"}', 'availability': card.availability.name, }), inline=False) embed.add_field(name=l10n.format_value('parameters'), value=l10n.format_value('parameters-desc', { 'total': card.max_power_with_limit_break, 'heart': card.max_parameters_with_limit_break[0], 'technique': card.max_parameters_with_limit_break[1], 'physical': card.max_parameters_with_limit_break[2], 'heart-emoji': str(ctx.bot.get_emoji(parameter_bonus_emoji_ids_by_parameter_id[1])), 'technique-emoji': str(ctx.bot.get_emoji(parameter_bonus_emoji_ids_by_parameter_id[2])), 'physical-emoji': str(ctx.bot.get_emoji(parameter_bonus_emoji_ids_by_parameter_id[3])), }), inline=True) skill: SkillMaster = card.skill embed.add_field(name=l10n.format_value('skill'), value=l10n.format_value('skill-desc', { 'name': card.skill_name, 'duration': f'{skill.min_seconds}-{skill.max_seconds}s', 'score-up': f'{skill.score_up_rate}%' if not skill.perfect_score_up_rate else f'{skill.score_up_rate}% + {skill.perfect_score_up_rate}% perfect', '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) embed.set_footer(text=l10n.format_value('card-id', {'card-id': f'{card.id:0>8}'})) return embed
def get_song_embed(self, ctx, song: MusicMaster): l10n = self.l10n[ctx] color_code = song.unit.main_color_code color = discord.Colour.from_rgb(*ImageColor.getcolor( color_code, 'RGB')) if color_code else discord.Embed.Empty embed = discord.Embed(title=song.name, color=color) embed.set_thumbnail(url=self.bot.asset_url + get_asset_filename(song.jacket_path)) embed.add_field( name=l10n.format_value('artist'), value=l10n.format_value( 'artist-desc', { 'lyricist': song.lyricist, 'composer': song.composer, 'arranger': song.arranger, 'unit-name': song.unit.name, 'special-unit-name': song.special_unit_name or 'None', }), inline=False) embed.add_field( name=l10n.format_value('info'), value=l10n.format_value( 'song-info-desc', { 'song-category': song.category.name, 'duration': self.format_duration(song.duration), 'bpm': song.bpm, 'section-trend': song.section_trend.name, 'sort-order': song.default_order, 'levels': ', '.join(c.display_level for c in song.charts.values()), 'chart-designers': ', '.join({ f'{c.designer.name} ({c.designer.id})': None for c in song.charts.values() }.keys()), 'release-date': fluent_date(ctx.convert_tz(song.start_datetime), dateStyle='medium', timeStyle='medium'), 'hidden': song.is_hidden, 'fair-use': song.can_fair_use, }), inline=False) embed.set_footer( text=l10n.format_value('song-id', {'song-id': f'{song.id:>07}'})) return embed
async def time_left(self, ctx: commands.Context, *, arg: commands.clean_content = ''): event, timezone = await self.parse_event_argument(ctx, arg) state = event.state() embed = discord.Embed(title=event.name) embed.set_thumbnail(url=self.bot.asset_url + get_asset_filename(event.logo_path)) progress = None now = dt.datetime.now(dt.timezone.utc) if state == EventState.Upcoming: time_delta_heading = 'Time Until Start' delta = event.start_datetime - now date_heading = 'Start Date' date_value = event.start_datetime elif state == EventState.Open: time_delta_heading = 'Time Until Close' delta = event.reception_close_datetime - now progress = 1 - ( delta / (event.reception_close_datetime - event.start_datetime)) date_heading = 'Close Date' date_value = event.reception_close_datetime elif state in (EventState.Closing, EventState.Ranks_Fixed): time_delta_heading = 'Time Until Results' delta = event.result_announcement_datetime - now date_heading = 'Results Date' date_value = event.result_announcement_datetime elif state == EventState.Results: time_delta_heading = 'Time Until End' delta = event.end_datetime - now date_heading = 'End Date' date_value = event.end_datetime else: time_delta_heading = 'Time Since End' delta = now - event.end_datetime date_heading = 'End Date' date_value = event.end_datetime date_value = date_value.astimezone(timezone) embed.add_field(name=time_delta_heading, value=self.format_timedelta(delta), inline=True) embed.add_field(name='Progress', value=f'{round(progress * 100, 2)}%' if progress is not None else 'N/A', inline=True) embed.add_field(name=date_heading, value=str(date_value), inline=True) await ctx.send(embed=embed)
def get_gacha_embed(self, ctx, gacha: GachaMaster): l10n = self.l10n[ctx] embed = discord.Embed(title=gacha.name) thumb_url = self.bot.asset_url + get_asset_filename(gacha.banner_path) embed.set_thumbnail(url=thumb_url) featured_text = '\n'.join( self.format_card_name_with_emoji(card) for card in gacha.pick_up_cards) or 'None' if len(featured_text) > 1024: featured_text = '\n'.join( self.format_card_name_short(card) for card in gacha.pick_up_cards) or 'None' if len(featured_text) > 1024: featured_text = l10n.format_value('too-many-results') def fmt_date(date): return fluent_date(date, dateStyle='medium', timeStyle='medium') embed.add_field( name=l10n.format_value('info'), value=l10n.format_value( 'info-desc', { 'start-date': fmt_date(ctx.convert_tz( gacha.start_datetime)), 'end-date': fmt_date(ctx.convert_tz(gacha.end_datetime)), 'event-name': gacha.event.name if gacha.event else 'None', 'pity-requirement': gacha.bonus_max_value or 'None', 'gacha-type': gacha.gacha_type.name, }), inline=False) embed.add_field(name=l10n.format_value('summary'), value=gacha.summary, inline=False) embed.add_field(name=l10n.format_value('featured'), value=l10n.format_value( 'featured-text', {'featured-text': featured_text or 'None'}), inline=False) embed.add_field(name=l10n.format_value('costs'), value='\n'.join( self.format_draw_data(draw, l10n) for draw in gacha.draw_data)) embed.set_footer(text=l10n.format_value( 'gacha-id', {'gacha-id': f'{gacha.id:>05}'})) return embed
async def get_tier_embed(self, tier: str, event: EventMaster): async with self.bot.session.get( 'http://www.projectdivar.com/eventdata/t20?chart=true' ) as resp: leaderboard = await resp.json(encoding='utf-8') data = leaderboard['statistics'].get(tier) if not data: return None if event.state() == EventState.Open: delta = event.reception_close_datetime - dt.datetime.now( dt.timezone.utc) time_left = self.format_timedelta(delta) progress = f'{round(100 * (1 - (delta / (event.reception_close_datetime - event.start_datetime))), 2)}%' else: time_left = 'N/A' progress = 'N/A' embed = discord.Embed(title=f'{event.name} [t{tier}]', timestamp=dt.datetime.now(dt.timezone.utc)) embed.set_thumbnail(url=self.bot.asset_url + get_asset_filename(event.logo_path)) average_rate = "\n( +" + str( math.ceil((data['rate'] * self.EPRATE_RESOLUTION) / data['count']) ) + " avg )" if int( tier ) <= 20 else "" # Only T20 is tracked in real-time, we can't guarantee <2hr intervals for other points so the rate returned is just overall rate. embed.add_field(name='Points', value=str(data['points']) + average_rate, inline=True) embed.add_field(name='Last Update', value=data['lastUpdate'] or 'None', inline=True) embed.add_field(name='Rate', value=f'{data["rate"]} pts/hr', inline=True) embed.add_field(name='Current Estimate', value=data['estimate'], inline=True) embed.add_field(name='Final Prediction', value=data['prediction'], inline=True) embed.add_field(name='\u200b', value='\u200b', inline=True) embed.add_field(name='Time Left', value=time_left, inline=True) embed.add_field(name='Progress', value=progress, inline=True) return embed
def get_login_bonus_embed(self, ctx, login_bonus: LoginBonusMaster): l10n = self.l10n[ctx] embed = discord.Embed(title=login_bonus.title) def fmt_date(date): return fluent_date(date, dateStyle='medium', timeStyle='medium') embed.add_field( name=l10n.format_value('info'), value=l10n.format_value( 'info-desc', { 'start-date': fmt_date(ctx.convert_tz(login_bonus.start_datetime)), 'end-date': fmt_date(ctx.convert_tz(login_bonus.end_datetime)), 'login-bonus-type': login_bonus.login_bonus_type.name, 'loop': login_bonus.loop, }), inline=False) def format_login_bonus(item): rewards = item.rewards if len(rewards) > 1: prefix = f'{item.sequence}. ' return prefix + ('\n' + ' ' * len(prefix)).join( reward.get_friendly_description() for reward in rewards) elif len(rewards) == 1: return f'{item.sequence}. {rewards[0].get_friendly_description()}' else: return l10n.format_value('none') reward_text = '```' + ('\n'.join( format_login_bonus(item) for item in login_bonus.items) or 'None') + '```' embed.add_field(name=l10n.format_value('rewards'), value=reward_text, inline=False) embed.set_image(url=self.bot.asset_url + get_asset_filename(login_bonus.image_path)) embed.set_footer(text=l10n.format_value( 'login-bonus-id', {'login-bonus-id': f'{login_bonus.id:>04}'})) return embed
def main(): for p in ['assets', 'assets_en']: base_path = Path(p) target_path = Path('export') asset_paths = [ 'music_jacket/*.jpg', 'ondemand/card_chara/*.jpg', 'ondemand/card_icon/*.jpg', 'ondemand/chart/*.png', 'ondemand/event/*/*.jpg', 'ondemand/event/*/*.png', 'ondemand/gacha/top/banner/*.png', 'ondemand/loginBonus/*.jpg', ] for asset_path in asset_paths: for path in base_path.glob(asset_path): target_file = target_path / get_asset_filename(path) if not target_file.exists(): shutil.copy(path, target_file)
def get_sections_embed(self, ctx, song: MusicMaster, difficulty): l10n = self.l10n[ctx] difficulty = ChartDifficulty(difficulty + 1) if difficulty not in song.charts: embed = discord.Embed( title=f'Mix: {song.name} [{difficulty.name}]', description=l10n.format_value('no-data')) embed.set_thumbnail(url=self.bot.asset_url + get_asset_filename(song.jacket_path)) return embed color_code = song.unit.main_color_code color = discord.Colour.from_rgb(*ImageColor.getcolor( color_code, 'RGB')) if color_code else discord.Embed.Empty chart = song.charts[difficulty] embed = discord.Embed( title=f'Mix: {song.name} [{chart.difficulty.name}]', color=color) embed.set_thumbnail(url=self.bot.asset_url + get_asset_filename(song.jacket_path)) embed.set_image(url=self.bot.asset_url + get_asset_filename(chart.mix_path)) note_counts = chart.note_counts mix_info = chart.mix_info embed.add_field(name=l10n.format_value('info'), value=l10n.format_value( 'sections-info-desc', { 'level': chart.display_level, 'unit-name': song.unit.name, 'bpm': song.bpm, 'section-trend': song.section_trend.name, }), inline=False) embed.add_field( name=l10n.format_value('section-begin'), value=l10n.format_value( 'section-desc', { 'time': f'{round(mix_info[ChartSectionType.Begin].duration, 2)}s', 'combo': note_counts[ChartSectionType.Begin].count, }), inline=True) embed.add_field( name=l10n.format_value('section-middle'), value=l10n.format_value( 'section-desc', { 'time': f'{round(mix_info[ChartSectionType.Middle].duration, 2)}s', 'combo': note_counts[ChartSectionType.Middle].count, }), inline=True) embed.add_field( name=l10n.format_value('section-end'), value=l10n.format_value( 'section-desc', { 'time': f'{round(mix_info[ChartSectionType.End].duration, 2)}s', 'combo': note_counts[ChartSectionType.End].count, }), inline=True) embed.set_footer(text=l10n.format_value( 'chart-id', {'chart-id': f'{chart.id:>08}'})) return embed
def get_chart_embed(self, ctx, song: MusicMaster, difficulty): l10n = self.l10n[ctx] difficulty = ChartDifficulty(difficulty + 1) if difficulty not in song.charts: embed = discord.Embed(title=f'{song.name} [{difficulty.name}]', description='No Data') embed.set_thumbnail(url=self.bot.asset_url + get_asset_filename(song.jacket_path)) return embed color_code = song.unit.main_color_code color = discord.Colour.from_rgb(*ImageColor.getcolor( color_code, 'RGB')) if color_code else discord.Embed.Empty chart = song.charts[difficulty] embed = discord.Embed(title=f'{song.name} [{chart.difficulty.name}]', color=color) embed.set_thumbnail(url=self.bot.asset_url + get_asset_filename(song.jacket_path)) embed.set_image(url=self.bot.asset_url + get_asset_filename(chart.image_path)) chart_data = chart.load_chart_data() note_counts = chart_data.get_note_counts() embed.add_field( name=l10n.format_value('info'), value=l10n.format_value( 'chart-info-desc', { 'level': chart.display_level, 'duration': self.format_duration(song.duration), 'unit-name': song.special_unit_name or song.unit.name, 'song-category': song.category.name, 'bpm': song.bpm, 'designer': f'{chart.designer.name} ({chart.designer.id})', 'skills': ', '.join('{:.2f}s'.format(t) if t not in chart_data.info. base_skill_times else '[{:.2f}s]'.format(t) for t in chart_data.info.skill_times), 'fever': f'{chart_data.info.fever_start:.2f}s - {chart_data.info.fever_end:.2f}s' }), inline=False) embed.add_field(name=l10n.format_value('combo'), value=l10n.format_value( 'combo-desc', { 'max-combo': chart.note_counts[ChartSectionType.Full].count, **note_counts, }), inline=True) embed.add_field(name=l10n.format_value('ratings'), value=f'NTS: {round(chart.trends[0] * 100, 2)}%\n' f'DNG: {round(chart.trends[1] * 100, 2)}%\n' f'SCR: {round(chart.trends[2] * 100, 2)}%\n' f'EFT: {round(chart.trends[3] * 100, 2)}%\n' f'TEC: {round(chart.trends[4] * 100, 2)}%\n', inline=True) embed.set_footer(text=l10n.format_value( 'chart-id', {'chart-id': f'{chart.id:>08}'})) return embed
def get_event_embed(self, ctx, event: EventMaster): l10n = self.l10n[ctx] timezone = ctx.preferences.timezone embed = discord.Embed(title=event.name) embed.set_thumbnail(url=self.bot.asset_url + get_asset_filename(event.logo_path)) duration_hour_part = round((event.duration.seconds / 3600), 2) duration_hour_part = duration_hour_part if not duration_hour_part.is_integer() else int(duration_hour_part) duration_hours = round((event.duration.days * 24 + event.duration.seconds / 3600), 2) duration_hours = duration_hours if not duration_hours.is_integer() else int(duration_hours) def fmt_date(date): return fluent_date(date, dateStyle='medium', timeStyle='medium') embed.add_field(name=l10n.format_value('info'), value=l10n.format_value('info-desc', { 'duration-days': event.duration.days, 'duration-hours': duration_hour_part, 'duration-total-hours': duration_hours, 'start-date': fmt_date(event.start_datetime.astimezone(timezone)), 'close-date': fmt_date(event.reception_close_datetime.astimezone(timezone)), 'rank-fix-date': fmt_date(event.rank_fix_start_datetime.astimezone(timezone)), 'results-date': fmt_date(event.result_announcement_datetime.astimezone(timezone)), 'end-date': fmt_date(event.end_datetime.astimezone(timezone)), 'story-unlock-date': fmt_date(event.story_unlock_datetime.astimezone(timezone)), 'status': event.state().name, }), inline=False) embed.add_field(name=l10n.format_value('event-type'), value=l10n.format_value('event-type-name', {'event-type': event.event_type.name}), inline=True) embed.add_field(name=l10n.format_value('bonus-characters'), value='\n'.join( f'{self.bot.get_emoji(unit_emoji_ids_by_unit_id[char.unit_id])} {char.full_name_english}' for char in event.bonus.characters ), inline=True) embed.add_field(name=l10n.format_value('bonus-attribute'), value=f'{self.bot.get_emoji(attribute_emoji_ids_by_attribute_id[event.bonus.attribute_id])} ' f'{event.bonus.attribute.en_name.capitalize()}' if event.bonus.attribute else 'None', inline=True) embed.add_field(name=l10n.format_value('parameter-point-bonus'), value=l10n.format_value('parameter-point-bonus-description', { 'parameter-emoji': f'{self.bot.get_emoji(parameter_bonus_emoji_ids_by_parameter_id[event.bonus.event_point_parameter_bonus_id + 1])}', 'parameter-bonus-rate': event.bonus.event_point_parameter_bonus_rate }) if event.bonus.event_point_parameter_bonus_rate else l10n.format_value('no-parameter-point-bonus'), inline=False) embed.add_field(name=l10n.format_value('point-bonus'), value=l10n.format_value('bonus-description', { 'attribute': f'{self.bot.get_emoji(event_point_emoji_id)} +{event.bonus.attribute_match_point_bonus_value}%' if event.bonus.attribute_match_point_bonus_value else 'None', 'character': f'{self.bot.get_emoji(event_point_emoji_id)} +{event.bonus.character_match_point_bonus_value}%' if event.bonus.character_match_point_bonus_value else 'None', 'both': f'{self.bot.get_emoji(event_point_emoji_id)} +{event.bonus.all_match_point_bonus_value}%' if event.bonus.all_match_point_bonus_value else 'None', }), inline=True) embed.add_field(name=l10n.format_value('parameter-bonus'), value=l10n.format_value('bonus-description', { 'attribute': f'{self.bot.get_emoji(parameter_bonus_emoji_ids_by_parameter_id[event.bonus.attribute_match_parameter_bonus_id])} +{event.bonus.attribute_match_parameter_bonus_value}%' if event.bonus.attribute_match_parameter_bonus_value else 'None', 'character': f'{self.bot.get_emoji(parameter_bonus_emoji_ids_by_parameter_id[event.bonus.character_match_parameter_bonus_id])} +{event.bonus.attribute_match_parameter_bonus_value}%' if event.bonus.attribute_match_parameter_bonus_value else 'None', 'both': f'{self.bot.get_emoji(parameter_bonus_emoji_ids_by_parameter_id[event.bonus.all_match_parameter_bonus_id])} +{event.bonus.all_match_parameter_bonus_value}%' if event.bonus.all_match_parameter_bonus_value else 'None', }), inline=True) embed.set_footer(text=l10n.format_value('event-id', {'event-id': event.id})) return embed
async def pull(self, ctx: PrefContext, *, arg: Optional[ParsedArguments]): if not arg: await ctx.send( 'A gacha id must be given (can be found using the !banner command and checking the footer).' ) return name = arg.text() arg.require_all_arguments_used() if not name.isnumeric(): await ctx.send( 'A gacha id must be given (can be found using the !banner command and checking the footer).' ) return gacha: GachaMaster = self.bot.master_filters.gacha.get_by_id( int(name), ctx) if not gacha: await ctx.send('Unknown banner.') return draw_data = [ d for d in gacha.draw_data if (d.stock_id == 902) or ( d.stock_id in (1, 2) and d.stock_amount == 3000) ] if len(draw_data) != 1: await ctx.send('Unsupported banner.') return draw_data = draw_data[0] tables = gacha.tables tables_rates = [ list(itertools.accumulate(t.rate for t in table)) for table in tables ] cards = [] for draw_amount, table_rate in zip(draw_data.draw_amounts, gacha.table_rates): rates = list(itertools.accumulate(table_rate.rates)) for _i in range(draw_amount): rng = random.randint(1, rates[-1]) table_index = next(i for i, s in enumerate(rates) if rng <= s) table_rates = tables_rates[table_index] rng = random.randint(1, table_rates[-1]) result_index = next(i for i, s in enumerate(table_rates) if rng <= s) cards.append(ctx.assets.card_master[tables[table_index] [result_index].card_id]) bonus = None current_pity = None if gacha.bonus_max_value: pity_data, _ = await PityCount.get_or_create(user_id=ctx.author.id, gacha_id=gacha.id) pity_data.counter += 10 current_pity = pity_data.counter if pity_data.counter >= gacha.bonus_max_value: bonus_tables = gacha.bonus_tables pity_data.counter -= gacha.bonus_max_value current_pity = pity_data.counter rates = list(itertools.accumulate( gacha.bonus_table_rate.rates)) rng = random.randint(1, rates[-1]) table_index = next(i for i, s in enumerate(rates) if rng <= s) table_rates = list( itertools.accumulate(t.rate for t in bonus_tables[table_index])) rng = random.randint(1, table_rates[-1]) result_index = next(i for i, s in enumerate(table_rates) if rng <= s) bonus = ctx.assets.card_master[bonus_tables[table_index] [result_index].card_id] await pity_data.save() img = await self.bot.loop.run_in_executor( self.bot.thread_pool, functools.partial(self.create_pull_image, cards, bonus)) buffer = BytesIO() img.save(buffer, 'png') buffer.seek(0) embed = discord.Embed(title=gacha.name) thumb_url = self.bot.asset_url + get_asset_filename(gacha.banner_path) embed.set_thumbnail(url=thumb_url) embed.set_image(url='attachment://pull.png') if current_pity is not None: embed.description = f'Pity: {current_pity}/{gacha.bonus_max_value}' await ctx.send(embed=embed, file=discord.File(fp=buffer, filename='pull.png'))
def get_gacha_table_embed(self, ctx, gacha: GachaMaster): l10n = self.l10n[ctx] embed = discord.Embed(title=gacha.name) thumb_url = self.bot.asset_url + get_asset_filename(gacha.banner_path) embed.set_thumbnail(url=thumb_url) def add_table_field(table_rate: GachaTableRateMaster, tables: Sequence[Sequence[GachaTableMaster]]): body = '' body_short = '' for table_normalized_rate, table in zip( table_rate.normalized_rates, tables): if table_normalized_rate == 0: continue rates = [t.rate for t in table] total_rate = sum(rates) rate_up_rate = max(rates) # Exclude tables with no rate up, except those with very few rate ups (mainly for pity pull) if rate_up_rate == min( rates) and rate_up_rate / total_rate < 0.05: continue rate_up_card_entries = [ t for t in table if t.rate == rate_up_rate ] for entry in rate_up_card_entries: body += f'`{table_normalized_rate * entry.rate / total_rate * 100: >6.3f}% {self.format_card_name_for_list(entry.card)}`\n' body_short += f'`{table_normalized_rate * entry.rate / total_rate * 100: >6.3f}% {self.format_card_name_short(entry.card)}`\n' if len(body) == 0: embed.add_field( name=table_rate.tab_name, value=f'`{l10n.format_value("none-or-too-many")}`', inline=False) elif len(body) <= 1000: embed.add_field(name=table_rate.tab_name, value=body, inline=False) elif len(body_short) <= 1000: embed.add_field(name=table_rate.tab_name, value=body_short, inline=False) else: embed.add_field(name=table_rate.tab_name, value=f'`{l10n.format_value("too-many")}`', inline=False) for table_rate in gacha.table_rates: add_table_field(table_rate, gacha.tables) if gacha.bonus_tables: add_table_field(gacha.bonus_table_rate, gacha.bonus_tables) if not embed.fields: embed.description = l10n.format_value("none-or-too-many") return embed