From a416e90db9d7c5c5e00af156a63435c00e38386a Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Sun, 9 Mar 2025 00:59:10 -0600 Subject: [PATCH] New postion sub logic --- exceptions.py | 8 +++- utilities/buttons.py | 109 ++++++++++++++++++++++++++++++++++++++++++ utilities/dropdown.py | 95 +++++++++++++++++++----------------- 3 files changed, 165 insertions(+), 47 deletions(-) diff --git a/exceptions.py b/exceptions.py index eb9bb5a..3e83391 100644 --- a/exceptions.py +++ b/exceptions.py @@ -6,15 +6,15 @@ logger = logging.getLogger('discord_app') def log_errors(func): """ - This wrapper function will force all exceptions to be logged with executiona and stack info. + This wrapper function will force all exceptions to be logged with execution and stack info. """ def wrap(*args, **kwargs): try: result = func(*args, **kwargs) except Exception as e: + logger.error(func.__name__) log_exception(e) - logger.info(func.__name__) return result return wrap @@ -113,3 +113,7 @@ class MissingRosterException(GameException): class LegalityCheckNotRequired(GameException): pass + + +class InvalidResponder(GameException): + pass diff --git a/utilities/buttons.py b/utilities/buttons.py index 05b34e9..9591b4f 100644 --- a/utilities/buttons.py +++ b/utilities/buttons.py @@ -4,11 +4,19 @@ from typing import Coroutine, Literal from dice import ab_roll, jump_roll from exceptions import * +from helpers import random_insult from in_game.gameplay_models import Game, Play, Team logger = logging.getLogger('discord_app') + +# def check_responder(func): +# def wrap(*args, **kwargs): +# try: +# result = func(*args, **kwargs) +# except Exc + class Confirm(discord.ui.View): def __init__(self, responders: list, timeout: float = 300.0, label_type: Literal['yes', 'confirm'] = 'confirm'): super().__init__(timeout=timeout) @@ -182,6 +190,107 @@ class ButtonOptions(discord.ui.View): await interaction.edit_original_response(view=self) +class SelectPosition(discord.ui.View): + def __init__(self, *, timeout: int = 30, responders: list[discord.User]): + if not isinstance(responders, list): + raise TypeError('responders must be a list') + + super().__init__(timeout=timeout) + + self.value = None + self.responders = responders + + async def check_responders(self, interaction: discord.Interaction): + if interaction.user not in self.responders: + await interaction.response.send_message( + content=random_insult(), + ephemeral=True, + delete_after=10.0 + ) + log_exception(InvalidResponder, f'{interaction.user.name} not in responders: {self.responders}') + + + async def button_press(self, interaction: discord.Interaction, this_position: str): + await self.check_responders(interaction) + self.stop() + self.value = this_position + await interaction.edit_original_response(view=self) + + + @discord.ui.button(label='LF', style=discord.ButtonStyle.blurple, row=0) + async def left_field(self, interaction: discord.Interaction, button: discord.ui.Button): + await self.button_press(interaction, button.label) + + + @discord.ui.button(label='CF', style=discord.ButtonStyle.blurple, row=0) + async def center_field(self, interaction: discord.Interaction, button: discord.ui.Button): + await self.button_press(interaction, button.label) + + + @discord.ui.button(label='RF', style=discord.ButtonStyle.blurple, row=0) + async def right_field(self, interaction: discord.Interaction, button: discord.ui.Button): + await self.button_press(interaction, button.label) + + + @discord.ui.button(label='3B', style=discord.ButtonStyle.green, row=1) + async def third_base(self, interaction: discord.Interaction, button: discord.ui.Button): + await self.button_press(interaction, button.label) + + + @discord.ui.button(label='SS', style=discord.ButtonStyle.green, row=1) + async def shortstop(self, interaction: discord.Interaction, button: discord.ui.Button): + await self.button_press(interaction, button.label) + + + @discord.ui.button(label='2B', style=discord.ButtonStyle.green, row=1) + async def second_base(self, interaction: discord.Interaction, button: discord.ui.Button): + await self.button_press(interaction, button.label) + + + @discord.ui.button(label='1B', style=discord.ButtonStyle.green, row=1) + async def first_base(self, interaction: discord.Interaction, button: discord.ui.Button): + await self.button_press(interaction, button.label) + + + @discord.ui.button(label='C', style=discord.ButtonStyle.gray, row=2) + async def catcher(self, interaction: discord.Interaction, button: discord.ui.Button): + await self.button_press(interaction, button.label) + + + @discord.ui.button(label='P', style=discord.ButtonStyle.gray, row=2) + async def pitcher(self, interaction: discord.Interaction, button: discord.ui.Button): + await self.button_press(interaction, button.label) + + + @discord.ui.button(label='PH/PR', style=discord.ButtonStyle.gray, row=2) + async def pinch_hitter(self, interaction: discord.Interaction, button: discord.ui.Button): + await self.button_press(interaction, button.label) + + +async def ask_position(interaction: discord.Interaction): + view = SelectPosition( + responders=[interaction.user], + timeout=15 + ) + + p_message = await interaction.channel.send( + content='Please select a position', + view=view + ) + await view.wait() + + if view.value: + await p_message.delete() + return view.value + + else: + await p_message.edit( + content='To move things along, I will set the position to PH.', + view=None + ) + return 'PH' + + 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 diff --git a/utilities/dropdown.py b/utilities/dropdown.py index 497b962..5bc3e7f 100644 --- a/utilities/dropdown.py +++ b/utilities/dropdown.py @@ -14,7 +14,7 @@ from helpers import get_card_embeds, random_insult 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 +from utilities.buttons import ask_confirm, ask_position from utilities.embeds import image_embed @@ -369,12 +369,17 @@ class SelectBatterSub(discord.ui.Select): if same_position: logger.info(f'same_position is True') position = last_lineup.position - pos_text = '' - view = None + # pos_text = '' + # view = None else: logger.info(f'same_position is False') - position = 'PH' - pos_text = 'What position will they play?' + position = await ask_position(interaction) + + if position == 'PH/PR': + if this_play.batter == last_lineup: + position = 'PH' + else: + position = 'PR' logger.info(f'Deactivating last_lineup') last_lineup.active = False @@ -409,57 +414,57 @@ class SelectBatterSub(discord.ui.Select): # self.session.commit() 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 + if this_play.batter == last_lineup: + logger.info(f'Setting new sub to current play batter') + this_play.batter = human_bat_lineup + this_play.batter_pos = position logger.info(f'Adding play to session: {this_play}') self.session.add(this_play) self.session.commit() - if not same_position: - 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' - } + # if not same_position: + # 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' + # } - logger.info(f'Prepping sub position') - pos_text = f'What position will {human_batter_card.player.name} play?' - options=[ - SelectOption( - label=f'{pos_name}', - value=pos_dict_list[pos_name], - default=pos_name=='Pinch Hitter' - ) - for pos_name in pos_dict_list.keys() - ] - logger.info(f'options: {options}') - view = SelectSubPosition( - self.session, - this_lineup=human_bat_lineup, - placeholder='Select Position', - options=options, - responders=[interaction.user] - ) + # logger.info(f'Prepping sub position') + # pos_text = f'What position will {human_batter_card.player.name} play?' + # options=[ + # SelectOption( + # label=f'{pos_name}', + # value=pos_dict_list[pos_name], + # default=pos_name=='Pinch Hitter' + # ) + # for pos_name in pos_dict_list.keys() + # ] + # logger.info(f'options: {options}') + # view = SelectSubPosition( + # self.session, + # this_lineup=human_bat_lineup, + # placeholder='Select Position', + # options=options, + # responders=[interaction.user] + # ) - logger.info(f'view: {view}') - logger.info(f'SelectSubPosition view is ready') + # logger.info(f'view: {view}') + # logger.info(f'SelectSubPosition view is ready') logger.info(f'Posting final sub message') - this_content = f'{human_batter_card.player.name_with_desc} has entered in the {self.batting_order} spot. {pos_text}' + this_content = f'{human_batter_card.player.name_with_desc} has entered in the {self.batting_order} spot.\n\nIf you have additional subs to make, run `/substitution` again or run `/gamestate` to see the current plate appearance.' await interaction.edit_original_response( - content=this_content, - view=view + content=this_content ) - await interaction.channel.send(content='If you have additional subs to make, run `/substitution` again or run `/gamestate` to see the current plate appearance.') class SelectPokemonEvolution(discord.ui.Select):