From 3db25b177abd4b4c3ccb6d631bb43ede9c043d69 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Thu, 26 Dec 2024 14:36:04 -0600 Subject: [PATCH] Refactor new game checks Trail batter-runner on uncapped hits --- cogs/gameplay.py | 159 ++++++++++++++++++++++---------- command_logic/logic_gameplay.py | 39 ++++++-- exceptions.py | 4 + gauntlets.py | 3 +- in_game/ai_manager.py | 2 + in_game/gameplay_queries.py | 8 +- 6 files changed, 155 insertions(+), 60 deletions(-) diff --git a/cogs/gameplay.py b/cogs/gameplay.py index 0806f7d..ed07c6b 100644 --- a/cogs/gameplay.py +++ b/cogs/gameplay.py @@ -11,7 +11,7 @@ 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, sub_batter_dropdown_view, triples, undo_play, update_game_settings, walks, xchecks, activate_last_play +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, new_game_conflicts, 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 @@ -166,30 +166,20 @@ class Gameplay(commands.Cog): 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?' - ) + try: + await new_game_conflicts(session, interaction) + except GameException as e: return - try: - away_team = await get_team_or_none(session, team_abbrev=away_team_abbrev) - except LookupError as e: + away_team = await get_team_or_none(session, team_abbrev=away_team_abbrev) + if away_team is None: await interaction.edit_original_response( content=f'Hm. I\'m not sure who **{away_team_abbrev}** is - check on that and try again!' ) return - try: - home_team = await get_team_or_none(session, team_abbrev=home_team_abbrev) - except LookupError as e: + + home_team = await get_team_or_none(session, team_abbrev=home_team_abbrev) + if home_team is None: await interaction.edit_original_response( content=f'Hm. I\'m not sure who **{home_team_abbrev}** is - check on that and try again!' ) @@ -332,35 +322,29 @@ class Gameplay(commands.Cog): 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?' - ) + try: + await new_game_conflicts(session, interaction) + except GameException as e: return main_team = await get_team_or_none( session, - gm_id=interaction.user.id + gm_id=interaction.user.id, + main_team=True ) + human_team = await get_team_or_none( + session, + gm_id=interaction.user.id, + gauntlet_team=True + ) + 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: + if not human_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!' ) @@ -377,7 +361,7 @@ class Gameplay(commands.Cog): 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)] + params=[('team_id', human_team['id']), ('gauntlet_id', this_event['id']), ('is_active', True)] ) if r_query['count'] == 0: @@ -392,7 +376,7 @@ class Gameplay(commands.Cog): else: r_query = await db_get( 'gauntletruns', - params=[('team_id', this_team['id']), ('is_active', True)] + params=[('team_id', human_team.id), ('is_active', True)] ) if r_query['count'] == 0: @@ -406,22 +390,34 @@ class Gameplay(commands.Cog): 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: + is_home = gauntlets.is_home_team(human_team, this_event, this_run) + ai_team = await gauntlets.get_opponent(session, human_team, this_event, this_run) + if ai_team 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}') + logger.info(f'opponent: {ai_team}') + + ai_role = await team_role(interaction, ai_team) + human_role = await team_role(interaction, human_team) + away_role = ai_role if is_home else human_role + home_role = human_role if is_home else ai_role + + conflict_games = get_active_games_by_team(session, team=human_team) + if len(conflict_games) > 0: + await interaction.edit_original_response( + content=f'Ope. The {human_team.sname} are already playing over in {interaction.guild.get_channel(conflict_games[0].channel_id).mention}' + ) + return current = await db_get('current') - game_code = gauntlets.get_game_code(this_team, this_event, this_run) + game_code = gauntlets.get_game_code(human_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, + away_team_id=ai_team.id if is_home else human_team.id, + home_team_id=human_team.id if is_home else ai_team.id, channel_id=interaction.channel_id, season=current['season'], week=current['week'], @@ -430,14 +426,79 @@ class Gameplay(commands.Cog): game_type=game_code ) logger.info( - f'Game {this_game.id} between {this_team.abbrev} and {opponent.abbrev} is posted!' + f'Game {this_game.id} between {human_team.abbrev} and {ai_team.abbrev} is initializing!' ) - t_role = await team_role(interaction, main_team) + # Get AI SP + await interaction.edit_original_response( + content=f'{ai_team.gmname} is looking for a Starting Pitcher...' + ) + ai_sp_lineup = await get_starting_pitcher( + session, + ai_team, + this_game, + is_home=True if not is_home else False, + league_name=game_code + ) + logger.info(f'Chosen SP in Game {this_game.id}: {ai_sp_lineup.player.name_with_desc}') + await interaction.edit_original_response( + content=f'The {ai_team.sname} are starting **{ai_sp_lineup.player.name_with_desc}**:\n\n{ai_sp_lineup.player.pitcher_card_url}' + ) + + # Get AI Lineup + final_message = await interaction.channel.send( + content=f'{ai_team.gmname} is filling out the {ai_team.sname} lineup card...' + ) + logger.info(f'Pulling lineup in Game {this_game.id}') + batter_lineups = await get_starting_lineup( + session, + team=ai_team, + game=this_game, + league_name=game_code, + sp_name=ai_sp_lineup.player.name + ) + await interaction.edit_original_response( content=f'Creating this game for {t_role.mention}:\n{this_game}' ) + # Check for last game settings + logger.info(f'Checking human team\'s automation preferences...') + g_query = session.exec(select(Game).where(or_(Game.home_team == human_team, Game.away_team == human_team)).order_by(Game.id.desc()).limit(1)).all() + + if len(g_query) > 0: + last_game = g_query[0] + this_game.auto_roll = last_game.auto_roll + this_game.roll_buttons = last_game.roll_buttons + logger.info(f'Setting auto_roll to {last_game.auto_roll} and roll_buttons to {last_game.roll_buttons}') + + # Commit game and lineups + session.add(this_game) + session.commit() + + await final_message.edit(content=f'The {ai_team.sname} lineup is in, pulling in scouting data...') + for batter in batter_lineups: + if batter.position != 'DH': + await get_position(session, batter.card, batter.position) + + embed = await get_scorebug_embed(session, this_game) + embed.clear_fields() + embed.add_field( + name=f'{ai_team.abbrev} Lineup', + value=this_game.team_lineup(session, ai_team) + ) + + # Get pitchers from rosterlinks + done = await get_full_roster_from_sheets(session, interaction, self.sheets, this_game, human_team, 1) + if done: + sp_view = starting_pitcher_dropdown_view(session, this_game, human_team) + await interaction.channel.send(content=f'### {human_team.lname} Starting Pitcher', view=sp_view) + + await final_message.edit( + content=f'{away_role.mention} @ {home_role.mention} is set!', + embed=embed + ) + # TODO: add new-game exhibition, unlimited, and ranked @commands.command(name='force-endgame', help='Mod: Force a game to end without stats') @@ -527,7 +588,7 @@ class Gameplay(commands.Cog): return this_play = this_game.current_play_or_none(session) - await self.post_play(session, interaction, this_play) + await self.post_play(session, interaction, this_play, full_length=include_lineups) @app_commands.command(name='settings-ingame', description='Change in-game settings') @app_commands.describe( diff --git a/command_logic/logic_gameplay.py b/command_logic/logic_gameplay.py index 3089e26..38dd3d9 100644 --- a/command_logic/logic_gameplay.py +++ b/command_logic/logic_gameplay.py @@ -14,7 +14,7 @@ from api_calls import db_delete, db_get, db_post from dice import DTwentyRoll, d_twenty_roll, frame_plate_check, sa_fielding_roll from exceptions import * from helpers import DEFENSE_LITERAL, SBA_COLOR, get_channel -from in_game.game_helpers import legal_check +from in_game.game_helpers import PUBLIC_FIELDS_CATEGORY_NAME, legal_check from in_game.gameplay_models import BattingCard, Game, Lineup, PositionRating, RosterLink, Team, Play from in_game.gameplay_queries import get_available_batters, get_batter_card, get_batting_statline, get_pitching_statline, 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 @@ -968,9 +968,10 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_play ranges_embed = this_of.team.embed ranges_embed.title = f'Tag Play' ranges_embed.description = f'{this_of.team.abbrev} {this_of.position} {this_of.card.player.name}\'s Throw vs {runner.name}' - ranges_embed.add_field(name=f'{this_of.position} Arm', value=f'{"+" if of_rating.arm > 0 else ""}{of_rating.arm}') ranges_embed.add_field(name=f'Runner Speed', value=runner_lineup.card.batterscouting.battingcard.running) - ranges_embed.add_field(name=f'{this_of.position} Mod', value=f'{of_mod}', inline=False) + ranges_embed.add_field(name=f'{this_of.position} Arm', value=f'{"+" if of_rating.arm > 0 else ""}{of_rating.arm}') + ranges_embed.add_field(name=f'{this_of.position} Mod', value=f'{of_mod}') + ranges_embed.add_field(name='', value='', inline=False) ranges_embed.add_field(name='Safe Range', value=safe_string) ranges_embed.add_field(name='Hold Range', value=hold_string) ranges_embed.add_field(name='Out Range', value=out_string) @@ -1320,7 +1321,6 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact of_rating = await get_position(session, this_card=outfielder.card, position=outfielder.position) c_rating = await get_position(session, this_play.catcher.card, position='C') runner_embed = this_play.batter.team.embed - runner_embed.add_field(name=f'{outfielder.position} Arm', value=f'{"+" if of_rating.arm > 0 else ""}{of_rating.arm}') safe_range = None @@ -1336,6 +1336,7 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact lead_runner_embed.title = f'{lead_runner.player.name} To {"Home" if lead_base == 4 else "Third"}' lead_runner_embed.description = f'{outfielder.team.abbrev} {outfielder.position} {outfielder.player.name}\'s Throw' lead_runner_embed.add_field(name=f'Runner Speed', value=lead_runner.card.batterscouting.battingcard.running) + lead_runner_embed.add_field(name=f'{outfielder.position} Arm', value=f'{"+" if of_rating.arm > 0 else ""}{of_rating.arm}') if this_play.starting_outs == 2: logger.info(f'Adding 2 for 2 outs') @@ -1353,11 +1354,13 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact # Build trail runner embed trail_runner_embed = copy.deepcopy(runner_embed) trail_bc = get_batter_card(this_lineup=trail_runner) + logger.info(f'trail runner batting card: {trail_bc}') + trail_runner_embed.title = f'{trail_runner.player.name} To {"Third" if trail_base == 3 else "Second"}' trail_runner_embed.description = f'{outfielder.team.abbrev} {outfielder.position} {outfielder.player.name}\'s Throw' trail_runner_embed.add_field(name=f'Runner Speed', value=trail_bc.running) - logger.info(f'trail runner batting card: {trail_bc}') + trail_runner_embed.add_field(name=f'{outfielder.position} Arm', value=f'{"+" if of_rating.arm > 0 else ""}{of_rating.arm}') trail_safe_range = trail_bc.running - 5 + of_rating.arm logger.info(f'trail_safe_range: {trail_safe_range}') @@ -1679,6 +1682,10 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact logger.info(f'run scored from first') this_play.rbi += 1 log_run_scored(session, lead_runner, this_play) + + if trail_runner != this_play.batter: + logger.info(f'Trail runner is not batter, advancing batter') + this_play.batter_final += 1 return this_play @@ -1834,6 +1841,10 @@ async def check_uncapped_advance(session: Session, interaction: discord.Interact if this_play.on_first_final > 3: this_play.rbi += 1 log_run_scored(session, lead_runner, this_play) + + if trail_runner != this_play.batter: + logger.info(f'Trail runner is not batter, advancing batter') + this_play.batter_final += 1 return this_play @@ -3177,7 +3188,7 @@ async def groundballs(session: Session, interaction: discord.Interaction, this_p question=f'Was that ball hit to either 2B or SS?', label_type='yes' ) - + if not to_mif: logger.info(f'Not to a MIF, gb 7') this_play = gb_result_7(session, this_play) @@ -3500,3 +3511,19 @@ def gb_result_13(session: Session, this_play: Play): this_play = gb_result_2(session, this_play) return this_play + + +async def new_game_conflicts(session: Session, interaction: discord.Interaction): + 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.' + ) + log_exception(GameException, f'{interaction.user} attempted to start a new game in {interaction.channel.name}, but there is another active game') + + 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?' + ) + log_exception(GameException, f'{interaction.user} attempted to start a new game in {interaction.channel.name} so they were redirected to {PUBLIC_FIELDS_CATEGORY_NAME}') diff --git a/exceptions.py b/exceptions.py index 2b9aadd..036a3fe 100644 --- a/exceptions.py +++ b/exceptions.py @@ -81,3 +81,7 @@ class InvalidResultException(GameException): class ButtonOptionNotChosen(Exception): pass + + +class MissingRoleException(GameException): + pass diff --git a/gauntlets.py b/gauntlets.py index 80728f9..3d06248 100644 --- a/gauntlets.py +++ b/gauntlets.py @@ -215,7 +215,6 @@ async def get_opponent(session: Session, this_team, this_event, this_run) -> Tea t_id = 25 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 @@ -244,6 +243,8 @@ async def get_opponent(session: Session, this_team, this_event, this_run) -> Tea else: return None + this_team = await get_team_or_none(session, team_id=t_id) + logger.info(f'Gauntlet opponent: {this_team}') return await get_team_or_none(session, t_id) diff --git a/in_game/ai_manager.py b/in_game/ai_manager.py index 370f429..b3fb665 100644 --- a/in_game/ai_manager.py +++ b/in_game/ai_manager.py @@ -317,6 +317,7 @@ async def get_starting_lineup(session: Session, team: Team, game: Game, league_n async def get_starting_pitcher( session: Session, this_team: Team, this_game: Game, is_home: bool, league_name: str) -> Lineup: d_100 = random.randint(1, 100) + logger.info(f'Getting a {league_name} starting pitcher for the {this_team.lname}; d100: {d_100}') if is_home: if d_100 <= 30: sp_rank = 1 @@ -339,6 +340,7 @@ async def get_starting_pitcher( sp_rank = 4 else: sp_rank = 5 + logger.info(f'chosen rank: {sp_rank}') sp_query = await db_get( f'teams/{this_team.id}/sp/{league_name}?sp_rank={sp_rank}{this_game.cardset_param_string}' diff --git a/in_game/gameplay_queries.py b/in_game/gameplay_queries.py index 6e810cf..aad5373 100644 --- a/in_game/gameplay_queries.py +++ b/in_game/gameplay_queries.py @@ -60,7 +60,7 @@ def get_active_games_by_team(session: Session, team: Team) -> list[Game]: async def get_team_or_none( - session: Session, team_id: int | None = None, gm_id: int | None = None, team_abbrev: str | None = None, skip_cache: bool = False, main_team: bool = None, gauntlet_team: bool = None) -> Team | None: + session: Session, team_id: int | None = None, gm_id: int | None = None, team_abbrev: str | None = None, skip_cache: bool = False, main_team: bool = None, gauntlet_team: bool = None, include_packs: bool = False) -> Team | None: logger.info(f'Getting team or none / team_id: {team_id} / gm_id: {gm_id} / team_abbrev: {team_abbrev} / skip_cache: {skip_cache} / main_team: {main_team} / gauntlet_team: {gauntlet_team}') if gm_id is not None: if main_team is None and gauntlet_team is None: @@ -106,18 +106,18 @@ async def get_team_or_none( return db_team if team_id is not None: - t_query = await db_get('teams', object_id=team_id, params=[('inc_packs', False)]) + t_query = await db_get('teams', object_id=team_id, params=[('inc_packs', include_packs)]) if t_query is not None: return cache_team(t_query) elif gm_id is not None: - t_query = await db_get('teams', params=[('gm_id', gm_id)]) + t_query = await db_get('teams', params=[('gm_id', gm_id), ('inc_packs', include_packs)]) if t_query['count'] != 0: for team in [x for x in t_query['teams'] if 'gauntlet' not in x['abbrev'].lower()]: return cache_team(team) elif team_abbrev is not None: - t_query = await db_get('teams', params=[('abbrev', team_abbrev)]) + t_query = await db_get('teams', params=[('abbrev', team_abbrev), ('inc_packs', include_packs)]) if t_query['count'] != 0: if 'gauntlet' in team_abbrev.lower(): return cache_team(t_query['teams'][0])