diff --git a/cogs/admins.py b/cogs/admins.py index 6608625..2311b52 100644 --- a/cogs/admins.py +++ b/cogs/admins.py @@ -3,6 +3,7 @@ import json import db_calls import db_calls_gameplay +from gauntlets import evolve_pokemon from helpers import * from db_calls import * from discord import Member @@ -585,5 +586,12 @@ class Admins(commands.Cog): await ctx.send(f'Here is your dropdown:', view=view) + @commands.command(name='test_evo', help='Mod: Test pokemon evolution') + @commands.is_owner() + async def test_evolution(self, ctx, team_abbrev: str): + this_team = await get_team_by_abbrev(team_abbrev) + await evolve_pokemon(this_team, ctx.channel) + + async def setup(bot): await bot.add_cog(Admins(bot)) diff --git a/cogs/gameplay.py b/cogs/gameplay.py index 3ee3221..5d370b3 100644 --- a/cogs/gameplay.py +++ b/cogs/gameplay.py @@ -2420,392 +2420,6 @@ class Gameplay(commands.Cog): f'{get_channel(interaction, "pd-news-ticker").mention}!' ) - """ - END OF THE NEW GAME END - """ - - # away_team = await db_get('teams', object_id=this_game.away_team_id) - # home_team = await db_get('teams', object_id=this_game.home_team_id) - # - # away_stats = { - # # 'p_lines': get_pitching_stats(this_game.id, team_id=away_team['id']), - # 'p_lines': [], - # 'b_lines': get_batting_stats(this_game.id, team_id=away_team['id']), - # 'f_lines': get_fielding_stats(this_game.id, team_id=away_team['id']) - # } - # home_stats = { - # # 'p_lines': get_pitching_stats(this_game.id, team_id=home_team['id']), - # 'p_lines': [], - # 'b_lines': get_batting_stats(this_game.id, team_id=home_team['id']), - # 'f_lines': get_fielding_stats(this_game.id, team_id=home_team['id']), - # # 'score': away_stats['p_lines'][0]['tm_runs'] - # } - # - # logging.debug(f'away_stats: {away_stats}\n\nhome_stats: {home_stats}') - # - # away_pitchers = await get_team_lineups( - # this_game.id, team_id=away_team['id'], inc_inactive=True, pitchers_only=True, as_string=False - # ) - # for line in away_pitchers: - # try: - # # logging.info(f'line: {line}') - # this_stats = get_pitching_stats(this_game.id, lineup_id=line.id) - # # logging.info(f'away / this_stats: {this_stats}') - # away_stats['p_lines'].extend(this_stats) - # if 'score' not in home_stats: - # # logging.info(f'score not in home_stats') - # home_stats['score'] = this_stats[0]['pl_runs'] - # else: - # # logging.info(f'score is in home_stats') - # home_stats['score'] += this_stats[0]['pl_runs'] - # if 'hits' not in home_stats: - # # logging.info(f'hits not in home_stats') - # home_stats['hits'] = this_stats[0]['pl_hit'] - # else: - # # logging.info(f'hits is in home_stats') - # home_stats['hits'] += this_stats[0]['pl_hit'] - # except Exception as e: - # bad_player = await get_player(this_game, line) - # logging.error(f'Unable to process stats for card_id {line.card_id} in Game {this_game.id}: ' - # f'{type(e)}: {e}') - # await interaction.edit_original_response( - # content=f'I was not able to process stats for {bad_player["name"]} - ' - # f'{get_cal_user(interaction).mention} help!') - # - # home_pitchers = await get_team_lineups( - # this_game.id, team_id=home_team['id'], inc_inactive=True, pitchers_only=True, as_string=False - # ) - # for line in home_pitchers: - # try: - # # logging.info(f'line: {line}') - # this_stats = get_pitching_stats(this_game.id, lineup_id=line.id) - # # logging.info(f'home / this_stats: {this_stats}') - # home_stats['p_lines'].extend(this_stats) - # if 'score' not in away_stats: - # # logging.info(f'score not in away_stats') - # away_stats['score'] = this_stats[0]['pl_runs'] - # else: - # # logging.info(f'score is in away_stats') - # away_stats['score'] += this_stats[0]['pl_runs'] - # if 'hits' not in away_stats: - # # logging.info(f'hits not in away_stats') - # away_stats['hits'] = this_stats[0]['pl_hit'] - # else: - # # logging.info(f'hits is in away_stats') - # away_stats['hits'] += this_stats[0]['pl_hit'] - # except Exception as e: - # bad_player = await get_player(this_game, line) - # logging.error(f'Unable to process stats for card_id {line.card_id} in Game {this_game.id}: ' - # f'{type(e)}: {e}') - # await interaction.edit_original_response( - # content=f'I was not able to process stats for {bad_player["name"]} - ' - # f'{get_cal_user(interaction).mention} help!' - # ) - # - # logging.debug(f'finished tallying pitcher stats') - # - # # away_stats['score'] = home_stats['p_lines'][0]['tm_runs'] - # try: - # decisions = get_pitching_decisions(this_game) - # except AttributeError as e: - # logging.error(f'Could not pull decisions for Game {this_game.id}: {e}') - # await interaction.edit_original_response( - # content=f'I was not able to calculate the Winning and Losing Pitcher for this game. Is the game ' - # f'ending early? {get_cal_user(interaction).mention} can probably help.' - # ) - # return - # logging.debug(f'decisions: {decisions}') - # - # winning_team = away_team if away_stats['score'] > home_stats['score'] else home_team - # losing_team = away_team if away_stats['score'] < home_stats['score'] else home_team - # - # # Post Game Rewards - # r_data = await self.post_rewards(winning_team, losing_team, this_game) - # win_reward = r_data['win_string'] - # loss_reward = r_data['loss_string'] - # - # # Check for Gauntlet game so rewards go to main team - # if gauntlet_team is not None: - # if winning_team['gmid'] == gauntlet_team['gmid']: - # winning_team = owner_team - # else: - # losing_team = owner_team - # - # logging.debug(f'away_stats (in /endgame function)\n\n{away_stats}') - # logging.debug(f'home_stats (in /endgame function)\n\n{home_stats}') - # logging.debug(f'winning_team: {winning_team}\nlosing_team: {losing_team}') - # - # logging.debug(f'Time to build statlines and submit the scorecard for this PD game!') - # # Post result - # success = await db_post( - # 'results', - # payload={ - # 'away_team_id': this_game.away_team_id, - # 'home_team_id': this_game.home_team_id, - # 'away_score': away_stats['score'], - # 'home_score': home_stats['score'], - # 'away_team_ranking': away_team['ranking'], - # 'home_team_ranking': home_team['ranking'], - # 'scorecard': f'Bot Game {this_game.id}', - # 'week': this_game.week_num, - # 'season': this_game.season, - # 'ranked': this_game.ranked, - # 'short_game': this_game.short_game, - # 'game_type': this_game.game_type - # } - # ) - # # Submit the stats - # batter_stats = [] - # pitcher_stats = [] - # doubles = '' - # triples = '' - # homers = '' - # s_bases = '' - # caught_s = '' - # - # for line in [*away_stats['b_lines'], *home_stats['b_lines']]: - # if line['pl_double']: - # if len(doubles): - # doubles += ', ' - # card = await db_get("cards", object_id=line["card_id"]) - # doubles += f'{card["player"]["p_name"]}' \ - # f'{" " if line["pl_double"] > 1 else ""}' \ - # f'{line["pl_double"] if line["pl_double"] > 1 else ""}' - # if line['pl_triple']: - # if len(triples): - # triples += ', ' - # card = await db_get("cards", object_id=line["card_id"]) - # triples += f'{card["player"]["p_name"]}' \ - # f'{" " if line["pl_triple"] > 1 else ""}' \ - # f'{line["pl_triple"] if line["pl_triple"] > 1 else ""}' - # if line['pl_homerun']: - # if len(homers): - # homers += ', ' - # card = await db_get("cards", object_id=line["card_id"]) - # homers += f'{card["player"]["p_name"]}' \ - # f'{" " if line["pl_homerun"] > 1 else ""}' \ - # f'{line["pl_homerun"] if line["pl_homerun"] > 1 else ""}' - # if line['pl_sb']: - # if len(s_bases): - # s_bases += ', ' - # card = await db_get("cards", object_id=line["card_id"]) - # s_bases += f'{card["player"]["p_name"]}' \ - # f'{" " if line["pl_sb"] > 1 else ""}' \ - # f'{line["pl_sb"] if line["pl_sb"] > 1 else ""}' - # batter_stats.append( - # { - # 'card_id': line['card_id'], - # 'team_id': line['team_id'], - # 'roster_num': - # this_game.away_roster_num if this_game.away_team_id == line['team_id'] - # else this_game.home_roster_num, - # 'vs_team_id': - # home_team['id'] if this_game.away_team_id == line['team_id'] - # else away_team['id'], - # 'pos': line['pos'], - # 'pa': line['pl_pa'], - # 'ab': line['pl_ab'], - # 'run': line['pl_run'], - # 'rbi': line['pl_rbi'], - # 'hit': line['pl_hit'], - # 'double': line['pl_double'], - # 'triple': line['pl_triple'], - # 'hr': line['pl_homerun'], - # 'bb': line['pl_bb'], - # 'so': line['pl_so'], - # 'hbp': line['pl_hbp'], - # 'sac': line['pl_sac'], - # 'ibb': line['pl_ibb'], - # 'gidp': line['pl_gidp'], - # 'sb': line['pl_sb'], - # 'cs': line['pl_cs'], - # 'bphr': line['pl_bphr'], - # 'bpfo': line['pl_bpfo'], - # 'bp1b': line['pl_bp1b'], - # 'bplo': line['pl_bplo'], - # 'week': this_game.week_num, - # 'season': this_game.season, - # 'game_id': this_game.id, - # } - # ) - # - # for line in [*away_stats['f_lines'], *home_stats['f_lines']]: - # if line['pl_csc']: - # if len(caught_s): - # caught_s += ', ' - # card = await db_get("cards", object_id=line["card_id"]) - # caught_s += f'{card["player"]["p_name"]}' \ - # f'{" " if line["pl_csc"] > 1 else ""}' \ - # f'{line["pl_csc"] if line["pl_csc"] > 1 else ""}' - # batter_stats.append( - # { - # 'card_id': line['card_id'], - # 'team_id': line['team_id'], - # 'roster_num': - # this_game.away_roster_num if this_game.away_team_id == line['team_id'] - # else this_game.home_roster_num, - # 'vs_team_id': - # home_team['id'] if this_game.away_team_id == line['team_id'] - # else away_team['id'], - # 'pos': line['pos'], - # 'xch': line['pl_xch'], - # 'xhit': line['pl_xhit'], - # 'error': line['pl_error'], - # 'pb': line['pl_pb'], - # 'sbc': line['pl_sbc'], - # 'csc': line['pl_csc'], - # 'week': this_game.week_num, - # 'season': this_game.season, - # 'game_id': this_game.id - # } - # ) - # - # for line in [*away_stats['p_lines'], *home_stats['p_lines']]: - # pitcher_stats.append( - # { - # 'card_id': line['card_id'], - # 'team_id': line['team_id'], - # 'roster_num': - # this_game.away_roster_num if this_game.away_team_id == line['team_id'] - # else this_game.home_roster_num, - # 'vs_team_id': - # home_team['id'] if this_game.away_team_id == line['team_id'] - # else away_team['id'], - # 'ip': (math.floor(line['pl_outs'] / 3) * 1.0) + ((line['pl_outs'] % 3) / 3.0), - # 'hit': line['pl_hit'], - # 'run': line['pl_runs'], - # 'erun': line['pl_eruns'], - # 'so': line['pl_so'], - # 'bb': line['pl_bb'], - # 'hbp': line['pl_hbp'], - # 'wp': line['pl_wild_pitch'], - # 'balk': line['pl_balk'], - # 'hr': line['pl_homerun'], - # 'ir': 0, - # 'irs': 0, - # 'gs': 1 if line['card_id'] in decisions['starters'] else 0, - # 'win': 1 if line['card_id'] == decisions['winner'] else 0, - # 'loss': 1 if line['card_id'] == decisions['loser'] else 0, - # 'hold': 1 if line['card_id'] in decisions['holds'] else 0, - # 'sv': 1 if line['card_id'] == decisions['save'] else 0, - # 'bsv': 1 if line['card_id'] in decisions['b_save'] else 0, - # 'week': this_game.week_num, - # 'season': this_game.season, - # 'game_id': this_game.id - # } - # ) - # - # doubles += '\n' if len(doubles) else '' - # triples += '\n' if len(triples) else '' - # homers += '\n' if len(homers) else '' - # s_bases += '\n' if len(s_bases) else '' - # caught_s += '\n' if len(caught_s) else '' - # - # await db_post('batstats', payload={'stats': batter_stats}) - # await db_post('pitstats', payload={'stats': pitcher_stats}) - # - # # Post a notification to PD - # last_play = get_current_play(this_game.id) - # inning = f'{last_play.inning_num if last_play.inning_half == "Bot" else last_play.inning_num - 1}' - # embed = get_team_embed( - # f'{away_team["lname"]} {away_stats["score"]} @ {home_stats["score"]} {home_team["lname"]} - F/' - # f'{inning}', - # winning_team - # ) - # - # if this_game.game_type == 'major-league': - # game_des = 'Major League' - # elif this_game.game_type == 'minor-league': - # game_des = 'Minor League' - # elif this_game.game_type == 'hall-of-fame': - # game_des = 'Hall of Fame' - # elif this_game.ranked: - # game_des = 'Ranked' - # elif 'gauntlet' in this_game.game_type: - # game_des = 'Gauntlet' - # else: - # game_des = 'Unlimited' - # embed.description = f'Score Report - {game_des} ' \ - # f'{"- 3-Inning Game" if this_game.short_game else " - 9-Inning Game"}' - # embed.add_field( - # name='Box Score', - # value=get_final_scorebug(away_team, home_team, away_stats, home_stats), - # inline=False - # ) - # embed.add_field( - # name='Location', - # value=f'{interaction.guild.get_channel(this_game.channel_id).mention}' - # ) - # wc_query = await db_get("cards", object_id=decisions["winner"]) - # lc_query = await db_get("cards", object_id=decisions["loser"]) - # if decisions["save"]: - # sv_query = await db_get("cards", object_id=decisions["save"]) - # embed.add_field( - # name='Pitching', - # value=f'Win: {wc_query["player"]["p_name"]}\n' - # f'Loss: {lc_query["player"]["p_name"]}\n' - # f'{"Save: " if decisions["save"] else ""}' - # f'{sv_query["player"]["p_name"] if decisions["save"] else ""}', - # inline=False - # ) - # if len(doubles) + len(triples) + len(homers) > 0: - # embed.add_field( - # name='Batting', - # value=f'{"2B: " if len(doubles) else ""}{doubles if len(doubles) else ""}' - # f'{"3B: " if len(triples) else ""}{triples if len(triples) else ""}' - # f'{"HR: " if len(homers) else ""}{homers if len(homers) else ""}', - # inline=False - # ) - # if len(s_bases) + len(caught_s) > 0: - # embed.add_field( - # name='Baserunning', - # value=f'{"SB: " if len(s_bases) else ""}{s_bases if len(s_bases) else ""}' - # f'{"CSc: " if len(caught_s) else ""}{caught_s if len(caught_s) else ""}', - # inline=False - # ) - # embed.add_field( - # name=f'{winning_team["abbrev"]} Rewards', - # value=win_reward - # ) - # embed.add_field( - # name=f'{losing_team["abbrev"]} Rewards', - # value=loss_reward - # ) - # embed.add_field( - # name='Highlights', - # value=f'Please share the highlights in {get_channel(interaction, "pd-news-ticker").mention}!', - # inline=False - # ) - # await send_to_channel(self.bot, 'pd-network-news', embed=embed) - # - # # Gauntlet results and reward - # if gauntlet_team is not None: - # await gauntlets.post_result( - # int(this_game.game_type.split('-')[3]), - # is_win=winning_team['gmid'] == gauntlet_team['gmid'], - # this_team=gauntlet_team, - # bot=self.bot, - # channel=interaction.channel - # ) - # - # this_run = await db_get('gauntletruns', object_id=int(this_game.game_type.split('-')[3])) - # if this_run['losses'] == 2: - # await send_to_channel( - # bot=self.bot, - # channel_name='pd-network-news', - # content=f'The {gauntlet_team["lname"]} won {this_run["wins"]} games before failing in the ' - # f'{this_run["gauntlet"]["name"]} gauntlet.', - # embed=None - # ) - # - # patch_game(this_game.id, active=False) - # - # logging.info(f'Game {this_game.id} is complete') - # if gauntlet_team is None: - # await interaction.edit_original_response( - # content=f'Good game! Go share the highlights in ' - # f'{get_channel(interaction, "pd-news-ticker").mention}!' - # ) @app_commands.command( name='read-lineup', diff --git a/gauntlets.py b/gauntlets.py index ab28b9e..96417b1 100644 --- a/gauntlets.py +++ b/gauntlets.py @@ -2,13 +2,16 @@ import copy import datetime import logging import random +from typing import Literal import discord +from discord import SelectOption from in_game import ai_manager import helpers from helpers import RARITY, get_or_create_role, send_to_channel, get_channel from db_calls import db_get, db_post, db_delete, db_patch +from utilities.dropdown import DropdownView, SelectPokemonEvolution async def wipe_team(this_team, interaction: discord.Interaction, delete_team: bool = False, delete_runs: bool = False): @@ -212,6 +215,31 @@ async def get_opponent(this_team, this_event, this_run): else: raise KeyError(f'Hmm...I do not know who you should be playing right now.') return await db_get('teams', object_id=t_id, none_okay=False) + elif this_event['id'] == 7: + if gp == 0: + t_id = 10 + elif gp == 1: + t_id = 20 + elif gp == 2: + t_id = 25 + elif gp == 3: + t_id = 27 + elif gp == 4: + t_id = 6 + elif gp == 5: + t_id = 5 + elif gp == 6: + t_id = 4 + elif gp == 7: + t_id = 16 + elif gp == 8: + t_id = 13 + elif gp == 9: + t_id = 30 + elif gp == 10: + t_id = 17 + else: + raise KeyError(f'Hmm...I do not know who you should be playing right now.') else: return None @@ -237,70 +265,6 @@ async def get_starting_pitcher(this_team, this_game, this_event, this_run): 'after_play': 0 } - # if this_event['id'] == 1: - # if this_team['id'] != 58: - # set_params = copy.deepcopy(ai_manager.MINOR_CARDSET_PARAMS) - # else: - # set_params = [] - # params = [ - # ('mlbclub', this_team['lname']), ('pos_include', 'SP'), ('pos_exclude', 'RP'), - # ('inc_dex', False), ('sort_by', 'cost-desc'), ('limit', 5) - # ] - # params.extend(set_params) - # elif this_event['id'] == 2: - # set_params = copy.deepcopy(ai_manager.GAUNTLET2_PARAMS) - # params = [ - # ('mlbclub', this_team['lname']), ('pos_include', 'SP'), ('pos_exclude', 'RP'), - # ('inc_dex', False), ('sort_by', 'cost-desc'), ('limit', 5) - # ] - # params.extend(set_params) - # else: - # raise KeyError(f'Pitcher not found for Gauntlet {this_event["id"]}') - # - # params = [ - # ('mlbclub', this_team['lname']), ('pos_include', 'SP'), ('pos_exclude', 'RP'), - # ('inc_dex', False), ('sort_by', 'cost-desc'), ('limit', 5) - # ] - # counter = 0 - # while True: - # counter += 1 - # # Pull starters sorted by current cost - # try: - # params.extend(set_params) - # pitchers = await db_get( - # endpoint='players', - # params=params, - # timeout=10 - # ) - # except ConnectionError as e: - # logging.error(f'Could not get pitchers for {this_team["lname"]}: {e}') - # raise ConnectionError(f'Error pulling starting pitchers for the {this_team["lname"]}. Cal help plz.') - # - # if pitchers['count'] == 0: - # logging.info(f'pitchers is None') - # del params - # params = [ - # ('mlbclub', this_team['lname']), ('pos_include', 'SP'), - # ('inc_dex', False), ('sort_by', 'cost-desc'), ('limit', 5) - # ] - # elif counter > 2: - # raise KeyError(f'Error pulling pitchers for the {this_team["lname"]}. Cal help plz.') - # else: - # break - # - # pitcher_num = games_played(this_run) % 5 - # card_id = await ai_manager.get_or_create_card(pitchers['players'][pitcher_num], this_team) - # - # return { - # 'game_id': this_game.id, - # 'team_id': this_team['id'], - # 'player_id': pitchers['players'][0]['player_id'], - # 'card_id': card_id, - # 'position': 'P', - # 'batting_order': 10, - # 'after_play': 0 - # } - async def run_draft(interaction: discord.Interaction, main_team, this_event, draft_team=None): if this_event['id'] == 1: @@ -332,6 +296,10 @@ async def run_draft(interaction: discord.Interaction, main_team, this_event, dra embed_description = f'{this_event["name"]}' base_params = [('cardset_id', 20), ('cardset_id', 21), ('cardset_id', 22), ('cardset_id', 16), ('cardset_id', 8), ('limit', 8)] + elif this_event['id'] == 7: + embed_title = f'{main_team['lname']} - {this_event['name']} Draft' + embed_description = f'{this_event["name"]}' + base_params = [('cardset_id', 5), ('cardset_id', 1), ('cardset_id', 3), ('cardset_id', 4), ('cardset_id', 23), ('cardset_id', 22), ('limit', 4)] else: logging.error(f'run_draft - Event ID {this_event["id"]} not recognized') raise KeyError(f'Draft data not found for Gauntlet {this_event["id"]}') @@ -586,19 +554,30 @@ async def run_draft(interaction: discord.Interaction, main_team, this_event, dra for z in helpers.get_all_pos(y): all_str[z] += f'{name_string}\n' - top_embed.add_field(name=f'HoFs ({counts["Hall of Fame"]}/{max_counts["Hall of Fame"]})', value=all_str['Hall of Fame'], inline=False) - top_embed.add_field(name=f'MVPs ({counts["MVP"]}/{max_counts["MVP"]})', value=all_str['MVP'], inline=False) - top_embed.add_field( - name=f'All-Stars ({counts["All-Star"]}/{max_counts["All-Star"]})', value=all_str['All-Star'], inline=False) - top_embed.add_field( - name=f'Starters ({counts["Starter"]}/{max_counts["Starter"]})', value=all_str['Starter'], inline=False) - top_embed.add_field( - name=f'Reserves ({counts["Reserve"]}/{max_counts["Reserve"]})', value=all_str['Reserve'], inline=False) - top_embed.add_field( - name=f'Replacements ({counts["Replacement"]}/{max_counts["Replacement"]})', - value=all_str['Replacement'], - inline=False - ) + if max_counts['Hall of Fame'] > 0: + top_embed.add_field(name=f'HoFs ({counts["Hall of Fame"]}/{max_counts["Hall of Fame"]})', value=all_str['Hall of Fame'], inline=False) + + if max_counts['MVP'] > 0: + top_embed.add_field(name=f'MVPs ({counts["MVP"]}/{max_counts["MVP"]})', value=all_str['MVP'], inline=False) + + if max_counts['All-Star'] > 0: + top_embed.add_field( + name=f'All-Stars ({counts["All-Star"]}/{max_counts["All-Star"]})', value=all_str['All-Star'], inline=False) + + if max_counts['Starter'] > 0: + top_embed.add_field( + name=f'Starters ({counts["Starter"]}/{max_counts["Starter"]})', value=all_str['Starter'], inline=False) + + if max_counts['Reserve'] > 0: + top_embed.add_field( + name=f'Reserves ({counts["Reserve"]}/{max_counts["Reserve"]})', value=all_str['Reserve'], inline=False) + + if max_counts['Replacement'] > 0: + top_embed.add_field( + name=f'Replacements ({counts["Replacement"]}/{max_counts["Replacement"]})', + value=all_str['Replacement'], + inline=False + ) bot_embed.add_field(name=f'Catcher', value=all_str['C'], inline=False) bot_embed.add_field(name=f'First Base', value=all_str['1B'], inline=False) @@ -1507,6 +1486,164 @@ async def run_draft(interaction: discord.Interaction, main_team, this_event, dra await last_message.edit(content=None, embeds=get_embeds(include_links=False)) elif this_event['id'] in [5, 6]: await draft_loop() + elif this_event['id'] == 7: + round_num = 1 + counter = 0 + while round_num <= 26 and counter < 50: + counter += 1 + params = copy.deepcopy(base_params) + logging.info(f'gauntlets.py - run_draft - event_id {this_event["id"]} / round_num: {round_num} / counter: {counter} / counts: {counts} / max_counts: {max_counts}') + + # Set rarity based on remaining counts + if counts['Hall of Fame'] < max_counts['Hall of Fame']: + params.extend([ + ('min_rarity', RARITY['HoF']), ('max_rarity', RARITY['HoF']) + ]) + elif counts['MVP'] < max_counts['MVP']: + params.extend([ + ('min_rarity', RARITY['MVP']), ('max_rarity', RARITY['MVP']) + ]) + elif counts['All-Star'] < max_counts['All-Star']: + params.extend([ + ('min_rarity', RARITY['All-Star']), ('max_rarity', RARITY['All-Star']) + ]) + elif counts['Starter'] < max_counts['Starter']: + if counts['Starter'] < 5: + params = [('cardset_id', 23), ('limit', 16)] + + params.extend([ + ('min_rarity', RARITY['Starter']), ('max_rarity', RARITY['Starter']) + ]) + elif counts['Reserve'] < max_counts['Reserve']: + params.extend([ + ('min_rarity', RARITY['Reserve']), ('max_rarity', RARITY['Reserve']) + ]) + else: + params.extend([ + ('min_rarity', RARITY['Replacement']), ('max_rarity', RARITY['Replacement']) + ]) + + this_batch = [] + for x in ['SP', 'RP', 'IF', 'OF']: + # Slot 1 - SP + if x == 'SP': + if counts['SP'] > 5: + slot_params = [('pos_exc', 'SP')] + if counts['RP'] > 7: + slot_params = [('pos_exc', 'RP')] + else: + slot_params = [('pos_inc', 'SP')] + # if counts['SP'] > 5: + # slot_params = [('pos_exc', 'SP')] + # elif counts['RP'] < 6: + # slot_params = [('pos_inc', 'RP')] + # else: + # slot_params = [('pos_exc', 'SP'), ('pos_exc', 'RP')] + + # Slot 2 - RP + elif x == 'RP': + logging.info(f'counts[RP]: {counts["RP"]}') + if counts['RP'] > 7: + slot_params = [('pos_exc', 'RP')] + if counts['SP'] > 5: + slot_params = [('pos_exc', 'SP')] + else: + slot_params = [('pos_inc', 'RP')] + + # Slot 3 - IF + elif x == 'IF': + slot_params = [] + for y in ['1B', '2B', '3B', 'SS', 'C']: + if (counts[y] > 1) and 0 in [ + counts['C'], counts['1B'], counts['2B'], counts['3B'], counts['SS'] + ]: + slot_params.append(('pos_exc', y)) + elif (y == 'C' and counts['C'] < 3) or (y != 'C' and counts[y] < 4): + slot_params.append(('pos_inc', y)) + # if counts['C'] < 2: + # slot_params.append(('pos_inc', 'C')) + # if len(slot_params) == 0: + # slot_params = [('pos_exc', 'C'), ('pos_exc', '1B'), ('pos_exc', '2B'), ('pos_exc', '3B'), + # ('pos_exc', 'SS')] + + # Slot 4 - OF + else: + slot_params = [] + for y in ['LF', 'CF', 'RF']: + if counts[y] > 4: + slot_params.append(('pos_exc', y)) + elif counts[y] > 1 and 0 in [counts['LF'], counts['CF'], counts['RF']]: + slot_params.append(('pos_exc', y)) + elif counts[y] < 5: + slot_params.append(('pos_inc', y)) + # if len(slot_params) == 0: + # slot_params = [('pos_exc', 'LF'), ('pos_exc', 'CF'), ('pos_exc', 'RF')] + + logging.info(f'this_batch: {this_batch}') + logging.info(f'slot_params: {slot_params}') + logging.info(f'params: {params}') + + # No position explicitly requested or denied + if len(slot_params) == 0: + if (counts['SP'] + counts['RP']) > ( + counts['C'] + counts['1B'] + counts['2B'] + counts['SS'] + counts['LF'] + counts['CF'] + + counts['RF']): + pos_counts = [ + ('C', counts['C']), ('1B', counts['1B']), ('2B', counts['2B']), ('3B', counts['3B']), + ('SS', counts['SS']), ('LF', counts['LF']), ('CF', counts['CF']), ('RF', counts['RF']) + ] + pos_counts.sort(key=lambda z: z[1]) + slot_params = [('pos_inc', pos_counts[0][0])] + else: + if counts['SP'] >= counts['RP']: + slot_params = [('pos_inc', 'RP')] + else: + slot_params = [('pos_inc', 'SP')] + + slot_params.extend(params) + p_query = await db_get('players/random', params=slot_params) + + if p_query['count'] > 0: + for i in p_query['players']: + if i['p_name'] not in p_names and i not in this_batch: + if i['cardset']['id'] == 23 and '420420' not in i['strat_code']: + pass + else: + this_batch.append(i) + break + + if len(this_batch) < 4: + logging.error(f'Pulled less than 4 players in gauntlet draft') + p_query = await db_get('players/random', params=params) + for i in p_query['players']: + if i['p_name'] not in p_names and i not in this_batch: + this_batch.append(i) + if len(this_batch) >= 4: + break + + if len(this_batch) < 4: + raise KeyError(f'This is embarassing, but I couldn\'t find enough players for you to draft from.') + + # Present choices and capture selection + p_choice = await helpers.get_choice_from_cards(interaction, this_batch, delete_message=True) + + # Add player to list and update counts + p_names.append(p_choice['p_name']) + counts[p_choice['rarity']['name']] += 1 + + all_players.append(p_choice) + + if p_choice['pos_1'] in ['SP', 'RP']: + counts[p_choice['pos_1']] += 1 + else: + for x in helpers.get_all_pos(p_choice): + if x in counts: + counts[x] += 1 + + # Update roster embed + round_num += 1 + await last_message.edit(content=None, embeds=get_embeds(include_links=False, round_num=round_num)) + else: logging.error(f'run_draft - No draft logic for Event ID {this_event["id"]}') raise KeyError(f'Draft data not found for Gauntlet {this_event["id"]}') @@ -1634,6 +1771,28 @@ async def end_run(this_run, this_event, this_team): return l_message +async def evolve_pokemon(this_team, channel): + c_query = await db_get( + 'cards', + params=[('team_id', this_team['id']), ('order_by', 'new'), ('limit', 26)] + ) + evolvable_mons = [x for x in c_query['cards'] if x['player']['cardset']['id'] in [23] and x['player']['fangr_id'] is not None and len(x['player']['fangr_id']) > 3] + + if len(evolvable_mons) > 0: + evo_target_options = [ + SelectOption(label=f'{x["player"]["rarity"]["name"]} | {x["player"]["p_name"]}', value=x['id']) for x in evolvable_mons + ] + view = DropdownView( + dropdown_objects=[SelectPokemonEvolution(options=evo_target_options, this_team=this_team)] + ) + await channel.send( + content='What? One of your pokemon is ready to evolve!\n\n-# The selected pokemon will be removed from your team and replaced with its evolution', + view=view + ) + else: + await channel.send('All of your Pokemon are fully evolved!') + + async def post_result(run_id: int, is_win: bool, this_team, bot, channel): this_run = await db_get('gauntletruns', object_id=run_id) this_event = await db_get('events', object_id=this_run['gauntlet']['id']) @@ -1678,7 +1837,7 @@ async def post_result(run_id: int, is_win: bool, this_team, bot, channel): team_id = None if x['reward']['pack_type']['id'] == 9: cardset_id = 18 - else: + elif this_event['id'] == 6: cardset_id = 20 team_id = None if x['reward']['pack_type']['id'] == 9: @@ -1692,6 +1851,19 @@ async def post_result(run_id: int, is_win: bool, this_team, bot, channel): }]} ) reward_string += f'- 1x {x["reward"]["pack_type"]["name"]} Pack' + elif x['reward']['pack_type'] and this_event['id'] in [7]: + if this_event['id'] == 7: + cardset_id = 23 + team_id = 91 + await db_post( + 'packs', payload={'packs': [{ + 'team_id': main_team['id'], + 'pack_type_id': x['reward']['pack_type']['id'], + 'pack_cardset_id': cardset_id if x['reward']['pack_type']['id'] != 8 else None, + 'pack_team_id': team_id if x['reward']['pack_type']['id'] == 8 else None + }]} + ) + reward_string += f'- 1x {x["reward"]["pack_type"]["name"]} Pack' elif x['reward']['pack_type']: await helpers.give_packs(main_team, 1, x['reward']['pack_type']) reward_string += f'- 1x {x["reward"]["pack_type"]["name"]} Pack' @@ -1744,4 +1916,9 @@ async def post_result(run_id: int, is_win: bool, this_team, bot, channel): embed=await get_embed(this_run) ) + # Evolve a card! + if this_run['id'] == 7 and this_run['wins'] < 10 and this_run['losses'] < 2: + await evolve_pokemon(this_team, channel) + + diff --git a/helpers.py b/helpers.py index 1c0beee..18b285e 100644 --- a/helpers.py +++ b/helpers.py @@ -27,6 +27,7 @@ PD_PLAYERS = 'Paper Dynasty Players' SBA_PLAYERS_ROLE_NAME = f'Season {SBA_SEASON} Players' PD_PLAYERS_ROLE_NAME = f'Paper Dynasty Players' PD_CARD_URL = 'https://sombaseball.ddns.net/cards/pd' +PKMN_REF_URL = 'https://pkmncards.com/card/' RATINGS_BATTER_FORMULA = '=IMPORTRANGE("1zDmlOw94gTzOAjqOpNdDZsg0O6rxNWkL4-XT6-iL2IE","guide_Batters!A1:CD")' RATINGS_PITCHER_FORMULA = '=IMPORTRANGE("1zDmlOw94gTzOAjqOpNdDZsg0O6rxNWkL4-XT6-iL2IE","guide_Pitchers!A1:BQ")' RATINGS_SHEET_KEY = '1zDmlOw94gTzOAjqOpNdDZsg0O6rxNWkL4-XT6-iL2IE' @@ -68,6 +69,7 @@ IMAGES = { 'pack-sta': f'{PD_CARD_URL}/pack-standard.png', 'pack-pre': f'{PD_CARD_URL}/pack-premium.png', 'pack-mar': f'{PD_CARD_URL}/sluggers/mario-gauntlet.png', + 'pack-pkmnbs': f'https://i.postimg.cc/635M4X52/pokemon-brilliantstars.jpg', 'mvp': { 'Arizona Diamondbacks': f'{PD_CARD_URL}/mvp/arizona-diamondbacks.gif', 'Atlanta Braves': f'{PD_CARD_URL}/mvp/atlanta-braves.gif', @@ -102,7 +104,8 @@ IMAGES = { 'Toronto Blue Jays': f'{PD_CARD_URL}/mvp/toronto-blue-jays.gif', 'Washington Nationals': f'{PD_CARD_URL}/mvp/washington-nationals.gif', 'Junior All Stars': f'{PD_CARD_URL}/mvp.png', - 'Mario Super Sluggers': f'{PD_CARD_URL}/mvp.png' + 'Mario Super Sluggers': f'{PD_CARD_URL}/mvp.png', + 'Pokemon': 'https://i.postimg.cc/ydzYB7BR/masterball.jpg' }, 'gauntlets': f'{PD_CARD_URL}/gauntlets.png' } @@ -241,7 +244,7 @@ SELECT_CARDSET_OPTIONS = [ discord.SelectOption(label='2013 Season', value='6'), discord.SelectOption(label='2012 Season', value='7') ] -ACTIVE_EVENT_LITERAL = Literal['1998 Season'] +ACTIVE_EVENT_LITERAL = Literal['1998 Season', 'Brilliant Stars'] class Question: @@ -849,6 +852,7 @@ class SelectBuyPacksCardset(discord.ui.Select): def __init__(self, team: dict, quantity: int, pack_type_id: int, pack_embed: discord.Embed, cost: int): options = [ discord.SelectOption(label='1998 Live'), + discord.SelectOption(label='Pokemon - Brilliant Stars'), discord.SelectOption(label='2024 Season'), discord.SelectOption(label='2023 Season'), discord.SelectOption(label='2022 Season'), @@ -892,6 +896,9 @@ class SelectBuyPacksCardset(discord.ui.Select): cardset_id = 17 elif self.values[0] == '1998 Live': cardset_id = 20 + elif self.values[0] == 'Pokemon - Brilliant Stars': + cardset_id = 23 + self.pack_embed.set_image(url=IMAGES['pack-pkmnbs']) self.pack_embed.description = f'{self.pack_embed.description} - {self.values[0]}' view = Confirm(responders=[interaction.user], timeout=30) @@ -1735,7 +1742,10 @@ async def get_card_embeds(card, include_stats=False) -> list: # embed.add_field(name='# Dupes', value=f'{count - 1} dupe{"s" if count - 1 != 1 else ""}') # embed.add_field(name='Team', value=f'{card["player"]["mlbclub"]}') - player_pages = f'[BBRef]({get_player_url(card["player"], "bbref")})' + if card['player']['franchise'] != 'Pokemon': + player_pages = f'[BBRef]({get_player_url(card["player"], "bbref")})' + else: + player_pages = f'[Pkmn]({PKMN_REF_URL}{card["player"]["bbref_id"]})' embed.add_field(name='Player Page', value=f'{player_pages}') embed.set_image(url=card["player"]["image"]) @@ -1744,6 +1754,28 @@ async def get_card_embeds(card, include_stats=False) -> list: embed.set_thumbnail(url=headshot) else: embed.set_thumbnail(url=IMAGES['logo']) + + if card['player']['franchise'] == 'Pokemon': + if card['player']['fangr_id'] is not None: + try: + evo_mon = await db_get('players', object_id=card['player']['fangr_id'], none_okay=True) + if evo_mon is not None: + embed.add_field( + name='Evolves Into', + value=f'{evo_mon["p_name"]}' + ) + except Exception as e: + logging.error('could not pull evolution: {e}', exc_info=True, stack_info=True) + if '420420' not in card['player']['strat_code']: + try: + evo_mon = await db_get('players', object_id=card['player']['strat_code'], none_okay=True) + if evo_mon is not None: + embed.add_field( + name='Evolves From', + value=f'{evo_mon["p_name"]}' + ) + except Exception as e: + logging.error('could not pull evolution: {e}', exc_info=True, stack_info=True) if include_stats: if b_query['count'] > 0: @@ -2244,6 +2276,7 @@ async def roll_for_cards(all_packs: list, extra_val=None) -> list: if pl['count'] != counts[key]['count']: mvp_flag = counts[key]['count'] - pl['count'] + logging.info(f'Set mvp flag to {mvp_flag} / cardset_id: {all_packs[0]["pack_cardset"]["id"]}') for x in pl['players']: this_pack_players.append(x) @@ -2252,12 +2285,21 @@ async def roll_for_cards(all_packs: list, extra_val=None) -> list: if x['rarity']['value'] >= 3: pull_notifs.append(x) - if mvp_flag: + if mvp_flag and all_packs[0]['pack_cardset']['id'] not in [23]: + logging.info(f'Adding {mvp_flag} MVPs for missing cards') pl = await db_get('players/random', params=[('min_rarity', 5), ('limit', mvp_flag)]) for x in pl['players']: this_pack_players.append(x) all_players.append(x) + + # Add dupes of Replacement/Reserve cards + elif mvp_flag: + logging.info(f'Adding {mvp_flag} duplicate pokemon cards') + for count in range(mvp_flag): + logging.info(f'Adding {pl["players"][0]["p_name"]} to the pack') + this_pack_players.append(x) + all_players.append(pl['players'][0]) success = await db_post( 'cards', @@ -2878,7 +2920,9 @@ async def paperdex_team_embed(team: dict, mlb_team: dict) -> list[discord.Embed] def get_pack_cover(pack): - if pack['pack_type']['name'] in ['Premium', 'MVP']: + if pack['pack_cardset'] is not None and pack['pack_cardset'] == 23: + return IMAGES['pack-pkmnbs'] + elif pack['pack_type']['name'] in ['Premium', 'MVP']: return IMAGES['pack-pre'] elif pack['pack_type']['name'] == 'Standard': return IMAGES['pack-sta'] diff --git a/utilities/buttons.py b/utilities/buttons.py new file mode 100644 index 0000000..333e510 --- /dev/null +++ b/utilities/buttons.py @@ -0,0 +1,364 @@ +import logging +import discord +from typing import Coroutine, Literal + +# from dice import ab_roll, jump_roll +# from exceptions import * +# from in_game.gameplay_models import Game, Play, Team + + +logger = logging.getLogger('discord_app') + +class Confirm(discord.ui.View): + def __init__(self, responders: list, timeout: float = 300.0, label_type: Literal['yes', 'confirm'] = 'confirm'): + super().__init__(timeout=timeout) + if not isinstance(responders, list): + raise TypeError('responders must be a list') + self.value = None + self.responders = responders + if label_type == 'yes': + self.confirm.label = 'Yes' + self.cancel.label = 'No' + + # When the confirm button is pressed, set the inner value to `True` and + # stop the View from listening to more input. + # We also send the user an ephemeral message that we're confirming their choice. + @discord.ui.button(label='Confirm', style=discord.ButtonStyle.green) + async def confirm(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + await interaction.response.send_message( + content='Get out of here', + ephemeral=True, + delete_after=10.0 + ) + + self.value = True + self.clear_items() + self.stop() + + # This one is similar to the confirmation button except sets the inner value to `False` + @discord.ui.button(label='Cancel', style=discord.ButtonStyle.grey) + async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + await interaction.response.send_message( + content='Get out of here', + ephemeral=True, + delete_after=10.0 + ) + + self.value = False + self.clear_items() + self.stop() + + +class ButtonOptions(discord.ui.View): + def __init__(self, labels: list[str], responders: list, timeout: float = 300.0, disable_chosen: bool = False): + logger.info(f'ButtonOptions - labels: {labels} / responders: {responders} / timeout: {timeout} / disable_chosen: {disable_chosen}') + + super().__init__(timeout=timeout) + if not isinstance(responders, list): + raise TypeError('responders must be a list') + if len(labels) > 5 or len(labels) < 1: + log_exception(ValueError, 'ButtonOptions support between 1 and 5 options') + + self.value = None + self.responders = responders + self.options = labels + self.disable_chosen = disable_chosen + # if len(labels) == 5: + # for count, x in enumerate(labels): + # if count == 0: + # self.option1.label = x + # if x is None or x.lower() == 'na' or x == 'N/A': + # self.remove_item(self.option1) + # if count == 1: + # self.option2.label = x + # if x is None or x.lower() == 'na' or x == 'N/A': + # self.remove_item(self.option2) + # if count == 2: + # self.option3.label = x + # if x is None or x.lower() == 'na' or x == 'N/A': + # self.remove_item(self.option3) + # if count == 3: + # self.option4.label = x + # if x is None or x.lower() == 'na' or x == 'N/A': + # self.remove_item(self.option4) + # if count == 4: + # self.option5.label = x + # if x is None or x.lower() == 'na' or x == 'N/A': + # self.remove_item(self.option5) + + # else: + all_options = [self.option1, self.option2, self.option3, self.option4, self.option5] + logger.info(f'all_options: {all_options}') + for count, x in enumerate(labels): + if x is None or x.lower() == 'na' or x.lower() == 'n/a': + self.remove_item(all_options[count]) + else: + all_options[count].label = x + + if len(labels) < 2: + self.remove_item(self.option2) + if len(labels) < 3: + self.remove_item(self.option3) + if len(labels) < 4: + self.remove_item(self.option4) + if len(labels) < 5: + self.remove_item(self.option5) + + @discord.ui.button(label='Option 1', style=discord.ButtonStyle.primary) + async def option1(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + await interaction.response.send_message( + content='Get out of here', + ephemeral=True, + delete_after=10.0 + ) + + self.stop() + if self.disable_chosen: + button.disabled = True + self.value = self.options[0] + await interaction.edit_original_response(view=self) + + @discord.ui.button(label='Option 2', style=discord.ButtonStyle.primary) + async def option2(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + await interaction.response.send_message( + content='Get out of here', + ephemeral=True, + delete_after=10.0 + ) + + self.stop() + if self.disable_chosen: + button.disabled = True + self.value = self.options[1] + await interaction.edit_original_response(view=self) + + @discord.ui.button(label='Option 3', style=discord.ButtonStyle.primary) + async def option3(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + await interaction.response.send_message( + content='Get out of here', + ephemeral=True, + delete_after=10.0 + ) + + self.stop() + if self.disable_chosen: + button.disabled = True + self.value = self.options[2] + await interaction.edit_original_response(view=self) + + @discord.ui.button(label='Option 4', style=discord.ButtonStyle.primary) + async def option4(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + await interaction.response.send_message( + content='Get out of here', + ephemeral=True, + delete_after=10.0 + ) + + self.stop() + if self.disable_chosen: + button.disabled = True + self.value = self.options[3] + await interaction.edit_original_response(view=self) + + @discord.ui.button(label='Option 5', style=discord.ButtonStyle.primary) + async def option5(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + await interaction.response.send_message( + content='Get out of here', + ephemeral=True, + delete_after=10.0 + ) + + self.stop() + if self.disable_chosen: + button.disabled = True + self.value = self.options[4] + await interaction.edit_original_response(view=self) + + +async def ask_confirm(interaction: discord.Interaction, question: str, label_type: Literal['yes', 'confirm'] = 'confirm', timeout: int = 60, delete_question: bool = True, custom_confirm_label: str = None, custom_cancel_label: str = None, embed: discord.Embed = None, delete_embed: bool = False) -> bool: + """ + button_callbacks: keys are button values, values are async functions + """ + try: + view = Confirm(responders=[interaction.user], timeout=timeout, label_type=label_type) + except AttributeError: + view = Confirm(responders=[interaction.author], timeout=timeout, label_type=label_type) + if custom_confirm_label: + view.confirm.label = custom_confirm_label + if custom_cancel_label: + view.cancel.label = custom_cancel_label + + q_message = await interaction.channel.send(question, view=view) + await view.wait() + + if view.value: + if delete_question: + await q_message.delete() + else: + await q_message.edit(content=question, view=None) + return True + + else: + if delete_question: + await q_message.delete() + else: + await q_message.edit(content=question, view=None) + return False + + +async def ask_with_buttons(interaction: discord.Interaction, button_options: list[str], question: str = None, timeout: int = 60, delete_question: bool = True, embeds: list[discord.Embed] = None, delete_embeds: bool = False, edit_original_interaction: bool = False, none_okay: bool = False) -> str: + """ + Returns text of button pressed + """ + logger.info(f'ask_with_buttons - button_options: {button_options} / question: {question} / timeout: {timeout} / delete_question: {delete_question} / embeds: {embeds} / delete_embeds: {delete_embeds} / edit_original_transaction: {edit_original_interaction}') + + if question is None and embeds is None: + log_exception(KeyError, 'At least one of question or embed must be provided') + + view = ButtonOptions( + responders=[interaction.user], + timeout=timeout, + labels=button_options + ) + logger.info(f'view: {view}') + # if edit_original_interaction: + # logger.info(f'editing message') + # q_message = await interaction.edit_original_response( + # content=question, + # view=view, + # embeds=embeds + # ) + # logger.info(f'edited') + # else: + # logger.info(f'posting message') + # q_message = await interaction.channel.send( + # content=question, + # view=view, + # embeds=embeds + # ) + logger.info(f'posting message') + q_message = await interaction.channel.send( + content=question, + view=view, + embeds=embeds + ) + await view.wait() + + if view.value: + return_val = view.value + + else: + return_val = None + + if question is not None and embeds is not None: + logger.info(f'checking for deletion with question and embeds') + if delete_question and delete_embeds: + logger.info(f'delete it all') + await q_message.delete() + elif delete_question: + logger.info(f'delete question') + await q_message.edit( + content=None + ) + elif delete_embeds: + logger.info(f'delete embeds') + await q_message.edit( + embeds=None + ) + elif return_val is None: + logger.info(f'remove view') + await q_message.edit( + view=None + ) + + elif (question is not None and delete_question) or (embeds is not None and delete_embeds): + logger.info(f'deleting message') + await q_message.delete() + + elif return_val is None: + logger.info(f'No reponse, remove view') + await q_message.edit( + view=None + ) + + if return_val is not None or none_okay: + return return_val + + log_exception(ButtonOptionNotChosen, 'Selecting an option is mandatory') + + +# class ScorebugButtons(discord.ui.View): +# def __init__(self, play: Play, embed: discord.Embed, timeout: float = 30): +# super().__init__(timeout=timeout) +# self.value = None +# self.batting_team = play.batter.team +# self.pitching_team = play.pitcher.team +# self.pitcher_card_url = play.pitcher.player.pitcher_card_url +# self.batter_card_url = play.batter.player.batter_card_url +# self.team = play.batter.team +# self.play = play +# self.had_chaos = False +# self.embed = embed + +# if play.on_base_code == 0: +# self.remove_item(self.button_jump) + +# async def interaction_check(self, interaction: discord.Interaction[discord.Client]) -> bool: +# logger.info(f'user id: {interaction.user.id} / batting_team: {self.batting_team}') +# if interaction.user.id == self.batting_team.gmid: +# logger.info(f'User {interaction.user.id} rolling in Game {self.play.game.id}') +# return True +# elif self.batting_team.is_ai and interaction.user.id == self.pitching_team.gmid: +# logger.info(f'User {interaction.user.id} rolling for AI in Game {self.play.game.id}') +# return True + +# logger.info(f'User {interaction.user.id} rejected in Game {self.play.game.id}') +# await interaction.response.send_message( +# content='Get out of here', +# ephemeral=True, +# delete_after=5.0 +# ) +# return False + +# # async def on_timeout(self) -> Coroutine[Any, Any, None]: +# # await self.interaction + +# @discord.ui.button(label='Roll AB', style=discord.ButtonStyle.primary) +# async def button_ab(self, interaction: discord.Interaction, button: discord.ui.Button): +# logger.info(f'User {interaction.user.id} rolling AB in Game {self.play.game.id}') + +# this_roll = ab_roll(self.team, self.play.game, allow_chaos=not self.had_chaos) +# logger.info(f'this_roll: {this_roll}') +# if this_roll.is_chaos: +# logger.info('AB Roll Is Chaos') +# self.had_chaos = True +# else: +# button.disabled = True + +# await interaction.channel.send(content=None, embeds=this_roll.embeds) + +# if this_roll.d_six_one > 3: +# logger.info(f'ScorebugButton - updating embed card to pitcher') +# self.embed.set_image(url=self.pitcher_card_url) +# logger.debug(f'embed image url: {self.embed.image}') + +# logger.debug(f'new embed: {self.embed}') +# await interaction.response.edit_message(view=self, embed=self.embed) + +# @discord.ui.button(label='Check Jump', style=discord.ButtonStyle.secondary) +# async def button_jump(self, interaction: discord.Interaction, button: discord.ui.Button): +# logger.info(f'User {interaction.user.id} rolling jump in Game {self.play.game.id}') + +# this_roll = jump_roll(self.team, self.play.game) +# button.disabled = True + +# await interaction.channel.send(content=None, embeds=this_roll.embeds) +# await interaction.response.edit_message(view=self) + diff --git a/utilities/dropdown.py b/utilities/dropdown.py new file mode 100644 index 0000000..c15d2d8 --- /dev/null +++ b/utilities/dropdown.py @@ -0,0 +1,335 @@ +import asyncio +import datetime +from typing import List +import discord +import logging + +from discord import SelectOption +from discord.utils import MISSING +# from sqlmodel import Session + +from db_calls import db_delete, db_get, db_post +# from exceptions import CardNotFoundException, PlayNotFoundException, log_exception +from helpers import get_card_embeds +# from in_game.game_helpers import legal_check +# from in_game.gameplay_models import Game, Lineup, Play, Team +# from in_game.gameplay_queries import get_one_lineup, get_position, get_card_or_none +from utilities.buttons import ask_confirm + + +logger = logging.getLogger('discord_app') + + +class DropdownOptions(discord.ui.Select): + def __init__(self, option_list: list, placeholder: str = 'Make your selection', min_values: int = 1, max_values: int = 1, callback=None): + # Set the options that will be presented inside the dropdown + # options = [ + # discord.SelectOption(label='Red', description='Your favourite colour is red', emoji='🟥'), + # discord.SelectOption(label='Green', description='Your favourite colour is green', emoji='🟩'), + # discord.SelectOption(label='Blue', description='Your favourite colour is blue', emoji='🟦'), + # ] + + # The placeholder is what will be shown when no option is chosen + # The min and max values indicate we can only pick one of the three options + # The options parameter defines the dropdown options. We defined this above + + # If a default option is set on any SelectOption, the View will not process if only the default is + # selected by the user + self.custom_callback = callback + super().__init__( + placeholder=placeholder, + min_values=min_values, + max_values=max_values, + options=option_list + ) + + async def callback(self, interaction: discord.Interaction): + # Use the interaction object to send a response message containing + # the user's favourite colour or choice. The self object refers to the + # Select object, and the values attribute gets a list of the user's + # selected options. We only want the first one. + # await interaction.response.send_message(f'Your favourite colour is {self.values[0]}') + logger.info(f'Dropdown callback: {self.custom_callback}') + await self.custom_callback(interaction, self.values) + + +class DropdownView(discord.ui.View): + """ + https://discordpy.readthedocs.io/en/latest/interactions/api.html#select + """ + def __init__(self, dropdown_objects: list[discord.ui.Select], timeout: float = 300.0): + super().__init__(timeout=timeout) + + # self.add_item(Dropdown()) + for x in dropdown_objects: + self.add_item(x) + + +# class SelectViewDefense(discord.ui.Select): +# def __init__(self, options: list, this_play: Play, base_embed: discord.Embed, session: Session, sorted_lineups: list[Lineup]): +# self.embed = base_embed +# self.session = session +# self.play = this_play +# self.sorted_lineups = sorted_lineups +# super().__init__(options=options) + +# async def callback(self, interaction: discord.Interaction): +# logger.info(f'SelectViewDefense - selection: {self.values[0]}') + +# this_lineup = self.session.get(Lineup, self.values[0]) +# self.embed.set_image(url=this_lineup.player.image) + +# select_player_options = [ +# discord.SelectOption(label=f'{x.position} - {x.player.name}', value=f'{x.id}', default=this_lineup.position == x.position) for x in self.sorted_lineups +# ] +# player_dropdown = SelectViewDefense( +# options=select_player_options, +# this_play=self.play, +# base_embed=self.embed, +# session=self.session, +# sorted_lineups=self.sorted_lineups +# ) +# new_view = DropdownView( +# dropdown_objects=[player_dropdown], +# timeout=60 +# ) + +# await interaction.response.edit_message(content=None, embed=self.embed, view=new_view) + + +# class SelectStartingPitcher(discord.ui.Select): +# def __init__(self, this_game: Game, this_team: Team, session: Session, league_name: str, custom_id: str = MISSING, placeholder: str | None = None, options: List[SelectOption] = ...) -> None: +# logger.info(f'Inside SelectStartingPitcher init function') +# self.game = this_game +# self.team = this_team +# self.session = session +# self.league_name = league_name +# super().__init__(custom_id=custom_id, placeholder=placeholder, options=options) + +# async def callback(self, interaction: discord.Interaction): +# logger.info(f'SelectStartingPitcher - selection: {self.values[0]}') + +# # Get Human SP card +# human_sp_card = await get_card_or_none(self.session, card_id=self.values[0]) +# if human_sp_card is None: +# log_exception(CardNotFoundException, f'Card ID {self.values[0]} not found') + +# if human_sp_card.team_id != self.team.id: +# logger.error(f'Card_id {self.values[0]} does not belong to {self.team.abbrev} in Game {self.game.id}') +# await interaction.channel.send( +# f'Uh oh. Card ID {self.values[0]} is {human_sp_card.player.name} and belongs to {human_sp_card.team.sname}. Will you double check that before we get started?' +# ) +# return + +# await get_position(self.session, human_sp_card, 'P') + +# legal_data = await legal_check([self.values[0]], difficulty_name=self.league_name) +# if not legal_data['legal']: +# await interaction.edit_original_response( +# content=f'It looks like this is a Ranked Legal game and {human_sp_card.player.name_with_desc} is not legal in {self.league_name} games. You can start a new game once you pick a new SP.' +# ) +# return + +# human_sp_lineup = Lineup( +# team_id=self.team.id, +# player_id=human_sp_card.player.id, +# card_id=self.values[0], +# position='P', +# batting_order=10, +# is_fatigued=False, +# game=self.game +# ) +# self.session.add(human_sp_lineup) +# self.session.commit() + + +# await interaction.response.edit_message( +# content=f'The {self.team.lname} are starting {human_sp_card.player.name_with_desc}', +# view=None +# ) + + +# class SelectSubPosition(discord.ui.Select): +# def __init__(self, session: Session, this_lineup: Lineup, custom_id = ..., placeholder = None, options: List[SelectOption] = ...): +# self.session = session +# self.this_lineup = this_lineup +# super().__init__(custom_id=custom_id, placeholder=placeholder, min_values=1, max_values=1, options=options, disabled=False) + +# async def callback(self, interaction: discord.Interaction): +# logger.info(f'Setting sub position to {self.values[0]}') +# await interaction.edit_original_response(view=None) + +# if self.values[0] == 'PH': +# await interaction.channel.send(content=f'Their position is set to Pinch Hitter.') +# return + +# else: +# await get_position(self.session, self.this_lineup.card_id, position=self.values[0]) + +# self.this_lineup.position = self.values[0] +# for option in self.options: +# if option.value == self.values[0]: +# this_label = option.label + +# self.this_lineup.position = self.values[0] +# self.session.add(self.this_lineup) +# self.session.commit() + +# await interaction.channel.send(content=f'Their position is set to {this_label}.') + + +# class SelectBatterSub(discord.ui.Select): +# def __init__(self, this_game: Game, this_team: Team, session: Session, batting_order: int, custom_id: str = MISSING, placeholder: str | None = None, options: List[SelectOption] = ...): +# logger.info(f'Inside SelectBatterSub init function') +# self.game = this_game +# self.team = this_team +# self.session = session +# # self.league_name = league_name +# self.batting_order = batting_order +# super().__init__(custom_id=custom_id, placeholder=placeholder, min_values=1, max_values=1, options=options) + +# async def callback(self, interaction: discord.Interaction): +# await interaction.response.defer() + +# logger.info(f'Setting batter sub to Card ID: {self.values[0]}') + +# # Get Human batter card +# human_batter_card = await get_card_or_none(self.session, card_id=self.values[0]) +# if human_batter_card is None: +# log_exception(CardNotFoundException, f'Card ID {self.values[0]} not found') + +# if human_batter_card.team_id != self.team.id: +# logger.error(f'Card_id {self.values[0]} does not belong to {self.team.abbrev} in Game {self.game.id}') +# await interaction.channel.send( +# f'Uh oh. Card ID {self.values[0]} is {human_batter_card.player.name} and belongs to {human_batter_card.team.sname}. Will you double check that before we get started?' +# ) +# return + +# this_play = self.game.current_play_or_none(self.session) +# if this_play is None: +# log_exception(PlayNotFoundException, 'Play not found during substitution') +# logger.info(f'this_play: {this_play}') + +# last_lineup = get_one_lineup( +# session=self.session, +# this_game=self.game, +# this_team=self.team, +# active=True, +# batting_order=self.batting_order +# ) + +# same_position = await ask_confirm( +# interaction, +# question=f'Will **{human_batter_card.player.name}** replace {last_lineup.player.name} as the {last_lineup.position}?', +# label_type='yes' +# ) + +# if same_position: +# position = last_lineup.position +# pos_text = '' +# view = None +# else: +# pos_dict_list = { +# 'Pinch Hitter': 'PH', +# 'Catcher': 'C', +# 'First Base': '1B', +# 'Second Base': '2B', +# 'Third Base': '3B', +# 'Shortstop': 'SS', +# 'Left Field': 'LF', +# 'Center Field': 'CF', +# 'Right Field': 'RF', +# 'Pinch Runner': 'PR', +# 'Pitcher': 'P' +# } + +# position = 'PH' +# pos_text = 'What position will they play?' +# options=[SelectSubPosition(label=f'{x}', value=pos_dict_list[x], default=x=='Pinch Hitter') for x in pos_dict_list] + +# view = DropdownView(dropdown_objects=options) + +# last_lineup.active = False +# self.session.add(last_lineup) +# logger.info(f'Set {last_lineup.card.player.name_with_desc} as inactive') + +# human_bat_lineup = Lineup( +# team=self.team, +# player=human_batter_card.player, +# card=human_batter_card, +# position=position, +# batting_order=self.batting_order, +# game=self.game, +# after_play=max(this_play.play_num - 1, 0), +# replacing_id=last_lineup.id +# ) +# logger.info(f'new lineup: {human_bat_lineup}') +# self.session.add(human_bat_lineup) +# # self.session.commit() + +# try: +# logger.info(f'Inserted {human_bat_lineup.card.player.name_with_desc} in the {self.batting_order} spot') +# this_play.batter = human_bat_lineup +# this_play.batter_pos = position +# except Exception as e: +# logger.error(e, exc_info=True, stack_info=True) + +# self.session.add(this_play) + +# self.session.commit() + +# await interaction.edit_original_response( +# content=f'{human_batter_card.player.name_with_desc} has entered in the {self.batting_order} spot. {pos_text}', +# view=view +# ) + + +class SelectPokemonEvolution(discord.ui.Select): + def __init__(self, *, placeholder = 'Evolve the selected Pokemon', min_values = 1, max_values = 1, options = List[SelectOption], this_team): + logging.info(f'Inside SelectPokemonEvolution init function') + + self.team = this_team + super().__init__(placeholder=placeholder, min_values=min_values, max_values=max_values, options=options) + + async def callback(self, interaction: discord.Interaction): + await interaction.response.defer() + + try: + card_id = self.values[0] + this_card = await db_get( + 'cards', + object_id=card_id, + none_okay=False + ) + evo_mon = await db_get( + 'players', + object_id=this_card['player']['fangr_id'], + none_okay=False + ) + p_query = await db_post( + 'packs/one', + payload={ + 'team_id': self.team['id'], + 'pack_type_id': 4, + 'open_time': datetime.datetime.timestamp(datetime.datetime.now()) * 1000} + ) + pack_id = p_query['id'] + await db_post( + 'cards', + payload={'cards': [ {'player_id': evo_mon['player_id'], 'team_id': self.team['id'], 'pack_id': pack_id}]}, + timeout=10 + ) + await interaction.edit_original_response( + content=f'## {this_card["player"]["p_name"].upper()} is evolving!', + embeds=await get_card_embeds(this_card), + view=None + ) + await db_delete('cards', object_id=card_id) + await asyncio.sleep(3) + await interaction.channel.send( + content=f'## {this_card["player"]["p_name"].upper()} evolved into {evo_mon["p_name"].upper()}!', + embeds=await get_card_embeds({'team': self.team, 'player': evo_mon}) + ) + except Exception as e: + logging.error(f'Failed to evolve a pokemon: {e}', exc_info=True, stack_info=True) + await interaction.edit_original_response(content=f'Oh no, the evolution failed! Go ping the shit out of Cal so he can evolve it for you!') diff --git a/utilities/embeds.py b/utilities/embeds.py new file mode 100644 index 0000000..fd6b47b --- /dev/null +++ b/utilities/embeds.py @@ -0,0 +1,21 @@ +import discord +from helpers import IMAGES, PD_SEASON, SBA_COLOR + + +def image_embed(image_url: str, title: str = None, color: str = None, desc: str = None, author_name: str = None, author_icon: str = None): + embed_color = int(SBA_COLOR, 16) + if color is not None: + embed_color = int(color, 16) + + embed = discord.Embed(color=embed_color) + + if title is not None: + embed.title = title + if desc is not None: + embed.description = desc + if author_name is not None: + icon = author_icon if author_icon is not None else IMAGES['logo'] + embed.set_author(name=author_name, icon_url=icon) + embed.set_footer(text=f'Paper Dynasty Season {PD_SEASON}', icon_url=IMAGES['logo']) + embed.set_image(url=image_url) + return embed \ No newline at end of file diff --git a/utilities/pages.py b/utilities/pages.py new file mode 100644 index 0000000..aec1cab --- /dev/null +++ b/utilities/pages.py @@ -0,0 +1,45 @@ +import discord +import logging + + +logger = logging.getLogger('discord_app') + + +class Pagination(discord.ui.View): + def __init__(self, responders: list, timeout: float = 300.0): + super().__init__(timeout=timeout) + if not isinstance(responders, list): + raise TypeError('responders must be a list') + + self.value = None + self.responders = responders + + @discord.ui.button(label='⏮️', style=discord.ButtonStyle.blurple) + async def left_button(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + logger.info(f'{interaction.user} is not in {self.responders}') + return + + self.value = 'left' + await interaction.response.defer() + self.stop() + + @discord.ui.button(label='❌️', style=discord.ButtonStyle.secondary) + async def cancel_button(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + logger.info(f'{interaction.user} is not in {self.responders}') + return + + self.value = 'cancel' + await interaction.response.defer() + self.stop() + + @discord.ui.button(label='⏭️', style=discord.ButtonStyle.blurple) + async def right_button(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + logger.info(f'{interaction.user} is not in {self.responders}') + return + + self.value = 'right' + await interaction.response.defer() + self.stop()