From 7f6472bbc6326e814387bd44d9199184bb0dfb7d Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Mon, 23 Dec 2024 10:09:11 -0600 Subject: [PATCH] Batter subs plus cleanup --- cogs/gameplay.py | 184 +++++++++++++++++++++++++++---- command_logic/logic_gameplay.py | 81 ++++++++++---- in_game/gameplay_models.py | 189 +------------------------------- in_game/gameplay_queries.py | 64 +++++------ in_game/managerai_responses.py | 6 +- utilities/buttons.py | 179 ++++++++++++++++++++++++------ utilities/dropdown.py | 146 +++++++++++++++++++++++- 7 files changed, 549 insertions(+), 300 deletions(-) diff --git a/cogs/gameplay.py b/cogs/gameplay.py index 6abeb4a..65bd98e 100644 --- a/cogs/gameplay.py +++ b/cogs/gameplay.py @@ -11,9 +11,10 @@ import pygsheets from sqlmodel import or_ from api_calls import db_get -from command_logic.logic_gameplay import advance_runners, bunts, chaos, complete_game, doubles, flyballs, frame_checks, get_full_roster_from_sheets, get_lineups_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, groundballs, hit_by_pitch, homeruns, is_game_over, lineouts, manual_end_game, popouts, read_lineup, show_defense_cards, singles, starting_pitcher_dropdown_view, steals, strikeouts, triples, undo_play, update_game_settings, walks, xchecks +from command_logic.logic_gameplay import advance_runners, bunts, chaos, complete_game, doubles, flyballs, frame_checks, get_full_roster_from_sheets, get_lineups_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, groundballs, hit_by_pitch, homeruns, is_game_over, lineouts, manual_end_game, popouts, read_lineup, show_defense_cards, singles, starting_pitcher_dropdown_view, steals, strikeouts, sub_batter_dropdown_view, triples, undo_play, update_game_settings, walks, xchecks, activate_last_play from dice import ab_roll from exceptions import GameNotFoundException, GoogleSheetsException, TeamNotFoundException, PlayNotFoundException, GameException, log_exception +import gauntlets from helpers import DEFENSE_LITERAL, PD_PLAYERS_ROLE_NAME, get_channel, team_role, user_has_role, random_gif, random_from_list # from in_game import ai_manager @@ -55,8 +56,16 @@ class Gameplay(commands.Cog): logger.error(msg=error, stack_info=True) await ctx.send(f'{error[:1600]}') - async def post_play(self, session: Session, interaction: discord.Interaction, this_play: Play, buffer_message: str = None): - logger.info(f'post_play - Posting new play') + async def post_play(self, session: Session, interaction: discord.Interaction, this_play: Play, buffer_message: str = None, full_length: bool = False): + logger.info(f'post_play - Posting new play: {this_play}') + + if this_play is None: + logger.info(f'this_play is None, searching for game in channel {interaction.channel.id}') + this_game = get_channel_game_or_none(session, interaction.channel.id) + this_play = activate_last_play(session, this_game) + if this_play is None: + log_exception(PlayNotFoundException, f'Attempting to display gamestate, but cannot find current play') + if is_game_over(this_play): logger.info(f'Game {this_play.game.id} seems to be over') await interaction.edit_original_response(content=f'Looks like this one is over!') @@ -83,39 +92,51 @@ class Gameplay(commands.Cog): await interaction.channel.send(content=f'I let Cal know his bot is stupid') scorebug_buttons, this_ab_roll = None, None - scorebug_embed = await get_scorebug_embed(session, this_play.game, full_length=False, classic=CLASSIC_EMBED) + scorebug_embed = await get_scorebug_embed(session, this_play.game, full_length=full_length, classic=CLASSIC_EMBED) if this_play.game.roll_buttons and interaction.user.id in [this_play.game.away_team.gmid, this_play.game.home_team.gmid]: + logger.info(f'Including scorebug buttons') scorebug_buttons = ScorebugButtons(this_play, scorebug_embed) if this_play.on_base_code == 0 and this_play.game.auto_roll and not this_play.batter.team.is_ai and not this_play.is_new_inning: + logger.info(f'Rolling ab') this_ab_roll = ab_roll(this_play.batter.team, this_play.game, allow_chaos=False) scorebug_buttons = None if this_ab_roll is not None and this_ab_roll.d_six_one > 3: + logger.info(f'Setting embed image to pitcher') scorebug_embed.set_image(url=this_play.pitcher.player.pitcher_card_url) if buffer_message is not None: + logger.info(f'Posting buffered message') await interaction.edit_original_response( content=buffer_message ) - await interaction.channel.send( + sb_message = await interaction.channel.send( content=None, embed=scorebug_embed, view=scorebug_buttons ) else: - await interaction.edit_original_response( + logger.info(f'Posting unbuffered message') + sb_message = await interaction.edit_original_response( content=None, embed=scorebug_embed, view=scorebug_buttons ) if this_ab_roll is not None: + logger.info(f'Posting ab roll') await interaction.channel.send( content=None, embeds=this_ab_roll.embeds ) + + if scorebug_buttons is not None: + logger.info(f'Posting scorebug buttons roll') + await scorebug_buttons.wait() + if not scorebug_buttons.value: + await sb_message.edit(view=None) async def complete_and_post_play(self, session: Session, interaction: discord.Interaction, this_play: Play, buffer_message: str = None): next_play = complete_play(session, this_play) @@ -293,7 +314,7 @@ class Gameplay(commands.Cog): ) # Get pitchers from rosterlinks - done = await get_full_roster_from_sheets(session, interaction, self.sheets, this_game, human_team, roster.value) + done = await get_full_roster_from_sheets(session, interaction, self.sheets, this_game, human_team, int(roster.value)) logger.info(f'done: {done}') if done: sp_view = starting_pitcher_dropdown_view(session, this_game, human_team) @@ -303,7 +324,120 @@ class Gameplay(commands.Cog): content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' f'Go ahead and set lineups with the `/read-lineup` command!', embed=embed - ) + ) + + @group_new_game.command(name='gauntlet', description='Start a new Gauntlet game against an AI') + @app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME) + async def new_game_gauntlet_command(self, interaction: discord.Interaction): + await interaction.response.defer() + + with Session(engine) as session: + conflict = get_channel_game_or_none(session, interaction.channel_id) + if conflict is not None: + await interaction.edit_original_response( + content=f'Ope. There is already a game going on in this channel. Please wait for it to complete ' + f'before starting a new one.' + ) + return + + if interaction.channel.category is None or interaction.channel.category.name != PUBLIC_FIELDS_CATEGORY_NAME: + await interaction.edit_original_response( + content=f'Why don\'t you head down to one of the Public Fields that way other humans can help if anything pops up?' + ) + return + + main_team = await get_team_or_none( + session, + gm_id=interaction.user.id + ) + if not main_team: + await interaction.edit_original_response( + content=f'I don\'t see a team for you, yet. You can sign up with the `/newteam` command!' + ) + return + + this_team = await get_team_or_none( + session, + team_abbrev=f'Gauntlet-{main_team.abbrev}' + ) + if not this_team: + await interaction.edit_original_response( + content=f'I don\'t see an active run for you. You can get started with the `/gauntlets start` command!' + ) + return + + e_query = await db_get('events', params=[('active', True)]) + if e_query['count'] == 0: + await interaction.edit_original_response( + content=f'Hm. It looks like there aren\'t any active gauntlets. What do we even pay Cal for?' + ) + return + + elif e_query['count'] == 1: + this_event = e_query['events'][0] + r_query = await db_get( + 'gauntletruns', + params=[('team_id', this_team['id']), ('gauntlet_id', this_event['id']), ('is_active', True)] + ) + + if r_query['count'] == 0: + await interaction.edit_original_response( + content=f'I don\'t see an active run for you. If you would like to start a new one, run ' + f'`/gauntlets start {this_event["name"]}` and we can get you started in no time!' + ) + return + + this_run = r_query['runs'][0] + + else: + r_query = await db_get( + 'gauntletruns', + params=[('team_id', this_team['id']), ('is_active', True)] + ) + + if r_query['count'] == 0: + await interaction.edit_original_response( + content=f'I don\'t see an active run for you. If you would like to start a new one, run ' + f'`/gauntlets start {e_query["events"][0]["name"]}` and we can get you started in no time!' + ) + return + else: + this_run = r_query['runs'][0] + this_event = r_query['runs'][0]['gauntlet'] + + # If not new or after draft, create new AI game + is_home = gauntlets.is_home_team(this_team, this_event, this_run) + opponent = await gauntlets.get_opponent(this_team, this_event, this_run) + if opponent is None: + await interaction.edit_original_response( + content=f'Yike. I\'m not sure who your next opponent is. Plz ping the shit out of Cal!' + ) + return + else: + logger.info(f'opponent: {opponent}') + + current = await db_get('current') + game_code = gauntlets.get_game_code(this_team, this_event, this_run) + + this_game = Game( + away_team_id=opponent.id if is_home else this_team.id, + home_team_id=this_team.id if is_home else opponent.id, + channel_id=interaction.channel_id, + season=current['season'], + week=current['week'], + first_message=None if interaction.message is None else interaction.message.channel.id, + ai_team='away' if is_home else 'home', + game_type=game_code + ) + logger.info( + f'Game {this_game.id} between {this_team.abbrev} and {opponent.abbrev} is posted!' + ) + t_role = await team_role(interaction, main_team) + + await interaction.edit_original_response( + content=f'Creating this game for {t_role.mention}:\n{this_game}' + ) + @commands.command(name='force-endgame', help='Mod: Force a game to end without stats') async def force_end_game_command(self, ctx: commands.Context): @@ -332,7 +466,8 @@ class Gameplay(commands.Cog): # if view.value: if nuke_game: - session.delete(this_game) + this_game.active = False + session.add(this_game) session.commit() await ctx.channel.send(content=random_gif(random_from_list(['i killed it', 'deed is done', 'gone forever']))) else: @@ -419,6 +554,23 @@ class Gameplay(commands.Cog): # await interaction.edit_original_response(content='Let\'s see, I didn\'t think this game was over...') await manual_end_game(session, interaction, this_game, current_play=this_play) + group_substitution = app_commands.Group(name='substitute', description='Make a substitution in active game') + + @group_substitution.command(name='batter', description='Make a batter substitution') + async def sub_batter_command(self, interaction: discord.Interaction, batting_order: Literal['this-spot', '1', '2', '3', '4', '5', '6', '7', '8', '9'] = 'this-spot'): + with Session(engine) as session: + this_game, owner_team, this_play = await checks_log_interaction(session, interaction, command_name='substitute batter') + + if batting_order == 'this-spot': + this_order = this_play.batting_order + else: + this_order = int(batting_order) + + logger.info(f'sub batter - this_play: {this_play}') + bat_view = sub_batter_dropdown_view(session, this_game, owner_team, this_order) + await interaction.edit_original_response(content=f'### {owner_team.lname} Substitution', view=bat_view) + + group_log = app_commands.Group(name='log', description='Log a play in this channel\'s game') @group_log.command(name='flyball', description='Flyballs: a, b, ballpark, bq, c') @@ -472,20 +624,6 @@ class Gameplay(commands.Cog): await self.complete_and_post_play(session, interaction, this_play, buffer_message='Single logged' if ((this_play.on_first or this_play.on_second) and single_type == 'uncapped') else None) - # complete_play(session, this_play) - - # if ((this_play.on_first or this_play.on_second) and single_type == 'uncapped'): - # await interaction.edit_original_response(content='Single logged') - # await interaction.channel.send( - # content=None, - # embed=await get_scorebug_embed(session, this_play.game, full_length=False, classic=CLASSIC_EMBED) - # ) - # else: - # await interaction.edit_original_response( - # content=None, - # embed=await get_scorebug_embed(session, this_play.game, full_length=False, classic=CLASSIC_EMBED) - # ) - @group_log.command(name='double', description='Doubles: **, ***, uncapped') async def log_double(self, interaction: discord.Interaction, double_type: Literal['**', '***', 'uncapped']): with Session(engine) as session: diff --git a/command_logic/logic_gameplay.py b/command_logic/logic_gameplay.py index 84b8193..1da4e6a 100644 --- a/command_logic/logic_gameplay.py +++ b/command_logic/logic_gameplay.py @@ -15,10 +15,10 @@ from exceptions import * from helpers import DEFENSE_LITERAL, SBA_COLOR, get_channel from in_game.game_helpers import legal_check from in_game.gameplay_models import BattingCard, Game, Lineup, PositionRating, RosterLink, Team, Play -from in_game.gameplay_queries import get_position, get_available_pitchers, get_card_or_none, get_channel_game_or_none, get_db_ready_decisions, get_db_ready_plays, get_game_lineups, get_last_team_play, get_one_lineup, get_player_id_from_dict, get_player_name_from_dict, get_player_or_none, get_sorted_lineups, get_team_or_none, get_players_last_pa, post_game_rewards +from in_game.gameplay_queries import get_available_batters, get_position, get_available_pitchers, get_card_or_none, get_channel_game_or_none, get_db_ready_decisions, get_db_ready_plays, get_game_lineups, get_last_team_play, get_one_lineup, get_player_id_from_dict, get_player_name_from_dict, get_player_or_none, get_sorted_lineups, get_team_or_none, get_players_last_pa, post_game_rewards from in_game.managerai_responses import DefenseResponse from utilities.buttons import ButtonOptions, Confirm, ask_confirm -from utilities.dropdown import DropdownView, SelectStartingPitcher, SelectViewDefense +from utilities.dropdown import DropdownView, SelectBatterSub, SelectStartingPitcher, SelectViewDefense from utilities.embeds import image_embed from utilities.pages import Pagination @@ -51,7 +51,7 @@ async def get_scorebug_embed(session: Session, this_game: Game, full_length: boo gt_string = ' - Flashback' elif 'exhibition' in this_game.game_type: gt_string = ' - Exhibition' - logger.info(f'gameplay_models - Game.get_scorebug_embed - this_game: {this_game} / gt_string: {gt_string}') + logger.info(f'get_scorebug_embed - this_game: {this_game} / gt_string: {gt_string}') embed = discord.Embed( title=f'{this_game.away_team.sname} @ {this_game.home_team.sname}{gt_string}', @@ -64,7 +64,7 @@ async def get_scorebug_embed(session: Session, this_game: Game, full_length: boo try: curr_play = this_game.initialize_play(session) except LineupsMissingException as e: - logger.debug(f'gameplay_models - Game.get_scorebug_embed - Could not initialize play') + logger.debug(f'get_scorebug_embed - Could not initialize play') if curr_play is not None: embed.add_field( @@ -72,6 +72,7 @@ async def get_scorebug_embed(session: Session, this_game: Game, full_length: boo value=curr_play.scorebug_ascii, inline=False ) + logger.info(f'curr_play: {curr_play}') def steal_string(batting_card: BattingCard) -> str: steal_string = '-/- (---)' @@ -244,6 +245,20 @@ def starting_pitcher_dropdown_view(session: Session, this_game: Game, human_team return DropdownView(dropdown_objects=[sp_selection]) +def sub_batter_dropdown_view(session: Session, this_game: Game, human_team: Team, batting_order: int): + batters = get_available_batters(session, this_game, human_team) + logger.info(f'batters: {batters}') + bat_selection = SelectBatterSub( + this_game=this_game, + this_team=human_team, + session=session, + batting_order=batting_order, + options=[SelectOption(label=f'{x.batterscouting.battingcard.hand.upper()} | {x.player.name_with_desc}', value=x.id) for x in batters], + placeholder='Select your Sub' + ) + return DropdownView(dropdown_objects=[bat_selection]) + + async def read_lineup(session: Session, interaction: discord.Interaction, this_game: Game, lineup_team: Team, sheets_auth, lineup: Choice[str], league_name: str): """ Commits lineups and rosterlinks @@ -368,10 +383,12 @@ def complete_play(session:Session, this_play: Play): """ Commits this_play and new_play """ + logger.info(f'Completing play {this_play.id} in game {this_play.game.id}') nso = this_play.starting_outs + this_play.outs runs_scored = 0 on_first, on_second, on_third = None, None, None + logger.info(f'Running bulk checks') is_go_ahead = False if nso >= 3: switch_sides = True @@ -435,12 +452,14 @@ def complete_play(session:Session, this_play: Play): obc = get_obc(on_first, on_second, on_third) + logger.info(f'Calculating re24') this_play.re24 = get_re24(this_play, runs_scored, new_obc=obc, new_starting_outs=nso) if nbo > 9: nbo = 1 new_batter = get_one_lineup(session, this_play.game, new_batter_team, batting_order=nbo) + logger.info(f'new_batter: {new_batter}') new_play = Play( game=this_play.game, @@ -468,6 +487,10 @@ def complete_play(session:Session, this_play: Play): this_play.wpa = get_wpa(this_play, new_play) this_play.locked = False this_play.complete = True + + logger.info(f'this_play: {this_play}') + logger.info(f'new_play: {new_play}') + session.add(this_play) session.add(new_play) session.commit() @@ -2069,22 +2092,22 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play: elif hit_result == 'G1': if this_play.on_first is not None and this_play.starting_outs < 2: - this_play = await gb_letter(session, interaction, this_play, 'B', position=this_play.check_pos, defender_is_in=defender_is_in) + this_play = await gb_letter(session, interaction, this_play, 'B', position=this_play.check_pos, defender_is_in=playing_in) else: - this_play = await gb_letter(session, interaction, this_play, 'A', position=this_play.check_pos, defender_is_in=defender_is_in) + this_play = await gb_letter(session, interaction, this_play, 'A', position=this_play.check_pos, defender_is_in=playing_in) elif hit_result == 'G2': if this_play.on_base_code > 0: - this_play = await gb_letter(session, interaction, this_play, 'C', position=this_play.check_pos, defender_is_in=defender_is_in) + this_play = await gb_letter(session, interaction, this_play, 'C', position=this_play.check_pos, defender_is_in=playing_in) else: - this_play = await gb_letter(session, interaction, this_play, 'B', position=this_play.check_pos, defender_is_in=defender_is_in) + this_play = await gb_letter(session, interaction, this_play, 'B', position=this_play.check_pos, defender_is_in=playing_in) elif hit_result == 'G3': if this_play.on_base_code > 0: this_play = await singles(session, interaction, this_play, '*') else: - this_play = await gb_letter(session, interaction, this_play, 'C', position=this_play.check_pos, defender_is_in=defender_is_in) + this_play = await gb_letter(session, interaction, this_play, 'C', position=this_play.check_pos, defender_is_in=playing_in) elif hit_result == 'SPD': this_play = singles(session, interaction, this_play, '*') @@ -2136,29 +2159,29 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play: to_right_side = position in ['1B', '2B'] if 'G3' in hit_result: - if this_play.on_base_code == 2 and not defender_is_in: + if this_play.on_base_code == 2 and not playing_in: this_play = await gb_result(session, interaction, this_play, 12) - elif defender_is_in and this_play.on_base_code == 5: + elif playing_in and this_play.on_base_code == 5: this_play = await gb_result(session, interaction, this_play, 7, to_mif, to_right_side) - elif defender_is_in and this_play.on_base_code in [3, 6]: + elif playing_in and this_play.on_base_code in [3, 6]: this_play = await gb_decide(session, interaction=interaction, this_play=this_play) - elif defender_is_in and this_play.on_base_code == 7: + elif playing_in and this_play.on_base_code == 7: this_play = await gb_result(session, interaction, this_play, 11) else: this_play = await gb_result(session, interaction, this_play, 3) elif 'G2' in hit_result: - if this_play.on_base_code == 7 and defender_is_in: + if this_play.on_base_code == 7 and playing_in: this_play = await gb_result(session, interaction, this_play, 11) - elif not defender_is_in and this_play.on_base_code in [3, 6]: + elif not playing_in and this_play.on_base_code in [3, 6]: this_play = await gb_result(session, interaction, this_play, 5, to_mif=to_mif) - elif defender_is_in and this_play.on_base_code in [3, 5, 6]: + elif playing_in and this_play.on_base_code in [3, 5, 6]: this_play = await gb_result(session, interaction, this_play, 1) elif this_play.on_base_code == 2: @@ -2168,16 +2191,16 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play: this_play = await gb_result(session, interaction, this_play, 4) elif 'G1' in hit_result: - if this_play.on_base_code == 7 and defender_is_in: + if this_play.on_base_code == 7 and playing_in: this_play = await gb_result(session, interaction, this_play, 10) - elif not defender_is_in and this_play.on_base_code == 4: + elif not playing_in and this_play.on_base_code == 4: this_play = await gb_result(session, interaction, this_play, 13) - elif not defender_is_in and this_play.on_base_code in [3, 6]: + elif not playing_in and this_play.on_base_code in [3, 6]: this_play = await gb_result(session, interaction, this_play, 3) - elif defender_is_in and this_play.on_base_code in [3, 5, 6]: + elif playing_in and this_play.on_base_code in [3, 5, 6]: this_play = await gb_result(session, interaction, this_play, 1) elif this_play.on_base_code == 2: @@ -2264,8 +2287,10 @@ async def xchecks(session: Session, interaction: discord.Interaction, this_play: def activate_last_play(session: Session, this_game: Game) -> Play: + logger.info(f'Pulling last play to complete and advance') p_query = session.exec(select(Play).where(Play.game == this_game).order_by(Play.id.desc()).limit(1)).all() + logger.info(f'last play: {p_query[0].id}') this_play = complete_play(session, p_query[0]) return this_play @@ -2273,6 +2298,7 @@ def activate_last_play(session: Session, this_game: Game) -> Play: def undo_play(session: Session, this_play: Play): this_game = this_play.game + after_play_min = max(1, this_play.play_num - 2) last_two_plays = session.exec(select(Play).where(Play.game == this_game).order_by(Play.id.desc()).limit(2)).all() @@ -2286,12 +2312,27 @@ def undo_play(session: Session, this_play: Play): last_two_ids = [last_two_plays[0].id, last_two_plays[1].id] logger.warning(f'Deleting plays: {last_two_ids}') session.exec(delete(Play).where(Play.id.in_(last_two_ids))) + + new_player_ids = [] + new_players = session.exec(select(Lineup).where(Lineup.game == this_game, Lineup.after_play >= after_play_min)).all() + logger.info(f'Subs to roll back: {new_players}') + for lineup in new_players: + new_players.append(lineup.id) + old_player = session.get(Lineup, lineup.replacing_id) + old_player.active = True + session.add(old_player) + + logger.warning(f'Deleting lineup IDs: {new_player_ids}') + session.exec(delete(Lineup).where(Lineup.id.in_(new_player_ids))) + session.commit() try: + logger.info(f'Attempting to initialize play for Game {this_game.id}...') this_play = this_game.initialize_play(session) logger.info(f'Initialized play: {this_play.id}') except PlayInitException: + logger.info(f'Plays found, attempting to active the last play') this_play = activate_last_play(session, this_game) logger.info(f'Re-activated play: {this_play.id}') diff --git a/in_game/gameplay_models.py b/in_game/gameplay_models.py index 63c0ebf..6238572 100644 --- a/in_game/gameplay_models.py +++ b/in_game/gameplay_models.py @@ -163,192 +163,6 @@ class Game(SQLModel, table=True): return this_play[0] else: return None - - # async def get_scorebug_embed(self, session: Session, full_length: bool = True, classic: bool = True) -> discord.Embed: - # gt_string = ' - Unlimited' - # if self.game_type == 'minor-league': - # gt_string = ' - Minor League' - # elif self.game_type == 'major-league': - # gt_string = ' - Major League' - # elif self.game_type == 'hall-of-fame': - # gt_string = ' - Hall of Fame' - # elif 'gauntlet' in self.game_type: - # gt_string = ' - Gauntlet' - # elif 'flashback' in self.game_type: - # gt_string = ' - Flashback' - # elif 'exhibition' in self.game_type: - # gt_string = ' - Exhibition' - # logger.info(f'gameplay_models - Game.get_scorebug_embed - this_game: {self} / gt_string: {gt_string}') - - # embed = discord.Embed( - # title=f'{self.away_team.sname} @ {self.home_team.sname}{gt_string}', - # color=int(SBA_COLOR, 16) - # ) - - # curr_play = self.current_play_or_none(session) - - # if curr_play is None: - # try: - # curr_play = self.initialize_play(session) - # except LineupsMissingException as e: - # logger.debug(f'gameplay_models - Game.get_scorebug_embed - Could not initialize play') - - # if curr_play is not None: - # embed.add_field( - # name='Game State', - # value=curr_play.scorebug_ascii, - # inline=False - # ) - - # def steal_string(batting_card: BattingCard) -> str: - # steal_string = '-/- (---)' - # if batting_card.steal_jump > 0: - # jump_chances = round(batting_card.steal_jump * 36) - - # if jump_chances == 6: - # good_jump = 7 - # elif jump_chances == 5: - # good_jump = 6 - # elif jump_chances == 4: - # good_jump = 5 - # elif jump_chances == 3: - # good_jump = 4 - # elif jump_chances == 2: - # good_jump = 3 - # elif jump_chances == 1: - # good_jump = 2 - # elif jump_chances == 7: - # good_jump = '4,5' - # elif jump_chances == 8: - # good_jump = '4,6' - # elif jump_chances == 9: - # good_jump = '3-5' - # elif jump_chances == 10: - # good_jump = '2-5' - # elif jump_chances == 11: - # good_jump = '6,7' - # elif jump_chances == 12: - # good_jump = '4-6' - # elif jump_chances == 13: - # good_jump = '2,4-6' - # elif jump_chances == 14: - # good_jump = '3-6' - # elif jump_chances == 15: - # good_jump = '2-6' - # elif jump_chances == 16: - # good_jump = '2,5-6' - # elif jump_chances == 17: - # good_jump = '3,5-6' - # elif jump_chances == 18: - # good_jump = '4-6' - # elif jump_chances == 19: - # good_jump = '2,4-7' - # elif jump_chances == 20: - # good_jump = '3-7' - # elif jump_chances == 21: - # good_jump = '2-7' - # elif jump_chances == 22: - # good_jump = '2-7,12' - # elif jump_chances == 23: - # good_jump = '2-7,11' - # elif jump_chances == 24: - # good_jump = '2,4-8' - # elif jump_chances == 25: - # good_jump = '3-8' - # elif jump_chances == 26: - # good_jump = '2-8' - # elif jump_chances == 27: - # good_jump = '2-8,12' - # elif jump_chances == 28: - # good_jump = '2-8,11' - # elif jump_chances == 29: - # good_jump = '3-9' - # elif jump_chances == 30: - # good_jump = '2-9' - # elif jump_chances == 31: - # good_jump = '2-9,12' - # elif jump_chances == 32: - # good_jump = '2-9,11' - # elif jump_chances == 33: - # good_jump = '2-10' - # elif jump_chances == 34: - # good_jump = '3-11' - # elif jump_chances == 35: - # good_jump = '2-11' - # else: - # good_jump = '2-12' - # steal_string = f'{"*" if batting_card.steal_auto else ""}{good_jump}/- ({batting_card.steal_high}-{batting_card.steal_low})' - # return steal_string - - # baserunner_string = '' - # if curr_play.on_first is not None: - # runcard = curr_play.on_first.card.batterscouting.battingcard - # baserunner_string += f'On First: {curr_play.on_first.player.name_card_link('batting')}\nSteal: {steal_string(runcard)}, Run: {runcard.running}' - # if curr_play.on_second is not None: - # baserunner_string += f'On Second: {curr_play.on_second.player.name_card_link('batting')}\nSteal: {steal_string(runcard)}, Run: {runcard.running}' - # if curr_play.on_third is not None: - # baserunner_string += f'On Third: {curr_play.on_third.player.name_card_link('batting')}\nSteal: {steal_string(runcard)}, Run: {runcard.running}' - # logger.info(f'gameplay_models - Game.get_scorebug_embed - baserunner_string: {baserunner_string}') - - # pit_string = f'{curr_play.pitcher.player.name_card_link('pitching')}' - # bat_string = f'{curr_play.batter.player.name_card_link('batting')}' - # if len(baserunner_string) > 0: - # pitchingcard = curr_play.pitcher.card.pitcherscouting.pitchingcard - # pit_string += f'\nHold: {pitchingcard.hold}, WP: {pitchingcard.wild_pitch}, Bk: {pitchingcard.balk}' - - # # battingcard = curr_play.batter.card.batterscouting.battingcard - # # bat_string += f'\nBunt: {battingcard.bunting}, HnR: {battingcard.hit_and_run}' - - # embed.add_field( - # name='Pitcher', - # value=pit_string - # ) - # embed.add_field( - # name='Batter', - # value=bat_string - # ) - - # if len(baserunner_string) > 0: - # embed.add_field(name=' ', value=' ', inline=False) - # embed.add_field(name='Baserunners', value=baserunner_string) - - # c_query = session.exec(select(PositionRating).where(PositionRating.player_id == curr_play.catcher.card.player.id, PositionRating.position == 'C', PositionRating.variant == curr_play.catcher.card.variant)).all() - # if len(c_query) > 0: - # catcher_rating = c_query[0] - # else: - # log_exception(PositionNotFoundException, f'No catcher rating found for {curr_play.catcher.card.player.name}') - - # cat_string = f'{curr_play.catcher.player.name_card_link('batter')}\nArm: {catcher_rating.arm}' - # embed.add_field(name='Catcher', value=cat_string) - - # ai_note = curr_play.ai_note - # logger.info(f'gameplay_models - Game.get_scorebug_embed - ai_note: {ai_note}') - # if len(ai_note) > 0: - # gm_name = self.home_team.gmname if self.ai_team == 'home' else self.away_team.gmname - # embed.add_field(name=f'{gm_name} will...', value=ai_note, inline=False) - # else: - # embed.add_field(name=' ', value=' ', inline=False) - - # if full_length: - # embed.add_field( - # name=f'{self.away_team.abbrev} Lineup', - # value=self.team_lineup(session, self.away_team) - # ) - # embed.add_field( - # name=f'{self.home_team.abbrev} Lineup', - # value=self.team_lineup(session, self.home_team) - # ) - # else: - # embed.add_field( - # name=f'{self.away_team.abbrev} Lineup', - # value=self.team_lineup(session, self.away_team) - # ) - # embed.add_field( - # name=f'{self.home_team.abbrev} Lineup', - # value=self.team_lineup(session, self.home_team) - # ) - - # return embed def initialize_play(self, session: Session): """ @@ -722,6 +536,9 @@ class ManagerAi(ManagerAiBase, table=True): this_resp.outfield_in = True this_resp.infield_in = True this_resp.ai_note += f'- play the outfield and infield in' + elif this_play.on_first and this_play.starting_outs == 1: + this_resp.corners_in = True + this_resp.ai_note += f'- play the corners in\n' elif abs(this_play.away_score - this_play.home_score) <= 3: this_resp.infield_in = True this_resp.ai_note += f'- play the whole infield in\n' diff --git a/in_game/gameplay_queries.py b/in_game/gameplay_queries.py index 091b3f7..be22f45 100644 --- a/in_game/gameplay_queries.py +++ b/in_game/gameplay_queries.py @@ -129,17 +129,19 @@ async def get_team_or_none( async def get_player_or_none(session: Session, player_id: int, skip_cache: bool = False) -> Player | None: - logger.info(f'gameplay_models - get_player_or_none - player_id: {player_id}') + logger.info(f'gameplay_models - get_player_or_none - player_id: {player_id} / skip_cache: {skip_cache}') if not skip_cache: this_player = session.get(Player, player_id) if this_player is not None: - logger.info(f'we found a cached player: {this_player} / created: {this_player.created}') + logger.info(f'we found a cached player: {this_player}\ncreated: {this_player.created}') tdelta = datetime.datetime.now() - this_player.created - logger.debug(f'tdelta: {tdelta}') + logger.info(f'tdelta: {tdelta}') if tdelta.total_seconds() < CACHE_LIMIT: + logger.info(f'returning this player') return this_player else: + logger.warning('Deleting old player record') session.delete(this_player) session.commit() @@ -164,37 +166,29 @@ async def get_player_or_none(session: Session, player_id: int, skip_cache: bool async def get_batter_scouting_or_none(session: Session, card: Card, skip_cache: bool = False) -> BatterScouting | None: - logger.info(f'Getting batting scouting for card ID #{card.id}: {card.player.name_with_desc}') + logger.info(f'Getting batting scouting for card ID #{card.id}: {card.player.name_with_desc} / skip_cache: {skip_cache}') s_query = await db_get(f'battingcardratings/player/{card.player.id}', none_okay=False) if s_query['count'] != 2: log_exception(DatabaseError, f'Scouting for {card.player.name_with_desc} was not found.') - this_scouting = session.get(BattingCard, s_query['ratings'][0]['battingcard']['id']) + this_scouting = session.exec(select(BatterScouting).where(BatterScouting.battingcard_id == s_query['ratings'][0]['battingcard']['id'])).all() + logger.info(f'this_scouting: {this_scouting}') + # this_scouting = session.get(BatterScouting, s_query['ratings'][0]['battingcard']['id']) - if this_scouting is not None: + if len(this_scouting) > 0: logger.info(f'we found a cached scouting: {this_scouting} / created {this_scouting.created}') tdelta = datetime.datetime.now() - this_scouting.created logger.debug(f'tdelta: {tdelta}') if tdelta.total_seconds() < CACHE_LIMIT: return this_scouting else: + session.delete(this_scouting.battingcard) + session.delete(this_scouting.ratings_vl) + session.delete(this_scouting.ratings_vr) session.delete(this_scouting) session.commit() - # if not skip_cache and card.batterscouting is not None: - # this_scouting = session.get(BatterScouting, card.batterscouting.id) - - # if this_scouting is not None: - # logger.info(f'we found a cached scouting: {this_scouting} / created {this_scouting.created}') - # tdelta = datetime.datetime.now() - this_scouting.created - # logger.debug(f'tdelta: {tdelta}') - # if tdelta.total_seconds() < CACHE_LIMIT: - # return this_scouting - # else: - # session.delete(this_scouting) - # session.commit() - def cache_scouting(batting_card: dict, ratings_vr: dict, ratings_vl: dict) -> BatterScouting: valid_bc = BattingCardBase.model_validate(batting_card, from_attributes=True) db_bc = BattingCard.model_validate(valid_bc) @@ -212,8 +206,10 @@ async def get_batter_scouting_or_none(session: Session, card: Card, skip_cache: ) session.add(db_scouting) + logger.info(f'caching scouting') session.commit() session.refresh(db_scouting) + logger.info(f'scouting id: {db_scouting.id} / battingcard: {db_scouting.battingcard.id} / vL: {db_scouting.ratings_vl.id} / vR: {db_scouting.ratings_vr.id}') return db_scouting return cache_scouting( @@ -222,16 +218,6 @@ async def get_batter_scouting_or_none(session: Session, card: Card, skip_cache: ratings_vl=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'L' else s_query['ratings'][1] ) - # s_query = await db_get(f'battingcardratings/player/{card.player.id}', none_okay=False) - # if s_query['count'] == 2: - # return cache_scouting( - # batting_card=s_query['ratings'][0]['battingcard'], - # ratings_vr=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'R' else s_query['ratings'][1], - # ratings_vl=s_query['ratings'][0] if s_query['ratings'][0]['vs_hand'] == 'L' else s_query['ratings'][1] - # ) - - # return None - async def get_pitcher_scouting_or_none(session: Session, card: Card, skip_cache: bool = False) -> PitcherScouting | None: logger.info(f'Getting pitching scouting for card ID #{card.id}: {card.player.name_with_desc}') @@ -240,9 +226,11 @@ async def get_pitcher_scouting_or_none(session: Session, card: Card, skip_cache: if s_query['count'] != 2: log_exception(DatabaseError, f'Scouting for {card.player.name_with_desc} was not found.') - this_scouting = session.get(PitcherScouting, s_query['ratings'][0]['pitchingcard']['id']) + this_scouting = session.exec(select(PitcherScouting).where(PitcherScouting.pitchingcard_id == s_query['ratings'][0]['pitchingcard']['id'])).all() + logger.info(f'this_scouting: {this_scouting}') + # this_scouting = session.get(PitcherScouting, s_query['ratings'][0]['pitchingcard']['id']) - if this_scouting is not None: + if len(this_scouting) > 0: logger.info(f'we found a cached scouting: {this_scouting} / created {this_scouting.created}') tdelta = datetime.datetime.now() - this_scouting.created logger.debug(f'tdelta: {tdelta}') @@ -251,6 +239,9 @@ async def get_pitcher_scouting_or_none(session: Session, card: Card, skip_cache: return this_scouting else: + session.delete(this_scouting.pitchingcard) + session.delete(this_scouting.ratings_vl) + session.delete(this_scouting.ratings_vr) session.delete(this_scouting) session.commit() @@ -271,8 +262,10 @@ async def get_pitcher_scouting_or_none(session: Session, card: Card, skip_cache: ) session.add(db_scouting) + logger.info(f'caching scouting') session.commit() session.refresh(db_scouting) + logger.info(f'scouting id: {db_scouting.id} / pitching: {db_scouting.pitchingcard.id} / vL: {db_scouting.ratings_vl.id} / vR: {db_scouting.ratings_vr.id}') return db_scouting scouting = cache_scouting( @@ -394,7 +387,7 @@ async def shared_get_scouting(session: Session, this_card: Card, which: Literal[ async def get_position(session: Session, this_card: Card, position: Literal['P', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF'], skip_cache: bool = False) -> PositionRating: - logger.info(f'Pulling position rating for {this_card.player.name_with_desc} at {position}') + logger.info(f'Pulling position rating for {this_card.player.name_with_desc} at {position} / skip_cache: {skip_cache}') if not skip_cache: this_pos = session.exec(select(PositionRating).where(PositionRating.player_id == this_card.player.id, PositionRating.position == position, PositionRating.variant == this_card.variant)).all() logger.info(f'Ratings found: {len(this_pos)}') @@ -435,7 +428,7 @@ async def get_position(session: Session, this_card: Card, position: Literal['P', async def get_or_create_ai_card(session: Session, player: Player, team: Team, skip_cache: bool = False, dev_mode: bool = False) -> Card: - logger.info(f'Getting or creating card for {player.name_with_desc} on the {team.sname}') + logger.info(f'Getting or creating card for {player.name_with_desc} on the {team.sname} / skip_cache: {skip_cache}') if not team.is_ai: err = f'Cannot create AI cards for human teams' logger.error(f'gameplay_models - get_or_create_ai_card: {err}') @@ -453,6 +446,7 @@ async def get_or_create_ai_card(session: Session, player: Player, team: Team, sk if tdelta.total_seconds() < CACHE_LIMIT: return this_card else: + logger.info(f'deleting card record') session.delete(this_card) session.commit() @@ -520,7 +514,7 @@ async def get_or_create_ai_card(session: Session, player: Player, team: Team, sk async def get_card_or_none(session: Session, card_id: int, skip_cache: bool = False) -> Card | None: - logger.info(f'Getting card {card_id}') + logger.info(f'Getting card {card_id} / skip_cache: {skip_cache}') if not skip_cache: this_card = session.get(Card, card_id) @@ -886,8 +880,10 @@ async def post_game_rewards(session: Session, winning_team: Team, losing_team: T def get_available_subs(session: Session, this_game: Game, this_team: Team) -> list[Card]: + logger.info(f'Getting all available subs') team_lineups = session.exec(select(Lineup).where(Lineup.game == this_game, Lineup.team == this_team)).all() used_card_ids = [x.card.id for x in team_lineups] + logger.info(f'USED CARD IDS: {used_card_ids}') all_roster_links = session.exec(select(RosterLink).where(RosterLink.game == this_game, RosterLink.team == this_team)).all() diff --git a/in_game/managerai_responses.py b/in_game/managerai_responses.py index 7d8755d..f877945 100644 --- a/in_game/managerai_responses.py +++ b/in_game/managerai_responses.py @@ -41,11 +41,11 @@ class DefenseResponse(AiResponse): corners_in: bool = False def defender_in(self, position: str): - if self.infield_in and position in ['C', '1B', '2B', '3B', 'SS', 'P']: + if self.infield_in and (position in ['C', '1B', '2B', '3B', 'SS', 'P']): return True - elif self.corners_in and position in ['C', '1B', '3B', 'P']: + elif self.corners_in and (position in ['C', '1B', '3B', 'P']): return True - elif self.outfield_in and position in ['LF', 'CF', 'RF']: + elif self.outfield_in and (position in ['LF', 'CF', 'RF']): return True return False diff --git a/utilities/buttons.py b/utilities/buttons.py index 62c63c2..5002a51 100644 --- a/utilities/buttons.py +++ b/utilities/buttons.py @@ -3,6 +3,7 @@ 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 @@ -51,34 +52,59 @@ class Confirm(discord.ui.View): class ButtonOptions(discord.ui.View): - def __init__(self, responders: list, timeout: float = 300.0, labels=None): + 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 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) + 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): @@ -89,9 +115,11 @@ class ButtonOptions(discord.ui.View): delete_after=10.0 ) - self.value = self.options[0] - self.clear_items() 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): @@ -102,9 +130,11 @@ class ButtonOptions(discord.ui.View): delete_after=10.0 ) - self.value = self.options[1] - self.clear_items() 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): @@ -115,9 +145,11 @@ class ButtonOptions(discord.ui.View): delete_after=10.0 ) - self.value = self.options[2] - self.clear_items() 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): @@ -128,9 +160,11 @@ class ButtonOptions(discord.ui.View): delete_after=10.0 ) - self.value = self.options[3] - self.clear_items() 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): @@ -141,9 +175,11 @@ class ButtonOptions(discord.ui.View): delete_after=10.0 ) - self.value = self.options[4] - self.clear_items() 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: @@ -177,6 +213,87 @@ async def ask_confirm(interaction: discord.Interaction, question: str, label_typ 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) diff --git a/utilities/dropdown.py b/utilities/dropdown.py index dfea1a6..4a09227 100644 --- a/utilities/dropdown.py +++ b/utilities/dropdown.py @@ -6,10 +6,11 @@ from discord import SelectOption from discord.utils import MISSING from sqlmodel import Session -from exceptions import CardNotFoundException, log_exception +from exceptions import CardNotFoundException, PlayNotFoundException, log_exception 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_position, get_card_or_none +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') @@ -143,6 +144,145 @@ class SelectStartingPitcher(discord.ui.Select): 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 + + # 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_batter_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 + + 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 + ) + \ No newline at end of file