import math import discord from discord import app_commands from discord.ext import commands from peewee import IntegrityError from typing import Optional, Literal import logging from helpers import SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME, random_conf_gif, SBA_SEASON, PD_SEASON, LOGO, \ get_team_embed, Confirm, get_pos_abbrev, SBA_COLOR from gameplay_helpers import * from db_calls import get_one_team, get_one_player from db_calls_gameplay import StratGame, StratPlay, StratLineup, get_one_game, pd_get_one_team, post_game, patch_game, \ get_game_team, post_lineups, pd_get_card_by_id, make_sub, get_player, player_link, get_team_lineups, \ get_current_play, post_play, get_one_lineup, advance_runners, patch_play, complete_play, get_batting_stats, \ get_pitching_stats, undo_play, get_latest_play, advance_one_runner, get_play_by_num logger = logging.getLogger('discord_app') class Gameplay(commands.Cog): def __init__(self, bot): self.bot = bot async def cog_command_error(self, ctx, error): await ctx.send(f'{error}\n\nRun !help to see the command requirements') async def slash_error(self, ctx, error): await ctx.send(f'{error}') def get_scorebug(self, home_team, away_team, curr_play): occupied = '●' unoccupied = '○' first_base = unoccupied if not curr_play.on_first else occupied second_base = unoccupied if not curr_play.on_second else occupied third_base = unoccupied if not curr_play.on_third else occupied half = '▲' if curr_play.inning_half == 'Top' else '▼' inning = f'{half} {curr_play.inning_num}' outs = f'{curr_play.starting_outs} Out{"s" if curr_play.starting_outs != 1 else ""}' game_string = f'```\n' \ f'{away_team["abbrev"]: ^4}{curr_play.away_score: ^3} {second_base}' \ f'{inning: >10}\n' \ f'{home_team["abbrev"]: ^4}{curr_play.home_score: ^3} {third_base} {first_base}' \ f'{outs: >8}\n```' return game_string async def get_game_state(self, game: StratGame) -> dict: curr_play = get_current_play(game.id) if curr_play is None: latest_play = get_latest_play(game.id) if not latest_play: away_lineup = await get_team_lineups(game.id, game.away_team_id) home_lineup = await get_team_lineups(game.id, game.home_team_id) if len(away_lineup) == 0 or len(home_lineup) == 0: game_state = { 'error': True, 'away_lineup': away_lineup, 'home_lineup': home_lineup } return game_state else: curr_play = post_play({ 'game_id': game.id, 'play_num': 1, 'batter_id': get_one_lineup(game.id, game.away_team_id, batting_order=1).id, 'pitcher_id': get_one_lineup(game.id, game.home_team_id, position='P').id, 'on_base_code': 0, 'inning_half': 'Top', 'inning_num': 1, 'batting_order': 1, 'starting_outs': 0, 'away_score': 0, 'home_score': 0 }) else: patch_play(latest_play.id, complete=False) curr_play = latest_play game_state = {'error': False, 'curr_play': curr_play} away_team = await get_game_team(game, team_id=game.away_team_id) home_team = await get_game_team(game, team_id=game.home_team_id) game_state['away_team'] = away_team game_state['home_team'] = home_team scorebug = self.get_scorebug(home_team, away_team, game_state['curr_play']) game_state['scorebug'] = scorebug batter = await get_player(game, game_state['curr_play'].batter) litmus = 0 try: pitcher = await get_player(game, game_state['curr_play'].pitcher) litmus = 1 catcher = await get_player( game, get_one_lineup( game.id, team_id=game.away_team_id if game_state['curr_play'].inning_half == 'Top' else game.home_team_id, position='C' ) ) except Exception as e: logger.info(f'ERROR: {e} / TYPE: {type(e)}') away_lineup = await get_team_lineups(game.id, game.away_team_id) home_lineup = await get_team_lineups(game.id, game.home_team_id) if litmus == 0: error_message = f'Please sub in a pitcher to continue' else: error_message = f'Please sub in a catcher to continue' game_state['error'] = True game_state['error_message'] = error_message game_state['away_lineup'] = away_lineup game_state['home_lineup'] = home_lineup return game_state game_state['batter'] = batter game_state['pitcher'] = pitcher game_state['catcher'] = catcher return game_state async def get_game_state_embed(self, game: StratGame, full_length=True): game_state = await self.get_game_state(game) logger.info(f'game_state: {game_state}') embed = get_team_embed( f'{game_state["away_team"]["sname"]} @ {game_state["home_team"]["sname"]}', team=game_state['home_team'], thumbnail=False ) if abs(game_state['curr_play'].home_score - game_state['curr_play'].away_score) >= 10: embed.description = 'Mercy rule in effect' if game.is_pd: embed.set_footer(text=f'PD Season {PD_SEASON}', icon_url=LOGO) embed.add_field(name='Game State', value=game_state['scorebug'], inline=False) if game_state['error']: # embed = discord.Embed( # title='Current Lineups', # color=int(SBA_COLOR, 16) # ) embed.add_field(name='Away Team', value=game_state['away_lineup'] if len(game_state['away_lineup']) else 'None, yet') embed.add_field(name='Home Team', value=game_state['home_lineup'] if len(game_state['home_lineup']) else 'None, yet') if game_state['error_message']: embed.set_footer(text=game_state['error_message'], icon_url=LOGO) return embed pitcher_string = f'{player_link(game, game_state["pitcher"])}' batter_string = f'{player_link(game, game_state["batter"])}' all_bat_stats = get_batting_stats(game.id, lineup_id=game_state['curr_play'].batter.id) if len(all_bat_stats): b_s = all_bat_stats[0] batter_string += f'\n{b_s["pl_hit"]}-{b_s["pl_ab"]}' for num, stat in [ (b_s['pl_double'], '2B'), (b_s['pl_triple'], '3B'), (b_s['pl_homerun'], 'HR'), (b_s['pl_rbi'], 'RBI'), (b_s['pl_bb'], 'BB'), (b_s['pl_hbp'], 'HBP'), (b_s['pl_ibb'], 'IBB'), (b_s['pl_so'], 'K'), (b_s['pl_gidp'], 'GIDP'), (b_s['pl_bpfo'], 'BPFO'), (b_s['pl_bplo'], 'BPLO') ]: if num: batter_string += f', {num if num > 1 else ""}{" " if num > 1 else ""}{stat}' all_pit_stats = get_pitching_stats(game.id, lineup_id=game_state['curr_play'].pitcher.id) if len(all_pit_stats): p_s = all_pit_stats[0] pitcher_string += f'\n{math.floor(p_s["pl_outs"]/3)}.{p_s["pl_outs"] % 3} IP' for num, stat in [ (p_s['pl_runs'], 'R'), (p_s['pl_hit'], 'H'), (p_s['pl_homerun'], 'HR'), (p_s['pl_so'], 'K'), (p_s['pl_bb'], 'BB'), (p_s['pl_hbp'], 'HBP'), (p_s['pl_wild_pitch'], 'WP'), (p_s['pl_balk'], 'BK'), ]: if num: pitcher_string += f', {num} {stat}' if stat == 'R' and num != p_s['pl_eruns']: pitcher_string += f', {p_s["pl_eruns"]} ER' # embed.add_field(name='Matchup', value=f'Pitcher: \nvs\nBatter: ', inline=False) embed.add_field(name='Pitcher', value=f'{pitcher_string}') embed.add_field(name='Batter', value=f'{batter_string}') embed.set_thumbnail(url=game_state["pitcher"]["image"]) embed.set_image(url=game_state["batter"]["image"]) baserunner_string = '' if game_state['curr_play'].on_first: runner = await get_player(game, game_state['curr_play'].on_first) baserunner_string += f'On First: {player_link(game, runner)}\n' if game_state['curr_play'].on_second: runner = await get_player(game, game_state['curr_play'].on_second) baserunner_string += f'On Second: {player_link(game, runner)}\n' if game_state['curr_play'].on_third: runner = await get_player(game, game_state['curr_play'].on_third) baserunner_string += f'On Third: {player_link(game, runner)}\n' embed.add_field( name='Baserunners', value=baserunner_string if len(baserunner_string) > 0 else 'None', inline=False ) if not full_length: return embed away_lineup = await get_team_lineups(game.id, game.away_team_id) home_lineup = await get_team_lineups(game.id, game.home_team_id) embed.add_field(name=f'{game_state["away_team"]["abbrev"]} Lineup', value=away_lineup) embed.add_field(name=f'{game_state["home_team"]["abbrev"]} Lineup', value=home_lineup) return embed @commands.hybrid_command(name='newgame', help='Start a new baseball game') @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) async def new_game_command( self, ctx: commands.Context, away_team_abbrev: str, home_team_abbrev: str, week_num: int, game_num: Optional[int] = None, is_pd: Optional[bool] = False): conflict = get_one_game(channel_id=ctx.channel.id, active=True) if conflict: await ctx.send(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 is_pd: away_team = pd_get_one_team(away_team_abbrev) home_team = pd_get_one_team(home_team_abbrev) if ctx.author.id not in [away_team['gmid'], home_team['gmid']]: await ctx.send('You can only start a new game if you GM one of the teams.') return else: away_team = await get_one_team(away_team_abbrev) home_team = await get_one_team(home_team_abbrev) if ctx.author.id not in [away_team['gmid'], away_team['gmid2'], home_team['gmid'], home_team['gmid2']]: await ctx.send('You can only start a new game if you GM one of the teams.') return post_game({ 'away_team_id': away_team['id'], 'home_team_id': home_team['id'], 'week_num': week_num, 'game_num': game_num, 'channel_id': ctx.channel.id, 'active': True, 'is_pd': is_pd }) await ctx.send(random_conf_gif()) @commands.hybrid_command(name='endgame', help='End game in this channel') @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) async def end_game_command(self, ctx: commands.Context): this_game = get_one_game(channel_id=ctx.channel.id, active=True) if not this_game: await ctx.send(f'Ope, I don\'t see a game in this channel.') return patch_game(this_game.id, active=False) # TODO: create result sheet and submit stats if this_game.is_pd: # Build the statlines # Submit the scorecard # Post a notification to PD for rewards pass else: # Send stats to sheets # Post sheets link pass await ctx.send(random_conf_gif()) @app_commands.command( name='setlineup', description='Set starting lineup for game in this channel; Player Name for SBa games / Card ID for PD games', ) @app_commands.describe( order_1_player='SBa: Player Name; PD: Card #', order_1_pos='C for Catcher, 1B for first base, P for pitcher, etc.', order_2_player='SBa: Player Name; PD: Card #', order_2_pos='C for Catcher, 1B for first base, P for pitcher, etc.', order_3_player='SBa: Player Name; PD: Card #', order_3_pos='C for Catcher, 1B for first base, P for pitcher, etc.', order_4_player='SBa: Player Name; PD: Card #', order_4_pos='C for Catcher, 1B for first base, P for pitcher, etc.', order_5_player='SBa: Player Name; PD: Card #', order_5_pos='C for Catcher, 1B for first base, P for pitcher, etc.', order_6_player='SBa: Player Name; PD: Card #', order_6_pos='C for Catcher, 1B for first base, P for pitcher, etc.', order_7_player='SBa: Player Name; PD: Card #', order_7_pos='C for Catcher, 1B for first base, P for pitcher, etc.', order_8_player='SBa: Player Name; PD: Card #', order_8_pos='C for Catcher, 1B for first base, P for pitcher, etc.', order_9_player='SBa: Player Name; PD: Card #', order_9_pos='C for Catcher, 1B for first base, P for pitcher, etc.', order_10_player='SBa: Player Name; PD: Card #; only used with a DH', order_10_pos='P for pitcher; only used with a DH' ) @app_commands.checks.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) async def set_lineup_command( self, interaction: discord.Interaction, team_abbrev: str, order_1_player: str, order_1_pos: str, order_2_player: str, order_2_pos: str, order_3_player: str, order_3_pos: str, order_4_player: str, order_4_pos: str, order_5_player: str, order_5_pos: str, order_6_player: str, order_6_pos: str, order_7_player: str, order_7_pos: str, order_8_player: str, order_8_pos: str, order_9_player: str, order_9_pos: str, order_10_player: Optional[str] = None, order_10_pos: Optional[str] = 'P'): this_game = get_one_game(channel_id=interaction.channel_id, active=True) if not this_game: await interaction.response.send_message(f'I dont\'t see an active game in this channel!') return owner_team = await get_game_team(this_game, interaction.user.id) lineup_team = await get_game_team(this_game, team_abbrev=team_abbrev) if not owner_team['id'] in [this_game.away_team_id, this_game.home_team_id]: await interaction.response.send_message('Bruh. Only GMs of the active teams can set lineups.') return if not lineup_team['id'] in [this_game.away_team_id, this_game.home_team_id]: await interaction.response.send_message(f'I do not see {lineup_team["sname"]} in this game. Please check the team abbrev and ' f'try again') return existing_lineups = await get_team_lineups(this_game.id, lineup_team['id']) if not existing_lineups or not len(existing_lineups): await interaction.response.send_message( f'It looks like the {lineup_team["sname"]} already have a lineup. Run `/substitution` to make changes.' ) return await interaction.response.defer() all_lineups = [] all_pos = [] lineup_data = [ (order_1_player, order_1_pos), (order_2_player, order_2_pos), (order_3_player, order_3_pos), (order_4_player, order_4_pos), (order_5_player, order_5_pos), (order_6_player, order_6_pos), (order_7_player, order_7_pos), (order_8_player, order_8_pos), (order_9_player, order_9_pos), (order_10_player, order_10_pos) ] for index, pair in enumerate(lineup_data): if not pair[0]: break if pair[1].upper() not in all_pos: all_pos.append(pair[1].upper()) else: raise SyntaxError(f'You have more than one {pair[1].upper()} in this lineup. Please update and set the ' f'lineup again.') if this_game.is_pd: this_card = pd_get_card_by_id(int(pair[0])) if this_card['team']['id'] != lineup_team['id']: raise SyntaxError(f'Easy there, champ. Looks like card ID {pair[0]} belongs to the ' f'{this_card["team"]["sname"]}. Try again with only cards you own.') player_id = this_card['player']['id'] card_id = pair[0] else: this_player = await get_one_player(pair[0], season=SBA_SEASON) if this_player['team']['id'] != lineup_team['id']: raise SyntaxError(f'Easy there, champ. Looks like {pair[0]} is on ' f'{this_player["team"]["sname"]}. Try again with only your own players.') player_id = this_player['id'] card_id = None this_lineup = { 'game_id': this_game.id, 'team_id': lineup_team['id'], 'player_id': player_id, 'card_id': card_id, 'position': pair[1].upper(), 'batting_order': index + 1, 'after_play': 0 } all_lineups.append(this_lineup) logger.info(f'Setting lineup for {owner_team["sname"]} in {"PD" if this_game.is_pd else "SBa"} game') post_lineups(all_lineups) try: await interaction.edit_original_response(content=None, embed=await self.get_game_state_embed(this_game)) except IntegrityError as e: logger.info(f'Unable to pull game_state for game_id {this_game.id} until both lineups are in: {e}') await interaction.response.send_message(f'Game state will be posted once both lineups are in') return @commands.hybrid_command( name='substitution', help='Make a lineup substitution; Player Name for SBa games / Card ID for PD games', aliases=['sub'] ) @app_commands.describe( new_player='For SBa game: enter the Player Name; for PD game: enter the Card ID (a number)') @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) async def substitution_command( self, ctx: commands.Context, team_abbrev: str, order_number: int, new_player: str, new_pos: str): this_game = get_one_game(channel_id=ctx.channel.id, active=True) if not this_game: await ctx.send(f'I dont\'t see an active game in this channel!') return owner_team = await get_game_team(this_game, ctx.author.id) lineup_team = await get_game_team(this_game, team_abbrev=team_abbrev) if not owner_team['id'] in [this_game.away_team_id, this_game.home_team_id]: await ctx.send('Bruh. Only GMs of the active teams can set lineups.') return if not lineup_team['id'] in [this_game.away_team_id, this_game.home_team_id]: await ctx.send(f'I do not see {lineup_team["sname"]} in this game. Please check the team abbrev and ' f'try again') return if this_game.is_pd: this_card = pd_get_card_by_id(int(new_player)) if this_card["team"]["id"] != lineup_team['id']: raise SyntaxError(f'Easy there, champ. Looks like card ID {new_player} belongs to the ' f'{this_card["team"]["sname"]}. Try again with only cards you own.') player_id = this_card['player']['id'] card_id = new_player else: this_player = await get_one_player(new_player, season=SBA_SEASON) if this_player['team']['id'] != lineup_team['id']: raise SyntaxError(f'Easy there, champ. Looks like {new_player} is on ' f'{this_player["team"]["sname"]}. Try again with only your own players.') player_id = this_player['id'] card_id = None curr_play = get_current_play(this_game.id) this_lineup = { 'game_id': this_game.id, 'team_id': lineup_team['id'], 'player_id': player_id, 'card_id': card_id, 'position': new_pos, 'batting_order': order_number, 'after_play': curr_play.play_num - 1 } make_sub(this_lineup) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game)) @commands.hybrid_command(name='gamestate', help='Post the current game state', aliases=['gs']) @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) async def game_state_command(self, ctx: commands.Context, include_lineups: bool = True): this_game = get_one_game(channel_id=ctx.channel.id, active=True) if not this_game: await ctx.send(f'I dont\'t see an active game in this channel!') return await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=include_lineups)) async def checks_log_play(self, ctx: commands.Context) -> (Optional[StratGame], Optional[dict], Optional[StratPlay]): this_game = get_one_game(channel_id=ctx.channel.id, active=True) # if not this_game: # return False, False, False owner_team = await get_game_team(this_game, ctx.author.id) if not owner_team['id'] in [this_game.away_team_id, this_game.home_team_id]: await ctx.send('Bruh. Only GMs of the active teams can log plays.') # return this_game, False, False this_play = get_current_play(this_game.id) if this_play.locked: await ctx.send(f'Looks like this play is already being advanced. Please wait to log the next play.') if not this_play.pitcher: await ctx.send(f'Please sub in a pitcher before logging a new play.') this_play = None return this_game, owner_team, this_play @commands.hybrid_group(name='log-onbase', help='Log a base hit in this channel\'s game') @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) async def log_onbase(self, ctx: commands.Context): if ctx.invoked_subcommand is None: await ctx.send('No play details listed. Type `/log-onbase` to see available commands.') @log_onbase.command(name='single-wellhit', help='Batter to first; runners advance two bases', aliases=['siwh', 'si**', '1b**', '1bwh']) async def log_single_wh_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return single_wellhit(this_play) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_onbase.command(name='single-onestar', help='Batter to first; runners advance one base', aliases=['si*', '1b*']) async def log_single_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return single_onestar(this_play) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_onbase.command(name='ballpark-single', help='Batter to first; runners advance one base', aliases=['bpsi', 'bp1b']) async def log_ballpark_single_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) advance_runners(this_play.id, num_bases=1) patch_play(this_play.id, pa=1, ab=1, hit=1, bp1b=1) complete_play(this_play.id, batter_to_base=1) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_onbase.command(name='single-uncapped', help='Batter to first; runners may attempt to advance a second base', aliases=['si', '1b']) async def log_single_uncapped_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True, pa=1, ab=1, hit=1) advance_runners(this_play.id, 1) this_play = get_current_play(this_game.id) batter_to_base = 1 logger.info(f'this_play: {this_play}') # Handle runner starting at second if this_play.on_second: this_runner = await get_player(this_game, this_play.on_second) view = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(f'Was {this_runner["name"]} sent home?', view=view) await view.wait() if view.value: await question.delete() view_two = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(content=f'Was {this_runner["name"]} safe at the plate?', view=view_two) await view_two.wait() if view_two.value: advance_one_runner(this_play.id, from_base=2, num_bases=2) this_play = patch_play(this_play.id, rbi=this_play.rbi + 1) else: this_play = patch_play(this_play.id, on_second_final=99, outs=this_play.outs + 1) await question.delete() else: await question.delete() logger.info(f'this_play: {this_play}') # Handle runner starting at first if this_play.on_first and (not this_play.on_second or this_play.on_second_final != 3): this_runner = await get_player(this_game, this_play.on_first) view = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(f'Was {this_runner["name"]} sent to third?', view=view) await view.wait() if view.value: await question.delete() view_two = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(content=f'Was {this_runner["name"]} safe at third?', view=view_two) await view_two.wait() if view_two.value: advance_one_runner(this_play.id, from_base=1, num_bases=2) this_play = get_current_play(this_game.id) else: this_play = patch_play(this_play.id, on_second_final=99, outs=1) await question.delete() else: await question.delete() logger.info(f'this_play: {this_play}') # Handle batter runner if either runner from first or runner from second advanced if (this_play.on_first and this_play.on_first_final != 2) or \ (this_play.on_second and this_play.on_second_final != 3): batter = await get_player(this_game, this_play.batter) view = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(content=f'Was {batter["name"]} sent to second?', view=view) await view.wait() if view.value: await question.delete() view_two = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(content=f'Was {batter["name"]} safe at second?', view=view_two) await view_two.wait() if view_two.value: batter_to_base = 2 await question.delete() else: await question.delete() logger.info(f'this_play: {this_play}') complete_play(this_play.id, batter_to_base=batter_to_base) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_onbase.command(name='double-twostar', help='Batter to second; runners advance two bases', aliases=['do**', '2b**']) async def log_double_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return double_twostar(this_play) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_onbase.command(name='double-uncapped', help='Batter to second; runners may attempt to advance a third base', aliases=['do', '2b']) async def log_double_uncapped_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True, pa=1, ab=1, hit=1, double=1) batter_to_base = 2 if this_play.on_first: this_runner = await get_player(this_game, this_play.on_first) view = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(f'Was {this_runner["name"]} sent home?', view=view) await view.wait() if view.value: await question.delete() view_two = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(content=f'Was {this_runner["name"]} safe at the plate?', view=view_two) await view_two.wait() if view_two.value: advance_runners(this_play.id, num_bases=3) else: patch_play(this_play.id, on_first_final=99, outs=1) await question.delete() batter = await get_player(this_game, this_play.batter) view_three = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send( content=f'Did {batter["name"]} attempt the advance to third?', view=view_three ) await view_three.wait() if view_three.value: await question.delete() view_four = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(content=f'Was {batter["name"]} safe at third?', view=view_four) await view_four.wait() if view_four.value: batter_to_base = 3 await question.delete() else: advance_runners(this_play.id, num_bases=2) await question.delete() complete_play(this_play.id, batter_to_base=batter_to_base) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_onbase.command(name='double-threestar', help='Batter to second; runners advance three bases', aliases=['dowh', 'do***']) async def log_double_wh_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return double_threestar(this_play) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_onbase.command(name='triple', help='Batter to third; all runners score', aliases=['tr', '3b']) async def log_triple_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return triple(this_play) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_onbase.command(name='homerun', help='Batter scores; all runners score', aliases=['hr', 'dong']) async def log_homerun_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) advance_runners(this_play.id, num_bases=4) patch_play(this_play.id, pa=1, ab=1, hit=1, homerun=1) complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_onbase.command(name='ballpark-homerun', help='Batter scores; all runners score', aliases=['bp-hr', 'bp-dong']) async def log_homerun_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) advance_runners(this_play.id, num_bases=3) patch_play(this_play.id, pa=1, ab=1, hit=1, homerun=1, bphr=1) complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_onbase.command(name='walk', help='Batter to first; runners advance if forced', aliases=['bb']) async def log_walk_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) advance_runners(this_play.id, num_bases=1, only_forced=True) patch_play(this_play.id, pa=1, walk=1) complete_play(this_play.id, batter_to_base=1) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_onbase.command(name='intentional-walk', help='Batter to first; runners advance if forced', aliases=['ibb']) async def log_int_walk_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) advance_runners(this_play.id, num_bases=1, only_forced=True) patch_play(this_play.id, pa=1, ibb=1) complete_play(this_play.id, batter_to_base=1) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_onbase.command(name='hit-by-pitch', help='Batter to first; runners advance if forced', aliases=['hbp']) async def log_hit_by_pitch_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) advance_runners(this_play.id, num_bases=1, only_forced=True) patch_play(this_play.id, pa=1, hbp=1) complete_play(this_play.id, batter_to_base=1) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @commands.hybrid_group(name='log-out', help='Log an out result in this channel\'s game') @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) async def log_out(self, ctx: commands.Context): if ctx.invoked_subcommand is None: await ctx.send('No play details listed. Type `/log-out` to see available commands.') @log_out.command(name='popout', help='Batter out; runners hold', aliases=['po']) async def log_popout_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1) advance_runners(this_play.id, num_bases=0) complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_out.command(name='strikeout', help='Batter out; runners hold', aliases=['so', 'k']) async def log_strikeout_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1, so=1) advance_runners(this_play.id, num_bases=0) complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_out.command(name='lineout', help='Batter out; runners hold', aliases=['lo']) async def log_lineout_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1) advance_runners(this_play.id, num_bases=0) complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_out.command(name='sac-bunt', help='Batter out; runners advance one base', aliases=['sacb', 'bunt']) async def log_sac_bunt_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) advance_runners(this_play.id, num_bases=1) patch_play(this_play.id, pa=1, sac=1, outs=1) complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_out.command(name='caught-stealing', help='One base runner is caught stealing', aliases=['cs']) @app_commands.describe( attempted_base='The base number the runner attempted to steal; 2 for 2nd, 3 for 3rd, 3 for home') async def log_caught_stealing_command(self, ctx: commands.Context, attempted_base: int): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) catcher = get_one_lineup( this_game.id, team_id=this_play.pitcher.team_id, position='C' ) if attempted_base == 4 and this_play.on_third: patch_play( this_play.id, cs=1, on_third_final=99, runner_id=this_play.on_third.id, catcher_id=catcher.id, outs=1 ) elif attempted_base == 3 and this_play.on_second: if not this_play.on_third: patch_play( this_play.id, cs=1, on_second_final=99, runner_id=this_play.on_second.id, catcher_id=catcher.id, outs=1 ) else: this_runner = await get_player(this_game, this_play.on_second) await ctx.send(f'Ope. Looks like {this_runner["name"]} is blocked by the runner at third.') return elif attempted_base == 2 and this_play.on_first: if not this_play.on_second: patch_play( this_play.id, cs=1, on_first_final=99, runner_id=this_play.on_first.id, catcher_id=catcher.id, outs=1 ) else: this_runner = await get_player(this_game, this_play.on_first) await ctx.send(f'Ope. Looks like {this_runner["name"]} is blocked by the runner at second.') return else: await ctx.send(f'Uh oh - I don\'t see a runner there to steal the bag.') return complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_out.command(name='flyball-a', help='Batter out; all runners advance', aliases=['flya']) async def log_flyballa_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1) if this_play.starting_outs < 2: advance_runners(this_play.id, 1) if this_play.on_third: patch_play(this_play.id, ab=0) complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_out.command(name='flyball-b', help='Batter out; runner on third scores', aliases=['flyb']) async def log_flyballb_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1) advance_runners(this_play.id, num_bases=0) if this_play.starting_outs < 2 and this_play.on_third: patch_play(this_play.id, ab=0, rbi=1) advance_one_runner(this_play.id, from_base=3, num_bases=1) if this_play.starting_outs < 2 and this_play.on_second: view = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send('Did the runner on second advance?', view=view) await view.wait() if view.value: advance_one_runner(this_play.id, from_base=2, num_bases=1) await question.edit(view=None) else: await question.delete() complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_out.command(name='flyball-bq', help='Batter out; runner on third may attempt to score', aliases=['flyb?']) async def log_flyballbq_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1) advance_runners(this_play.id, num_bases=0) if this_play.starting_outs < 2 and this_play.on_third: view = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send('Did the runner on third advance?', view=view) await view.wait() if view.value: advance_one_runner(this_play.id, from_base=3, num_bases=1) patch_play(this_play.id, ab=0, rbi=1) await question.edit(view=None) else: await question.delete() complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_out.command(name='flyball-c', help='Batter out; no runners advance', aliases=['flyc']) async def log_flyballc_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1) advance_runners(this_play.id, num_bases=0) complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @commands.hybrid_group(name='log-play', help='Log a result in this channel\'s game') @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) async def log_play(self, ctx: commands.Context): if ctx.invoked_subcommand is None: await ctx.send('No play details listed. Type `/log` to see available commands.') @log_play.command(name='undo-play', help='Remove the most recent play from the log', aliases=['undo', 'rollback']) async def log_undo_play_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return undo_play(this_play.id) undo_play(get_current_play(this_game.id).id) complete_play(get_current_play(this_game.id).id, batter_to_base=get_latest_play(this_game.id).batter_final) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_play.command(name='stolen-base', help='One base runner steals a base', aliases=['sb']) @app_commands.describe(stolen_base='The base number stolen by the runner; 2 for 2nd, 3 for 3rd, 4 for home') async def log_stolen_base_command(self, ctx: commands.Context, stolen_base: int): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) catcher = get_one_lineup( this_game.id, team_id=this_play.pitcher.team_id, position='C' ) if stolen_base == 4 and this_play.on_third: patch_play( this_play.id, sb=1, on_third_final=4, runner_id=this_play.on_third.id, catcher_id=catcher.id ) elif stolen_base == 3 and this_play.on_second: if not this_play.on_third: patch_play( this_play.id, sb=1, on_second_final=3, runner_id=this_play.on_second.id, catcher_id=catcher.id ) else: this_runner = await get_player(this_game, this_play.on_second) await ctx.send(f'Ope. Looks like {this_runner["name"]} is blocked by the runner at third.') patch_play(this_play.id, locked=False) return elif stolen_base == 2 and this_play.on_first: if not this_play.on_second: patch_play( this_play.id, sb=1, on_first_final=2, runner_id=this_play.on_first.id, catcher_id=catcher.id ) else: this_runner = await get_player(this_game, this_play.on_first) await ctx.send(f'Ope. Looks like {this_runner["name"]} is blocked by the runner at second.') patch_play(this_play.id, locked=False) return else: await ctx.send(f'Uh oh - I don\'t see a runner there to steal the bag.') patch_play(this_play.id, locked=False) return complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_play.command(name='wild-pitch', help='All runners advance one base', aliases=['wp']) async def log_wild_pitch_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return advance_runners(this_play.id, 1) patch_play(this_play.id, rbi=0, wp=1) complete_play(this_play.id) @log_play.command(name='passed-ball', help='All runners advance one base', aliases=['pb']) async def log_passed_ball_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return advance_runners(this_play.id, 1) patch_play(this_play.id, rbi=0, pb=1) complete_play(this_play.id) @log_play.command(name='balk', help='All runners advance one base', aliases=['bk']) async def log_balk_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return advance_runners(this_play.id, 1) patch_play(this_play.id, rbi=0, balk=1) complete_play(this_play.id) @log_play.command(name='pickoff', help='One baserunner is out on the basepaths', aliases=['pick']) async def log_pickoff_command(self, ctx: commands.Context, from_base: int): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) if from_base == 3 and this_play.on_third: patch_play(this_play.id, on_third_final=99, runner_id=this_play.on_third.id, outs=1) elif from_base == 2 and this_play.on_second: patch_play(this_play.id, on_second_final=99, runner_id=this_play.on_second.id, outs=1) elif from_base == 1 and this_play.on_first: patch_play(this_play.id, on_first_final=99, runner_id=this_play.on_first.id, outs=1) else: await ctx.send(f'Uh oh - I don\'t see a runner there to be picked off.') patch_play(this_play.id, locked=False) return complete_play(this_play.id) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @commands.hybrid_group(name='show-card', help='Display an active player\'s card') @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) async def show_player(self, ctx: commands.Context): if ctx.invoked_subcommand is None: await ctx.send('No player details listed. Type `/show-player` to see available commands.') @show_player.command(name='defense', help='Display a defender\'s player card', aliases=['pick']) async def show_defense_command( self, ctx: commands.Context, position: Literal[ 'Catcher', 'First Base', 'Second Base', 'Third Base', 'Shortstop', 'Left Field', 'Center Field', 'Right Field']): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return defender = await get_player( game=this_game, lineup_member=get_one_lineup( this_game.id, team_id=this_play.pitcher.team_id, position=get_pos_abbrev(position) ) ) embed = get_team_embed(f'{defender["team"]["sname"]} {position}', defender['team']) embed.description = f'{defender["name"]}' embed.set_image(url=defender['image']) if this_game.is_pd: embed.set_footer(text=f'PD Season {PD_SEASON}', icon_url=LOGO) await ctx.send(content=None, embed=embed) @log_out.command(name='groundball-a', help='Potential double play ground ball', aliases=['gba']) async def log_groundballa_command(self, ctx: commands.Context): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return batter_to_base = None patch_play(this_play.id, locked=True) if this_play.starting_outs == 2 or this_play.on_base_code == 0: patch_play(this_play.id, pa=1, ab=1, outs=1) else: if this_play.on_base_code == 1: runner = await get_player(this_game, this_play.on_first) view = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(f'Is {runner["name"]} out at second on the double play?', view=view) await view.wait() if view.value: await question.delete() patch_play(this_play.id, on_first_final=False, pa=1, ab=1, outs=2) else: await question.delete() await ctx.send(f'Okay so it wasn\'t a gb A then? Go ahead and log a new play.') patch_play(this_play.id, locked=False) return elif this_play.on_base_code == 7: runner = await get_player(this_game, this_play.on_third) view = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(f'Is {runner["name"]} out on the home-to-first double play?', view=view) await view.wait() if view.value: await question.delete() advance_runners(this_play.id, 1) patch_play(this_play.id, on_third_final=False, pa=1, ab=1, outs=2, rbi=0) else: await question.delete() runner = await get_player(this_game, this_play.on_second) view = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(f'Is {runner["name"]} out at second on the double play?', view=view) await view.wait() if view.value(): await question.delete() advance_runners(this_play.id, 1) patch_play(this_play.id, on_first_final=False, pa=1, ab=1, outs=2, rbi=0) else: await question.delete() await ctx.send(f'Okay so it wasn\'t a gb A then? Go ahead and log a new play.') patch_play(this_play.id, locked=False) return else: num_outs = 1 bases = ['third', 'second', 'first'] for count, x in enumerate([this_play.on_third, this_play.on_second, this_play.on_first]): if x: runner = await get_player(this_game, x) view = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send( f'Did {runner["name"]} advance from {bases[count]} on the play?', view=view ) await view.wait() num_bases = 0 if view.value: await question.delete() advance_one_runner(this_play.id, from_base=3 - count, num_bases=1) else: await question.delete() view = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send(f'Was {runner["name"]} doubled off?', view=view) await view.wait() if view.value: await question.delete() if count == 0: patch_play(this_play.id, on_third_final=False) elif count == 1: patch_play(this_play.id, on_second_final=False) else: patch_play(this_play.id, on_first_final=False) num_outs += 1 else: await question.delete() if count == 0: patch_play(this_play.id, on_third_final=3) elif count == 1: patch_play(this_play.id, on_second_final=2) else: patch_play(this_play.id, on_first_final=1) if this_play.on_third: batter = await get_player(this_game, this_play.batter) view = Confirm(responders=[ctx.author], timeout=60) question = await ctx.send( f'Is {batter["name"]} out at first?', view=view ) await view.wait() if view.value: await question.delete() else: await question.delete() num_outs -= 1 batter_to_base = 1 patch_play(this_play.id, pa=1, ab=1, outs=num_outs) complete_play(this_play.id, batter_to_base=batter_to_base) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) @log_play.command(name='xcheck', help='Defender makes an x-check') async def log_xcheck_command(self, ctx: commands.Context, position: Literal[ 'Catcher', 'First Base', 'Second Base', 'Third Base', 'Shortstop', 'Left Field', 'Center Field', 'Right Field'], hit_allowed: Literal['out', 'single*', 'single**', 'double**', 'double***', 'triple'], error_allowed: Literal['out', '1 base', '2 bases', '3 bases']): this_game, owner_team, this_play = await self.checks_log_play(ctx) if False in (this_game, owner_team, this_play): return pos = get_pos_abbrev(position) defender = get_one_lineup( this_game.id, team_id=this_play.pitcher.team_id, position=pos ) logger.info(f'defender: {defender}') patch_play(this_play.id, defender_id=defender.id, error=1 if error_allowed != 'out' else 0, check_pos=pos) # Not hit and no error if hit_allowed == 'out' and error_allowed == 'out': await ctx.send(f'Just logged the x-check! Please log the resulting play to continue (e.g. \'flyball-b\' or ' f'\'groundball-a\')') return # Hit and error if hit_allowed != 'out' and error_allowed != 'out': batter_to_base = 1 if hit_allowed == 'triple': triple(this_play, comp_play=False) elif 'double' in hit_allowed: double_threestar(this_play, comp_play=False) if error_allowed == '1 base': batter_to_base = 3 elif error_allowed == '3 bases': batter_to_base = 4 # 2 base error is the only one handled differently between doubles elif hit_allowed == 'double***': batter_to_base = 4 else: batter_to_base = 3 # Both singles are handled the same else: single_wellhit(this_play, comp_play=False) if error_allowed == '1 base': batter_to_base = 2 else: batter_to_base = 3 complete_play(this_play.id, batter_to_base=batter_to_base) # Either hit or error num_bases = None if error_allowed == 'out': if hit_allowed == 'single*': single_onestar(this_play) elif hit_allowed == 'single**': single_wellhit(this_play) elif hit_allowed == 'double**': double_twostar(this_play) elif hit_allowed == 'double***': double_threestar(this_play) elif hit_allowed == 'triple': triple(this_play) else: if error_allowed == '1 base': num_bases = 1 elif error_allowed == '2 bases': num_bases = 2 elif error_allowed == '3 bases': num_bases = 3 advance_runners(this_play.id, num_bases=num_bases, is_error=True) complete_play(this_play.id, batter_to_base=num_bases) await ctx.send(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) # TODO: ground balls async def setup(bot): await bot.add_cog(Gameplay(bot))