From 7d54d9ea34f9b3d1ad867ec0bda49ee5a16a8d0c Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Sat, 1 Feb 2025 21:32:40 -0600 Subject: [PATCH] Add unlimited new-game Add pitcher substitution Add AI pitcher subs --- cogs/gameplay.py | 164 ++++++++++++++++++++++++++++++-- command_logic/logic_gameplay.py | 35 +++++-- helpers.py | 2 + 3 files changed, 184 insertions(+), 17 deletions(-) diff --git a/cogs/gameplay.py b/cogs/gameplay.py index c9afa48..1e9c666 100644 --- a/cogs/gameplay.py +++ b/cogs/gameplay.py @@ -13,7 +13,7 @@ import sqlalchemy from sqlmodel import func, or_ from api_calls import db_get -from command_logic.logic_gameplay import bunts, chaos, complete_game, doubles, flyballs, frame_checks, get_full_roster_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, groundballs, hit_by_pitch, homeruns, is_game_over, lineouts, manual_end_game, new_game_checks, new_game_conflicts, popouts, read_lineup, select_ai_reliever, show_defense_cards, singles, starting_pitcher_dropdown_view, steals, strikeouts, sub_batter_dropdown_view, substitute_player, triples, undo_play, update_game_settings, walks, xchecks, activate_last_play +from command_logic.logic_gameplay import bunts, chaos, complete_game, doubles, flyballs, frame_checks, get_full_roster_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, groundballs, hit_by_pitch, homeruns, is_game_over, lineouts, manual_end_game, new_game_checks, new_game_conflicts, popouts, read_lineup, relief_pitcher_dropdown_view, select_ai_reliever, show_defense_cards, singles, starting_pitcher_dropdown_view, steals, strikeouts, sub_batter_dropdown_view, substitute_player, triples, undo_play, update_game_settings, walks, xchecks, activate_last_play from dice import ab_roll from exceptions import * import gauntlets @@ -23,9 +23,9 @@ from helpers import CARDSETS, DEFENSE_LITERAL, PD_PLAYERS_ROLE_NAME, SELECT_CARD from in_game.ai_manager import get_starting_pitcher, get_starting_lineup from in_game.game_helpers import PUBLIC_FIELDS_CATEGORY_NAME, legal_check from in_game.gameplay_models import GameCardsetLink, Lineup, Play, Session, engine, player_description, select, Game -from in_game.gameplay_queries import get_cardset_or_none, get_one_lineup, get_position, get_channel_game_or_none, get_active_games_by_team, get_game_lineups, get_team_or_none +from in_game.gameplay_queries import get_cardset_or_none, get_one_lineup, get_plays_by_pitcher, get_position, get_channel_game_or_none, get_active_games_by_team, get_game_lineups, get_team_or_none -from utilities.buttons import Confirm, ScorebugButtons, ask_confirm +from utilities.buttons import Confirm, ScorebugButtons, ask_confirm, ask_with_buttons from utilities.dropdown import DropdownView @@ -434,7 +434,7 @@ class Gameplay(commands.Cog): channel_id=interaction.channel_id, season=current['season'], week=current['week'], - first_message=None if interaction.message is None else interaction.message.channel.id, + first_message=None if interaction.message is None else interaction.message.id, ai_team='away' if is_home else 'home', away_roster_id=69 if is_home else int(roster.value), home_roster_id=int(roster.value) if is_home else 69, @@ -552,7 +552,7 @@ class Gameplay(commands.Cog): channel_id=interaction.channel_id, season=current['season'], week=week_num, - first_message=None if interaction.message is None else interaction.message.channel.id, + first_message=None if interaction.message is None else interaction.message.id, ai_team='away' if away_team.is_ai else 'home', game_type='exhibition' ) @@ -689,8 +689,106 @@ class Gameplay(commands.Cog): view=view ) - # TODO: add new-game unlimited, and ranked + @group_new_game.command(name='unlimited', description='Start a new Unlimited game against another human') + @app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME) + async def new_game_unlimited_command(self, interaction: discord.Interaction, away_team_abbrev: str, home_team_abbrev: str): + await interaction.response.defer() + + with Session(engine) as session: + teams = await new_game_checks(session, interaction, away_team_abbrev, home_team_abbrev) + if teams is None: + logger.error(f'Received None from new_game_checks, cancelling new game') + return + + away_team = teams['away_team'] + home_team = teams['home_team'] + + if away_team.is_ai or home_team.is_ai: + await interaction.edit_original_response( + content=f'Unlimited games are for two human-run teams. To play against the AI, you can play `mlb-campaign`, `gauntlet`, or `exhibition` game modes.' + ) + return + + current = await db_get('current') + week_num = current['week'] + logger.info(f'gameplay - new_game_unlimited - Season: {current["season"]} / Week: {week_num} / Away Team: {away_team.description} / Home Team: {home_team.description}') + + this_game = Game( + away_team_id=away_team.id, + home_team_id=home_team.id, + away_roster_id=None, + home_roster_id=None, + channel_id=interaction.channel_id, + season=current['season'], + week=week_num, + first_message=None if interaction.message is None else interaction.message.id, + game_type='exhibition' + ) + + await interaction.edit_original_response( + content=f'Let\'s get set up for **{away_team.lname}** @ **{home_team.lname}**!' + ) + + away_role = await team_role(interaction, away_team) + home_role = await team_role(interaction, home_team) + + away_roster_id = await ask_with_buttons( + interaction=interaction, + button_options=[ + 'Primary', 'Secondary', 'Ranked' + ], + question=f'{away_role.mention}\nWhich roster should I pull for you?', + delete_question=False, + confirmation_message=f'Got it! As soon as the {home_team.sname} select their roster, I will pull them both in at once.' + ) + + home_roster_id = await ask_with_buttons( + interaction=interaction, + button_options=[ + 'Primary', 'Secondary', 'Ranked' + ], + question=f'{home_role.mention}\nWhich roster should I pull for you?', + delete_question=False, + confirmation_message=f'Got it! Off to Sheets I go for the {away_team.abbrev} roster...' + ) + + if away_roster_id and home_roster_id: + if away_roster_id == 'Primary': + away_roster_id = 1 + elif away_roster_id == 'Secondary': + away_roster_id = 2 + else: + away_roster_id = 3 + + if home_roster_id == 'Primary': + home_roster_id = 1 + elif home_roster_id == 'Secondary': + home_roster_id = 2 + else: + home_roster_id = 3 + + logger.info(f'Setting roster IDs - away: {away_roster_id} / home: {home_roster_id}') + this_game.away_roster_id = away_roster_id + this_game.home_roster_id = home_roster_id + session.add(this_game) + session.commit() + + logger.info(f'Pulling away team\'s roster') + away_roster = await get_full_roster_from_sheets(session, interaction, self.sheets, this_game, away_team, away_roster_id) + + # if away_roster: + logger.info(f'Pulling home team\'s roster') + await interaction.channel.send( + content=f'And now for the {home_team.abbrev} sheet...' + ) + home_roster = await get_full_roster_from_sheets(session, interaction, self.sheets, this_game, home_team,home_roster_id) + + # if home_roster: + await interaction.channel.send( + content=f'{away_role.mention} @ {home_role.mention}\n\nThe game is set, both of you may run `/set ` to start!' + ) + @commands.command(name='force-endgame', help='Mod: Force a game to end without stats') async def force_end_game_command(self, ctx: commands.Context): @@ -750,8 +848,11 @@ class Gameplay(commands.Cog): ) return - this_team = this_game.away_team if this_game.ai_team == 'home' else this_game.home_team - if interaction.user.id != this_team.gmid: + if this_game.away_team.gmid == interaction.user.id: + this_team = this_game.away_team + elif this_game.home_team.gmid == interaction.user.id: + this_team = this_game.home_team + else: logger.info(f'{interaction.user.name} tried to run a command in Game {this_game.id} when they aren\'t a GM in the game.') await interaction.edit_original_response(content='Bruh. Only GMs of the active teams can pull lineups.') return @@ -796,8 +897,11 @@ class Gameplay(commands.Cog): ) return - this_team = this_game.human_team - if interaction.user.id != this_team.gmid: + if this_game.away_team.gmid == interaction.user.id: + this_team = this_game.away_team + elif this_game.home_team.gmid == interaction.user.id: + this_team = this_game.home_team + else: logger.info(f'{interaction.user.name} tried to run a command in Game {this_game.id} when they aren\'t a GM in the game.') await interaction.edit_original_response(content='Bruh. Only GMs of the active teams can pull lineups.') return @@ -903,6 +1007,46 @@ class Gameplay(commands.Cog): logger.info(f'sub batter - this_play: {this_play}') bat_view = sub_batter_dropdown_view(session, this_game, owner_team, this_order, [interaction.user]) await interaction.edit_original_response(content=f'### {owner_team.lname} Substitution', view=bat_view) + + @group_substitution.command(name='pitcher', description='Make a pitching substitution') + async def sub_pitcher_command(self, interaction: discord.Interaction, batting_order: Literal['dh-spot', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] = '10'): + with Session(engine) as session: + this_game, owner_team, this_play = await checks_log_interaction(session, interaction, command_name='substitute batter') + + if owner_team != this_play.pitcher.team: + logger.warning(f'User {interaction.user.name} ({owner_team.abbrev}) tried to run a sub for the {this_play.pitcher.team.lname}') + await interaction.edit_original_response( + content=f'Please run pitcher subs when your team is on defense. If you are pinch-hitting for a pitcher already in the lineup, use `/substitute batter`' + ) + return + + if batting_order != '10' and this_play.pitcher.batting_order == 10: + forfeit_dh = await ask_confirm( + interaction, + f'Are you sure you want to forfeit the DH?' + ) + if not forfeit_dh: + await interaction.edit_original_response( + content=f'Fine, be that way.' + ) + return + + if not this_play.is_new_inning: + pitcher_plays = get_plays_by_pitcher(session, this_game, this_play.pitcher) + batters_faced = sum(1 for x in pitcher_plays if x.pa == 1) + + if batters_faced < 3: + await interaction.edit_original_response( + content=f'Looks like **{this_play.pitcher.player.name}** has only faced {batters_faced} of the 3-batter minimum.' + ) + return + + rp_view = relief_pitcher_dropdown_view(session, this_game, this_play.pitcher.team, batting_order, responders=[interaction.user]) + rp_message = await interaction.edit_original_response( + content=f'### {this_play.pitcher.team.lname} Relief Pitcher', + view=rp_view + ) + group_log = app_commands.Group(name='log', description='Log a play in this channel\'s game') diff --git a/command_logic/logic_gameplay.py b/command_logic/logic_gameplay.py index 48cac00..8a80db4 100644 --- a/command_logic/logic_gameplay.py +++ b/command_logic/logic_gameplay.py @@ -20,7 +20,7 @@ from in_game.gameplay_models import BattingCard, Card, Game, Lineup, PositionRat from in_game.gameplay_queries import get_active_games_by_team, get_available_batters, get_batter_card, get_batting_statline, get_game_cardset_links, get_or_create_ai_card, get_pitcher_runs_by_innings, get_pitching_statline, get_plays_by_pitcher, 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, ask_with_buttons -from utilities.dropdown import DropdownView, SelectBatterSub, SelectStartingPitcher, SelectViewDefense +from utilities.dropdown import DropdownView, SelectBatterSub, SelectReliefPitcher, SelectStartingPitcher, SelectViewDefense from utilities.embeds import image_embed from utilities.pages import Pagination @@ -336,6 +336,23 @@ def starting_pitcher_dropdown_view(session: Session, this_game: Game, human_team return DropdownView(dropdown_objects=[sp_selection]) +def relief_pitcher_dropdown_view(session: Session, this_game: Game, human_team: Team, batting_order: int, responders: list[discord.User] = None): + pitchers = get_available_pitchers(session, this_game, human_team) + logger.info(f'sorted pitchers: {pitchers}') + if len(pitchers) == 0: + log_exception(MissingRosterException, 'No pitchers were found to select RP') + rp_selection = SelectReliefPitcher( + this_game=this_game, + this_team=human_team, + batting_order=batting_order, + session=session, + options=[SelectOption(label=f'{x.player.name_with_desc} (S{x.pitcherscouting.pitchingcard.starter_rating}/R{x.pitcherscouting.pitchingcard.relief_rating})', value=x.id) for x in pitchers], + placeholder='Select your relief pitcher', + responders=responders + ) + return DropdownView(dropdown_objects=[rp_selection]) + + def sub_batter_dropdown_view(session: Session, this_game: Game, human_team: Team, batting_order: int, responders: list[discord.User]): batters = get_available_batters(session, this_game, human_team) logger.info(f'batters: {batters}') @@ -363,7 +380,7 @@ async def read_lineup(session: Session, interaction: discord.Interaction, this_g ) if len(existing_lineups) > 1: await interaction.edit_original_response( - f'It looks like the {lineup_team.sname} already have a lineup. Run `/substitution` to make changes.' + f'It looks like the {lineup_team.sname} already have a lineup. Run `/substitute` to make changes.' ) return @@ -2841,9 +2858,10 @@ def undo_play(session: Session, this_play: Play): 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) + for x in new_players: + logger.info(f'Marking {x} for deletion') + new_player_ids.append(x.id) + old_player = session.get(Lineup, x.replacing_id) old_player.active = True session.add(old_player) @@ -3786,13 +3804,16 @@ def substitute_player(session, this_play: Play, old_player: Lineup, new_player: replacing_id=old_player.id ) logger.info(f'new_lineup: {new_lineup}') - session.add(new_lineup) logger.info(f'De-activating last player') old_player.active = False - session.add(old_player) + + logger.info(f'Updating play\'s pitcher') + this_play.pitcher = new_lineup + session.add(this_play) + session.commit() session.refresh(new_lineup) return new_lineup diff --git a/helpers.py b/helpers.py index a0e70fe..73486d3 100644 --- a/helpers.py +++ b/helpers.py @@ -325,6 +325,8 @@ INSULTS = [ 'Why are you even here? Get lost.', 'Why are you even here? Scram.', 'Why are you even here? No one knows who you are.', + 'HEY, DON\'T TOUCH ME!', + 'Hey, don\'t touch me!' ]