paper-dynasty-discord/cogs/gameplay.py
2023-04-28 13:49:38 -05:00

3435 lines
162 KiB
Python

import asyncio
import logging
import math
import copy
import os
import ai_manager
import discord
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
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_last_inning_end_play, 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=5)
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
all_games = get_last_game_ids(6)
games_active = False
for game_id in all_games:
g_query = get_one_game(game_id)
if g_query.active:
games_active = True
if games_active:
embed = get_team_embed(f'Live Scoreboard')
for game_id in all_games:
try:
gs = await self.get_game_state(get_one_game(game_id=game_id))
logging.debug(f'curr_play: {gs["curr_play"]}')
g_message = gs['scorebug']
if not gs['curr_play'].game.active:
g_dec = get_pitching_decisions(gs['curr_play'].game)
winner = await get_player(gs["curr_play"].game, g_dec["w_lineup"])
loser = await get_player(gs["curr_play"].game, g_dec["l_lineup"])
if g_dec['s_lineup']:
saver = await get_player(gs['curr_play'].game, g_dec['s_lineup'])
s_string = f'Save: {saver["name"]}\n' \
f'Location: {guild.get_channel(gs["channel_id"]).mention}\n' \
f'\n-------------------------'
else:
s_string = f'Location: {guild.get_channel(gs["channel_id"]).mention}\n' \
f'-------------------------'
g_message += f'Win: {winner["name"]}\n' \
f'Loss: {loser["name"]}\n' \
f'{s_string}'
else:
games_active = True
g_message += f'Pitcher: {gs["pitcher"]["name"]}\n' \
f'Batter: {gs["batter"]["name"]}\n' \
f'Location: {guild.get_channel(gs["channel_id"]).mention}\n' \
f'-------------------------'
embed.add_field(
name=f'{gs["away_team"]["sname"]} @ {gs["home_team"]["sname"]}',
value=g_message,
inline=False
)
except Exception as e:
logging.error(
f'Skipping Game {game_id} in scoreboard: {e}'
)
current = db_get('current')
try:
s_channel = self.bot.get_channel(int(os.environ.get('SCOREBOARD_CHANNEL')))
message_id = current['live_scoreboard']
if message_id == 0:
s_message = await send_to_channel(
self.bot,
'live-scoreboard',
content=None,
embed=embed
)
db_patch('current', object_id=current['id'], params=[('live_scoreboard', s_message.id)])
else:
s_message = await s_channel.fetch_message(message_id)
await s_message.edit(content=None, embed=embed)
except Exception as e:
logging.error(f'Unable to post 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 <command_name> 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 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}')
embed = discord.Embed(
title=f'{game_state["away_team"]["sname"]} @ {game_state["home_team"]["sname"]}',
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'
embed.add_field(
name='Baserunners', value=baserunner_string if len(baserunner_string) > 0 else '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'])
ai_note = ai_data['note']
gm_name = ai_data['gm_name']
embed.set_thumbnail(url=ai_data['pitcher']["image"])
# 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 = 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 = 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 = get_team_by_abbrev(away_team_abbrev)
home_team = 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 = 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 <ranked / unlimited>` 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:
await interaction.edit_original_response(
content='Hall of Fame games are coming next season! For now, you can play an Unlimited Minor League '
'game or against other humans!'
)
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 = 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 = 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 = 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')
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 = get_team_by_abbrev(away_team_abbrev)
home_team = 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 = 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 = get_team_by_abbrev(away_team_abbrev)
home_team = 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 = 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']):
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 = db_get('current')
week_num = current['week']
e_query = 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 = get_team_by_owner(interaction.user.id)
team = 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 = 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 = 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 = 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 = gauntlets.get_starting_pitcher(opponent, this_game, this_event, this_run)
all_lineups.append(starter)
this_card = 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())
@commands.hybrid_command(name='endgame', help='End game in this channel')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def end_game_command(self, ctx: commands.Context):
this_game = get_one_game(channel_id=ctx.channel.id, active=True)
if not this_game:
await ctx.send(f'Ope, I don\'t see a game in this channel.')
return
logging.info(f'Ending Game {this_game.id}')
response = await ctx.send(f'Let\'s see what we\'ve got here...')
owner_team = await get_game_team(this_game, ctx.author.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 ctx.send('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 \
ctx.author.id != self.bot.owner_id:
await ctx.send('Bruh. Only GMs of the active teams can end games.')
return
await response.edit(content=None, embed=await self.get_game_state_embed(this_game))
view = Confirm(responders=[ctx.author], timeout=60, label_type='yes')
question = await ctx.send(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 = db_get('teams', object_id=this_game.away_team_id)
home_team = 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 ctx.send(f'I was not able to process stats for {bad_player["name"]} - {get_cal_user(ctx).mention}'
f' 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 ctx.send(f'I was not able to process stats for {bad_player["name"]} - {get_cal_user(ctx).mention}'
f' 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 ctx.send(f'I was not able to calculate the Winning and Losing Pitcher for this game. Is the game '
f'ending early? {get_cal_user(ctx).mention} can probably help.')
return
logging.debug(f'decisions: {decisions}')
wr_query = db_get(
'gamerewards', params=[('name', f'{"Short" if this_game.short_game else "Full"} Game Win')])
lr_query = 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.')
win_reward = wr_query['gamerewards'][0]
loss_reward = lr_query['gamerewards'][0]
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
# 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}')
if this_game.is_pd:
logging.debug(f'Time to build statlines and submit the scorecard for this PD game!')
# Post result
success = 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 += ', '
doubles += f'{db_get("cards", object_id=line["card_id"])["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 += ', '
triples += f'{db_get("cards", object_id=line["card_id"])["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 += ', '
homers += f'{db_get("cards", object_id=line["card_id"])["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 += ', '
s_bases += f'{db_get("cards", object_id=line["card_id"])["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 += ', '
caught_s += f'{db_get("cards", object_id=line["card_id"])["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 ''
db_post('batstats', payload={'stats': batter_stats})
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'{ctx.guild.get_channel(this_game.channel_id).mention}'
)
embed.add_field(
name='Pitching',
value=f'Win: {db_get("cards", object_id=decisions["winner"])["player"]["p_name"]}\n'
f'Loss: {db_get("cards", object_id=decisions["loser"])["player"]["p_name"]}\n'
f'{"Save: " if decisions["save"] else ""}'
f'{db_get("cards", object_id=decisions["save"])["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=f'1x {win_reward["pack_type"]["name"]} Pack\n{win_reward["money"]}'
)
embed.add_field(
name=f'{losing_team["abbrev"]} Rewards',
value=f'{loss_reward["money"]}'
)
embed.add_field(
name='Highlights',
value=f'Please share the highlights in {get_channel(ctx, "pd-news-ticker").mention}!',
inline=False
)
await send_to_channel(self.bot, 'pd-network-news', embed=embed)
# Post rewards
give_packs(winning_team, num_packs=1, pack_type=win_reward['pack_type'])
db_post(f'teams/{winning_team["id"]}/money/{win_reward["money"]}')
db_post(f'teams/{losing_team["id"]}/money/{loss_reward["money"]}')
# 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,
ctx=ctx
)
this_run = 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
)
else:
logging.debug(f'Time to build statlines and share the scorecard for this SBa game!')
# Send stats to sheets
# Post sheets link
pass
patch_game(this_game.id, active=False)
logging.info(f'Game {this_game.id} is complete')
if gauntlet_team is None:
await ctx.send(f'Good game! Go share the highlights in {get_channel(ctx, "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 = []
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 = 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]
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 = 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 = 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:
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')
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 = 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!')
@commands.hybrid_command(
name='substitution',
help='Make a lineup substitution; Player Name for SBa games / Card ID for PD games',
aliases=['sub']
)
@app_commands.describe(
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, ctx: commands.Context, 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 = get_one_game(channel_id=ctx.channel.id, active=True)
if not this_game:
await ctx.send(f'I dont\'t see an active game in this channel!')
return
owner_team = await get_game_team(this_game, ctx.author.id)
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 ctx.send('Bruh. Only GMs of the active teams can set lineups.')
return
if not lineup_team['id'] in [this_game.away_team_id, this_game.home_team_id]:
await ctx.send(f'I do not see {lineup_team["sname"]} in this game. Please check the team abbrev and '
f'try again')
return
try:
if this_game.is_pd:
this_card = 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 ctx.send(f'I could not find card_id {new_player}.')
else:
await ctx.send(f'I could not find {new_player.title()}.')
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 ctx.send(content=None, embed=await self.get_game_state_embed(this_game))
@commands.hybrid_command(name='gamestate', help='Post the current game state', aliases=['gs'])
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def game_state_command(self, ctx: commands.Context, include_lineups: bool = True):
this_game = get_one_game(channel_id=ctx.channel.id, active=True)
if not this_game:
await ctx.send(f'I dont\'t see an active game in this channel!')
return
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, pitchers_only=True, as_string=False
)
make_sub(ai_manager.get_relief_pitcher(this_play, ai_team, used_pitchers, 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='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)
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)
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 = 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
if position.value == 'C':
view = Confirm([interaction.user], label_type='yes')
question = await interaction.channel.send(f'Did {defender["name"]} give up a chaos result?', view=view)
await view.wait()
if view.value:
await question.delete()
view = ButtonOptions(responders=[interaction.user], labels=['WP', 'X (PB)', None, None, None])
question = await interaction.channel.send(
f'Which chaos result did {defender["name"]} allow?', view=view
)
await view.wait()
if view.value == 'WP' or view.value == 'X (PB)':
await question.delete()
if view.value == 'WP':
advance_runners(this_play.id, 1)
patch_play(this_play.id, rbi=0, wp=1)
elif view.value == 'X (PB)':
advance_runners(this_play.id, 1)
patch_play(this_play.id, rbi=0, pb=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)
)
return
else:
await question.delete()
else:
await question.delete()
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 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:
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))