import asyncio import logging import math import copy import os import ai_manager import discord import dice import gauntlets from discord import app_commands from discord.app_commands import Choice from discord.ext import commands, tasks from peewee import IntegrityError from typing import Optional, Literal from dice import sa_fielding_roll from helpers import SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME, random_conf_gif, SBA_SEASON, PD_SEASON, IMAGES, \ get_team_embed, Confirm, get_pos_abbrev, SBA_COLOR, get_roster_lineups, Question, give_packs, send_to_channel, \ get_channel, get_or_create_role, team_role, get_cal_user, get_card_embeds, ButtonOptions, get_ratings_guide, \ get_team_by_owner, get_roster_sheet, get_role from gameplay_helpers import * from db_calls import db_get, db_patch, db_post, db_delete, get_team_by_abbrev from db_calls_gameplay import StratGame, StratPlay, StratLineup, StratManagerAi, get_sba_team, get_sba_player, \ post_game, patch_game, get_game_team, post_lineups, 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, count_team_games, get_final_scorebug, \ get_fielding_stats, get_pitching_decisions, get_or_create_bullpen, get_active_games, patch_lineup, \ get_last_game_ids, get_plays, get_manager, get_one_game, load_ai, ai_batting, undo_subs class Gameplay(commands.Cog): def __init__(self, bot): self.bot = bot self.batter_ratings = None self.pitcher_ratings = None self.live_scoreboard.start() @tasks.loop(minutes=3) async def live_scoreboard(self): guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) if not guild: logging.error(f'Cannot access guild; pausing for 15 seconds') await asyncio.sleep(15) guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) if not guild: logging.error(f'Still cannot access guild; trying again in 10 minutes') return score_channel = discord.utils.get(guild.text_channels, name='live-pd-scores') player_role = discord.utils.get(guild.roles, name=PD_PLAYERS_ROLE_NAME) active_games = get_active_games(6) if len(active_games) == 0: await score_channel.set_permissions(player_role, read_messages=False) return try: embed = get_team_embed('Live Scoreboard') valid_scores = False for x in active_games: await asyncio.sleep(1) gs = await self.get_game_state(x) if not gs['error']: valid_scores = True channel = guild.get_channel(gs["channel_id"]) g_message = gs['scorebug'] g_message += f'Pitcher: {gs["pitcher"]["name"]}\n' \ f'Batter: {gs["batter"]["name"]}\n' \ f'Location: {channel.mention if channel is not None else "Your Butt"}\n' \ f'-------------------------' embed.add_field( name=f'{gs["away_team"]["sname"]} @ {gs["home_team"]["sname"]}', value=g_message, inline=False ) if valid_scores: async for message in score_channel.history(limit=25): await message.delete() await score_channel.set_permissions(player_role, read_messages=True) await score_channel.send(content=None, embed=embed) else: await score_channel.set_permissions(player_role, read_messages=False) return except Exception as e: logging.error(f'Could not update live_scoreboard: {e}') @tasks.loop(hours=36) async def update_ratings_guides(self): guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) if not guild: logging.error(f'Cannot access guild; pausing ratings guide for 20 seconds') await asyncio.sleep(20) guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) if not guild: logging.error(f'Still cannot access guild; trying again in 1 minutes') await asyncio.sleep(60) guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) if not guild: logging.error(f'Still cannot access guild; dueces') return data = get_ratings_guide(get_sheets(self.bot)) if data['valid']: self.batter_ratings = data['batter_ratings'] self.pitcher_ratings = data['pitcher_ratings'] else: logging.error(f'gameplay - pulled bad ratings guide data') 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 '▼' if curr_play.game.active: inning = f'{half} {curr_play.inning_num}' outs = f'{curr_play.starting_outs} Out{"s" if curr_play.starting_outs != 1 else ""}' else: inning = f'F/{curr_play.inning_num if curr_play.inning_half == "Bot" else curr_play.inning_num - 1}' outs = '' game_string = f'```\n' \ f'{away_team["abbrev"].replace("Gauntlet-", ""): ^4}{curr_play.away_score: ^3} {second_base}' \ f'{inning: >10}\n' \ f'{home_team["abbrev"].replace("Gauntlet-", ""): ^4}{curr_play.home_score: ^3} {third_base} ' \ f'{first_base}{outs: >8}\n```' return game_string async def post_rewards(self, winning_team: dict, losing_team: dict, this_game: StratGame): wr_query = await db_get( 'gamerewards', params=[('name', f'{"Short" if this_game.short_game else "Full"} Game Win')]) lr_query = await db_get( 'gamerewards', params=[('name', f'{"Short" if this_game.short_game else "Full"} Game Loss')]) if not wr_query['count'] or not lr_query['count']: raise KeyError(f'Game Rewards were not found. Leaving this game active.') human_team = winning_team if losing_team['is_ai'] else losing_team ai_team = winning_team if winning_team['is_ai'] else losing_team win_reward = wr_query['gamerewards'][0] loss_reward = lr_query['gamerewards'][0] win_string = f'1x {win_reward["pack_type"]["name"]} Pack\n' # Post Team Choice packs if this_game.ai_team is not None and not this_game.short_game and 'gauntlet' not in this_game.game_type and \ losing_team['is_ai']: r_query = await db_get( 'results', params=[ ('team_one_id', winning_team['id']), ('team_two_id', losing_team['id']), ('game_type', this_game.game_type), ('season', this_game.season) ] ) wins = 0 for x in r_query['results']: if (x['away_score'] > x['home_score'] and x['away_team']['id'] == human_team['id']) or ( x['home_score'] > x['away_score'] and x['home_team']['id'] == human_team['id']): wins += 1 async def post_tc_pack(): await db_post( 'packs/one', payload={ 'team_id': human_team['id'], 'pack_type_id': 8, 'pack_team_id': losing_team['id'] } ) if r_query['count'] > 0: if this_game.game_type == 'minor-league' and wins % 6 == 0: await post_tc_pack() win_string += f'1x {losing_team["abbrev"]} Team Choice pack\n' elif this_game.game_type == 'major-league' and wins % 4 == 0: await post_tc_pack() win_string += f'1x {losing_team["abbrev"]} Team Choice pack\n' elif this_game.game_type == 'hall-of-fame' and wins % 2 == 0: await post_tc_pack() win_string += f'1x {losing_team["abbrev"]} Team Choice pack\n' win_string += f'{win_reward["money"]}₼\n' loss_string = f'{loss_reward["money"]}₼\n' # Post rewards if 'gauntlet' in this_game.game_type: if 'Gauntlet' in winning_team['abbrev']: t_query = await db_get('teams', params=[('abbrev', winning_team['abbrev'].split('-')[1])]) winning_team = t_query['teams'][0] if 'Gauntlet' in losing_team['abbrev']: t_query = await db_get('teams', params=[('abbrev', losing_team['abbrev'].split('-')[1])]) losing_team = t_query['teams'][0] await give_packs(winning_team, num_packs=1, pack_type=win_reward['pack_type']) await db_post(f'teams/{winning_team["id"]}/money/{win_reward["money"]}') await db_post(f'teams/{losing_team["id"]}/money/{loss_reward["money"]}') data = { 'win_string': win_string, 'loss_string': loss_string } return data async def get_game_state(self, game: StratGame) -> dict: 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) curr_play = get_current_play(game.id) if curr_play is None: 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 not away_lineup or not home_lineup: game_state = { 'error': True, 'away_lineup': away_lineup, 'home_lineup': home_lineup, 'away_team': away_team, 'home_team': home_team } logging.error(f'One ore more lineups not submitted in Game {game.id}\n\ngame_state: {game_state}') return game_state else: logging.info(f'looking for home ({game.home_team_id}) pitcher in Game {game.id}') pitcher = get_one_lineup(game.id, team_id=game.home_team_id, position='P') logging.info(f'pitcher: {pitcher}') curr_play = post_play({ 'game_id': game.id, 'play_num': 1, 'batter_id': get_one_lineup(game.id, team_id=game.away_team_id, batting_order=1).id, 'pitcher_id': pitcher.id if pitcher else None, 'on_base_code': 0, 'inning_half': 'Top', 'inning_num': 1, 'batting_order': 1, 'starting_outs': 0, 'away_score': 0, 'home_score': 0 }) game_state = {'error': False, 'curr_play': curr_play, 'away_team': away_team, 'home_team': home_team} 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: if not game_state['curr_play'].pitcher: p_search = get_one_lineup( game.id, team_id=game.away_team_id if game_state['curr_play'].inning_half == 'Bot' else game.home_team_id, position='P' ) if p_search: patch_play(game_state['curr_play'].id, pitcher_id=p_search.id) 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 == 'Bot' else game.home_team_id, position='C' ) ) except Exception as e: logging.error(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 game_state['channel_id'] = game.channel_id return game_state async def get_game_state_embed(self, game: StratGame, full_length=True, for_liveboard=False): game_state = await self.get_game_state(game) logging.debug(f'game_state: {game_state}') gt_string = ' - Unlimited' if game.game_type == 'minor-league': gt_string = ' - Minor League' elif game.game_type == 'major-league': gt_string = ' - Major League' elif game.game_type == 'hall-of-fame': gt_string = ' - Hall of Fame' elif 'gauntlet' in game.game_type: gt_string = ' - Gauntlet' embed = discord.Embed( title=f'{game_state["away_team"]["sname"]} @ {game_state["home_team"]["sname"]}{gt_string}', color=int(SBA_COLOR, 16) ) if game.is_pd: footer_text = f'PD Season {PD_SEASON}' if game.short_game: footer_text += f' - Reminder: all pitchers have POW(1) in 3-inning games' embed.set_footer(text=footer_text, icon_url=IMAGES['logo']) else: embed.set_footer(text=f'SBa Season {SBA_SEASON}', icon_url=IMAGES['logo']) 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 game_state['away_lineup'] else 'None, yet') embed.add_field(name='Home Team', value=game_state['home_lineup'] if game_state['home_lineup'] else 'None, yet') if 'error_message' in game_state: embed.set_footer(text=game_state['error_message'], icon_url=IMAGES['logo']) return embed embed.add_field(name='Game State', value=game_state['scorebug'], inline=False) if abs(game_state['curr_play'].home_score - game_state['curr_play'].away_score) >= 10: embed.description = '***Mercy rule in effect***' pitcher_string = f'{player_link(game, game_state["pitcher"])}' batter_string = f'{game_state["curr_play"].batter.batting_order}. {player_link(game, game_state["batter"])} - ' \ f'{game_state["curr_play"].batter.position}' 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' if game.short_game and p_s["pl_outs"] >= 3: pitcher_string += f'\n***F A T I G U E D***' # 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}') if game_state['batter']['image2']: embed.set_image(url=game_state["batter"]["image2"]) else: 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' if len(baserunner_string) > 0: embed.add_field(name=' ', value=' ', inline=False) embed.add_field( name='Baserunners', value=baserunner_string ) embed.add_field( name='Catcher', value=f'{player_link(game, game_state["catcher"])}' ) else: embed.add_field( name='Baserunners', value='None', inline=False ) # If an AI team is playing if True in [game_state['pitcher']['team']['is_ai'], game_state['batter']['team']['is_ai']]: ai_note = '' gm_name = '' logging.debug(f'Checking AI stuff') # AI Team is pitching if game_state['pitcher']['team']['is_ai']: ai_data = await ai_manager.pitching_ai_note(game_state['curr_play'], game_state['pitcher']) logging.info(f'ai_data: {ai_data}') ai_note = ai_data['note'] gm_name = ai_data['gm_name'] embed.set_thumbnail(url=ai_data['pitcher']["image"]) if ai_data['sub'] is not None: embed.add_field( name='SUBSTITUTION', value=f'The {game_state["pitcher"]["team"]["sname"]} have brought in ' f'{ai_data["sub"]["description"]} to pitch.' ) # AI Team is batting elif game_state['batter']['team']['is_ai']: embed.set_thumbnail(url=game_state["pitcher"]["image"]) ai_data = ai_manager.batting_ai_note(game_state['curr_play'], game_state['batter']) ai_note = ai_data['note'] gm_name = ai_data['gm_name'] if ai_note: embed.add_field( name=f'{gm_name} will...', value=ai_note, 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 async def groundballs( self, interaction, this_game, this_play: StratPlay, groundball_type: str, comp_play: bool = True): batter_to_base = None bases = ['third', 'second', 'first'] 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 groundball_type == 'a': if this_play.on_base_code == 1: patch_play(this_play.id, on_first_final=False, pa=1, ab=1, outs=2) elif this_play.on_base_code == 4: if this_play.starting_outs == 1: patch_play(this_play.id, pa=1, ab=1, outs=2) else: view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Is the double play at second and first?', view=view ) await view.wait() if view.value: await question.delete() patch_play( this_play.id, pa=1, ab=1, outs=2, on_second_final=3, on_first_final=False, ) else: await question.delete() view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Is the double play at third and second?', view=view ) await view.wait() if view.value: await question.delete() patch_play( this_play.id, pa=1, ab=1, outs=2, on_second_final=False, on_first_final=False ) batter_to_base = 1 else: await question.edit( content=f'Hmm...not sure what else this could be. If you expected a different ' f'result, let Cal know.', view=None ) return elif this_play.on_base_code == 7: if this_play.starting_outs == 1: patch_play(this_play.id, pa=1, ab=1, outs=2) else: runner = await get_player(this_game, this_play.on_third) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.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() advance_runners(this_play.id, 1) patch_play(this_play.id, on_first_final=False, pa=1, ab=1, outs=2, rbi=0) else: num_outs = 1 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=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Was {runner["name"]} sent from {bases[count]} on the play?', view=view ) await view.wait() num_bases = 0 if view.value: await question.delete() view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Was {runner["name"]} thrown out?', 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=4) elif count == 1: patch_play(this_play.id, on_second_final=3) else: patch_play(this_play.id, on_first_final=2) 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=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.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) if num_outs + this_play.starting_outs == 3: advance_runners(this_play.id, 0) elif groundball_type == 'b': if this_play.on_base_code == 3 or this_play.on_base_code == 6 or this_play.on_base_code == 2: if this_play.on_base_code == 2: runner = await get_player(this_game, this_play.on_second) from_base = 'second' else: runner = await get_player(this_game, this_play.on_third) from_base = 'third' view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Was {runner["name"]} sent from {from_base} on the play?', view=view ) await view.wait() if view.value: advance_runners(this_play.id, 1) await question.delete() view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Was {runner["name"]} thrown out?', view=view ) await view.wait() if view.value: await question.delete() if from_base == 'third': patch_play(this_play.id, on_third_final=False) # from second else: patch_play(this_play.id, on_second_final=False) else: await question.delete() else: await question.delete() advance_runners(this_play.id, 0) patch_play(this_play.id, pa=1, ab=1, outs=1) elif this_play.on_base_code == 1 or this_play.on_base_code == 4: advance_runners(this_play.id, 1) patch_play(this_play.id, on_first_final=False, pa=1, ab=1, outs=1) batter_to_base = 1 else: num_outs = 0 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=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Was {runner["name"]} sent from {bases[count]} on the play?', view=view ) await view.wait() num_bases = 0 if view.value: await question.delete() view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Was {runner["name"]} thrown out?', 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() advance_one_runner(this_play.id, from_base=3 - count, num_bases=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 num_outs == 0: batter = await get_player(this_game, this_play.batter) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Is {batter["name"]} out at first?', view=view ) await view.wait() if view.value: await question.delete() num_outs += 1 else: await question.delete() await interaction.edit_original_response( content=f'Okay so it wasn\'t a gb B then? Go ahead and log a new play.' ) patch_play(this_play.id, locked=False) return else: batter_to_base = 1 patch_play(this_play.id, pa=1, ab=1, outs=num_outs) elif groundball_type == 'c': if this_play.on_base_code == 7: runner = await get_player(this_game, this_play.on_third) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Is {runner["name"]} forced out at home?', 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=1, rbi=0) batter_to_base = 1 else: await question.delete() advance_runners(this_play.id, 1) patch_play(this_play.id, pa=1, ab=1, outs=1) elif this_play.on_third: num_outs = 0 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=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Was {runner["name"]} sent from {bases[count]} on the play?', view=view ) await view.wait() if view.value: await question.delete() view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Was {runner["name"]} thrown out?', 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=4) elif count == 1: patch_play(this_play.id, on_second_final=3) else: patch_play(this_play.id, on_first_final=2) 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 not num_outs: num_outs += 1 logging.debug(f'should be patching the gb C now...') patch_play(this_play.id, pa=1, ab=1, outs=num_outs) else: advance_runners(this_play.id, 1) patch_play(this_play.id, pa=1, ab=1, outs=1) if comp_play: complete_play(this_play.id, batter_to_base=batter_to_base) async def flyballs(self, interaction, this_game, this_play, flyball_type, comp_play: bool = True): num_outs = 1 if flyball_type == 'a': 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) elif flyball_type == 'b' or flyball_type == 'ballpark': patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1, bpfo=1 if flyball_type == 'ballpark' else 0) 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: ai_hint = '' if this_game.ai_team and ai_batting(this_game, this_play): ai_hint = f'*The runner will {get_manager(this_game).tag_from_second(this_play.starting_outs + 1)}*' runner = await get_player(this_game, this_play.on_second) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Was {runner["name"]} sent from second on the play?\n\n{ai_hint}', view=view ) await view.wait() if view.value: await question.delete() view = ButtonOptions( responders=[interaction.user], timeout=60, labels=['Tagged Up', 'Hold at 2nd', 'Out at 3rd', None, None] ) question = await interaction.channel.send( f'What was the result of {runner["name"]} tagging from second?', view=view ) await view.wait() if view.value: await question.delete() if view.value == 'Tagged Up': advance_one_runner(this_play.id, from_base=2, num_bases=1) elif view.value == 'Out at 3rd': num_outs += 1 patch_play(this_play.id, on_second_final=False, outs=num_outs) else: await question.delete() else: await question.delete() elif flyball_type == 'b?': 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: ai_hint = '' if ai_batting(this_game, this_play): ai_hint = f'*The runner will {get_manager(this_game).tag_from_third(this_play.starting_outs + 1)}*' runner = await get_player(this_game, this_play.on_third) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Was {runner["name"]} sent from third on the play?\n\n{ai_hint}', view=view ) await view.wait() if view.value: await question.delete() view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Was {runner["name"]} thrown out?', view=view ) await view.wait() if view.value: await question.delete() patch_play(this_play.id, on_third_final=False) num_outs += 1 patch_play(this_play.id, outs=num_outs) else: await question.delete() advance_one_runner(this_play.id, from_base=3, num_bases=1) else: await question.delete() elif flyball_type == 'c': patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1) advance_runners(this_play.id, num_bases=0) if comp_play: complete_play(this_play.id) # @commands.command(name='tl') # @commands.is_owner() # async def test_lineup_command(self, ctx, team_abbrev: str): # t_query = await db_get('teams', params=[('abbrev', team_abbrev)]) # if t_query['count'] > 0: # team = t_query['teams'][0] # await ctx.send(f'Pulling a lineup for the {team["lname"]}...') # lineups = ai_manager.build_lineup(team, 69420, 'l', 'minor-league') # l_output = '' # for line in lineups: # l_output += f'{line["batting_order"]} - {line["player_name"]} - {line["position"]}\n' # await ctx.send(f'Lineups:\n\n{l_output}') # else: # await ctx.send(f'No team found with abbrev {team_abbrev}') # # @commands.command(name='tr') # @commands.is_owner() # async def test_reliever_command(self, ctx, game_id: int): # this_game = get_one_game(game_id=game_id) # ai_id = this_game.away_team_id if this_game.ai_team == 'away' else this_game.home_team_id # ai_team = await db_get('teams', object_id=ai_id) # used_pitchers = await get_team_lineups( # game_id=game_id, team_id=ai_team['id'], inc_inactive=True, pitchers_only=True, as_string=False # ) # reliever = ai_manager.get_relief_pitcher(get_current_play(game_id), ai_team, used_pitchers) # await ctx.send(f'Reliever selected:\n\n{reliever}') group_new_game = app_commands.Group(name='new-game', description='Start a new baseball game') # @group_new_game.command(name='sba', description='Start a new SBa league game') # @commands.has_any_role(SBA_PLAYERS_ROLE_NAME) # async def new_game_command( # self, interaction: discord.Interaction, away_team_abbrev: str, home_team_abbrev: str, week_num: int, # game_num: int): # await interaction.response.defer() # # conflict = get_one_game(channel_id=interaction.channel.id, active=True) # if conflict: # 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 # # away_team = get_sba_team(away_team_abbrev) # home_team = get_sba_team(home_team_abbrev) # for x in [away_team, home_team]: # conflict = count_team_games(x['id']) # if conflict['count']: # await interaction.edit_original_response( # content=f'Ope. The {x["sname"]} are already playing over in ' # f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}' # ) # return # # if interaction.user.id not in [away_team['gmid'], away_team['gmid2'], home_team['gmid'], home_team['gmid2']]: # await interaction.edit_original_response( # content='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'], # 'season': SBA_SEASON, # 'week_num': week_num, # 'game_num': game_num, # 'channel_id': interaction.channel.id, # 'active': True, # 'is_pd': False, # }) # logging.info(f'game is posted!') # # await interaction.edit_original_response( # content=f'The game is set! Go ahead and set lineups with `/setlineup`' # ) @group_new_game.command(name='mlb-campaign', description='Start a new MLB Campaign game against an AI') @commands.has_any_role(PD_PLAYERS_ROLE_NAME) async def new_game_campaign_command( self, interaction: discord.Interaction, league: Literal['Minor League', 'Major League', 'Hall of Fame'], away_team_abbrev: str, home_team_abbrev: str, num_innings: Literal[9, 3]): await interaction.response.defer() conflict = get_one_game(channel_id=interaction.channel.id, active=True) if conflict: 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 try: if interaction.channel.category.name != 'Public Fields': await interaction.response.send_message( f'Why don\'t you head down to one of the Public Fields that way other humans can help if anything ' f'pops up?' ) return except Exception as e: logging.error(f'Could not check channel category: {e}') away_team = await get_team_by_abbrev(away_team_abbrev) home_team = await get_team_by_abbrev(home_team_abbrev) if not away_team: await interaction.edit_original_response( content=f'Sorry, I don\'t know who **{away_team_abbrev.upper()}** is.' ) return if not home_team: await interaction.edit_original_response( content=f'Sorry, I don\'t know who **{home_team_abbrev.upper()}** is.' ) return # if True in [away_team['is_ai'], home_team['is_ai']] and is_ranked: # await interaction.edit_original_response( # content=f'Sorry, ranked games can only be played against human teams. Run `/new-game` again with ' # f'`is_ranked` set to False to play against the ' # f'{away_team["sname"] if away_team["is_ai"] else home_team["sname"]}.' # ) # return for x in [away_team, home_team]: if not x['is_ai']: conflict = count_team_games(x['id']) if conflict['count']: await interaction.edit_original_response( content=f'Ope. The {x["sname"]} are already playing over in ' f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}' ) return current = await db_get('current') week_num = current['week'] # logging.debug(f'away: {away_team} / home: {home_team} / week: {week_num} / ranked: {is_ranked}') logging.debug(f'away: {away_team} / home: {home_team} / week: {week_num}') if not away_team['is_ai'] and not home_team['is_ai']: logging.error(f'MLB Campaign game between {away_team["abbrev"]} and {home_team["abbrev"]} has no AI') await interaction.edit_original_response( content=f'I don\'t see an AI team in this MLB Campaign game. Run `/new-game mlb-campaign` again with ' f'an AI for a campaign game or `/new-game ` for a human game.' ) return ai_team = away_team if away_team['is_ai'] else home_team if interaction.user.id not in [away_team['gmid'], home_team['gmid']]: await interaction.edit_original_response( content='You can only start a new game if you GM one of the teams.' ) return if 'Minor' in league: league_name = 'minor-league' elif 'Major' in league: can_play = False for x in interaction.user.roles: if x.name == 'PD - Major League': can_play = True if not can_play: await interaction.edit_original_response( content=f'Ope. Looks like you haven\'t received the **PD - Major League** role, yet!\n\n' f'To play **Major League** games, you need to defeat all 30 MLB teams in the Minor League ' f'campaign. You can see your progress with `/record`.\n\n' f'If you have completed the Minor League campaign, go ping Cal to get your new role!') return league_name = 'major-league' else: can_play = False for x in interaction.user.roles: if x.name == 'PD - Hall of Fame': can_play = True if not can_play: await interaction.edit_original_response( content=f'Ope. Looks like you haven\'t received the **PD - Hall of Fame** role, yet!\n\n' f'To play **Hall of Fame** games, you need to defeat all 30 MLB teams in the Minor League ' f'and Major League campaign. You can see your progress with `/record`.\n\n' f'If you have completed the Major League campaign, go ping Cal to get your new role!') return league_name = 'hall-of-fame' this_game = post_game({ 'away_team_id': away_team['id'], 'home_team_id': home_team['id'], 'week_num': week_num, 'channel_id': interaction.channel.id, 'active': True, 'is_pd': True, 'ranked': False, 'season': current['season'], 'short_game': True if num_innings == 3 else False, 'game_type': league_name }) logging.info( f'Game {this_game.id} between {away_team_abbrev.upper()} and {home_team_abbrev.upper()} is posted!' ) away_role = await team_role(interaction, away_team) home_role = await team_role(interaction, home_team) # Get AI Lineup try: await interaction.edit_original_response( content=f'I am getting a lineup card from the {ai_team["sname"]}...' ) logging.info(f'new-game - calling lineup for {ai_team["abbrev"]}') all_lineups = await ai_manager.build_lineup(ai_team, this_game.id, league_name) logging.info(f'new-game - got lineup for {ai_team["abbrev"]}') except Exception as e: patch_game(this_game.id, active=False) logging.error(f'could not start an AI game with {ai_team["sname"]}: {e}') await interaction.edit_original_response( content=f'Looks like the {ai_team["sname"]} lineup card didn\'t come through clearly. I\'ll sort ' f'this out with {ai_team["gmname"]} and {get_cal_user(interaction).mention}. I\'ll end ' f'this game - why don\'t you play against somebody else for now?' ) return # Get AI Starting Pitcher try: await interaction.edit_original_response( content=f'Now to decide on a Starting Pitcher...' ) if ai_team['id'] == this_game.away_team_id: patch_game(this_game.id, away_roster_num=69, ai_team='away') else: patch_game(this_game.id, home_roster_num=69, ai_team='home') # starter = starting_pitcher(ai_team, self.bot, True if home_team['is_ai'] else False) starter = await ai_manager.get_starting_pitcher( ai_team, this_game.id, True if home_team['is_ai'] else False, league_name ) all_lineups.append(starter) this_card = await db_get(f'cards', object_id=starter['card_id']) await interaction.channel.send( content=f'The {ai_team["sname"]} are starting **{this_card["player"]["description"]}**:\n\n' f'{this_card["player"]["image"]}' ) except Exception as e: patch_game(this_game.id, active=False) logging.error(f'could not start an AI game with {ai_team["sname"]}: {e}') await interaction.edit_original_response( content=f'Looks like the {ai_team["sname"]} rotation didn\'t come through clearly. I\'ll sort ' f'this out with {ai_team["gmname"]} and {get_cal_user(interaction).mention}. I\'ll end ' f'this game - why don\'t you play against somebody else for now?' ) return logging.debug(f'Setting lineup for {ai_team["sname"]} in PD game') logging.info(f'lineups: {all_lineups}') post_lineups(all_lineups) await interaction.channel.send( content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' f'Go ahead and set lineups with the `/readlineup` command!', embed=await self.get_game_state_embed(this_game, full_length=False) ) return @group_new_game.command(name='ranked', description='Start a new Ranked game against another human') @commands.has_any_role(PD_PLAYERS_ROLE_NAME) async def new_game_ranked_command( self, interaction: discord.Interaction, away_team_abbrev: str, home_team_abbrev: str, num_innings: Literal[9, 3]): await interaction.response.defer() conflict = get_one_game(channel_id=interaction.channel.id, active=True) if conflict: 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 try: if interaction.channel.category.name != 'Public Fields': await interaction.response.send_message( f'Why don\'t you head down to one of the Public Fields that way other humans can help if anything ' f'pops up?' ) return except Exception as e: logging.error(f'Could not check channel category: {e}') away_team = await get_team_by_abbrev(away_team_abbrev) home_team = await get_team_by_abbrev(home_team_abbrev) if not away_team: await interaction.edit_original_response( content=f'Sorry, I don\'t know who **{away_team_abbrev.upper()}** is.' ) return if not home_team: await interaction.edit_original_response( content=f'Sorry, I don\'t know who **{home_team_abbrev.upper()}** is.' ) return if away_team['is_ai'] or home_team['is_ai']: logging.error(f'Ranked game between {away_team["abbrev"]} and {home_team["abbrev"]} has an AI') await interaction.edit_original_response( content=f'Only human vs human games can be ranked - run `/new-game` again and double-check the ' f'game type you want! If you have questions, feel free to post up in #paper-dynasty-chat' ) return for x in [away_team, home_team]: if not x['is_ai']: conflict = count_team_games(x['id']) if conflict['count']: await interaction.edit_original_response( content=f'Ope. The {x["sname"]} are already playing over in ' f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}' ) return current = await db_get('current') week_num = current['week'] logging.debug(f'away: {away_team} / home: {home_team} / week: {week_num} / ranked: True') if interaction.user.id not in [away_team['gmid'], home_team['gmid']]: await interaction.edit_original_response( content='You can only start a new game if you GM one of the teams.' ) return this_game = post_game({ 'away_team_id': away_team['id'], 'home_team_id': home_team['id'], 'week_num': week_num, 'channel_id': interaction.channel.id, 'active': True, 'is_pd': True, 'ranked': True, 'season': current['season'], 'short_game': True if num_innings == 3 else False, 'game_type': 'ranked' }) logging.info( f'Game {this_game.id} between {away_team_abbrev.upper()} and {home_team_abbrev.upper()} is posted!' ) away_role = await team_role(interaction, away_team) home_role = await team_role(interaction, home_team) await interaction.channel.send( content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' f'Go ahead and set lineups with the `/readlineup` command!', embed=await self.get_game_state_embed(this_game, full_length=False) ) return @group_new_game.command(name='unlimited', description='Start a new Unlimited game against another human') @commands.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, num_innings: Literal[9, 3]): await interaction.response.defer() conflict = get_one_game(channel_id=interaction.channel.id, active=True) if conflict: 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 try: if interaction.channel.category.name != 'Public Fields': await interaction.response.send_message( f'Why don\'t you head down to one of the Public Fields that way other humans can help if anything ' f'pops up?' ) return except Exception as e: logging.error(f'Could not check channel category: {e}') away_team = await get_team_by_abbrev(away_team_abbrev) home_team = await get_team_by_abbrev(home_team_abbrev) if not away_team: await interaction.edit_original_response( content=f'Sorry, I don\'t know who **{away_team_abbrev.upper()}** is.' ) return if not home_team: await interaction.edit_original_response( content=f'Sorry, I don\'t know who **{home_team_abbrev.upper()}** is.' ) return if away_team['is_ai'] or home_team['is_ai']: logging.error(f'Ranked game between {away_team["abbrev"]} and {home_team["abbrev"]} has an AI') await interaction.edit_original_response( content=f'This command is for human v human games - run `/new-game` again and double-check the ' f'game type you want! If you have questions, feel free to post up in #paper-dynasty-chat' ) return for x in [away_team, home_team]: if not x['is_ai']: conflict = count_team_games(x['id']) if conflict['count']: await interaction.edit_original_response( content=f'Ope. The {x["sname"]} are already playing over in ' f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}' ) return current = await db_get('current') week_num = current['week'] logging.debug(f'away: {away_team} / home: {home_team} / week: {week_num} / ranked: True') if interaction.user.id not in [away_team['gmid'], home_team['gmid']]: await interaction.edit_original_response( content='You can only start a new game if you GM one of the teams.' ) return this_game = post_game({ 'away_team_id': away_team['id'], 'home_team_id': home_team['id'], 'week_num': week_num, 'channel_id': interaction.channel.id, 'active': True, 'is_pd': True, 'ranked': False, 'season': current['season'], 'short_game': True if num_innings == 3 else False, 'game_type': 'unlimited' }) logging.info( f'Game {this_game.id} between {away_team_abbrev.upper()} and {home_team_abbrev.upper()} is posted!' ) away_role = await team_role(interaction, away_team) home_role = await team_role(interaction, home_team) await interaction.channel.send( content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' f'Go ahead and set lineups with the `/readlineup` command!', embed=await self.get_game_state_embed(this_game, full_length=False) ) return @group_new_game.command(name='gauntlet', description='Start a new Gauntlet game against an AI') @commands.has_any_role(PD_PLAYERS_ROLE_NAME) async def new_game_gauntlet_command( self, interaction: discord.Interaction, event_name: Literal['Paper Sluggers', 'Flashback Gauntlet']): await interaction.response.defer() conflict = get_one_game(channel_id=interaction.channel.id, active=True) if conflict: 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 try: if interaction.channel.category.name != 'Public Fields': await interaction.response.send_message( f'Why don\'t you head down to one of the Public Fields that way other humans can help if anything ' f'pops up?' ) return except Exception as e: logging.error(f'Could not check channel category: {e}') current = await db_get('current') week_num = current['week'] e_query = await db_get('events', params=[("name", event_name), ("active", True)]) if e_query['count'] == 0: await interaction.edit_original_response( content=f'It looks like the {event_name} has ended! Cal should really remove it from this list.' ) return this_event = e_query['events'][0] main_team = await get_team_by_owner(interaction.user.id) team = await get_team_by_abbrev(f'Gauntlet-{main_team["abbrev"]}') 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 if not 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 conflict = count_team_games(team['id']) if conflict['count']: await interaction.edit_original_response( content=f'Ope. The {team["sname"]} are already playing over in ' f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}' ) return # Get Gauntlet run r_query = await db_get( 'gauntletruns', params=[('team_id', 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] # If not new or after draft, create new AI game is_home = gauntlets.is_home_team(team, this_event, this_run) opponent = await gauntlets.get_opponent(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. {get_cal_user(interaction)} help plz!' ) return game_code = gauntlets.get_game_code(team, this_event, this_run) this_game = post_game({ 'away_team_id': opponent['id'] if is_home else team['id'], 'home_team_id': team['id'] if is_home else opponent['id'], 'week_num': week_num, 'channel_id': interaction.channel.id, 'active': True, 'is_pd': True, 'ranked': False, 'season': current['season'], 'short_game': False, 'game_type': game_code }) logging.info( f'Game {this_game.id} between {team["abbrev"]} and {opponent["abbrev"]} is posted!' ) t_role = await team_role(interaction, main_team) # Get AI Lineup try: await interaction.edit_original_response( content=f'I am getting a lineup card from the {opponent["sname"]}...' ) logging.info(f'new-game - calling lineup for {opponent["abbrev"]}') all_lineups = await gauntlets.build_lineup(opponent, this_game, this_event) logging.info(f'new-game-gauntlet - got lineup for {opponent["abbrev"]}') except Exception as e: patch_game(this_game.id, active=False) logging.error(f'could not start a gauntlet game with {opponent["sname"]}: {e}') await interaction.edit_original_response( content=f'Looks like the {opponent["sname"]} lineup card didn\'t come through clearly. I\'ll sort ' f'this out with {opponent["gmname"]} and {get_cal_user(interaction).mention}. I\'ll end ' f'this game - why don\'t you play against somebody else for now?' ) return # Get AI Starting Pitcher try: await interaction.edit_original_response( content=f'Now to decide on a Starting Pitcher...' ) if opponent['id'] == this_game.away_team_id: patch_game(this_game.id, away_roster_num=69, ai_team='away') else: patch_game(this_game.id, home_roster_num=69, ai_team='home') starter = await gauntlets.get_starting_pitcher(opponent, this_game, this_event, this_run) all_lineups.append(starter) this_card = await db_get(f'cards', object_id=starter['card_id']) await interaction.channel.send( content=f'The {opponent["sname"]} are starting **{this_card["player"]["description"]}**:\n\n' f'{this_card["player"]["image"]}' ) except Exception as e: patch_game(this_game.id, active=False) logging.error(f'could not start an AI game with {opponent["sname"]}: {e}') await interaction.edit_original_response( content=f'Looks like the {opponent["sname"]} rotation didn\'t come through clearly. I\'ll sort ' f'this out with {opponent["gmname"]} and {get_cal_user(interaction).mention}. I\'ll end ' f'this game - why don\'t you play against somebody else for now?' ) return logging.debug(f'Setting lineup for {opponent["sname"]} in PD Gauntlet game {game_code}') post_lineups(all_lineups) await interaction.channel.send( content=f'Game {gauntlets.games_played(this_run) + 1} of the run is set!\n\n' f'Go ahead and set lineups with the `/readlineup` command!', embed=await self.get_game_state_embed(this_game, full_length=False) ) return @commands.command(name='force-endgame', help='Mod: Force a game to end without stats') @commands.is_owner() async def force_end_game_command(self, ctx: commands.Context): this_game = get_one_game(channel_id=ctx.channel.id, active=True) try: await ctx.send(content=None, embed=await self.get_game_state_embed(this_game)) except Exception as e: logging.error(f'could not post game state embed: {e}') question = await ctx.send( f'Something is very borked here and I can\'t post the embed. Imma nuke this game now...' ) patch_game(this_game.id, active=False) await question.edit(content='Done and dusted.', view=None) return view = Confirm(responders=[ctx.author], timeout=60, label_type='yes') question = await ctx.send(f'Should I nuke this game?', view=view) await view.wait() if view.value: patch_game(this_game.id, active=False) await question.edit(content='It\'s gone.', view=None) else: await question.edit(content='It stays.', view=None) @commands.command(name='check-decisions', help='Mod: Calculate pitching decisions of current game') @commands.is_owner() async def check_decisions_command(self, ctx: commands.Context): this_game = get_one_game(channel_id=ctx.channel.id, active=True) get_pitching_decisions(this_game) await ctx.send(random_conf_gif()) @app_commands.command(name='endgame', description='End game in this channel') @app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME) async def end_game_command(self, interaction: discord.Interaction): await interaction.response.defer() this_game = get_one_game(channel_id=interaction.channel.id, active=True) if not this_game: await interaction.edit_original_response(content='Ope, I don\'t see a game in this channel.') return logging.info(f'Ending Game {this_game.id}') response = await interaction.edit_original_response(content=f'Let\'s see what we\'ve got here...') owner_team = await get_game_team(this_game, interaction.user.id) gauntlet_team = None if 'gauntlet' in this_game.game_type: gauntlet_team = await get_game_team(this_game, team_abbrev=f'Gauntlet-{owner_team["abbrev"]}') if not {owner_team['id'], gauntlet_team['id']}.intersection( [this_game.away_team_id, this_game.home_team_id]): await interaction.edit_original_response(content='Bruh. Only GMs of the active teams can end games.') return elif not owner_team['id'] in [this_game.away_team_id, this_game.home_team_id] and \ interaction.user.id != self.bot.owner_id: await interaction.edit_original_response(content='Bruh. Only GMs of the active teams can end games.') return latest_play = get_latest_play(this_game.id) """ If the game isn't over Not 0 outs Top of inning <= 9 and not 10-run lead Top 9th inning or earlier and not 10-run lead """ valid_end = False if latest_play.starting_outs == 0: if latest_play.inning_half == 'top': if latest_play.inning_num > 9 and latest_play.away_score != latest_play.home_score: valid_end = True if abs(latest_play.home_score - latest_play.away_score) >= 10: valid_end = True elif latest_play.inning_half == 'bot' and abs(latest_play.home_score - latest_play.away_score) >= 10: valid_end = True elif abs(latest_play.home_score - latest_play.away_score) >= 10 and latest_play.inning_half == 'bot': valid_end = True if not valid_end: view = Confirm(responders=[interaction.user]) question = await interaction.channel.send( 'It doesn\'t look like this game is over, yet. I can end it, but no rewards will be paid out.\n\n' 'Should I end this game?', view=view ) await view.wait() if view.value: patch_game(this_game.id, active=False) await question.edit( content='Roger dodger - it is game over.', view=None ) return else: await question.edit( content='It stays.', view=None ) return await response.edit(content=None, embed=await self.get_game_state_embed(this_game, full_length=False)) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.edit_original_response(content=f'Should I end this game?', view=view) await view.wait() if view.value: await question.edit(content='I\'ll tally the scorecard now...', view=None) else: await question.edit(content='It stays.', view=None) return away_team = await db_get('teams', object_id=this_game.away_team_id) home_team = await db_get('teams', object_id=this_game.home_team_id) away_stats = { # 'p_lines': get_pitching_stats(this_game.id, team_id=away_team['id']), 'p_lines': [], 'b_lines': get_batting_stats(this_game.id, team_id=away_team['id']), 'f_lines': get_fielding_stats(this_game.id, team_id=away_team['id']) } home_stats = { # 'p_lines': get_pitching_stats(this_game.id, team_id=home_team['id']), 'p_lines': [], 'b_lines': get_batting_stats(this_game.id, team_id=home_team['id']), 'f_lines': get_fielding_stats(this_game.id, team_id=home_team['id']), # 'score': away_stats['p_lines'][0]['tm_runs'] } logging.debug(f'away_stats: {away_stats}\n\nhome_stats: {home_stats}') away_pitchers = await get_team_lineups( this_game.id, team_id=away_team['id'], inc_inactive=True, pitchers_only=True, as_string=False ) for line in away_pitchers: try: # logging.info(f'line: {line}') this_stats = get_pitching_stats(this_game.id, lineup_id=line.id) # logging.info(f'away / this_stats: {this_stats}') away_stats['p_lines'].extend(this_stats) if 'score' not in home_stats: # logging.info(f'score not in home_stats') home_stats['score'] = this_stats[0]['pl_runs'] else: # logging.info(f'score is in home_stats') home_stats['score'] += this_stats[0]['pl_runs'] if 'hits' not in home_stats: # logging.info(f'hits not in home_stats') home_stats['hits'] = this_stats[0]['pl_hit'] else: # logging.info(f'hits is in home_stats') home_stats['hits'] += this_stats[0]['pl_hit'] except Exception as e: bad_player = await get_player(this_game, line) logging.error(f'Unable to process stats for card_id {line.card_id} in Game {this_game.id}: ' f'{type(e)}: {e}') await interaction.edit_original_response( content=f'I was not able to process stats for {bad_player["name"]} - ' f'{get_cal_user(interaction).mention} help!') home_pitchers = await get_team_lineups( this_game.id, team_id=home_team['id'], inc_inactive=True, pitchers_only=True, as_string=False ) for line in home_pitchers: try: # logging.info(f'line: {line}') this_stats = get_pitching_stats(this_game.id, lineup_id=line.id) # logging.info(f'home / this_stats: {this_stats}') home_stats['p_lines'].extend(this_stats) if 'score' not in away_stats: # logging.info(f'score not in away_stats') away_stats['score'] = this_stats[0]['pl_runs'] else: # logging.info(f'score is in away_stats') away_stats['score'] += this_stats[0]['pl_runs'] if 'hits' not in away_stats: # logging.info(f'hits not in away_stats') away_stats['hits'] = this_stats[0]['pl_hit'] else: # logging.info(f'hits is in away_stats') away_stats['hits'] += this_stats[0]['pl_hit'] except Exception as e: bad_player = await get_player(this_game, line) logging.error(f'Unable to process stats for card_id {line.card_id} in Game {this_game.id}: ' f'{type(e)}: {e}') await interaction.edit_original_response( content=f'I was not able to process stats for {bad_player["name"]} - ' f'{get_cal_user(interaction).mention} help!' ) logging.debug(f'finished tallying pitcher stats') # away_stats['score'] = home_stats['p_lines'][0]['tm_runs'] try: decisions = get_pitching_decisions(this_game) except AttributeError as e: logging.error(f'Could not pull decisions for Game {this_game.id}: {e}') await interaction.edit_original_response( content=f'I was not able to calculate the Winning and Losing Pitcher for this game. Is the game ' f'ending early? {get_cal_user(interaction).mention} can probably help.' ) return logging.debug(f'decisions: {decisions}') winning_team = away_team if away_stats['score'] > home_stats['score'] else home_team losing_team = away_team if away_stats['score'] < home_stats['score'] else home_team # Post Game Rewards r_data = await self.post_rewards(winning_team, losing_team, this_game) win_reward = r_data['win_string'] loss_reward = r_data['loss_string'] # Check for Gauntlet game so rewards go to main team if gauntlet_team is not None: if winning_team['gmid'] == gauntlet_team['gmid']: winning_team = owner_team else: losing_team = owner_team logging.debug(f'away_stats (in /endgame function)\n\n{away_stats}') logging.debug(f'home_stats (in /endgame function)\n\n{home_stats}') logging.debug(f'winning_team: {winning_team}\nlosing_team: {losing_team}') logging.debug(f'Time to build statlines and submit the scorecard for this PD game!') # Post result success = await db_post( 'results', payload={ 'away_team_id': this_game.away_team_id, 'home_team_id': this_game.home_team_id, 'away_score': away_stats['score'], 'home_score': home_stats['score'], 'away_team_ranking': away_team['ranking'], 'home_team_ranking': home_team['ranking'], 'scorecard': f'Bot Game {this_game.id}', 'week': this_game.week_num, 'season': this_game.season, 'ranked': this_game.ranked, 'short_game': this_game.short_game, 'game_type': this_game.game_type } ) # Submit the stats batter_stats = [] pitcher_stats = [] doubles = '' triples = '' homers = '' s_bases = '' caught_s = '' for line in [*away_stats['b_lines'], *home_stats['b_lines']]: if line['pl_double']: if len(doubles): doubles += ', ' card = await db_get("cards", object_id=line["card_id"]) doubles += f'{card["player"]["p_name"]}' \ f'{" " if line["pl_double"] > 1 else ""}' \ f'{line["pl_double"] if line["pl_double"] > 1 else ""}' if line['pl_triple']: if len(triples): triples += ', ' card = await db_get("cards", object_id=line["card_id"]) triples += f'{card["player"]["p_name"]}' \ f'{" " if line["pl_triple"] > 1 else ""}' \ f'{line["pl_triple"] if line["pl_triple"] > 1 else ""}' if line['pl_homerun']: if len(homers): homers += ', ' card = await db_get("cards", object_id=line["card_id"]) homers += f'{card["player"]["p_name"]}' \ f'{" " if line["pl_homerun"] > 1 else ""}' \ f'{line["pl_homerun"] if line["pl_homerun"] > 1 else ""}' if line['pl_sb']: if len(s_bases): s_bases += ', ' card = await db_get("cards", object_id=line["card_id"]) s_bases += f'{card["player"]["p_name"]}' \ f'{" " if line["pl_sb"] > 1 else ""}' \ f'{line["pl_sb"] if line["pl_sb"] > 1 else ""}' batter_stats.append( { 'card_id': line['card_id'], 'team_id': line['team_id'], 'roster_num': this_game.away_roster_num if this_game.away_team_id == line['team_id'] else this_game.home_roster_num, 'vs_team_id': home_team['id'] if this_game.away_team_id == line['team_id'] else away_team['id'], 'pos': line['pos'], 'pa': line['pl_pa'], 'ab': line['pl_ab'], 'run': line['pl_run'], 'rbi': line['pl_rbi'], 'hit': line['pl_hit'], 'double': line['pl_double'], 'triple': line['pl_triple'], 'hr': line['pl_homerun'], 'bb': line['pl_bb'], 'so': line['pl_so'], 'hbp': line['pl_hbp'], 'sac': line['pl_sac'], 'ibb': line['pl_ibb'], 'gidp': line['pl_gidp'], 'sb': line['pl_sb'], 'cs': line['pl_cs'], 'bphr': line['pl_bphr'], 'bpfo': line['pl_bpfo'], 'bp1b': line['pl_bp1b'], 'bplo': line['pl_bplo'], 'week': this_game.week_num, 'season': this_game.season, 'game_id': this_game.id, } ) for line in [*away_stats['f_lines'], *home_stats['f_lines']]: if line['pl_csc']: if len(caught_s): caught_s += ', ' card = await db_get("cards", object_id=line["card_id"]) caught_s += f'{card["player"]["p_name"]}' \ f'{" " if line["pl_csc"] > 1 else ""}' \ f'{line["pl_csc"] if line["pl_csc"] > 1 else ""}' batter_stats.append( { 'card_id': line['card_id'], 'team_id': line['team_id'], 'roster_num': this_game.away_roster_num if this_game.away_team_id == line['team_id'] else this_game.home_roster_num, 'vs_team_id': home_team['id'] if this_game.away_team_id == line['team_id'] else away_team['id'], 'pos': line['pos'], 'xch': line['pl_xch'], 'xhit': line['pl_xhit'], 'error': line['pl_error'], 'pb': line['pl_pb'], 'sbc': line['pl_sbc'], 'csc': line['pl_csc'], 'week': this_game.week_num, 'season': this_game.season, 'game_id': this_game.id } ) for line in [*away_stats['p_lines'], *home_stats['p_lines']]: pitcher_stats.append( { 'card_id': line['card_id'], 'team_id': line['team_id'], 'roster_num': this_game.away_roster_num if this_game.away_team_id == line['team_id'] else this_game.home_roster_num, 'vs_team_id': home_team['id'] if this_game.away_team_id == line['team_id'] else away_team['id'], 'ip': (math.floor(line['pl_outs'] / 3) * 1.0) + ((line['pl_outs'] % 3) / 3.0), 'hit': line['pl_hit'], 'run': line['pl_runs'], 'erun': line['pl_eruns'], 'so': line['pl_so'], 'bb': line['pl_bb'], 'hbp': line['pl_hbp'], 'wp': line['pl_wild_pitch'], 'balk': line['pl_balk'], 'hr': line['pl_homerun'], 'ir': 0, 'irs': 0, 'gs': 1 if line['card_id'] in decisions['starters'] else 0, 'win': 1 if line['card_id'] == decisions['winner'] else 0, 'loss': 1 if line['card_id'] == decisions['loser'] else 0, 'hold': 1 if line['card_id'] in decisions['holds'] else 0, 'sv': 1 if line['card_id'] == decisions['save'] else 0, 'bsv': 1 if line['card_id'] in decisions['b_save'] else 0, 'week': this_game.week_num, 'season': this_game.season, 'game_id': this_game.id } ) doubles += '\n' if len(doubles) else '' triples += '\n' if len(triples) else '' homers += '\n' if len(homers) else '' s_bases += '\n' if len(s_bases) else '' caught_s += '\n' if len(caught_s) else '' await db_post('batstats', payload={'stats': batter_stats}) await db_post('pitstats', payload={'stats': pitcher_stats}) # Post a notification to PD last_play = get_current_play(this_game.id) inning = f'{last_play.inning_num if last_play.inning_half == "Bot" else last_play.inning_num - 1}' embed = get_team_embed( f'{away_team["lname"]} {away_stats["score"]} @ {home_stats["score"]} {home_team["lname"]} - F/' f'{inning}', winning_team ) if this_game.game_type == 'major-league': game_des = 'Major League' elif this_game.game_type == 'minor-league': game_des = 'Minor League' elif this_game.ranked: game_des = 'Ranked' elif 'gauntlet' in this_game.game_type: game_des = 'Gauntlet' else: game_des = 'Unlimited' embed.description = f'Score Report - {game_des} ' \ f'{"- 3-Inning Game" if this_game.short_game else " - 9-Inning Game"}' embed.add_field( name='Box Score', value=get_final_scorebug(away_team, home_team, away_stats, home_stats), inline=False ) embed.add_field( name='Location', value=f'{interaction.guild.get_channel(this_game.channel_id).mention}' ) wc_query = await db_get("cards", object_id=decisions["winner"]) lc_query = await db_get("cards", object_id=decisions["loser"]) if decisions["save"]: sv_query = await db_get("cards", object_id=decisions["save"]) embed.add_field( name='Pitching', value=f'Win: {wc_query["player"]["p_name"]}\n' f'Loss: {lc_query["player"]["p_name"]}\n' f'{"Save: " if decisions["save"] else ""}' f'{sv_query["player"]["p_name"] if decisions["save"] else ""}', inline=False ) if len(doubles) + len(triples) + len(homers) > 0: embed.add_field( name='Batting', value=f'{"2B: " if len(doubles) else ""}{doubles if len(doubles) else ""}' f'{"3B: " if len(triples) else ""}{triples if len(triples) else ""}' f'{"HR: " if len(homers) else ""}{homers if len(homers) else ""}', inline=False ) if len(s_bases) + len(caught_s) > 0: embed.add_field( name='Baserunning', value=f'{"SB: " if len(s_bases) else ""}{s_bases if len(s_bases) else ""}' f'{"CSc: " if len(caught_s) else ""}{caught_s if len(caught_s) else ""}', inline=False ) embed.add_field( name=f'{winning_team["abbrev"]} Rewards', value=win_reward ) embed.add_field( name=f'{losing_team["abbrev"]} Rewards', value=loss_reward ) embed.add_field( name='Highlights', value=f'Please share the highlights in {get_channel(interaction, "pd-news-ticker").mention}!', inline=False ) await send_to_channel(self.bot, 'pd-network-news', embed=embed) # Gauntlet results and reward if gauntlet_team is not None: await gauntlets.post_result( int(this_game.game_type.split('-')[3]), is_win=winning_team['gmid'] == gauntlet_team['gmid'], this_team=gauntlet_team, bot=self.bot, channel=interaction.channel ) this_run = await db_get('gauntletruns', object_id=int(this_game.game_type.split('-')[3])) if this_run['losses'] == 2: await send_to_channel( bot=self.bot, channel_name='pd-network-news', content=f'The {gauntlet_team["lname"]} won {this_run["wins"]} games before failing in the ' f'{this_run["gauntlet"]["name"]} gauntlet.', embed=None ) patch_game(this_game.id, active=False) logging.info(f'Game {this_game.id} is complete') if gauntlet_team is None: await interaction.edit_original_response( content=f'Good game! Go share the highlights in ' f'{get_channel(interaction, "pd-news-ticker").mention}!' ) @app_commands.command( name='readlineup', description='Import a saved lineup directly from the team sheet for PD games' ) @app_commands.describe( team_abbrev='The 2- to 4-digit abbreviation for the team being set', roster='Which roster to pull from your sheet?', lineup='Which handedness lineup are you using?', sp_card_id='Light gray number to the left of the pitcher\'s name on your depth chart' ) @app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME) async def read_lineup_command( self, interaction: discord.Interaction, team_abbrev: str, roster: Literal['Primary', 'Secondary', 'Ranked'], lineup: Literal['v Right', 'v Left'], sp_card_id: int): 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 if not this_game.is_pd: await interaction.response.send_message(f'Lineup imports are only supported for PD games right now.' f'Please use the `/setlineup` command.') return owner_team = await get_game_team(this_game, interaction.user.id) if 'gauntlet' in this_game.game_type: lineup_team = await get_game_team(this_game, team_abbrev=f'Gauntlet-{owner_team["abbrev"]}') else: lineup_team = await get_game_team(this_game, team_abbrev=team_abbrev) if owner_team['gmid'] != lineup_team['gmid']: 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 try again' ) return existing_lineups = await get_team_lineups(this_game.id, lineup_team['id']) if 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.send_message(f'Let\'s put this lineup card together...') if lineup == 'v Right': l_num = 1 else: l_num = 2 if roster == 'Primary': roster_num = 1 elif roster == 'Secondary': roster_num = 2 else: roster_num = 3 lineup_cells = get_roster_lineups(lineup_team, self.bot, roster_num, l_num) all_lineups = [] all_pos = [] card_ids = [] for index, row in enumerate(lineup_cells): if '' in row: break if row[0].upper() not in all_pos: all_pos.append(row[0].upper()) else: raise SyntaxError(f'You have more than one {row[0].upper()} in this lineup. Please ' f'update and set the lineup again.') this_card = await db_get(f'cards', object_id=int(row[1])) if this_card['team']['id'] != lineup_team['id']: raise SyntaxError(f'Easy there, champ. Looks like card ID {row[1]} belongs to the ' f'{this_card["team"]["sname"]}. Try again with only cards you own.') player_id = this_card['player']['player_id'] card_id = row[1] card_ids.append(str(card_id)) this_lineup = { 'game_id': this_game.id, 'team_id': lineup_team['id'], 'player_id': player_id, 'card_id': card_id, 'position': row[0].upper(), 'batting_order': index + 1, 'after_play': 0 } all_lineups.append(this_lineup) if lineup_team['id'] == this_game.away_team_id: patch_game(this_game.id, away_roster_num=roster_num) else: patch_game(this_game.id, home_roster_num=roster_num) if lineup_team['is_ai']: starter = starting_pitcher(lineup_team, self.bot, True if this_game.ai_team == 'home' else False) this_card = await db_get(f'cards', object_id=int(starter)) all_lineups.append({ 'game_id': this_game.id, 'team_id': lineup_team['id'], 'player_id': this_card['player']['player_id'], 'card_id': starter, 'position': 'P', 'batting_order': 10, 'after_play': 0 }) all_pos.append('P') else: this_card = await db_get(f'cards', object_id=sp_card_id) logging.debug(f'this_card: {this_card}') if this_card['team']['id'] != lineup_team['id']: logging.error( f'Card_id {sp_card_id} does not belong to {lineup_team["abbrev"]} in Game {this_game.id}' ) await interaction.channel.send( f'Uh oh. Card ID {sp_card_id} is {this_card["player"]["p_name"]} and belongs to ' f'{this_card["team"]["sname"]}. Will you double check that before we get started?') return else: card_ids.append(str(sp_card_id)) all_lineups.append({ 'game_id': this_game.id, 'team_id': lineup_team['id'], 'player_id': this_card['player']['player_id'], 'card_id': sp_card_id, 'position': 'P', 'batting_order': 10, 'after_play': 0 }) all_pos.append('P') # Check roster legality if this_game.game_type in ['major-league', 'hall-of-fame']: l_string = "&card_id=".join(card_ids) legality = await db_post(f'cards/legal-check/ranked?card_id={l_string}') if legality['count'] > 0: il_string = "\n- ".join(legality['bad_cards']) await interaction.edit_original_response( content=f'It looks like this is a Ranked Legal game and I see the following cards as illegal. ' f'Please adjust your lineup and re-submit!\n\n' f'- {il_string}' ) return logging.debug(f'Setting lineup for {lineup_team["sname"]} in PD game') post_lineups(all_lineups) # while True: # # Get Starting Pitcher # this_q = Question( # self.bot, interaction.channel, f'Please enter the card_id of the {lineup_team["sname"]} SP', # 'int', 45 # ) # resp = await this_q.ask([interaction.user]) # # if not resp: # await interaction.edit_original_response( # content='Please enter the Starting Pitcher using the `/substitution` command' # ) # return # else: if 'P' not in all_pos: await interaction.channel.send( content=f'Please enter the Starting pitcher using the `/starting-pitcher` command' ) return try: await interaction.channel.send(content=None, embed=await self.get_game_state_embed(this_game)) except IntegrityError as e: logging.debug(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.command(name='get-bullpen', help='Mod: Sync an AI bullpen') @commands.is_owner() async def get_bullpen_command(self, ctx: commands.Context, team_id): team = await db_get('teams', object_id=team_id) if not team: await ctx.send(f'I did not find a team with id {team_id}') return await ctx.send(f'I\'m getting bullpen data from {team["gmname"]}...') get_or_create_bullpen(team, self.bot) await ctx.send(f'Got it!') @app_commands.command( name='substitution', description='Make a lineup substitution; Player Name for SBa games / Card ID for PD games' ) @app_commands.describe( order_number='Batting order of new player; 10 for pitchers if there is a DH', new_player='For PD game: enter the Card ID (a number); or SBa game: enter the Player Name') @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) async def substitution_command( self, interaction: discord.Interaction, team_abbrev: str, new_player: str, order_number: Literal[ 'this-spot', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], new_pos: Literal['P', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF', 'DH', 'PH', 'PR']): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return owner_team = await get_game_team(this_game, interaction.user.id) if 'gauntlet' in this_game.game_type: lineup_team = await get_game_team(this_game, team_abbrev=f'Gauntlet-{owner_team["abbrev"]}') else: lineup_team = await get_game_team(this_game, team_abbrev=team_abbrev) if owner_team['gmid'] != lineup_team['gmid']: await interaction.edit_original_response(content='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.edit_original_response( content=f'I do not see {lineup_team["sname"]} in this game. Please check the team abbrev and try again' ) return try: if this_game.is_pd: this_card = await db_get(f'cards', object_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']['player_id'] card_id = new_player else: this_player = get_sba_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 except Exception as e: logging.error(f'Game {this_game} - Could not find player {new_player}') if this_game.is_pd: await interaction.edit_original_response( content=f'I could not find card_id {new_player}.' ) else: await interaction.edit_original_response( content=f'I could not find {new_player.title()}.' ) return if this_game.game_type in ['major-league', 'hall-of-fame']: legality = await db_post(f'cards/legal-check/ranked?card_id={new_player}') if legality['count'] > 0: il_string = "\n- ".join(legality['bad_cards']) await interaction.edit_original_response( content=f'It looks like this is a Ranked Legal game and I see the following cards as illegal. ' f'Please adjust your lineup and re-submit!\n\n' f'- {il_string}' ) return this_play = get_current_play(this_game.id) batting_order = int(order_number) if order_number != 'this-spot' else this_play.batting_order # Check for simple position change in_lineup = get_one_lineup(this_game.id, team_id=lineup_team['id'], active=True, batting_order=batting_order) if in_lineup.card_id == int(card_id): post_pos = patch_lineup(in_lineup.id, position=new_pos) else: 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.upper(), 'batting_order': batting_order, 'after_play': curr_play.play_num - 1 if curr_play else 0 } make_sub(this_lineup) await interaction.edit_original_response( 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 response = await ctx.send(f'Building the scorebug now...') await response.edit(content=None, embed=await self.get_game_state_embed(this_game, full_length=include_lineups)) async def checks_log_interaction(self, interaction: discord.Interaction, block_rollback: Optional[bool] = False) \ -> (Optional[StratGame], Optional[dict], Optional[StratPlay]): await interaction.response.defer() this_game = get_one_game(channel_id=interaction.channel.id, active=True) if not this_game: await interaction.edit_original_response(content='I don\'t see a game in this channel.') return False, False, False owner_team = await get_game_team(this_game, interaction.user.id) if 'gauntlet' in this_game.game_type: owner_team = await get_game_team(this_game, team_abbrev=f'Gauntlet-{owner_team["abbrev"]}') if not owner_team['id'] in [this_game.away_team_id, this_game.home_team_id]: logging.info(f'{interaction.user.display_name} tried to run a command in Game {this_game.id} when they ' f'aren\'t a GM in the game.') await interaction.edit_original_response(content='Bruh. Only GMs of the active teams can log plays.') # return this_game, False, False this_play = get_current_play(this_game.id) # Allow specific commands to not rollback the play (e.g. /show-card) if this_play.locked and not block_rollback: logging.info(f'{interaction.user.display_name} tried to run a command in Game {this_game.id} while the ' f'play was locked.') undo_play(this_play.id) await interaction.edit_original_response( content=f'Looks like your game got hung up there for a second. I just rolled it back a play to ' f'release it. If you have any more issues, let Cal know.' ) this_play = get_current_play(this_game.id) if not this_play.pitcher: logging.info(f'{interaction.user.display_name} tried to run a command in Game {this_game.id} without a ' f'pitcher in the lineup.') await interaction.edit_original_response(content=f'Please sub in a pitcher before logging a new play.') this_play = None return this_game, owner_team, this_play group_log = app_commands.Group(name='log', description='Log a play in this channel\'s game') @group_log.command(name='ai-pitcher-sub', description='Run automated substitution when the AI wants a change') async def ai_pitcher_sub(self, interaction: discord.Interaction): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return away_team = await get_game_team(this_game, team_id=this_game.away_team_id) home_team = await get_game_team(this_game, team_id=this_game.home_team_id) ai_team = away_team if owner_team == home_team else home_team if this_play.batter.team_id == ai_team['id']: await interaction.edit_original_response( content=f'It looks like the {ai_team["sname"]} are batting now. If they need a sub, please do so when ' f'they are back on defense.' ) return curr_pitcher = get_one_lineup(this_game.id, team_id=ai_team['id'], position='P') pit_plays = get_plays(this_game.id, curr_pitcher.id) logging.debug(f'pit_plays for sub in Game {this_game.id}: {pit_plays}') if pit_plays['count'] < 2: pitcher = await get_player(this_game, curr_pitcher) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'It doesn\'t look like {pitcher["name"]} is fatigued - are you sure they should be subbed out?', view=view ) await view.wait() if view.value: await question.delete() await interaction.channel.send( content=f'Thanks! I let {get_cal_user(interaction).mention} know there was an issue with ' f'{ai_team["abbrev"]} pitchers.' ) else: await question.delete() await interaction.edit_original_response(content=f'Okay, I\'ll leave them in!') return # new_pitcher = await next_pitcher(this_play, ai_team, self.bot) # logging.debug(f'new_pitcher: {new_pitcher}') # # this_lineup = { # 'game_id': this_game.id, # 'team_id': ai_team['id'], # 'player_id': new_pitcher['player']['player_id'], # 'card_id': new_pitcher['card_id'], # 'position': 'P', # 'batting_order': 10, # 'after_play': this_play.play_num - 1 # } # make_sub(this_lineup) used_pitchers = await get_team_lineups( game_id=this_play.game.id, team_id=ai_team['id'], inc_inactive=True, as_string=False ) make_sub(await ai_manager.get_relief_pitcher(this_play, ai_team, this_game.game_type)) await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game) ) @group_log.command(name='flyball', description='Flyballs: a, b, ballpark, bq, c') async def log_flyball( self, interaction: discord.Interaction, flyball_type: Literal['a', 'b', 'ballpark', 'b?', 'c']): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) await self.flyballs(interaction, this_game, this_play, flyball_type) await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='groundball', description='Groundballs: a, b, c') async def log_groundball(self, interaction: discord.Interaction, groundball_type: Literal['a', 'b', 'c']): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) await self.groundballs(interaction, this_game, this_play, groundball_type) await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='single', description='Singles: *, **, ballpark, uncapped') async def log_single( self, interaction: discord.Interaction, single_type: Literal['*', '**', 'ballpark', 'uncapped']): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) if single_type == '**': single_wellhit(this_play) elif single_type == '*': single_onestar(this_play) elif single_type == 'ballpark': 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) elif single_type == 'uncapped': 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 if this_play.on_base_code in [1, 2, 4, 5, 6, 7]: ai_manager = get_manager(this_game) ai_is_batting = ai_batting(this_game, this_play) to_bases = [None, None, 'to second', 'to third', 'home'] at_bases = [None, None, 'at second', 'at third', 'at home'] if this_play.on_second: lead_runner = await get_player(this_game, this_play.on_second) lead_base = 4 if this_play.on_first: trail_runner = await get_player(this_game, this_play.on_first) trail_base = 3 else: trail_runner = await get_player(this_game, this_play.batter) trail_base = 2 else: lead_runner = await get_player(this_game, this_play.on_first) lead_base = 3 trail_runner = await get_player(this_game, this_play.batter) trail_base = 2 ai_hint = '' if this_game.ai_team and ai_is_batting: ai_hint = f'*The runner will ' \ f'{ai_manager.uncapped_advance(lead_base, this_play.starting_outs)}*' view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Is {lead_runner["name"]} being sent {to_bases[lead_base]}?\n\n{ai_hint}', view=view ) await view.wait() if view.value: await question.delete() ai_hint = '' if this_game.ai_team and not ai_is_batting: ai_hint = f'*The defense will ' \ f'{ai_manager.throw_lead_runner(lead_base, this_play.starting_outs)}*' view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Is the defense throwing {to_bases[lead_base]}?\n\n{ai_hint}', view=view ) await view.wait() if view.value: await question.delete() ai_hint = '' if this_game.ai_team and ai_is_batting: ai_hint = f'*The runner will ' \ f'{ai_manager.trail_advance(trail_base, this_play.starting_outs, True if lead_base == 4 else False)}*' view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Is {trail_runner["name"]} being sent {to_bases[trail_base]}?\n\n{ai_hint}', view=view ) await view.wait() if view.value: await question.delete() ai_hint = '' if this_game.ai_team and not ai_is_batting: ai_hint = f'*The defense will ' \ f'{ai_manager.throw_which_runner(lead_base, this_play.starting_outs)}*' view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') view.confirm.label = 'Home Plate' if lead_base == 4 else 'Third Base' view.cancel.label = 'Third Base' if trail_base == 3 else 'Second Base' question = await interaction.channel.send( f'Is the throw going {to_bases[lead_base]} or {to_bases[trail_base]}?\n\n{ai_hint}', view=view ) await view.wait() # Throw to lead runner if view.value: await question.delete() view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( content=f'Was {lead_runner["name"]} thrown out {at_bases[lead_base]}?', view=view ) await view.wait() if view.value: await question.delete() advance_runners(this_play.id, 2) batter_to_base = 2 if lead_base == 4: patch_play(this_play.id, on_second_final=False, outs=1) else: patch_play(this_play.id, on_first_final=False, outs=1) else: await question.delete() advance_runners(this_play.id, 2) batter_to_base = 2 # Throw to trail runner else: await question.delete() view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( content=f'Was {trail_runner["name"]} thrown out {at_bases[trail_base]}?', view=view ) await view.wait() if view.value: await question.delete() advance_runners(this_play.id, 2) if trail_base == 3: patch_play(this_play.id, on_first_final=False, outs=1) batter_to_base = 2 else: patch_play(this_play.id, outs=1) batter_to_base = None else: await question.delete() advance_runners(this_play.id, 2) batter_to_base = 2 else: await question.delete() view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( content=f'Was {lead_runner["name"]} thrown out {at_bases[lead_base]}?', view=view ) await view.wait() if view.value: await question.delete() if lead_base == 4: patch_play(this_play.id, on_second_final=False, outs=1) else: patch_play(this_play.id, on_first_final=False, outs=1) else: await question.delete() advance_one_runner(this_play.id, from_base=lead_base - 2, num_bases=2) else: await question.delete() advance_one_runner(this_play.id, from_base=lead_base - 2, num_bases=2) else: await question.delete() logging.debug(f'post process batter runner on_second_final: {this_play.on_second_final}') complete_play(this_play.id, batter_to_base=batter_to_base) await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='frame-pitch', description=f'Walk/strikeout split; determined by home plate umpire') async def log_frame_check(self, interaction: discord.Interaction): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) frame_result = dice.frame_plate_check(owner_team, this_game.id) logging.info(f'Frame Result:\n{frame_result}') await interaction.edit_original_response( content=None, embed=frame_result['embed'] ) if frame_result['is_walk']: advance_runners(this_play.id, num_bases=1, only_forced=True) patch_play(this_play.id, pa=1, walk=1) else: patch_play(this_play.id, pa=1, ab=1, outs=1, so=1) advance_runners(this_play.id, 0) complete_play(this_play.id, batter_to_base=1) await interaction.channel.send( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='double', description='Doubles: **, ***, uncapped') async def log_double(self, interaction: discord.Interaction, double_type: Literal['**', '***', 'uncapped']): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) if double_type == '**': double_twostar(this_play) elif double_type == '***': double_threestar(this_play) elif double_type == 'uncapped': advance_runners(this_play.id, 2) this_play = patch_play(this_play.id, locked=True, pa=1, ab=1, hit=1, double=1) batter_to_base = 2 ai_is_batting = ai_batting(this_game, this_play) ai_manager = get_manager(this_game) if this_play.on_first: ai_hint = '' if this_game.ai_team and ai_is_batting: if this_play.on_first: ai_hint = f'*The runner will attempt to advance*' else: ai_hint = f'*The runner will ' \ f'{ai_manager.uncapped_advance(4, this_play.starting_outs)}*' runner_to_home = await get_player(this_game, this_play.on_first) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Is {runner_to_home["name"]} being sent home?\n\n{ai_hint}', view=view ) await view.wait() if view.value: await question.delete() ai_hint = '' if this_game.ai_team and not ai_is_batting: ai_hint = f'*The defense will {ai_manager.throw_lead_runner(4, this_play.starting_outs)}*' # Throw for lead runner? view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Is the defense throwing home?\n\n{ai_hint}', view=view ) await view.wait() # Yes: Send Trail runner? if view.value: await question.delete() ai_hint = '' if this_game.ai_team and ai_is_batting: ai_hint = f'*The trail runner will ' \ f'{ai_manager.trail_advance(3, this_play.starting_outs, True)}*' batter_runner = await get_player(this_game, this_play.batter) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Is {batter_runner["name"]} being sent to third?\n\n{ai_hint}', view=view ) await view.wait() # Yes: Throw lead or trail? if view.value: await question.delete() ai_hint = '' if this_game.ai_team and not ai_is_batting: ai_hint = f'*The defense will ' \ f'{ai_manager.throw_which_runner(4, this_play.starting_outs)}*' view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') view.confirm.label = 'Home Plate' view.cancel.label = 'Third Base' question = await interaction.channel.send( f'Is the throw going to home plate or to third base?\n\n{ai_hint}', view=view ) await view.wait() # Throw home if view.value: batter_to_base = 3 await question.delete() view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( content=f'Was {runner_to_home["name"]} thrown out at the plate?', view=view ) await view.wait() # Out at the plate if view.value: await question.delete() patch_play(this_play.id, on_first_final=False, outs=1) # Safe at the plate else: await question.delete() advance_runners(this_play.id, 3) # Throw to third else: await question.delete() advance_runners(this_play.id, num_bases=3) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( content=f'Was {batter_runner["name"]} thrown out at third?', view=view ) await view.wait() # Out at third if view.value: await question.delete() batter_to_base = None advance_runners(this_play.id, num_bases=3) patch_play(this_play.id, outs=1) # Safe at home and third else: await question.delete() # No: Safe at home? else: await question.delete() view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( content=f'Was {runner_to_home["name"]} thrown out at the plate?', view=view ) await view.wait() # Out at the plate if view.value: await question.delete() patch_play(this_play.id, on_first_final=False, outs=1) # Safe at the plate else: await question.delete() advance_runners(this_play.id, num_bases=3) # No: End of play else: await question.delete() advance_runners(this_play.id, num_bases=3) else: await question.delete() complete_play(this_play.id, batter_to_base=batter_to_base) if double_type == 'uncapped': await interaction.channel.send( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) else: await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='triple', description='Triples: no sub-types') async def log_triple(self, interaction: discord.Interaction): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) triple(this_play) await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='homerun', description='Home Runs: ballpark, no-doubt') async def log_homerun(self, interaction: discord.Interaction, homerun_type: Literal['ballpark', 'no-doubt']): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) 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) if homerun_type == 'ballpark': patch_play(this_play.id, pa=1, ab=1, hit=1, homerun=1, bphr=1) elif homerun_type == 'no-doubt': patch_play(this_play.id, pa=1, ab=1, hit=1, homerun=1) complete_play(this_play.id, batter_to_base=4) await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='walk', description='Walks: unintentional, intentional') async def log_walk(self, interaction: discord.Interaction, walk_type: Literal['unintentional', 'intentional']): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) 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) if walk_type == 'unintentional': patch_play(this_play.id, pa=1, walk=1) elif walk_type == 'intentional': patch_play(this_play.id, pa=1, ibb=1) complete_play(this_play.id, batter_to_base=1) await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='chaos', description='Chaos: wild-pitch, passed-ball, balk, pickoff') async def log_chaos(self, interaction: discord.Interaction, chaos_type: Literal['wild-pitch', 'passed-ball', 'balk', 'pickoff']): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) advance_runners(this_play.id, 0) if chaos_type == 'wild-pitch': advance_runners(this_play.id, 1) patch_play(this_play.id, rbi=0, wp=1) elif chaos_type == 'passed-ball': advance_runners(this_play.id, 1) patch_play(this_play.id, rbi=0, pb=1) elif chaos_type == 'balk': advance_runners(this_play.id, 1) patch_play(this_play.id, rbi=0, balk=1) elif chaos_type == 'pickoff': # Get from base runner_flag = False patch_play(this_play.id, pick=1) if this_play.on_base_code in [1, 2, 3]: if this_play.on_first: patch_play(this_play.id, on_first_final=False, runner_id=this_play.on_first.id, outs=1) elif this_play.on_second: patch_play(this_play.id, on_second_final=False, runner_id=this_play.on_second.id, outs=1) elif this_play.on_third: patch_play(this_play.id, on_third_final=False, runner_id=this_play.on_third.id, outs=1) else: if this_play.on_first: this_runner = await get_player(this_game, this_play.on_first) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send(f'Was {this_runner["name"]} picked off?', view=view) await view.wait() if view.value: patch_play(this_play.id, on_first_final=False, runner_id=this_play.on_first.id, outs=1) runner_flag = True await question.delete() if this_play.on_second and not runner_flag: this_runner = await get_player(this_game, this_play.on_second) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send(f'Was {this_runner["name"]} picked off?', view=view) await view.wait() if view.value: patch_play(this_play.id, on_second_final=False, runner_id=this_play.on_second.id, outs=1) runner_flag = True await question.delete() if this_play.on_third and not runner_flag: this_runner = await get_player(this_game, this_play.on_third) view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send(f'Was {this_runner["name"]} picked off?', view=view) await view.wait() if view.value: patch_play(this_play.id, on_third_final=False, runner_id=this_play.on_third.id, outs=1) runner_flag = True await question.delete() if not runner_flag: await interaction.edit_original_response(content=f'So I guess nobody got picked off?') patch_play(this_play.id, locked=False) return complete_play(this_play.id) await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='stealing', description='Running: stolen-base, caught-stealing') @app_commands.describe(to_base='Base the runner is advancing to; 2 for 2nd, 3 for 3rd, 4 for Home') async def log_stealing( self, interaction: discord.Interaction, running_type: Literal['stolen-base', 'caught-stealing', 'steal-plus-overthrow'], to_base: Literal[2, 3, 4]): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) 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' ) advance_runners(this_play.id, 0) if running_type == 'stolen-base': if to_base == 'Home' 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 to_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 interaction.edit_original_response( content=f'Ope. Looks like {this_runner["name"]} is blocked by the runner at third.' ) patch_play(this_play.id, locked=False) return if this_play.on_first: advance_one_runner( this_play.id, from_base=1, num_bases=1 ) elif to_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 interaction.edit_original_response( content=f'Ope. Looks like {this_runner["name"]} is blocked by the runner at second.' ) patch_play(this_play.id, locked=False) return else: await interaction.edit_original_response( content=f'Uh oh - I don\'t see a runner there to steal the bag.' ) patch_play(this_play.id, locked=False) return if running_type == 'steal-plus-overthrow': if to_base == 'Home' 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 to_base == 3 and this_play.on_second: advance_runners(this_play.id, 1) if not this_play.on_third: patch_play( this_play.id, sb=1, on_second_final=4, runner_id=this_play.on_second.id, catcher_id=catcher.id ) else: this_runner = await get_player(this_game, this_play.on_second) await interaction.edit_original_response( content=f'Ope. Looks like {this_runner["name"]} is blocked by the runner at third.' ) patch_play(this_play.id, locked=False) return elif to_base == 2 and this_play.on_first: if not this_play.on_second: advance_runners(this_play.id, 1) patch_play( this_play.id, sb=1, on_first_final=3, runner_id=this_play.on_first.id, catcher_id=catcher.id ) else: this_runner = await get_player(this_game, this_play.on_first) await interaction.edit_original_response( content=f'Ope. Looks like {this_runner["name"]} is blocked by the runner at second.' ) patch_play(this_play.id, locked=False) return else: await interaction.edit_original_response( content=f'Uh oh - I don\'t see a runner there to steal the bag.' ) patch_play(this_play.id, locked=False) return patch_play(this_play.id, error=1) elif running_type == 'caught-stealing': if to_base == 'Home' and this_play.on_third: patch_play( this_play.id, cs=1, on_third_final=False, runner_id=this_play.on_third.id, catcher_id=catcher.id, outs=1 ) elif to_base == 3 and this_play.on_second: if not this_play.on_third: patch_play( this_play.id, cs=1, on_second_final=False, 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 interaction.edit_original_response( content=f'Ope. Looks like {this_runner["name"]} is blocked by the runner at third.' ) patch_play(this_play.id, locked=False) return elif to_base == 2 and this_play.on_first: if not this_play.on_second: patch_play( this_play.id, cs=1, on_first_final=False, 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 interaction.edit_original_response( content=f'Ope. Looks like {this_runner["name"]} is blocked by the runner at second.' ) patch_play(this_play.id, locked=False) return else: await interaction.edit_original_response( content=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 interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='strikeout', description='Strikeout') async def log_strikeout(self, interaction: discord.Interaction): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) patch_play(this_play.id, pa=1, ab=1, outs=1, so=1) advance_runners(this_play.id, 0) complete_play(this_play.id) await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='popout', description='Popout') async def log_popout(self, interaction: discord.Interaction): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) patch_play(this_play.id, pa=1, ab=1, outs=1) advance_runners(this_play.id, 0) complete_play(this_play.id) await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='lineout', description='Lineouts: one out, ballpark, max outs') async def log_lineout( self, interaction: discord.Interaction, lineout_type: Literal['one-out', 'ballpark', 'max-outs']): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=True) advance_runners(this_play.id, 0) if lineout_type == 'one-out' or this_play.starting_outs == 2 or this_play.on_base_code == 0: patch_play(this_play.id, pa=1, ab=1, outs=1) elif lineout_type == 'ballpark': patch_play(this_play.id, pa=1, ab=1, outs=1, bplo=1) elif lineout_type == 'max-outs': bases = ['third', 'second', 'first'] num_outs = 1 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=[interaction.user], timeout=60, label_type='yes') question = await interaction.channel.send( f'Is {runner["name"]} out at {bases[count]} on the play?', 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) patch_play(this_play.id, pa=1, ab=1, outs=num_outs) complete_play(this_play.id) await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='hit-by-pitch', description='Batter to first; runners advance if forced') async def log_hit_by_pitch_command(self, interaction: discord.Interaction): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=False) 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 interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='sac-bunt', description='Batter out; runners advance one base') async def log_sac_bunt_command(self, interaction: discord.Interaction): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=False) 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 interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='undo-play', description='Remove the most recent play from the log') async def log_undo_play_command(self, interaction: discord.Interaction): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=False) try: logging.debug(f'Undoing play {this_play.id} in Game {this_game.id}: Batter {this_play.batter}') undo_play(this_game.id) except AttributeError as e: logging.error(f'Could not undo play {this_play.id} in game {this_game.id}') try: last_completed = get_latest_play(this_game.id) logging.debug(f'Undoing play {last_completed.id} in Game {this_game.id}') undo_play(this_game.id) except AttributeError as e: logging.error(f'Could not undo second play in game {this_game.id}') latest_play = get_latest_play(this_game.id) logging.debug(f'Latest completed play is Play {latest_play.id}; batter_to_base: {latest_play.batter_final}') undo_subs(this_game, latest_play.play_num) await interaction.edit_original_response( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) @group_log.command(name='xcheck', description='Defender makes an x-check') @app_commands.choices(position=[ Choice(name='Pitcher', value='P'), Choice(name='Catcher', value='C'), Choice(name='First Base', value='1B'), Choice(name='Second Base', value='2B'), Choice(name='Third Base', value='3B'), Choice(name='Shortstop', value='SS'), Choice(name='Left Field', value='LF'), Choice(name='Center Field', value='CF'), Choice(name='Right Field', value='RF'), ]) async def log_xcheck_command(self, interaction: discord.Interaction, position: Choice[str]): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return patch_play(this_play.id, locked=False) def_team_id = this_game.away_team_id if this_play.inning_half == 'Bot' else this_game.home_team_id def_team = await db_get('teams', object_id=def_team_id) d_lineup = get_one_lineup(this_game.id, team_id=this_play.pitcher.team_id, position=position.value) defender = await get_player(this_game, d_lineup) patch_play(this_play.id, defender_id=d_lineup.id, check_pos=position.value) defender_embed = get_team_embed(f'{def_team["sname"]} {position.value}', def_team) defender_embed.description = f'{defender["name"]}' defender_embed.set_image(url=defender['image']) embeds = [defender_embed] dice_embeds = sa_fielding_roll(position.value, def_team) embeds.extend(dice_embeds) all_embeds = [x for x in embeds if x is not None] await interaction.edit_original_response( content=None, embeds=all_embeds ) hit_allowed, error_allowed, chaos_allowed = None, None, None view = Confirm([interaction.user], label_type='yes') question = await interaction.channel.send(f'Did {defender["name"]} give up a hit?', view=view) await view.wait() if view.value: await question.delete() view = ButtonOptions( responders=[interaction.user], labels=['single*', 'single**', 'double**', 'double***', 'triple'] ) question = await interaction.channel.send(f'Which hit did {defender["name"]} allow?', view=view) await view.wait() if not view.value: await question.edit(f'Hmm...you keep thinking on it and get back to me when you\'re ready.') return else: await question.delete() hit_allowed = view.value else: await question.delete() hit_allowed = 'out' view = Confirm([interaction.user], label_type='yes') question = await interaction.channel.send(f'Did {defender["name"]} give up an error?', view=view) await view.wait() if view.value: await question.delete() view = ButtonOptions( responders=[interaction.user], labels=['1 base', '2 bases', '3 bases', None, None] ) question = await interaction.channel.send(f'How many bases did {defender["name"]} allow?', view=view) await view.wait() if not view.value: await question.edit(f'Hmm...you keep thinking on it and get back to me when you\'re ready.') return else: await question.delete() error_allowed = view.value else: await question.delete() error_allowed = 'no error' # Not hit and no error if hit_allowed == 'out' and error_allowed == 'no error': if this_play.on_base_code == 0: patch_play(this_play.id, pa=1, ab=1, outs=1) advance_runners(this_play.id, 0) complete_play(this_play.id) else: if position.value not in ['LF', 'CF', 'RF']: view = ButtonOptions( responders=[interaction.user], labels=['gb A', 'gb B', 'gb C', None if position.value != 'C' else 'FO', None if position.value != 'C' else 'PO'] ) question = await interaction.channel.send(f'What was the result of the play?', view=view) await view.wait() if not view.value: await question.delete() if view.value == 'gb A': await self.groundballs(interaction, this_game, this_play, groundball_type='a') elif view.value == 'gb B': await self.groundballs(interaction, this_game, this_play, groundball_type='b') elif view.value == 'gb C': await self.groundballs(interaction, this_game, this_play, groundball_type='c') else: patch_play(this_play.id, pa=1, ab=1, outs=1) advance_runners(this_play.id, 0) complete_play(this_play.id) patch_play(this_play.id, locked=False) return else: await question.delete() if view.value in ['FO', 'PO']: patch_play(this_play.id, pa=1, ab=1, outs=1) advance_runners(this_play.id, 0) complete_play(this_play.id) else: if view.value == 'gb A': gb_code = 'a' elif view.value == 'gb B': gb_code = 'b' else: gb_code = 'c' await self.groundballs(interaction, this_game, this_play, gb_code) else: view = ButtonOptions(responders=[interaction.user], labels=['fly A', 'fly B', 'fly C', None, None]) question = await interaction.channel.send(f'What was the result of the play?', view=view) await view.wait() if not view.value: await question.delete() await interaction.channel.send( content=f'Just logged the x-check! Please log the resulting play to continue (e.g. ' f'\'flyball-b\' or \'flyball-c\')' ) patch_play(this_play.id, locked=False) return else: await question.delete() if view.value == 'fly A': fly_code = 'a' elif view.value == 'fly B': fly_code = 'b' else: fly_code = 'c' await self.flyballs(interaction, this_game, this_play, fly_code) # Hit and error elif hit_allowed != 'out' and error_allowed != 'no error': patch_play(this_play.id, error=1) if hit_allowed == 'triple': triple(this_play, comp_play=False) advance_runners(this_play.id, num_bases=4) batter_to_base = 4 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: advance_runners(this_play.id, 3) batter_to_base = 3 complete_play(this_play.id, batter_to_base=batter_to_base) # Either hit or error else: num_bases = None if error_allowed == 'no error': 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 position.value == 'C': view = ButtonOptions( responders=[interaction.user], labels=['gb A', 'gb B', 'gb C', 'FO', 'PO'] ) question = await interaction.channel.send(f'What was the result of the play?', view=view) await view.wait() if not view.value: await question.edit( content=f'Hmm...you keep thinking on it and get back to me when you\'re ready.', view=None ) return elif view.value == 'FO': await question.edit(content=f'**The ball dropped foul; batter swings again.**', view=None) patch_play(this_play.id, defender_id=False, check_pos='false') await interaction.channel.send( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) return else: await question.delete() patch_play(this_play.id, error=1) 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) patch_play(this_play.id, locked=False) await interaction.channel.send( content=None, embed=await self.get_game_state_embed(this_game, full_length=False) ) # @group_log.command(name='xcheck', description='Defender makes an x-check') # async def log_xcheck_command(self, interaction: discord.Interaction, position: Literal[ # 'Pitcher', '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['no error', '1 base', '2 bases', '3 bases']): # this_game, owner_team, this_play = await self.checks_log_interaction(interaction) # if False in (this_game, owner_team, this_play): # return # # patch_play(this_play.id, locked=False) # # pos = get_pos_abbrev(position) # defender = get_one_lineup( # this_game.id, team_id=this_play.pitcher.team_id, position=pos # ) # logging.info(f'defender: {defender}') # patch_play(this_play.id, defender_id=defender.id, error=1 if error_allowed != 'no-error' else 0, check_pos=pos) # # # Not hit and no error # if hit_allowed == 'out' and error_allowed == 'no error': # await interaction.edit_original_response( # content=f'Just logged the x-check! Please log the resulting play to continue (e.g. \'flyball-b\' or ' # f'\'groundball-a\')' # ) # patch_play(this_play.id, locked=False) # return # # # Hit and error # elif hit_allowed != 'out' and error_allowed != 'no error': # if hit_allowed == 'triple': # triple(this_play, comp_play=False) # batter_to_base = 4 # 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 # else: # num_bases = None # if error_allowed == 'no error': # 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) # # patch_play(this_play.id, locked=False) # await interaction.edit_original_response( # content=None, embed=await self.get_game_state_embed(this_game, full_length=False) # ) group_show = app_commands.Group(name='show-card', description='Display the player card for an active player') @group_show.command(name='defense', description='Display a defender\'s player card') async def show_defense_command( self, interaction: discord.Interaction, 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_interaction(interaction, block_rollback=True) 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=IMAGES['logo']) await interaction.edit_original_response(content=None, embed=embed) @group_show.command(name='lineup', description='Display a batting team member\'s player card') async def show_batting_team_command( self, interaction: discord.Interaction, batting_order: Optional[int] = None, relative_order: Optional[Literal['on-deck', 'in-the-hole']] = None): this_game, owner_team, this_play = await self.checks_log_interaction(interaction) if False in (this_game, owner_team, this_play): return if batting_order: lineup_member = get_one_lineup( this_game.id, team_id=this_play.batter.team_id, batting_order=batting_order ) elif relative_order: modifier = 1 if relative_order == 'on-deck' else 2 b_order = this_play.batter.batting_order + modifier if b_order == 10: b_order = 1 if b_order == 11: b_order = 2 lineup_member = get_one_lineup( this_game.id, team_id=this_play.batter.team_id, batting_order=b_order ) else: await interaction.edit_original_response( content=f'You need to select either a batting order or relative order.' ) patch_play(this_play.id, locked=False) return this_player = await get_player(game=this_game, lineup_member=lineup_member) embed = get_team_embed(f'{this_player["team"]["sname"]} #{lineup_member.batting_order} Batter', this_player['team']) embed.description = f'{this_player["name"]}' embed.set_image(url=this_player['image']) if this_game.is_pd: embed.set_footer(text=f'PD Season {PD_SEASON}', icon_url=IMAGES['logo']) await interaction.edit_original_response(content=None, embed=embed) @commands.command(name='load-ai', hidden=True) @commands.is_owner() async def load_ai_command(self, ctx): await ctx.send(f'{load_ai()}') # @commands.command(name='pentest', hidden=True) # @commands.is_owner() # async def pen_test_command(self, ctx, team_id: int = 3): # this_pen = get_or_create_bullpen({'id': team_id}, self.bot) # await ctx.send(f'{this_pen}') async def setup(bot): await bot.add_cog(Gameplay(bot))