paper-dynasty-discord/cogs/gameplay_legacy.py
Cal Corum ee80cd72ae fix: apply Black formatting and resolve ruff lint violations
Run Black formatter across 83 files and fix 1514 ruff violations:
- E722: bare except → typed exceptions (17 fixes)
- E711/E712/E721: comparison style fixes with noqa for SQLAlchemy (44 fixes)
- F841: unused variable assignments (70 fixes)
- F541/F401: f-string and import cleanup (1383 auto-fixes)

Remaining 925 errors are all F403/F405 (star imports) — structural,
requires converting to explicit imports in a separate effort.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:37:46 -05:00

5714 lines
234 KiB
Python

import asyncio
import logging
import discord
import math
import os
from in_game import ai_manager
import dice
import gauntlets
from discord import app_commands
from discord.app_commands import Choice
from discord.ext import commands, tasks
from peewee import IntegrityError
from typing import Literal, Optional
from dice import sa_fielding_roll_legacy
from helpers import (
SBA_PLAYERS_ROLE_NAME,
PD_PLAYERS_ROLE_NAME,
random_conf_gif,
PD_SEASON,
IMAGES,
get_pos_abbrev,
SBA_COLOR,
get_roster_lineups,
give_packs,
send_to_channel,
get_channel,
team_role,
get_cal_user,
ButtonOptions,
get_ratings_guide,
get_team_by_owner,
player_desc,
player_pcard,
player_bcard,
get_team_embed,
Confirm,
get_sheets,
Dropdown,
SELECT_CARDSET_OPTIONS,
DropdownView,
)
from in_game.ai_manager import check_pitching_sub
from in_game.game_helpers import (
single_onestar,
single_wellhit,
double_twostar,
double_threestar,
triple,
runner_on_first,
runner_on_second,
runner_on_third,
gb_result_1,
gb_result_2,
gb_result_3,
gb_result_4,
gb_result_5,
gb_result_6,
gb_result_7,
gb_result_8,
gb_result_9,
gb_result_10,
gb_result_11,
gb_result_12,
gb_result_13,
gb_decide,
show_outfield_cards,
legal_check,
get_pitcher,
)
from api_calls import db_get, db_patch, db_post, db_delete, get_team_by_abbrev
from db_calls_gameplay import (
StratGame,
StratPlay,
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_pitching_decisions,
get_or_create_bullpen,
get_active_games,
patch_lineup,
get_plays,
get_manager,
get_one_game,
load_ai,
ai_batting,
undo_subs,
get_dbready_plays,
)
logger = logging.getLogger("discord_app")
class Gameplay(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.batter_ratings = None
self.pitcher_ratings = None
self.live_scoreboard.start()
@tasks.loop(minutes=3)
async def live_scoreboard(self):
guild = self.bot.get_guild(int(os.environ.get("GUILD_ID")))
score_channel = discord.utils.get(guild.text_channels, name="live-pd-scores")
player_role = discord.utils.get(guild.roles, name=PD_PLAYERS_ROLE_NAME)
active_games = get_active_games(6)
if len(active_games) == 0:
await score_channel.set_permissions(player_role, read_messages=False)
return
try:
embed = get_team_embed("Live Scoreboard")
valid_scores = False
for x in active_games:
await asyncio.sleep(1)
gs = await self.get_game_state(x)
if not gs["error"]:
valid_scores = True
channel = guild.get_channel(gs["channel_id"])
g_message = gs["scorebug"]
g_message += (
f'Pitcher: {gs["pitcher"]["p_name"]}\n'
f'Batter: {gs["batter"]["p_name"]}\n'
f'Location: {channel.mention if channel is not None else "Your Butt"}\n'
f"-------------------------"
)
gt_string = "Unlimited"
if x.game_type == "minor-league":
gt_string = "Minor League"
elif x.game_type == "major-league":
gt_string = "Major League"
elif x.game_type == "hall-of-fame":
gt_string = "Hall of Fame"
elif "gauntlet" in x.game_type:
gt_string = "Gauntlet"
elif x.game_type == "flashback":
gt_string = "Flashback"
elif "exhibition" in x.game_type:
gt_string = "Exhibition"
embed.add_field(
name=f'{gs["away_team"]["sname"]} @ {gs["home_team"]["sname"]} - {gt_string}',
value=g_message,
inline=False,
)
if valid_scores:
async for message in score_channel.history(limit=25):
await message.delete()
await score_channel.set_permissions(player_role, read_messages=True)
await score_channel.send(content=None, embed=embed)
else:
await score_channel.set_permissions(player_role, read_messages=False)
return
except Exception as e:
logger.error(f"Could not update live_scoreboard: {e}")
@live_scoreboard.before_loop
async def before_live_scoreboard(self):
await self.bot.wait_until_ready()
@tasks.loop(hours=36)
async def update_ratings_guides(self):
guild = self.bot.get_guild(int(os.environ.get("GUILD_ID")))
if not guild:
logger.error("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:
logger.error("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:
logger.error("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:
logger.error("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[:1600]}")
async def post_stratgame(self, this_game: StratGame, forfeit: bool = False):
return await db_post(
"games",
payload={
"season": this_game.season,
"game_type": this_game.game_type,
"away_team_id": this_game.away_team_id,
"home_team_id": this_game.home_team_id,
"week": this_game.week_num,
"ranked": this_game.ranked,
"short_game": this_game.short_game,
"forfeit": forfeit,
},
)
async def post_allplays(self, this_game: StratGame, final_game_id: int):
all_plays = get_dbready_plays(this_game.id, db_game_id=final_game_id)
await asyncio.sleep(0.5)
return await db_post("plays", payload={"plays": all_plays}, timeout=15)
async def post_decisions(self, this_game: StratGame, final_game_id: int):
all_dec = get_pitching_decisions(this_game, final_game_id)
await asyncio.sleep(0.5)
return await db_post("decisions", payload={"decisions": all_dec}, timeout=5)
def get_scorebug(self, home_team, away_team, curr_play):
occupied = ""
unoccupied = ""
first_base = unoccupied if not curr_play.on_first else occupied
second_base = unoccupied if not curr_play.on_second else occupied
third_base = unoccupied if not curr_play.on_third else occupied
half = "" if curr_play.inning_half == "Top" else ""
if curr_play.game.active:
inning = f"{half} {curr_play.inning_num}"
outs = f'{curr_play.starting_outs} Out{"s" if curr_play.starting_outs != 1 else ""}'
else:
inning = f'F/{curr_play.inning_num if curr_play.inning_half == "Bot" else curr_play.inning_num - 1}'
outs = ""
game_string = (
f"```\n"
f'{away_team["abbrev"].replace("Gauntlet-", ""): ^4}{curr_play.away_score: ^3} {second_base}'
f"{inning: >10}\n"
f'{home_team["abbrev"].replace("Gauntlet-", ""): ^4}{curr_play.home_score: ^3} {third_base} '
f"{first_base}{outs: >8}\n```"
)
return game_string
async def post_rewards(
self, winning_team: dict, losing_team: dict, this_game: StratGame
):
wr_query = await db_get(
"gamerewards",
params=[
("name", f'{"Short" if this_game.short_game else "Full"} Game Win')
],
)
lr_query = await db_get(
"gamerewards",
params=[
("name", f'{"Short" if this_game.short_game else "Full"} Game Loss')
],
)
if not wr_query["count"] or not lr_query["count"]:
raise KeyError("Game Rewards were not found. Leaving this game active.")
human_team = winning_team if losing_team["is_ai"] else losing_team
_ai_team = winning_team if winning_team["is_ai"] else losing_team
win_reward = wr_query["gamerewards"][0]
loss_reward = lr_query["gamerewards"][0]
win_string = f'1x {win_reward["pack_type"]["name"]} Pack\n'
# Post Team Choice packs
if (
this_game.ai_team is not None
and not this_game.short_game
and "gauntlet" not in this_game.game_type
and losing_team["is_ai"]
):
g_query = await db_get(
"games",
params=[
("team1_id", human_team["id"]),
("game_type", this_game.game_type),
("season", this_game.season),
],
)
wins = 0
for x in g_query["games"]:
if (
x["away_score"] > x["home_score"]
and x["away_team"]["id"] == human_team["id"]
) or (
x["home_score"] > x["away_score"]
and x["home_team"]["id"] == human_team["id"]
):
wins += 1
async def post_tc_pack():
await db_post(
"packs/one",
payload={
"team_id": human_team["id"],
"pack_type_id": 8,
"pack_team_id": losing_team["id"],
},
)
if g_query["count"] > 0:
if this_game.game_type == "minor-league" and wins % 6 == 0:
await post_tc_pack()
win_string += f'1x {losing_team["abbrev"]} Team Choice pack\n'
elif this_game.game_type == "major-league" and wins % 4 == 0:
await post_tc_pack()
win_string += f'1x {losing_team["abbrev"]} Team Choice pack\n'
elif this_game.game_type == "hall-of-fame" and wins % 2 == 0:
await post_tc_pack()
win_string += f'1x {losing_team["abbrev"]} Team Choice pack\n'
win_string += f'{win_reward["money"]}\n'
loss_string = f'{loss_reward["money"]}\n'
# Post rewards
if "gauntlet" in this_game.game_type:
if "Gauntlet" in winning_team["abbrev"]:
t_query = await db_get(
"teams", params=[("abbrev", winning_team["abbrev"].split("-")[1])]
)
winning_team = t_query["teams"][0]
if "Gauntlet" in losing_team["abbrev"]:
t_query = await db_get(
"teams", params=[("abbrev", losing_team["abbrev"].split("-")[1])]
)
losing_team = t_query["teams"][0]
await give_packs(winning_team, num_packs=1, pack_type=win_reward["pack_type"])
await db_post(f'teams/{winning_team["id"]}/money/{win_reward["money"]}')
await db_post(f'teams/{losing_team["id"]}/money/{loss_reward["money"]}')
data = {"win_string": win_string, "loss_string": loss_string}
return data
async def get_game_state(self, game: StratGame) -> dict:
away_team = await get_game_team(game, team_id=game.away_team_id)
home_team = await get_game_team(game, team_id=game.home_team_id)
curr_play = get_current_play(game.id)
if curr_play is None:
away_lineup = await get_team_lineups(game.id, game.away_team_id)
home_lineup = await get_team_lineups(game.id, game.home_team_id)
logger.info(f"away_lineup: {away_lineup}")
logger.info(f"home_lineup: {home_lineup}")
if len(away_lineup) < 200 or len(home_lineup) < 200:
game_state = {
"error": True,
"away_lineup": away_lineup,
"home_lineup": home_lineup,
"away_team": away_team,
"home_team": home_team,
}
logger.error(
f"One ore more lineups not submitted in Game {game.id}\n\ngame_state: {game_state}"
)
return game_state
else:
logger.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"
)
logger.debug(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,
}
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:
logger.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 = "Please sub in a pitcher to continue"
else:
error_message = "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
if curr_play.inning_half == "Top":
game_state["pitcher"]["team"] = home_team
game_state["catcher"]["team"] = home_team
game_state["batter"]["team"] = away_team
else:
game_state["pitcher"]["team"] = away_team
game_state["catcher"]["team"] = away_team
game_state["batter"]["team"] = home_team
logger.debug(f"game_state: {game_state}")
return game_state
async def initialize_play_plus_embed(
self, game: StratGame, full_length=True, for_liveboard=False
):
game_state = await self.get_game_state(game)
logger.debug(f"game_state: {game_state}")
gt_string = " - Unlimited"
if game.game_type == "minor-league":
gt_string = " - Minor League"
elif game.game_type == "major-league":
gt_string = " - Major League"
elif game.game_type == "hall-of-fame":
gt_string = " - Hall of Fame"
elif "gauntlet" in game.game_type:
gt_string = " - Gauntlet"
elif "flashback" in game.game_type:
gt_string = " - Flashback"
elif "exhibition" in game.game_type:
gt_string = " - Exhibition"
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
logger.debug("no errors")
pitching_sub = None
ai_note = ""
gm_name = ""
fatigue = await ai_manager.is_pitcher_fatigued(game_state["curr_play"])
logger.debug("do AI stuff")
# If an AI team is playing
if True in [
game_state["pitcher"]["team"]["is_ai"],
game_state["batter"]["team"]["is_ai"],
]:
logger.debug("Checking AI stuff")
# AI Team is pitching
if game_state["pitcher"]["team"]["is_ai"]:
if fatigue:
pitching_sub = await check_pitching_sub(
game_state["curr_play"], game_state["pitcher"]["team"]
)
if pitching_sub is not None:
game_state = await self.get_game_state(game)
ai_data = await ai_manager.pitching_ai_note(
game_state["curr_play"], game_state["pitcher"]
)
logger.debug(f"ai_data: {ai_data}")
ai_note = ai_data["note"]
gm_name = ai_data["gm_name"]
# AI Team is batting
if game_state["batter"]["team"]["is_ai"]:
# embed.set_thumbnail(url=player_pcard(game_state["pitcher"]))
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 (
pitching_sub is not None
or (fatigue and pitching_sub is None)
or abs(
game_state["curr_play"].home_score - game_state["curr_play"].away_score
)
>= 10
):
color = discord.Colour.red()
else:
color = int(SBA_COLOR, 16)
embed = discord.Embed(
title=f'{game_state["away_team"]["sname"]} @ {game_state["home_team"]["sname"]}{gt_string}',
color=color,
)
logger.info("got embed")
footer_text = f"Paper Dynasty Season {PD_SEASON}"
if game.short_game:
footer_text += " - Reminder: all pitchers have POW(1) in 3-inning games"
embed.set_footer(text=footer_text, icon_url=IMAGES["logo"])
embed.add_field(name="Game State", value=game_state["scorebug"], inline=False)
embed.set_thumbnail(url=player_pcard(game_state["pitcher"]))
logger.info("check mercy")
if (
abs(game_state["curr_play"].home_score - game_state["curr_play"].away_score)
>= 10
):
embed.description = "***Mercy rule in effect***"
logger.info("set pitcher string")
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}'
)
logger.info("pull bat stats")
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}'
)
logger.info("pull pitcher stats")
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 fatigue and pitching_sub is None:
pitcher_string += "\n***F A T I G U E D***"
logger.info("set embed pitcher/batter")
embed.add_field(name="Pitcher", value=f"{pitcher_string}")
embed.add_field(name="Batter", value=f"{batter_string}")
embed.set_image(url=player_bcard(game_state["batter"]))
logger.info("get baserunners")
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"
logger.info("set baserunners")
if len(baserunner_string) > 0:
embed.add_field(name=" ", value=" ", inline=False)
embed.add_field(name="Baserunners", value=baserunner_string)
embed.add_field(
name="Catcher", value=f'{player_link(game, game_state["catcher"])}'
)
else:
embed.add_field(name="Baserunners", value="None", inline=False)
if len(ai_note) > 0:
embed.add_field(name=f"{gm_name} will...", value=ai_note, inline=False)
if pitching_sub is not None:
embed.add_field(
name="SUBSTITUTION",
value=f'The {game_state["pitcher"]["team"]["sname"]} have brought in '
f"**{player_desc(pitching_sub)}** to pitch",
)
logger.info(f"if not full length: return embed: {embed}")
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_old(
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(
"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(
"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="Hmm...not sure what else this could be. If you expected a different "
"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["p_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["p_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["p_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["p_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["p_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["p_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["p_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["p_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["p_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="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["p_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["p_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["p_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
logger.debug("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 groundballs(
self,
interaction,
this_game,
this_play: StratPlay,
groundball_type: str,
comp_play: bool = True,
):
batter_to_base = None
if this_play.starting_outs == 2 or this_play.on_base_code == 0:
patch_play(this_play.id, pa=1, ab=1, outs=1)
elif (
this_play.starting_outs == 1
and groundball_type == "a"
and this_play.on_base_code == 1
):
patch_play(this_play.id, pa=1, ab=1, outs=2, on_first_final=False)
else:
playing_in = False
if runner_on_third(this_play):
view = Confirm(
responders=[interaction.user], timeout=60, label_type="yes"
)
question = await interaction.channel.send(
"Was the defender playing in?", view=view
)
await view.wait()
if view.value:
playing_in = True
else:
playing_in = False
await question.delete()
logger.info(
f"playing_in: {playing_in} / obc: {this_play.on_base_code} / gb_type: {groundball_type}"
)
if not playing_in:
if groundball_type == "a":
if this_play.on_base_code == 0:
batter_to_base = gb_result_1(this_play)
elif this_play.on_base_code in [1, 4, 5, 7]:
batter_to_base = gb_result_2(this_play)
elif this_play.on_base_code in [3, 6]:
view = Confirm(
responders=[interaction.user], timeout=60, label_type="yes"
)
question = await interaction.channel.send(
"Was the ball hit to either the 2B or SS?", view=view
)
await view.wait()
if view.value:
to_mif = True
else:
to_mif = False
await question.delete()
batter_to_base = gb_result_5(this_play, to_mif)
else:
view = Confirm(
responders=[interaction.user], timeout=60, label_type="yes"
)
question = await interaction.channel.send(
"Was the ball hit to either the 1B or 2B?", view=view
)
await view.wait()
if view.value:
to_right_side = True
else:
to_right_side = False
await question.delete()
batter_to_base = gb_result_6(this_play, to_right_side)
elif groundball_type == "b":
if this_play.on_base_code == 0:
batter_to_base = gb_result_1(this_play)
elif this_play.on_base_code in [1, 4, 5, 7]:
batter_to_base = gb_result_4(this_play)
elif this_play.on_base_code in [3, 6]:
view = Confirm(
responders=[interaction.user], timeout=60, label_type="yes"
)
question = await interaction.channel.send(
"Was the ball hit to either the 2B or SS?", view=view
)
await view.wait()
if view.value:
to_mif = True
else:
to_mif = False
await question.delete()
batter_to_base = gb_result_5(this_play, to_mif)
else:
view = Confirm(
responders=[interaction.user], timeout=60, label_type="yes"
)
question = await interaction.channel.send(
"Was the ball hit to either the 1B or 2B?", view=view
)
await view.wait()
if view.value:
to_right_side = True
else:
to_right_side = False
await question.delete()
batter_to_base = gb_result_6(this_play, to_right_side)
else:
if this_play.on_base_code == 0:
batter_to_base = gb_result_1(this_play)
else:
batter_to_base = gb_result_3(this_play)
else:
if groundball_type == "a":
if this_play.on_base_code == 7:
batter_to_base = gb_result_10(this_play)
else:
batter_to_base = gb_result_7(this_play)
elif groundball_type == "b":
if this_play.on_base_code == 7:
batter_to_base = gb_result_11(this_play)
elif this_play.on_base_code == 5:
batter_to_base = gb_result_9(this_play)
else:
batter_to_base = gb_result_1(this_play)
else:
if this_play.on_base_code == 7:
batter_to_base = gb_result_11(this_play)
else:
batter_to_base = gb_result_8(this_play)
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:
logger.debug("calling of embed")
await show_outfield_cards(interaction, this_play)
logger.debug("done with of embed")
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["p_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["p_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:
logger.debug("calling of embed")
await show_outfield_cards(interaction, this_play)
logger.debug("done with of embed")
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["p_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["p_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)
group_new_game = app_commands.Group(
name="new-game", description="Start a new baseball game"
)
@group_new_game.command(
name="mlb-campaign", description="Start a new MLB Campaign game against an AI"
)
@app_commands.describe(
sp_card_id="Light gray number to the left of the pitcher's name on your depth chart"
)
@commands.has_any_role(PD_PLAYERS_ROLE_NAME)
async def new_game_campaign_command(
self,
interaction: discord.Interaction,
league: Literal["Minor League", "Flashback", "Major League", "Hall of Fame"],
away_team_abbrev: str,
home_team_abbrev: str,
num_innings: Literal[9, 3],
sp_card_id: int,
):
await interaction.response.defer()
conflict = get_one_game(channel_id=interaction.channel.id, active=True)
if conflict:
await interaction.edit_original_response(
content="Ope. There is already a game going on in this channel. Please wait for it to complete "
"before starting a new one."
)
return
try:
if interaction.channel.category.name != "Public Fields":
await interaction.response.send_message(
"Why don't you head down to one of the Public Fields that way other humans can help if anything "
"pops up?"
)
return
except Exception as e:
logger.error(f"Could not check channel category: {e}")
away_team = await get_team_by_abbrev(away_team_abbrev)
home_team = await get_team_by_abbrev(home_team_abbrev)
if not away_team:
await interaction.edit_original_response(
content=f"Sorry, I don't know who **{away_team_abbrev.upper()}** is."
)
return
if not home_team:
await interaction.edit_original_response(
content=f"Sorry, I don't know who **{home_team_abbrev.upper()}** is."
)
return
# if True in [away_team['is_ai'], home_team['is_ai']] and is_ranked:
# await interaction.edit_original_response(
# content=f'Sorry, ranked games can only be played against human teams. Run `/new-game` again with '
# f'`is_ranked` set to False to play against the '
# f'{away_team["sname"] if away_team["is_ai"] else home_team["sname"]}.'
# )
# return
for x in [away_team, home_team]:
if not x["is_ai"]:
conflict = count_team_games(x["id"])
if conflict["count"]:
await interaction.edit_original_response(
content=f'Ope. The {x["sname"]} are already playing over in '
f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}'
)
return
current = await db_get("current")
week_num = current["week"]
# logger.debug(f'away: {away_team} / home: {home_team} / week: {week_num} / ranked: {is_ranked}')
logger.debug(f"away: {away_team} / home: {home_team} / week: {week_num}")
if not away_team["is_ai"] and not home_team["is_ai"]:
logger.error(
f'MLB Campaign game between {away_team["abbrev"]} and {home_team["abbrev"]} has no AI'
)
await interaction.edit_original_response(
content="I don't see an AI team in this MLB Campaign game. Run `/new-game mlb-campaign` again with "
"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
human_team = away_team if home_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 "Flashback" 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="Ope. Looks like you haven't completed the Minor League campaign, yet!\n\n"
"To play **Flashback** games, you need to defeat all 30 MLB teams in the Minor League "
"campaign. You can see your progress with `/record`.\n\n"
"If you have completed the Minor League campaign, go ping Cal to get your new role!"
)
return
league_name = "flashback"
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="Ope. Looks like you haven't received the **PD - Major League** role, yet!\n\n"
"To play **Major League** games, you need to defeat all 30 MLB teams in the Minor League "
"campaign. You can see your progress with `/record`.\n\n"
"If you have completed the Minor League campaign, go ping Cal to get your new role!"
)
return
league_name = "major-league"
else:
can_play = False
for x in interaction.user.roles:
if x.name == "PD - Hall of Fame":
can_play = True
if not can_play:
await interaction.edit_original_response(
content="Ope. Looks like you haven't received the **PD - Hall of Fame** role, yet!\n\n"
"To play **Hall of Fame** games, you need to defeat all 30 MLB teams in the Minor League "
"and Major League campaign. You can see your progress with `/record`.\n\n"
"If you have completed the Major League campaign, go ping Cal to get your new role!"
)
return
league_name = "hall-of-fame"
this_game = post_game(
{
"away_team_id": away_team["id"],
"home_team_id": home_team["id"],
"week_num": week_num,
"channel_id": interaction.channel.id,
"active": True,
"is_pd": True,
"ranked": False,
"season": current["season"],
"short_game": True if num_innings == 3 else False,
"game_type": league_name,
}
)
logger.info(
f"Game {this_game.id} ({league_name}) between {away_team_abbrev.upper()} and "
f"{home_team_abbrev.upper()} is posted!"
)
away_role = await team_role(interaction, away_team)
home_role = await team_role(interaction, home_team)
all_lineups = []
# Get Human SP
human_sp_card = await db_get("cards", object_id=sp_card_id)
if human_sp_card["team"]["id"] != human_team["id"]:
logger.error(
f'Card_id {sp_card_id} does not belong to {human_team["abbrev"]} in Game {this_game.id}'
)
patch_game(this_game.id, active=False)
await interaction.channel.send(
f'Uh oh. Card ID {sp_card_id} is {human_team["player"]["p_name"]} and belongs to '
f'{human_sp_card["team"]["sname"]}. Will you double check that before we get started?'
)
return
if this_game.game_type in ["major-league", "hall-of-fame"]:
l_check = await legal_check([human_sp_card["id"]], "ranked")
if not l_check["legal"]:
patch_game(this_game.id, active=False)
await interaction.edit_original_response(
content=f'It looks like this is a Ranked Legal game and {player_desc(human_sp_card["player"])} is '
f"not legal in Ranked. You can start a new game once you pick a new SP."
)
return
if this_game.game_type == "flashback":
l_check = await legal_check([human_sp_card["id"]], "flashback")
if not l_check["legal"]:
patch_game(this_game.id, active=False)
await interaction.edit_original_response(
content=f'It looks like this is a Flashback game and {player_desc(human_sp_card["player"])} is '
f"not legal in Flashback. You can start a new game once you pick a new SP."
)
return
all_lineups.append(
{
"game_id": this_game.id,
"team_id": human_team["id"],
"player_id": human_sp_card["player"]["player_id"],
"card_id": sp_card_id,
"position": "P",
"batting_order": 10,
"after_play": 0,
}
)
# Get AI Starting Pitcher
try:
await interaction.edit_original_response(
content="Now to decide on a Starting Pitcher..."
)
if ai_team["id"] == this_game.away_team_id:
patch_game(this_game.id, away_roster_num=69, ai_team="away")
else:
patch_game(this_game.id, home_roster_num=69, ai_team="home")
# starter = starting_pitcher(ai_team, self.bot, True if home_team['is_ai'] else False)
starter = await ai_manager.get_starting_pitcher(
ai_team,
this_game.id,
True if home_team["is_ai"] else False,
league_name,
)
all_lineups.append(starter)
ai_sp = await db_get("players", object_id=starter["player_id"])
this_card = await db_get("cards", object_id=starter["card_id"])
await interaction.channel.send(
content=f'The {ai_team["sname"]} are starting **{player_desc(this_card["player"])}**:\n\n'
f'{player_pcard(this_card["player"])}'
)
except Exception as e:
patch_game(this_game.id, active=False)
logger.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
# Get AI Lineup
try:
await interaction.edit_original_response(
content=f'I am getting a lineup card from the {ai_team["sname"]}...'
)
logger.info(f'new-game - calling lineup for {ai_team["abbrev"]}')
batters = await ai_manager.build_lineup(
ai_team, this_game.id, league_name, sp_name=ai_sp["p_name"]
)
all_lineups.extend(batters)
logger.info(f'new-game - got lineup for {ai_team["abbrev"]}')
except Exception as e:
patch_game(this_game.id, active=False)
logger.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
logger.debug(f'Setting lineup for {ai_team["sname"]} in PD game')
logger.debug(f"lineups: {all_lineups}")
post_lineups(all_lineups)
await interaction.channel.send(
content=f"{away_role.mention} @ {home_role.mention} is set!\n\n"
f"Go ahead and set lineups with the `/read-lineup` command!",
embed=await self.initialize_play_plus_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="Ope. There is already a game going on in this channel. Please wait for it to complete "
"before starting a new one."
)
return
try:
if interaction.channel.category.name != "Public Fields":
await interaction.response.send_message(
"Why don't you head down to one of the Public Fields that way other humans can help if anything "
"pops up?"
)
return
except Exception as e:
logger.error(f"Could not check channel category: {e}")
away_team = await get_team_by_abbrev(away_team_abbrev)
home_team = await get_team_by_abbrev(home_team_abbrev)
if not away_team:
await interaction.edit_original_response(
content=f"Sorry, I don't know who **{away_team_abbrev.upper()}** is."
)
return
if not home_team:
await interaction.edit_original_response(
content=f"Sorry, I don't know who **{home_team_abbrev.upper()}** is."
)
return
if away_team["is_ai"] or home_team["is_ai"]:
logger.error(
f'Ranked game between {away_team["abbrev"]} and {home_team["abbrev"]} has an AI'
)
await interaction.edit_original_response(
content="Only human vs human games can be ranked - run `/new-game` again and double-check the "
"game type you want! If you have questions, feel free to post up in #paper-dynasty-chat"
)
return
for x in [away_team, home_team]:
if not x["is_ai"]:
conflict = count_team_games(x["id"])
if conflict["count"]:
await interaction.edit_original_response(
content=f'Ope. The {x["sname"]} are already playing over in '
f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}'
)
return
current = await db_get("current")
week_num = current["week"]
logger.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",
}
)
logger.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 `/read-lineup` command!",
embed=await self.initialize_play_plus_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="Ope. There is already a game going on in this channel. Please wait for it to complete "
"before starting a new one."
)
return
try:
if interaction.channel.category.name != "Public Fields":
await interaction.response.send_message(
"Why don't you head down to one of the Public Fields that way other humans can help if anything "
"pops up?"
)
return
except Exception as e:
logger.error(f"Could not check channel category: {e}")
away_team = await get_team_by_abbrev(away_team_abbrev)
home_team = await get_team_by_abbrev(home_team_abbrev)
if not away_team:
await interaction.edit_original_response(
content=f"Sorry, I don't know who **{away_team_abbrev.upper()}** is."
)
return
if not home_team:
await interaction.edit_original_response(
content=f"Sorry, I don't know who **{home_team_abbrev.upper()}** is."
)
return
if away_team["is_ai"] or home_team["is_ai"]:
logger.error(
f'Ranked game between {away_team["abbrev"]} and {home_team["abbrev"]} has an AI'
)
await interaction.edit_original_response(
content="This command is for human v human games - run `/new-game` again and double-check the "
"game type you want! If you have questions, feel free to post up in #paper-dynasty-chat"
)
return
for x in [away_team, home_team]:
if not x["is_ai"]:
conflict = count_team_games(x["id"])
if conflict["count"]:
await interaction.edit_original_response(
content=f'Ope. The {x["sname"]} are already playing over in '
f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}'
)
return
current = await db_get("current")
week_num = current["week"]
logger.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",
}
)
logger.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 `/read-lineup` command!",
embed=await self.initialize_play_plus_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["2024 Season", "Super Ultra Championship"],
sp_card_id: int,
):
await interaction.response.defer()
conflict = get_one_game(channel_id=interaction.channel.id, active=True)
if conflict:
await interaction.edit_original_response(
content="Ope. There is already a game going on in this channel. Please wait for it to complete "
"before starting a new one."
)
return
try:
if interaction.channel.category.name != "Public Fields":
await interaction.response.send_message(
"Why don't you head down to one of the Public Fields that way other humans can help if anything "
"pops up?"
)
return
except Exception as e:
logger.error(f"Could not check channel category: {e}")
current = await db_get("current")
week_num = current["week"]
e_query = await db_get(
"events", params=[("name", event_name), ("active", True)]
)
if e_query["count"] == 0:
await interaction.edit_original_response(
content=f"It looks like the {event_name} has ended! Cal should really remove it from this list."
)
return
this_event = e_query["events"][0]
if interaction.user.id == 258104532423147520:
main_team = await get_team_by_abbrev("SKB")
else:
main_team = await get_team_by_owner(interaction.user.id)
team = await get_team_by_abbrev(f'Gauntlet-{main_team["abbrev"]}')
if not main_team:
await interaction.edit_original_response(
content="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="I don't see an active run for you. You can get started with the `/gauntlets start` command!"
)
return
conflict = count_team_games(team["id"])
if conflict["count"]:
await interaction.edit_original_response(
content=f'Ope. The {team["sname"]} are already playing over in '
f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}'
)
return
# Get Gauntlet run
r_query = await db_get(
"gauntletruns",
params=[
("team_id", team["id"]),
("gauntlet_id", this_event["id"]),
("is_active", True),
],
)
if r_query["count"] == 0:
await interaction.edit_original_response(
content=f"I don't see an active run for you. If you would like to start a new one, run "
f'`/gauntlets start {this_event["name"]}` and we can get you started in no time!'
)
return
this_run = r_query["runs"][0]
# If not new or after draft, create new AI game
is_home = gauntlets.is_home_team(team, this_event, this_run)
opponent = await gauntlets.get_opponent(team, this_event, this_run)
if opponent is None:
await interaction.edit_original_response(
content=f"Yike. I'm not sure who your next opponent is. {get_cal_user(interaction)} help plz!"
)
return
else:
logger.info(f"opponent: {opponent}")
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,
}
)
logger.info(
f'Game {this_game.id} between {team["abbrev"]} and {opponent["abbrev"]} is posted!'
)
await team_role(interaction, main_team)
all_lineups = []
# Get Human SP
human_sp_card = await db_get("cards", object_id=sp_card_id)
if human_sp_card["team"]["id"] != team["id"]:
logger.error(
f'Card_id {sp_card_id} does not belong to {team["abbrev"]} in Game {this_game.id}'
)
await interaction.channel.send(
f'Uh oh. Card ID {sp_card_id} is {team["player"]["p_name"]} and belongs to '
f'{human_sp_card["team"]["sname"]}. Will you double check that before we get started?'
)
return
logger.info(
f'Appending Human SP ({human_sp_card["player"]["p_name"]}) to all_lineups'
)
all_lineups.append(
{
"game_id": this_game.id,
"team_id": team["id"],
"player_id": human_sp_card["player"]["player_id"],
"card_id": sp_card_id,
"position": "P",
"batting_order": 10,
"after_play": 0,
}
)
# Get AI Starting Pitcher
try:
logger.info(f'Getting SP for {opponent["abbrev"]}')
await interaction.edit_original_response(
content="Now to decide on a Starting Pitcher..."
)
if opponent["id"] == this_game.away_team_id:
patch_game(this_game.id, away_roster_num=69, ai_team="away")
else:
patch_game(this_game.id, home_roster_num=69, ai_team="home")
starter = await gauntlets.get_starting_pitcher(
opponent, this_game, this_event, this_run
)
all_lineups.append(starter)
ai_sp = await db_get("players", object_id=starter["player_id"])
this_card = await db_get("cards", object_id=starter["card_id"])
await interaction.channel.send(
content=f'The {opponent["sname"]} are starting **{player_desc(this_card["player"])}**:\n\n'
f'{this_card["player"]["image"]}'
)
except Exception as e:
patch_game(this_game.id, active=False)
logger.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
# Get AI Lineup
try:
await interaction.edit_original_response(
content=f'I am getting a lineup card from the {opponent["sname"]}...'
)
logger.info(f'new-game - calling lineup for {opponent["abbrev"]}')
batters = await gauntlets.build_lineup(
opponent, this_game, this_event, ai_sp["p_name"]
)
all_lineups.extend(batters)
logger.info(f'new-game-gauntlet - got lineup for {opponent["abbrev"]}')
except Exception as e:
patch_game(this_game.id, active=False)
logger.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
logger.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 `/read-lineup` command!",
embed=await self.initialize_play_plus_embed(this_game, full_length=False),
)
return
@group_new_game.command(
name="exhibition", description="Start a new custom game against an AI"
)
@commands.has_any_role(PD_PLAYERS_ROLE_NAME)
async def new_game_exhibition_command(
self,
interaction: discord.Interaction,
away_team_abbrev: str,
home_team_abbrev: str,
sp_card_id: int,
num_innings: Literal[9, 3] = 9,
cardsets: Literal[
"Minor League", "Major League", "Hall of Fame", "Flashback", "Custom"
] = "Custom",
):
await interaction.response.defer()
conflict = get_one_game(channel_id=interaction.channel.id, active=True)
if conflict:
await interaction.edit_original_response(
content="Ope. There is already a game going on in this channel. Please wait for it to complete "
"before starting a new one."
)
return
try:
if interaction.channel.category.name != "Public Fields":
await interaction.response.send_message(
"Why don't you head down to one of the Public Fields that way other humans can help if anything "
"pops up?"
)
return
except Exception as e:
logger.error(f"Could not check channel category: {e}")
away_team = await get_team_by_abbrev(away_team_abbrev)
home_team = await get_team_by_abbrev(home_team_abbrev)
if not away_team:
await interaction.edit_original_response(
content=f"Sorry, I don't know who **{away_team_abbrev.upper()}** is."
)
return
if not home_team:
await interaction.edit_original_response(
content=f"Sorry, I don't know who **{home_team_abbrev.upper()}** is."
)
return
for x in [away_team, home_team]:
if not x["is_ai"]:
conflict = count_team_games(x["id"])
if conflict["count"]:
await interaction.edit_original_response(
content=f'Ope. The {x["sname"]} are already playing over in '
f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}'
)
return
current = await db_get("current")
week_num = current["week"]
# logger.debug(f'away: {away_team} / home: {home_team} / week: {week_num} / ranked: {is_ranked}')
logger.debug(f"away: {away_team} / home: {home_team} / week: {week_num}")
if not away_team["is_ai"] and not home_team["is_ai"]:
logger.error(
f'Exhibition game between {away_team["abbrev"]} and {home_team["abbrev"]} has no AI'
)
await interaction.edit_original_response(
content="I don't see an AI team in this Exhibition game. Run `/new-game mlb-campaign` again with "
"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
human_team = away_team if home_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
league_name = "exhibition"
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,
}
)
logger.info(
f"Game {this_game.id} ({league_name}) between {away_team_abbrev.upper()} and "
f"{home_team_abbrev.upper()} is posted!"
)
away_role = await team_role(interaction, away_team)
home_role = await team_role(interaction, home_team)
all_lineups = [] # Get Human SP
human_sp_card = await db_get("cards", object_id=sp_card_id)
if human_sp_card["team"]["id"] != human_team["id"]:
logger.error(
f'Card_id {sp_card_id} does not belong to {human_team["abbrev"]} in Game {this_game.id}'
)
patch_game(this_game.id, active=False)
await interaction.channel.send(
f'Uh oh. Card ID {sp_card_id} is {human_sp_card["player"]["p_name"]} and belongs to '
f'{human_sp_card["team"]["sname"]}. Will you double check that before we get started?'
)
return
all_lineups.append(
{
"game_id": this_game.id,
"team_id": human_team["id"],
"player_id": human_sp_card["player"]["player_id"],
"card_id": sp_card_id,
"position": "P",
"batting_order": 10,
"after_play": 0,
}
)
async def get_ai_sp_roster(
interaction, this_game, ai_team, home_team, league_name, all_lineups
):
# Get AI Starting Pitcher
try:
await interaction.channel.send(
content="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 = await ai_manager.get_starting_pitcher(
ai_team,
this_game.id,
True if home_team["is_ai"] else False,
league_name,
)
all_lineups.append(starter)
ai_sp = await db_get("players", object_id=starter["player_id"])
this_card = await db_get("cards", object_id=starter["card_id"])
await interaction.channel.send(
content=f'The {ai_team["sname"]} are starting **{player_desc(this_card["player"])}**:\n\n'
f'{player_pcard(this_card["player"])}'
)
except Exception as e:
patch_game(this_game.id, active=False)
logger.error(f'could not start an AI game with {ai_team["sname"]}: {e}')
await interaction.channel.send(
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?"
)
raise KeyError(
f'A Starting Pitcher could not be found for the {ai_team["lname"]}.'
)
# Get AI Lineup
try:
await interaction.channel.send(
content=f'I am getting a lineup card from the {ai_team["sname"]}...'
)
logger.info(f'new-game - calling lineup for {ai_team["abbrev"]}')
batters = await ai_manager.build_lineup(
ai_team, this_game.id, league_name, sp_name=ai_sp["p_name"]
)
all_lineups.extend(batters)
logger.info(f'new-game - got lineup for {ai_team["abbrev"]}')
except Exception as e:
patch_game(this_game.id, active=False)
logger.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
logger.debug(f'Setting lineup for {ai_team["sname"]} in PD game')
logger.debug(f"lineups: {all_lineups}")
post_lineups(all_lineups)
if cardsets in ["Minor League", "Major League", "Hall of Fame", "Flashback"]:
if cardsets == "Minor League":
cardset_ids = "17,8"
backup_cardset_ids = "13"
elif cardsets == "Major League":
cardset_ids = "17,18,13,11,7,8"
backup_cardset_ids = "9,3"
elif cardsets == "Hall of Fame":
all_c = [str(x) for x in range(1, 20)]
cardset_ids = f'{",".join(all_c)}'
backup_cardset_ids = None
else:
# Flashback cardsets
cardset_ids = "11,7,6,12"
backup_cardset_ids = "13,5"
this_game = patch_game(
this_game.id,
cardset_ids=cardset_ids,
backup_cardset_ids=backup_cardset_ids,
)
await get_ai_sp_roster(
interaction, this_game, ai_team, home_team, league_name, 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 `/read-lineup` command!",
embed=await self.initialize_play_plus_embed(
this_game, full_length=False
),
)
else:
async def my_callback(interaction: discord.Interaction, values):
cardset_ids = ",".join(values)
patch_game(this_game.id, cardset_ids=cardset_ids)
# await interaction.response.send_message(
# f'Your selection{"s are" if len(values) > 1 else " is"}: {", ".join(values)}')
await get_ai_sp_roster(
interaction, this_game, ai_team, home_team, league_name, 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 `/read-lineup` command!",
embed=await self.initialize_play_plus_embed(
this_game, full_length=False
),
)
my_dropdown = Dropdown(
option_list=SELECT_CARDSET_OPTIONS,
placeholder="Select up to 8 cardsets to include",
callback=my_callback,
max_values=len(SELECT_CARDSET_OPTIONS),
)
view = DropdownView([my_dropdown])
await interaction.edit_original_response(content=None, view=view)
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.initialize_play_plus_embed(this_game)
)
except Exception as e:
logger.error(f"could not post game state embed: {e}")
question = await ctx.send(
"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("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, this_game.id)
await ctx.send(random_conf_gif())
@app_commands.command(name="end-game", description="End game in this channel")
@app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME)
async def end_game_command(self, interaction: discord.Interaction):
await interaction.response.defer()
this_game = get_one_game(channel_id=interaction.channel.id, active=True)
if not this_game:
await interaction.edit_original_response(
content="Ope, I don't see a game in this channel."
)
return
logger.info(f"Ending Game {this_game.id}")
response = await interaction.edit_original_response(
content="Let's see what we've got here..."
)
owner_team = await get_game_team(this_game, interaction.user.id)
gauntlet_team = None
if "gauntlet" in this_game.game_type:
gauntlet_team = await get_game_team(
this_game, team_abbrev=f'Gauntlet-{owner_team["abbrev"]}'
)
if not {owner_team["id"], gauntlet_team["id"]}.intersection(
[this_game.away_team_id, this_game.home_team_id]
):
await interaction.edit_original_response(
content="Bruh. Only GMs of the active teams can end games."
)
return
elif (
owner_team["id"] not in [this_game.away_team_id, this_game.home_team_id]
and interaction.user.id != self.bot.owner_id
):
await interaction.edit_original_response(
content="Bruh. Only GMs of the active teams can end games."
)
return
latest_play = get_latest_play(this_game.id)
if latest_play is None:
await self.post_stratgame(this_game, forfeit=True)
await send_to_channel(
self.bot,
"pd-network-news",
f'The **{owner_team["lname"]}** made an oopsie-poopsie and had to call off their game down in '
f"{interaction.channel.mention}",
)
this_game = patch_game(this_game.id, active=False)
await interaction.edit_original_response(
content="Roger dodger - it is game over."
)
return
valid_end = False
logger.debug(f"latest play: {latest_play}")
if not this_game.short_game:
if latest_play.starting_outs == 0:
logger.debug("no outs")
if latest_play.inning_half.lower() == "top":
logger.debug("top of inning")
if (
latest_play.inning_num > 9
and latest_play.away_score != latest_play.home_score
):
logger.debug("after the ninth and not tied")
valid_end = True
if abs(latest_play.home_score - latest_play.away_score) >= 10:
logger.debug("not after ninth, but mercy")
valid_end = True
if latest_play.inning_half.lower() == "bot":
if (
latest_play.home_score > latest_play.away_score
) and latest_play.inning_num >= 9:
logger.debug("bottom half and home team winning")
valid_end = True
if abs(latest_play.home_score - latest_play.away_score) >= 10:
logger.debug("bottom half and mercy")
valid_end = True
elif (
abs(latest_play.home_score - latest_play.away_score) >= 10
and latest_play.inning_half.lower() == "bot"
):
logger.info("bottom half and it is a mercy")
valid_end = True
elif (
latest_play.inning_num >= 9
and latest_play.inning_half.lower() == "bot"
and latest_play.home_score > latest_play.away_score
):
valid_end = True
else:
if latest_play.starting_outs == 0:
logger.debug("no outs")
if latest_play.inning_half.lower() == "top":
logger.debug("top of inning")
if (
latest_play.inning_num > 3
and latest_play.away_score != latest_play.home_score
):
logger.debug("after the ninth and not tied")
valid_end = True
if abs(latest_play.home_score - latest_play.away_score) >= 10:
logger.debug("not after ninth, but mercy")
valid_end = True
if latest_play.inning_half.lower() == "bot":
if (
latest_play.home_score > latest_play.away_score
) and latest_play.inning_num >= 3:
logger.debug("bottom half and home team winning")
valid_end = True
if abs(latest_play.home_score - latest_play.away_score) >= 10:
logger.debug("bottom half and mercy")
valid_end = True
elif (
abs(latest_play.home_score - latest_play.away_score) >= 10
and latest_play.inning_half.lower() == "bot"
):
logger.info("bottom half and it is a mercy")
valid_end = True
elif (
latest_play.inning_num >= 3
and latest_play.inning_half.lower() == "bot"
and latest_play.home_score > latest_play.away_score
):
valid_end = True
# valid_end = True # TODO: REMOVE THIS BEFORE GO-LIVE
if not valid_end:
view = Confirm(responders=[interaction.user])
question = await interaction.channel.send(
"It doesn't look like this game is over, yet. I can end it, but no rewards will be paid out and "
"you will take the L.\n\n"
"Should I end this game?",
view=view,
)
await view.wait()
if view.value:
await question.delete() # New database driven stat submission
failure = False
final_game = await self.post_stratgame(this_game)
# Send Plays to db
try:
resp = await self.post_allplays(this_game, final_game["id"])
if not resp:
failure = True
except Exception as e:
logger.error(f"end-game - Could not post plays: {e}")
failure = True
# Send Decisions to db
try:
resp = await self.post_decisions(this_game, final_game["id"])
if not resp:
failure = True
except Exception as e:
logger.error(f"end-game - Could not post decisions: {e}")
failure = True
if failure:
try:
await db_delete("decisions/game", object_id=final_game["id"])
except Exception:
logger.error("could not delete decisions")
try:
await db_delete("plays/game", object_id=final_game["id"])
except Exception:
logger.error("could not delete plays")
try:
await db_delete("games", object_id=final_game["id"])
except Exception:
logger.error("could not delete game")
await interaction.channel.send(
content="That did not go well and I wasn't able to submit this game. I recommend pinging Cal so he "
"can fix this."
)
return
await send_to_channel(
self.bot,
"pd-network-news",
f'The **{owner_team["lname"]}** had to cut out early from their game down in '
f"{interaction.channel.mention}",
)
this_game = patch_game(this_game.id, active=False)
await interaction.edit_original_response(
content="Roger dodger - it is game over."
)
return
else:
await question.edit(content="It stays.", view=None)
return
await response.edit(
content=None,
embed=await self.initialize_play_plus_embed(this_game, full_length=False),
)
view = Confirm(responders=[interaction.user], timeout=60, label_type="yes")
question = await interaction.edit_original_response(
content="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
# New database driven stat submission
failure = False
final_game = await self.post_stratgame(this_game)
# Send Plays to db
try:
resp = await self.post_allplays(this_game, final_game["id"])
if not resp:
failure = True
except Exception as e:
logger.error(f"end-game - Could not post plays: {e}")
failure = True
# Send Decisions to db
try:
resp = await self.post_decisions(this_game, final_game["id"])
if not resp:
failure = True
except Exception as e:
logger.error(f"end-game - Could not post decisions: {e}")
failure = True
if failure:
try:
await db_delete("decisions/game", object_id=final_game["id"])
except Exception:
logger.error("could not delete decisions")
try:
await db_delete("plays/game", object_id=final_game["id"])
except Exception:
logger.error("could not delete plays")
try:
await db_delete("games", object_id=final_game["id"])
except Exception:
logger.error("could not delete game")
await interaction.channel.send(
content="That did not go well and I wasn't able to submit this game. I recommend pinging Cal so he "
"can fix this."
)
return
gs = await db_get(f'plays/game-summary/{final_game["id"]}')
await db_patch(
"games",
object_id=gs["game"]["id"],
params=[
("away_score", gs["runs"]["away"]),
("home_score", gs["runs"]["home"]),
],
)
away_team = gs["teams"]["away"]
home_team = gs["teams"]["home"]
winning_team = (
away_team if latest_play.away_score > latest_play.home_score else home_team
)
losing_team = (
away_team if latest_play.away_score < latest_play.home_score else home_team
)
# Post Game Rewards
r_data = await self.post_rewards(winning_team, losing_team, this_game)
win_reward = r_data["win_string"]
loss_reward = r_data["loss_string"]
# Post a notification to PD
logger.debug("getting inning")
inning = f'{latest_play.inning_num if latest_play.inning_half == "Bot" else latest_play.inning_num - 1}'
embed = get_team_embed(
f'{away_team["lname"]} {latest_play.away_score} @ {latest_play.home_score} {home_team["lname"]} - F/'
f"{inning}",
winning_team,
)
logger.debug("setting location")
embed.add_field(
name="Location",
value=f"{interaction.guild.get_channel(this_game.channel_id).mention}",
)
embed.add_field(name="Game ID", value=f'{final_game["id"]}')
logger.debug("getting league name")
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.game_type == "hall-of-fame":
game_des = "Hall of Fame"
elif this_game.game_type == "flashback":
game_des = "Flashback"
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"}'
)
logger.debug("building box score")
embed.add_field(
name="Box Score",
value=f"```\n"
f"Team | R | H | E |\n"
f'{away_team["abbrev"].replace("Gauntlet-", ""): <4} | {gs["runs"]["away"]: >2} | '
f'{gs["hits"]["away"]: >2} | {gs["errors"]["away"]: >2} |\n'
f'{home_team["abbrev"].replace("Gauntlet-", ""): <4} | {gs["runs"]["home"]: >2} | '
f'{gs["hits"]["home"]: >2} | {gs["errors"]["home"]: >2} |\n'
f"\n```",
inline=False,
)
logger.debug("getting potg string")
tp = gs["top-players"][0]
potg_string = f'{player_desc(tp["player"])} - '
if "hr" in tp:
potg_string += f'{tp["hit"]}-{tp["ab"]}'
if tp["hr"] > 0:
num = f'{tp["hr"]} ' if tp["hr"] > 1 else ""
potg_string += f", {num}HR"
if tp["triple"] > 0:
num = f'{tp["triple"]} ' if tp["triple"] > 1 else ""
potg_string += f", {num}3B"
if tp["double"] > 0:
num = f'{tp["double"]} ' if tp["double"] > 1 else ""
potg_string += f", {num}2B"
if tp["run"] > 0:
potg_string += f', {tp["run"]} R'
if tp["rbi"] > 0:
potg_string += f', {tp["rbi"]} RBI'
else:
potg_string = f'{player_desc(tp["player"])} - {tp["ip"]} IP, {tp["run"]} R'
if tp["run"] != tp["e_run"]:
potg_string += f' ({tp["e_run"]} ER)'
potg_string += f', {tp["hit"]} H, {tp["so"]} K'
potg_string += f', {tp["re24"]:.2f} re24'
embed.add_field(name="Player of the Game", value=potg_string, inline=False)
logger.info(f"potg: {potg_string}")
logger.debug("getting pitcher string")
pit_string = (
f'Win: {gs["pitchers"]["win"]["p_name"]}\n'
f'Loss: {gs["pitchers"]["loss"]["p_name"]}\n'
)
if gs["pitchers"]["save"] is not None:
pit_string += f'Save: {player_desc(gs["pitchers"]["save"])}'
embed.add_field(
name="Pitching",
value=pit_string,
)
def name_list(raw_list: list) -> str:
logger.info(f"raw_list: {raw_list}")
player_dict = {}
for x in raw_list:
if x["player_id"] not in player_dict:
player_dict[x["player_id"]] = x
data_dict = {}
for x in raw_list:
if x["player_id"] not in data_dict:
data_dict[x["player_id"]] = 1
else:
data_dict[x["player_id"]] += 1
r_string = ""
logger.info(f"players: {player_dict} / data: {data_dict}")
first = True
for p_id in data_dict:
r_string += f'{", " if not first else ""}{player_dict[p_id]["p_name"]}'
if data_dict[p_id] > 1:
r_string += f" {data_dict[p_id]}"
first = False
return r_string
logger.info("getting running string")
if len(gs["running"]["sb"]) + len(gs["running"]["csc"]) > 0:
run_string = ""
if len(gs["running"]["sb"]) > 0:
run_string += f'SB: {name_list(gs["running"]["sb"])}\n'
if len(gs["running"]["csc"]) > 0:
run_string += f'CSc: {name_list(gs["running"]["csc"])}'
embed.add_field(name="Baserunning", value=run_string)
logger.info("getting xbh string")
if len(gs["xbh"]["2b"]) + len(gs["xbh"]["3b"]) + len(gs["xbh"]["hr"]) > 0:
bat_string = ""
if len(gs["xbh"]["2b"]) > 0:
bat_string += f'2B: {name_list(gs["xbh"]["2b"])}\n'
if len(gs["xbh"]["3b"]) > 0:
bat_string += f'3B: {name_list(gs["xbh"]["3b"])}\n'
if len(gs["xbh"]["hr"]) > 0:
bat_string += f'HR: {name_list(gs["xbh"]["hr"])}\n'
else:
bat_string = "Oops! All bitches! No XBH from either team."
logger.info("building embed")
embed.add_field(name="Batting", value=bat_string, inline=False)
embed.add_field(name=f'{winning_team["abbrev"]} Rewards', value=win_reward)
embed.add_field(name=f'{losing_team["abbrev"]} Rewards', value=loss_reward)
embed.add_field(
name="Highlights",
value=f'Please share the highlights in {get_channel(interaction, "pd-news-ticker").mention}!',
inline=False,
)
logger.info("sending scorebug")
await send_to_channel(self.bot, "pd-network-news", embed=embed)
# Gauntlet results and reward
if gauntlet_team is not None:
await gauntlets.post_result(
int(this_game.game_type.split("-")[3]),
is_win=winning_team["gmid"] == gauntlet_team["gmid"],
this_team=gauntlet_team,
bot=self.bot,
channel=interaction.channel,
responders=[interaction.user],
)
this_run = await db_get(
"gauntletruns", object_id=int(this_game.game_type.split("-")[3])
)
if this_run["losses"] == 2:
await send_to_channel(
bot=self.bot,
channel_name="pd-network-news",
content=f'The {gauntlet_team["lname"]} won {this_run["wins"]} games before failing in the '
f'{this_run["gauntlet"]["name"]} gauntlet.',
embed=None,
)
patch_game(this_game.id, active=False)
logger.info(f"Game {this_game.id} is complete")
if gauntlet_team is None:
await interaction.channel.send(
content=f"Good game! Go share the highlights in "
f'{get_channel(interaction, "pd-news-ticker").mention}!'
)
"""
END OF THE NEW GAME END
"""
# away_team = await db_get('teams', object_id=this_game.away_team_id)
# home_team = await db_get('teams', object_id=this_game.home_team_id)
#
# away_stats = {
# # 'p_lines': get_pitching_stats(this_game.id, team_id=away_team['id']),
# 'p_lines': [],
# 'b_lines': get_batting_stats(this_game.id, team_id=away_team['id']),
# 'f_lines': get_fielding_stats(this_game.id, team_id=away_team['id'])
# }
# home_stats = {
# # 'p_lines': get_pitching_stats(this_game.id, team_id=home_team['id']),
# 'p_lines': [],
# 'b_lines': get_batting_stats(this_game.id, team_id=home_team['id']),
# 'f_lines': get_fielding_stats(this_game.id, team_id=home_team['id']),
# # 'score': away_stats['p_lines'][0]['tm_runs']
# }
#
# logger.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:
# # logger.info(f'line: {line}')
# this_stats = get_pitching_stats(this_game.id, lineup_id=line.id)
# # logger.info(f'away / this_stats: {this_stats}')
# away_stats['p_lines'].extend(this_stats)
# if 'score' not in home_stats:
# # logger.info(f'score not in home_stats')
# home_stats['score'] = this_stats[0]['pl_runs']
# else:
# # logger.info(f'score is in home_stats')
# home_stats['score'] += this_stats[0]['pl_runs']
# if 'hits' not in home_stats:
# # logger.info(f'hits not in home_stats')
# home_stats['hits'] = this_stats[0]['pl_hit']
# else:
# # logger.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)
# logger.error(f'Unable to process stats for card_id {line.card_id} in Game {this_game.id}: '
# f'{type(e)}: {e}')
# await interaction.edit_original_response(
# content=f'I was not able to process stats for {bad_player["name"]} - '
# f'{get_cal_user(interaction).mention} help!')
#
# home_pitchers = await get_team_lineups(
# this_game.id, team_id=home_team['id'], inc_inactive=True, pitchers_only=True, as_string=False
# )
# for line in home_pitchers:
# try:
# # logger.info(f'line: {line}')
# this_stats = get_pitching_stats(this_game.id, lineup_id=line.id)
# # logger.info(f'home / this_stats: {this_stats}')
# home_stats['p_lines'].extend(this_stats)
# if 'score' not in away_stats:
# # logger.info(f'score not in away_stats')
# away_stats['score'] = this_stats[0]['pl_runs']
# else:
# # logger.info(f'score is in away_stats')
# away_stats['score'] += this_stats[0]['pl_runs']
# if 'hits' not in away_stats:
# # logger.info(f'hits not in away_stats')
# away_stats['hits'] = this_stats[0]['pl_hit']
# else:
# # logger.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)
# logger.error(f'Unable to process stats for card_id {line.card_id} in Game {this_game.id}: '
# f'{type(e)}: {e}')
# await interaction.edit_original_response(
# content=f'I was not able to process stats for {bad_player["name"]} - '
# f'{get_cal_user(interaction).mention} help!'
# )
#
# logger.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:
# logger.error(f'Could not pull decisions for Game {this_game.id}: {e}')
# await interaction.edit_original_response(
# content=f'I was not able to calculate the Winning and Losing Pitcher for this game. Is the game '
# f'ending early? {get_cal_user(interaction).mention} can probably help.'
# )
# return
# logger.debug(f'decisions: {decisions}')
#
# winning_team = away_team if away_stats['score'] > home_stats['score'] else home_team
# losing_team = away_team if away_stats['score'] < home_stats['score'] else home_team
#
# # Post Game Rewards
# r_data = await self.post_rewards(winning_team, losing_team, this_game)
# win_reward = r_data['win_string']
# loss_reward = r_data['loss_string']
#
# # Check for Gauntlet game so rewards go to main team
# if gauntlet_team is not None:
# if winning_team['gmid'] == gauntlet_team['gmid']:
# winning_team = owner_team
# else:
# losing_team = owner_team
#
# logger.debug(f'away_stats (in /endgame function)\n\n{away_stats}')
# logger.debug(f'home_stats (in /endgame function)\n\n{home_stats}')
# logger.debug(f'winning_team: {winning_team}\nlosing_team: {losing_team}')
#
# logger.debug(f'Time to build statlines and submit the scorecard for this PD game!')
# # Post result
# success = await db_post(
# 'results',
# payload={
# 'away_team_id': this_game.away_team_id,
# 'home_team_id': this_game.home_team_id,
# 'away_score': away_stats['score'],
# 'home_score': home_stats['score'],
# 'away_team_ranking': away_team['ranking'],
# 'home_team_ranking': home_team['ranking'],
# 'scorecard': f'Bot Game {this_game.id}',
# 'week': this_game.week_num,
# 'season': this_game.season,
# 'ranked': this_game.ranked,
# 'short_game': this_game.short_game,
# 'game_type': this_game.game_type
# }
# )
# # Submit the stats
# batter_stats = []
# pitcher_stats = []
# doubles = ''
# triples = ''
# homers = ''
# s_bases = ''
# caught_s = ''
#
# for line in [*away_stats['b_lines'], *home_stats['b_lines']]:
# if line['pl_double']:
# if len(doubles):
# doubles += ', '
# card = await db_get("cards", object_id=line["card_id"])
# doubles += f'{card["player"]["p_name"]}' \
# f'{" " if line["pl_double"] > 1 else ""}' \
# f'{line["pl_double"] if line["pl_double"] > 1 else ""}'
# if line['pl_triple']:
# if len(triples):
# triples += ', '
# card = await db_get("cards", object_id=line["card_id"])
# triples += f'{card["player"]["p_name"]}' \
# f'{" " if line["pl_triple"] > 1 else ""}' \
# f'{line["pl_triple"] if line["pl_triple"] > 1 else ""}'
# if line['pl_homerun']:
# if len(homers):
# homers += ', '
# card = await db_get("cards", object_id=line["card_id"])
# homers += f'{card["player"]["p_name"]}' \
# f'{" " if line["pl_homerun"] > 1 else ""}' \
# f'{line["pl_homerun"] if line["pl_homerun"] > 1 else ""}'
# if line['pl_sb']:
# if len(s_bases):
# s_bases += ', '
# card = await db_get("cards", object_id=line["card_id"])
# s_bases += f'{card["player"]["p_name"]}' \
# f'{" " if line["pl_sb"] > 1 else ""}' \
# f'{line["pl_sb"] if line["pl_sb"] > 1 else ""}'
# batter_stats.append(
# {
# 'card_id': line['card_id'],
# 'team_id': line['team_id'],
# 'roster_num':
# this_game.away_roster_num if this_game.away_team_id == line['team_id']
# else this_game.home_roster_num,
# 'vs_team_id':
# home_team['id'] if this_game.away_team_id == line['team_id']
# else away_team['id'],
# 'pos': line['pos'],
# 'pa': line['pl_pa'],
# 'ab': line['pl_ab'],
# 'run': line['pl_run'],
# 'rbi': line['pl_rbi'],
# 'hit': line['pl_hit'],
# 'double': line['pl_double'],
# 'triple': line['pl_triple'],
# 'hr': line['pl_homerun'],
# 'bb': line['pl_bb'],
# 'so': line['pl_so'],
# 'hbp': line['pl_hbp'],
# 'sac': line['pl_sac'],
# 'ibb': line['pl_ibb'],
# 'gidp': line['pl_gidp'],
# 'sb': line['pl_sb'],
# 'cs': line['pl_cs'],
# 'bphr': line['pl_bphr'],
# 'bpfo': line['pl_bpfo'],
# 'bp1b': line['pl_bp1b'],
# 'bplo': line['pl_bplo'],
# 'week': this_game.week_num,
# 'season': this_game.season,
# 'game_id': this_game.id,
# }
# )
#
# for line in [*away_stats['f_lines'], *home_stats['f_lines']]:
# if line['pl_csc']:
# if len(caught_s):
# caught_s += ', '
# card = await db_get("cards", object_id=line["card_id"])
# caught_s += f'{card["player"]["p_name"]}' \
# f'{" " if line["pl_csc"] > 1 else ""}' \
# f'{line["pl_csc"] if line["pl_csc"] > 1 else ""}'
# batter_stats.append(
# {
# 'card_id': line['card_id'],
# 'team_id': line['team_id'],
# 'roster_num':
# this_game.away_roster_num if this_game.away_team_id == line['team_id']
# else this_game.home_roster_num,
# 'vs_team_id':
# home_team['id'] if this_game.away_team_id == line['team_id']
# else away_team['id'],
# 'pos': line['pos'],
# 'xch': line['pl_xch'],
# 'xhit': line['pl_xhit'],
# 'error': line['pl_error'],
# 'pb': line['pl_pb'],
# 'sbc': line['pl_sbc'],
# 'csc': line['pl_csc'],
# 'week': this_game.week_num,
# 'season': this_game.season,
# 'game_id': this_game.id
# }
# )
#
# for line in [*away_stats['p_lines'], *home_stats['p_lines']]:
# pitcher_stats.append(
# {
# 'card_id': line['card_id'],
# 'team_id': line['team_id'],
# 'roster_num':
# this_game.away_roster_num if this_game.away_team_id == line['team_id']
# else this_game.home_roster_num,
# 'vs_team_id':
# home_team['id'] if this_game.away_team_id == line['team_id']
# else away_team['id'],
# 'ip': (math.floor(line['pl_outs'] / 3) * 1.0) + ((line['pl_outs'] % 3) / 3.0),
# 'hit': line['pl_hit'],
# 'run': line['pl_runs'],
# 'erun': line['pl_eruns'],
# 'so': line['pl_so'],
# 'bb': line['pl_bb'],
# 'hbp': line['pl_hbp'],
# 'wp': line['pl_wild_pitch'],
# 'balk': line['pl_balk'],
# 'hr': line['pl_homerun'],
# 'ir': 0,
# 'irs': 0,
# 'gs': 1 if line['card_id'] in decisions['starters'] else 0,
# 'win': 1 if line['card_id'] == decisions['winner'] else 0,
# 'loss': 1 if line['card_id'] == decisions['loser'] else 0,
# 'hold': 1 if line['card_id'] in decisions['holds'] else 0,
# 'sv': 1 if line['card_id'] == decisions['save'] else 0,
# 'bsv': 1 if line['card_id'] in decisions['b_save'] else 0,
# 'week': this_game.week_num,
# 'season': this_game.season,
# 'game_id': this_game.id
# }
# )
#
# doubles += '\n' if len(doubles) else ''
# triples += '\n' if len(triples) else ''
# homers += '\n' if len(homers) else ''
# s_bases += '\n' if len(s_bases) else ''
# caught_s += '\n' if len(caught_s) else ''
#
# await db_post('batstats', payload={'stats': batter_stats})
# await db_post('pitstats', payload={'stats': pitcher_stats})
#
# # Post a notification to PD
# last_play = get_current_play(this_game.id)
# inning = f'{last_play.inning_num if last_play.inning_half == "Bot" else last_play.inning_num - 1}'
# embed = get_team_embed(
# f'{away_team["lname"]} {away_stats["score"]} @ {home_stats["score"]} {home_team["lname"]} - F/'
# f'{inning}',
# winning_team
# )
#
# if this_game.game_type == 'major-league':
# game_des = 'Major League'
# elif this_game.game_type == 'minor-league':
# game_des = 'Minor League'
# elif this_game.game_type == 'hall-of-fame':
# game_des = 'Hall of Fame'
# elif this_game.ranked:
# game_des = 'Ranked'
# elif 'gauntlet' in this_game.game_type:
# game_des = 'Gauntlet'
# else:
# game_des = 'Unlimited'
# embed.description = f'Score Report - {game_des} ' \
# f'{"- 3-Inning Game" if this_game.short_game else " - 9-Inning Game"}'
# embed.add_field(
# name='Box Score',
# value=get_final_scorebug(away_team, home_team, away_stats, home_stats),
# inline=False
# )
# embed.add_field(
# name='Location',
# value=f'{interaction.guild.get_channel(this_game.channel_id).mention}'
# )
# wc_query = await db_get("cards", object_id=decisions["winner"])
# lc_query = await db_get("cards", object_id=decisions["loser"])
# if decisions["save"]:
# sv_query = await db_get("cards", object_id=decisions["save"])
# embed.add_field(
# name='Pitching',
# value=f'Win: {wc_query["player"]["p_name"]}\n'
# f'Loss: {lc_query["player"]["p_name"]}\n'
# f'{"Save: " if decisions["save"] else ""}'
# f'{sv_query["player"]["p_name"] if decisions["save"] else ""}',
# inline=False
# )
# if len(doubles) + len(triples) + len(homers) > 0:
# embed.add_field(
# name='Batting',
# value=f'{"2B: " if len(doubles) else ""}{doubles if len(doubles) else ""}'
# f'{"3B: " if len(triples) else ""}{triples if len(triples) else ""}'
# f'{"HR: " if len(homers) else ""}{homers if len(homers) else ""}',
# inline=False
# )
# if len(s_bases) + len(caught_s) > 0:
# embed.add_field(
# name='Baserunning',
# value=f'{"SB: " if len(s_bases) else ""}{s_bases if len(s_bases) else ""}'
# f'{"CSc: " if len(caught_s) else ""}{caught_s if len(caught_s) else ""}',
# inline=False
# )
# embed.add_field(
# name=f'{winning_team["abbrev"]} Rewards',
# value=win_reward
# )
# embed.add_field(
# name=f'{losing_team["abbrev"]} Rewards',
# value=loss_reward
# )
# embed.add_field(
# name='Highlights',
# value=f'Please share the highlights in {get_channel(interaction, "pd-news-ticker").mention}!',
# inline=False
# )
# await send_to_channel(self.bot, 'pd-network-news', embed=embed)
#
# # Gauntlet results and reward
# if gauntlet_team is not None:
# await gauntlets.post_result(
# int(this_game.game_type.split('-')[3]),
# is_win=winning_team['gmid'] == gauntlet_team['gmid'],
# this_team=gauntlet_team,
# bot=self.bot,
# channel=interaction.channel
# )
#
# this_run = await db_get('gauntletruns', object_id=int(this_game.game_type.split('-')[3]))
# if this_run['losses'] == 2:
# await send_to_channel(
# bot=self.bot,
# channel_name='pd-network-news',
# content=f'The {gauntlet_team["lname"]} won {this_run["wins"]} games before failing in the '
# f'{this_run["gauntlet"]["name"]} gauntlet.',
# embed=None
# )
#
# patch_game(this_game.id, active=False)
#
# logger.info(f'Game {this_game.id} is complete')
# if gauntlet_team is None:
# await interaction.edit_original_response(
# content=f'Good game! Go share the highlights in '
# f'{get_channel(interaction, "pd-news-ticker").mention}!'
# )
@app_commands.command(
name="read-lineup",
description="Import a saved lineup directly from the team sheet for PD games",
)
@app_commands.describe(
roster="Which roster to pull from your sheet?",
lineup="Which handedness lineup are you using?",
)
@app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME)
async def read_lineup_command(
self,
interaction: discord.Interaction,
roster: Literal["Primary", "Secondary", "Ranked"],
lineup: Literal["v Right", "v Left"],
):
this_game = get_one_game(channel_id=interaction.channel_id, active=True)
if not this_game:
await interaction.response.send_message(
"I dont't see an active game in this channel!"
)
return
if not this_game.is_pd:
await interaction.response.send_message(
"Lineup imports are only supported for PD games right now."
"Please use the `/setlineup` command."
)
return
lineup_team = await get_game_team(this_game, interaction.user.id)
logger.debug(f"read_lineup_command - lineup_team: {lineup_team}")
if "gauntlet" in this_game.game_type:
lineup_team = await get_game_team(
this_game, team_abbrev=f'Gauntlet-{lineup_team["abbrev"]}'
)
if lineup_team["id"] not in [this_game.away_team_id, this_game.home_team_id]:
logger.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 pull lineups."
)
return
existing_lineups = await get_team_lineups(
this_game.id, lineup_team["id"], as_string=False
)
logger.debug(f"read_lineup_command - existing_lineups:\n{existing_lineups}")
if len(existing_lineups) > 1:
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(
"Let's put this lineup card together..."
)
if lineup == "v Right":
l_num = 1
else:
l_num = 2
if roster == "Primary":
roster_num = 1
elif roster == "Secondary":
roster_num = 2
else:
roster_num = 3
lineup_cells = get_roster_lineups(lineup_team, self.bot, roster_num, l_num)
all_lineups = []
all_pos = []
card_ids = []
for index, row in enumerate(lineup_cells):
if "" in row:
break
if row[0].upper() not in all_pos:
all_pos.append(row[0].upper())
else:
raise SyntaxError(
f"You have more than one {row[0].upper()} in this lineup. Please "
f"update and set the lineup again."
)
this_card = await db_get("cards", object_id=int(row[1]))
if this_card is None:
raise LookupError(
f"Your {row[0].upper()} has a Card ID or {int(row[1])} and I cannot find that card. Did you sell "
f"that card by chance? Or did you sell a duplicate and the bot sold the one you were using?"
)
if this_card["team"]["id"] != lineup_team["id"]:
raise SyntaxError(
f"Easy there, champ. Looks like card ID {row[1]} belongs to the "
f'{this_card["team"]["sname"]}. Try again with only cards you own.'
)
player_id = this_card["player"]["player_id"]
card_id = row[1]
card_ids.append(str(card_id))
this_lineup = {
"game_id": this_game.id,
"team_id": lineup_team["id"],
"player_id": player_id,
"card_id": card_id,
"position": row[0].upper(),
"batting_order": index + 1,
"after_play": 0,
}
all_lineups.append(this_lineup)
if lineup_team["id"] == this_game.away_team_id:
patch_game(this_game.id, away_roster_num=roster_num)
else:
patch_game(this_game.id, home_roster_num=roster_num)
# Check roster legality
if this_game.game_type in ["major-league", "hall-of-fame"]:
l_check = await legal_check(card_ids, "ranked")
if not l_check["legal"]:
await interaction.edit_original_response(
content=f"It looks like this is a Ranked Legal game and I see the following cards as illegal. "
f"Please adjust your lineup and re-submit!\n\n"
f'- {l_check["error_string"]}'
)
return
if this_game.game_type == "flashback":
l_check = await legal_check(card_ids, "flashback")
if not l_check["legal"]:
patch_game(this_game.id, active=False)
await interaction.edit_original_response(
content=f"It looks like this is a Flashback game and I see the following cards as illegal. "
f"Please adjust your lineup and re-submit!\n\n"
f'- {l_check["error_string"]}'
)
return
logger.debug(f'Setting lineup for {lineup_team["sname"]} in PD game')
post_lineups(all_lineups)
try:
await interaction.channel.send(
content=None, embed=await self.initialize_play_plus_embed(this_game)
)
except IntegrityError as e:
logger.debug(
f"Unable to pull game_state for game_id {this_game.id} until both lineups are in: {e}"
)
await interaction.response.send_message(
"Game state will be posted once both lineups are in"
)
return
@commands.command(name="get-bullpen", help="Mod: Sync an AI bullpen")
@commands.is_owner()
async def get_bullpen_command(self, ctx: commands.Context, team_id):
team = await db_get("teams", object_id=team_id)
if not team:
await ctx.send(f"I did not find a team with id {team_id}")
return
await ctx.send(f'I\'m getting bullpen data from {team["gmname"]}...')
get_or_create_bullpen(team, self.bot)
await ctx.send("Got it!")
group_substitution = app_commands.Group(
name="substitute", description="Make a substitution in active game"
)
@group_substitution.command(name="batter", description="Make a batter substitution")
@app_commands.describe(
batting_order="Batting order of new player",
new_player="Enter the Card ID (a number)",
)
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME)
async def sub_batter_command(
self,
interaction: discord.Interaction,
new_player: int,
batting_order: Literal[
"this-spot", "1", "2", "3", "4", "5", "6", "7", "8", "9"
],
new_pos: Literal[
"P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "DH", "PH", "PR"
],
):
this_game, owner_team, this_play = await self.checks_log_interaction(
interaction
)
if False in (this_game, owner_team, this_play):
return
this_card = await db_get("cards", object_id=int(new_player))
if this_card["team"]["id"] != owner_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
if this_game.game_type in ["major-league", "hall-of-fame", "tens"]:
legality = await db_post(
f"cards/legal-check/{this_game.game_type}?card_id={new_player}"
)
if legality["count"] > 0:
il_string = "\n- ".join(legality["bad_cards"])
await interaction.edit_original_response(
content=f'It looks like this is a {this_game.game_type.replace("-", " ").title()} game and I see '
f"the following cards as illegal. Please take another look and re-submit!\n\n"
f"- {il_string}"
)
return
batting_order = (
int(batting_order)
if batting_order != "this-spot"
else this_play.batting_order
)
# Check for simple position change
in_lineup = get_one_lineup(
this_game.id,
team_id=owner_team["id"],
active=True,
batting_order=batting_order,
)
if in_lineup.card_id == int(card_id):
patch_lineup(in_lineup.id, position=new_pos)
else:
this_lineup = {
"game_id": this_game.id,
"team_id": owner_team["id"],
"player_id": player_id,
"card_id": card_id,
"position": new_pos.upper(),
"batting_order": batting_order,
"after_play": this_play.play_num - 1 if this_play else 0,
}
make_sub(this_lineup)
await interaction.edit_original_response(
content=None, embed=await self.initialize_play_plus_embed(this_game)
)
@group_substitution.command(name="pitcher", description="Make a pitching change")
@app_commands.describe(
new_player="Enter the Card ID (a number)",
batting_order="Only enter this if you are forfeiting the DH",
)
@commands.has_any_role(PD_PLAYERS_ROLE_NAME)
async def sub_pitcher_command(
self,
interaction: discord.Interaction,
new_player: int,
batting_order: Literal[
"this-spot", "1", "2", "3", "4", "5", "6", "7", "8", "9"
] = None,
):
this_game, owner_team, this_play = await self.checks_log_interaction(
interaction
)
if False in (this_game, owner_team, this_play):
return
this_card = await db_get("cards", object_id=int(new_player))
if this_card["team"]["id"] != owner_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
new_post = False
# Check for simple position change
in_lineup = get_one_lineup(
this_game.id, team_id=owner_team["id"], active=True, card_id=card_id
)
if in_lineup is not None:
new_pitcher = patch_lineup(in_lineup.id, position="P")
view = Confirm(responders=[interaction.user], timeout=60, label_type="yes")
q_text = (
f'It looks like you are forfeiting the DH by moving {player_desc(this_card["player"])} to '
f"the mound. __You will not be able to undo this sub even with the undo-play command__ so real "
f"quick: are you sure?"
)
question = await interaction.channel.send(q_text, view=view)
await view.wait()
if view.value:
await question.edit(
content=f"~~{q_text}~~\nFuck the DH \U0000270a", view=None
)
else:
await question.delete()
await interaction.edit_original_response(
content=f"~~{q_text}~~\nI will hold off for now."
)
await interaction.channel.send(
content=None, embed=await self.initialize_play_plus_embed(this_game)
)
return
old_pitcher = get_pitcher(this_game, this_play)
patch_lineup(old_pitcher.id, active=False)
patch_play(this_play.id, pitcher_id=new_pitcher.id)
this_play.pitcher = new_pitcher
else:
if this_play is None:
after_play = 0
old_pit_order = 10
if batting_order is None:
batting_order = 10
else:
batting_order = int(batting_order)
else:
if this_play.pitcher.team_id != owner_team["id"]:
await interaction.edit_original_response(
content="It looks like your team is batting right now - "
"please make this sub once you take the field."
)
return
old_pit_order = this_play.pitcher.batting_order
if batting_order is None:
batting_order = old_pit_order
else:
batting_order = int(batting_order)
after_play = this_play.play_num - 1
this_lineup = {
"game_id": this_game.id,
"team_id": owner_team["id"],
"player_id": player_id,
"card_id": card_id,
"position": "P",
"batting_order": batting_order,
"after_play": after_play,
}
make_sub(this_lineup)
if old_pit_order != batting_order:
patch_lineup(this_play.pitcher.id, active=False)
if new_post:
await interaction.channel.send(
content=None, embed=await self.initialize_play_plus_embed(this_game)
)
else:
await interaction.edit_original_response(
content=None, embed=await self.initialize_play_plus_embed(this_game)
)
# @group_substitution.command(name='forfeit-dh', description='Have the pitcher bat in the DH spot')
# @commands.has_any_role(PD_PLAYERS_ROLE_NAME)
# async def sub_forfeitdh_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
#
@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("I dont't see an active game in this channel!")
return
response = await ctx.send("Building the scorebug now...")
await response.edit(
content=None,
embed=await self.initialize_play_plus_embed(
this_game, full_length=include_lineups
),
)
async def checks_log_interaction(
self, interaction: discord.Interaction, block_rollback: Optional[bool] = False
) -> tuple[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 owner_team["id"] not in [this_game.away_team_id, this_game.home_team_id]:
logger.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)
if this_play is not None:
# Allow specific commands to not rollback the play (e.g. /show-card)
if this_play.locked and not block_rollback:
logger.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="Looks like your game got hung up there for a second. I just rolled it back a play to "
"release it. If you have any more issues, let Cal know."
)
this_play = get_current_play(this_game.id)
if not this_play.pitcher:
logger.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="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["id"] == home_team["id"] 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
view = Confirm(responders=[interaction.user], timeout=60, label_type="yes")
question = await interaction.channel.send(
"Whoa there, cowpoke. The AI can automatically make pitcher subs now and will let pitchers cook in some "
"cases. Are you sure you want to manually run a sub for them?",
view=view,
)
await view.wait()
if view.value:
await question.edit(
content="~~Whoa there, cowpoke. The AI can automatically make pitcher subs now and will let pitchers "
"cook in some cases. Are you sure you want to manually run a sub for them?~~\n\nI let Cal "
"know his stupid AI isn't working.",
view=None,
)
await send_to_channel(
self.bot,
"commissioners-office",
content=f"{interaction.user.display_name} just ran a manual sub here: {question.jump_url}",
)
else:
await question.delete()
await interaction.edit_original_response(
content="Jkjkjk, we will let the ai decide when to make subs.",
embed=await self.initialize_play_plus_embed(
this_game, full_length=False
),
)
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)
logger.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["p_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="Okay, I'll leave them in!"
)
return
# new_pitcher = await next_pitcher(this_play, ai_team, self.bot)
# logger.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)
await get_team_lineups(
game_id=this_play.game.id,
team_id=ai_team["id"],
inc_inactive=True,
as_string=False,
)
make_sub(
await ai_manager.get_relief_pitcher(this_play, ai_team, this_game.game_type)
)
await interaction.edit_original_response(
content=None,
embed=await self.initialize_play_plus_embed(this_game, full_length=False),
)
@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)
if flyball_type not in ["a", "c"] and this_play.on_base_code > 0:
await interaction.edit_original_response(content="Flyball has been logged")
await interaction.channel.send(
content=None,
embed=await self.initialize_play_plus_embed(
this_game, full_length=False
),
)
else:
await interaction.edit_original_response(
content=None,
embed=await self.initialize_play_plus_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.initialize_play_plus_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)
await db_get("teams", object_id=this_play.pitcher.team_id)
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)}*"
)
logger.debug("calling of embed")
await show_outfield_cards(interaction, this_play)
logger.debug("done with of embed")
view = Confirm(
responders=[interaction.user], timeout=60, label_type="yes"
)
question = await interaction.channel.send(
f'Is {lead_runner["p_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["p_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["p_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["p_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["p_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()
logger.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)
if single_type == "uncapped":
await interaction.edit_original_response(
content="Uncapped single has been logged"
)
await interaction.channel.send(
content=None,
embed=await self.initialize_play_plus_embed(
this_game, full_length=False
),
)
else:
await interaction.edit_original_response(
content=None,
embed=await self.initialize_play_plus_embed(
this_game, full_length=False
),
)
@group_log.command(
name="frame-pitch",
description="Walk/strikeout split; determined by home plate umpire",
)
async def log_frame_check(self, interaction: discord.Interaction):
this_game, owner_team, this_play = await self.checks_log_interaction(
interaction
)
if False in (this_game, owner_team, this_play):
return
patch_play(this_play.id, locked=True)
frame_result = dice.frame_plate_check(owner_team, this_game.id)
logger.info(f"Frame Result:\n{frame_result}")
await interaction.edit_original_response(
content=None, embed=frame_result["embed"]
)
if frame_result["is_walk"]:
advance_runners(this_play.id, num_bases=1, only_forced=True)
patch_play(this_play.id, pa=1, walk=1)
batter_to_base = 1
else:
patch_play(this_play.id, pa=1, ab=1, outs=1, so=1)
advance_runners(this_play.id, 0)
batter_to_base = None
complete_play(this_play.id, batter_to_base=batter_to_base)
await interaction.channel.send(
content=None,
embed=await self.initialize_play_plus_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)
this_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 = "*The runner will attempt to advance*"
else:
ai_hint = (
f"*The runner will "
f"{this_manager.uncapped_advance(4, this_play.starting_outs)}*"
)
logger.debug("calling of embed")
await show_outfield_cards(interaction, this_play)
logger.debug("done with of embed")
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["p_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 {this_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"{this_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["p_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"{this_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["p_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["p_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:
batter_to_base = 3
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["p_name"]} thrown out at the plate?',
view=view,
)
await view.wait()
# Out at the plate
if view.value:
await question.delete()
patch_play(this_play.id, on_first_final=False, outs=1)
# Safe at the plate
else:
await question.delete()
advance_runners(this_play.id, num_bases=3)
# No: End of play
else:
await question.delete()
advance_runners(this_play.id, num_bases=3)
else:
await question.delete()
complete_play(this_play.id, batter_to_base=batter_to_base)
if double_type == "uncapped":
await interaction.edit_original_response(
content="Uncapped double has been logged"
)
await interaction.channel.send(
content=None,
embed=await self.initialize_play_plus_embed(
this_game, full_length=False
),
)
else:
await interaction.edit_original_response(
content=None,
embed=await self.initialize_play_plus_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.initialize_play_plus_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.initialize_play_plus_embed(this_game, full_length=False),
)
@group_log.command(name="walk", description="Walks: unintentional, intentional")
async def log_walk(
self,
interaction: discord.Interaction,
walk_type: Literal["unintentional", "intentional"],
):
this_game, owner_team, this_play = await self.checks_log_interaction(
interaction
)
if False in (this_game, owner_team, this_play):
return
patch_play(this_play.id, locked=True)
advance_runners(this_play.id, num_bases=1, only_forced=True)
if walk_type == "unintentional":
patch_play(this_play.id, pa=1, walk=1)
elif walk_type == "intentional":
patch_play(this_play.id, pa=1, ibb=1)
complete_play(this_play.id, batter_to_base=1)
await interaction.edit_original_response(
content=None,
embed=await self.initialize_play_plus_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["p_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["p_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["p_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="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.initialize_play_plus_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 == 4 and this_play.on_third:
patch_play(
this_play.id,
sb=1,
on_third_final=4,
runner_id=this_play.on_third.id,
catcher_id=catcher.id,
)
if this_play.on_second:
advance_one_runner(this_play.id, 2, 1)
if this_play.on_first:
advance_one_runner(this_play.id, 1, 1)
elif to_base == 3 and this_play.on_second:
if not this_play.on_third:
advance_runners(this_play.id, 1, is_error=True)
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["p_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["p_name"]} is blocked by the runner at second.'
)
patch_play(this_play.id, locked=False)
return
else:
await interaction.edit_original_response(
content="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 == 4 and this_play.on_third:
patch_play(
this_play.id,
sb=1,
on_third_final=4,
runner_id=this_play.on_third.id,
catcher_id=catcher.id,
)
if this_play.on_second:
advance_one_runner(this_play.id, 2, 2)
if this_play.on_first:
advance_one_runner(this_play.id, 1, 2)
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["p_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["p_name"]} is blocked by the runner at second.'
)
patch_play(this_play.id, locked=False)
return
else:
await interaction.edit_original_response(
content="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 == 4 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,
)
if this_play.on_second:
advance_one_runner(this_play.id, 2, 1)
if this_play.on_first:
advance_one_runner(this_play.id, 1, 1)
elif to_base == 3 and this_play.on_second:
if not this_play.on_third:
advance_runners(this_play.id, 1)
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["p_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["p_name"]} is blocked by the runner at second.'
)
patch_play(this_play.id, locked=False)
return
else:
await interaction.edit_original_response(
content="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.initialize_play_plus_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.initialize_play_plus_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.initialize_play_plus_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["p_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.initialize_play_plus_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.initialize_play_plus_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.initialize_play_plus_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:
logger.debug(
f"Undoing play {this_play.id} in Game {this_game.id}: Batter {this_play.batter}"
)
undo_play(this_game.id)
except AttributeError:
logger.error(f"Could not undo play {this_play.id} in game {this_game.id}")
try:
last_completed = get_latest_play(this_game.id)
logger.debug(f"Undoing play {last_completed.id} in Game {this_game.id}")
undo_play(this_game.id)
except AttributeError:
logger.error(f"Could not undo second play in game {this_game.id}")
latest_play = get_latest_play(this_game.id)
logger.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.initialize_play_plus_embed(this_game, full_length=False),
)
@group_log.command(name="xcheck", description="Defender makes an x-check")
@app_commands.choices(
position=[
Choice(name="Pitcher", value="P"),
Choice(name="Catcher", value="C"),
Choice(name="First Base", value="1B"),
Choice(name="Second Base", value="2B"),
Choice(name="Third Base", value="3B"),
Choice(name="Shortstop", value="SS"),
Choice(name="Left Field", value="LF"),
Choice(name="Center Field", value="CF"),
Choice(name="Right Field", value="RF"),
]
)
async def log_xcheck_command(
self, interaction: discord.Interaction, position: Choice[str]
):
this_game, owner_team, this_play = await self.checks_log_interaction(
interaction
)
if False in (this_game, owner_team, this_play):
return
patch_play(this_play.id, locked=False)
def_team_id = (
this_game.away_team_id
if this_play.inning_half == "Bot"
else this_game.home_team_id
)
def_team = await db_get("teams", object_id=def_team_id)
d_lineup = get_one_lineup(
this_game.id, team_id=this_play.pitcher.team_id, position=position.value
)
defender = await get_player(this_game, d_lineup)
patch_play(this_play.id, defender_id=d_lineup.id, check_pos=position.value)
defender_embed = get_team_embed(
f'{def_team["sname"]} {position.value}', def_team
)
defender_embed.description = f'{defender["p_name"]}'
defender_embed.set_image(url=defender["image"])
embeds = [defender_embed]
dice_embeds = sa_fielding_roll_legacy(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 = None, None
view = Confirm([interaction.user], label_type="yes")
question = await interaction.channel.send(
f'Did {defender["p_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["p_name"]} allow?', view=view
)
await view.wait()
if not view.value:
await question.edit(
"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["p_name"]} give up an error or rare play?', view=view
)
await view.wait()
if view.value:
await question.delete()
view = ButtonOptions(
responders=[interaction.user],
labels=["1 base", "2 bases", "3 bases", "Rare Play", None],
)
question = await interaction.channel.send(
f'What kind of error did {defender["p_name"]} allow?', view=view
)
await view.wait()
if not view.value:
await question.edit(
"Hmm...you keep thinking on it and get back to me when you're ready."
)
return
else:
await question.delete()
error_allowed = view.value
else:
await question.delete()
error_allowed = "no error"
# Not hit and no error
if hit_allowed == "out" and error_allowed == "no error":
if this_play.on_base_code == 0:
patch_play(this_play.id, pa=1, ab=1, outs=1)
advance_runners(this_play.id, 0)
complete_play(this_play.id)
else:
if position.value not in ["LF", "CF", "RF"]:
view = ButtonOptions(
responders=[interaction.user],
labels=[
"G1",
"G2",
"G3",
None if position.value != "C" else "FO",
None if position.value != "C" else "PO",
],
)
question = await interaction.channel.send(
"What was the result of the play?", view=view
)
logger.info(f"obc: {this_play.on_base_code}")
await view.wait()
logger.info(f"gameplay - view: {view} / view.value: {view.value}")
gb_type = view.value
logger.info(f"gameplay - gb_type: {gb_type}")
if not view.value:
await question.edit("Okay, we can try this again later.")
else:
await question.delete()
playing_in = False
batter_to_base = None
if runner_on_third(this_play) and gb_type not in ["FO", "PO"]:
view = Confirm(
responders=[interaction.user],
timeout=60,
label_type="yes",
)
question = await interaction.channel.send(
"Was the defender playing in?", view=view
)
await view.wait()
if view.value:
playing_in = True
else:
playing_in = False
await question.delete()
logger.info(
f"bot playing_in: {playing_in} / view.value: {view.value} / gb_type: {gb_type}"
)
if gb_type == "G1":
if (not playing_in and this_play.on_base_code == 0) or (
playing_in and (this_play.on_base_code in [3, 6])
):
batter_to_base = gb_result_1(this_play)
elif playing_in and this_play.on_base_code == 5:
batter_to_base = gb_result_7(this_play)
elif (
not playing_in and this_play.on_base_code in [1, 5, 7]
) or (playing_in and (this_play.on_base_code in [1, 4])):
batter_to_base = gb_result_2(this_play)
elif not playing_in and this_play.on_base_code in [3, 6]:
batter_to_base = gb_result_3(this_play)
elif this_play.on_base_code == 2:
if position.value == "3B":
this_def = "3b"
elif position.value in ["1B", "2B"]:
this_def = "1b-2b"
else:
this_def = "ss-p-c"
batter_to_base = await gb_result_12(
this_play, this_def, interaction
)
elif playing_in and this_play.on_base_code == 7:
batter_to_base = gb_result_10(this_play)
elif not playing_in and this_play.on_base_code == 4:
if position.value in ["C", "3B"]:
this_def = "c-3b"
else:
this_def = "else"
batter_to_base = gb_result_13(this_play, this_def)
elif gb_type == "G2":
if (not playing_in and this_play.on_base_code == 0) or (
playing_in and this_play.on_base_code in [3, 6]
):
batter_to_base = gb_result_1(this_play)
elif playing_in and this_play.on_base_code == 5:
batter_to_base = gb_result_7(this_play)
elif (
not playing_in
and this_play.on_base_code in [1, 4, 5, 7]
) or (playing_in and (this_play.on_base_code in [1, 4])):
batter_to_base = gb_result_4(this_play)
elif not playing_in and this_play.on_base_code in [3, 6]:
if position.value in ["SS", "2B"]:
to_mif = True
else:
to_mif = False
batter_to_base = gb_result_5(this_play, to_mif)
elif playing_in and this_play.on_base_code == 7:
batter_to_base = gb_result_11(this_play)
elif this_play.on_base_code == 2:
if position.value == "3B":
this_def = "3b"
elif position.value in ["1B", "2B"]:
this_def = "1b-2b"
else:
this_def = "ss-p-c"
batter_to_base = await gb_result_12(
this_play, this_def, interaction
)
elif gb_type == "G3":
if not playing_in and this_play.on_base_code == 0:
batter_to_base = gb_result_1(this_play)
elif playing_in and this_play.on_base_code == 5:
batter_to_base = gb_result_7(this_play)
elif playing_in and this_play.on_base_code == 7:
batter_to_base = gb_result_11(this_play)
elif not playing_in and this_play.on_base_code == 2:
if position.value == "3B":
this_def = "3b"
elif position.value in ["1B", "2B"]:
this_def = "1b-2b"
else:
this_def = "ss-p-c"
batter_to_base = await gb_result_12(
this_play, this_def, interaction
)
elif playing_in and this_play.on_base_code in [3, 6]:
batter_to_base = await gb_decide(
this_play,
interaction,
await get_player(
this_play.game, this_play.on_third
),
3,
)
else:
batter_to_base = gb_result_3(this_play)
else:
logger.info("no match; log out")
patch_play(this_play.id, pa=1, ab=1, outs=1)
advance_runners(this_play.id, 0)
complete_play(this_play.id, batter_to_base)
else:
view = ButtonOptions(
responders=[interaction.user],
labels=["F1", "F2", "F3", None, None],
)
question = await interaction.channel.send(
"What was the result of the play?", view=view
)
await view.wait()
if not view.value:
await question.delete()
await interaction.channel.send(
content="Just logged the x-check! Please log the resulting play to continue (e.g. "
"'flyball-b' or 'flyball-c')"
)
patch_play(this_play.id, locked=False)
return
else:
await question.delete()
if view.value == "F1":
fly_code = "a"
elif view.value == "F2":
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 == "Rare Play":
patch_play(this_play.id, outs=1)
batter_to_base = None
elif 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
elif hit_allowed == "single**":
single_wellhit(this_play, comp_play=False)
if error_allowed == "Rare Play":
patch_play(this_play.id, outs=1)
batter_to_base = None
elif error_allowed == "1 base":
batter_to_base = 2
else:
advance_runners(this_play.id, 3)
batter_to_base = 3
else:
single_onestar(this_play, False)
if error_allowed == "2 bases":
advance_runners(this_play.id, 3, True)
batter_to_base = 3
elif error_allowed == "1 base" or (
error_allowed == "Rare Play" and not runner_on_first(this_play)
):
single_wellhit(this_play, False)
batter_to_base = 2
patch_play(this_play.id, error=True)
else:
batter_to_base = 1
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)
elif error_allowed == "Rare Play":
if position.value not in ["LF", "CF", "RF"]:
view = ButtonOptions(
responders=[interaction.user],
labels=[
"G1",
"G2",
"G3",
None if position.value != "C" else "FO",
None if position.value != "C" else "PO",
],
)
question = await interaction.channel.send(
"What was the result of the play?", view=view
)
logger.info(f"obc: {this_play.on_base_code}")
await view.wait()
logger.info(f"gameplay - view: {view} / view.value: {view.value}")
gb_type = view.value
logger.info(f"gameplay - gb_type: {gb_type}")
if not view.value:
await question.edit("Okay, we can try this again later.")
else:
await question.delete()
if gb_type == "G1":
if runner_on_first(this_play):
await self.groundballs(
interaction, this_game, this_play, "b"
)
else:
await self.groundballs(
interaction, this_game, this_play, "a"
)
elif gb_type == "G2":
if this_play.on_base_code != 0:
await self.groundballs(
interaction, this_game, this_play, "c"
)
else:
await self.groundballs(
interaction, this_game, this_play, "b"
)
elif gb_type == "G3":
if this_play.on_base_code != 0:
await single_onestar(this_play)
else:
await self.groundballs(
interaction, this_game, this_play, "c"
)
elif gb_type == "PO":
single_onestar(this_play)
elif gb_type == "FO":
advance_runners(this_play.id, num_bases=1, only_forced=True)
patch_play(this_play.id, pa=1, ab=1, hit=0, error=1)
complete_play(this_play.id, batter_to_base=1)
else:
view = ButtonOptions(
responders=[interaction.user],
labels=["F1", "F2", "F3", None, None],
)
question = await interaction.channel.send(
"What was the result of the play?", view=view
)
await view.wait()
if not view.value:
await question.delete()
await interaction.channel.send(
content="Just logged the x-check! Please log the resulting play to continue (e.g. "
"'flyball-b' or 'flyball-c')"
)
patch_play(this_play.id, locked=False)
return
else:
await question.delete()
if view.value == "F1":
fly_code = "a"
elif view.value == "F2":
fly_code = "b"
else:
fly_code = "c"
if this_play.starting_outs == 2 or this_play.on_base_code == 0:
await self.flyballs(
interaction, this_game, this_play, fly_code
)
else:
if fly_code == "a":
patch_play(this_play.id, pa=1, ab=1, outs=1)
advance_runners(this_play.id, 2)
if this_play.on_third or this_play.on_second:
patch_play(this_play.id, ab=0)
elif fly_code == "b":
if runner_on_third(this_play):
patch_play(
this_play.id,
pa=1,
ab=1,
outs=2,
on_third_final=False,
)
else:
await self.flyballs(
interaction,
this_game,
this_play,
fly_code,
comp_play=False,
)
elif fly_code == "c":
patch_play(this_play.id, pa=1, ab=1, outs=2)
if runner_on_third(this_play):
patch_play(this_play.id, on_third_final=False)
elif runner_on_second(this_play):
patch_play(this_play.id, on_second_final=False)
else:
patch_play(this_play.id, on_first_final=False)
complete_play(this_play.id)
else:
if position.value == "C":
view = ButtonOptions(
responders=[interaction.user],
labels=["G1/G2/G3/SPD", "FO", "PO"],
)
question = await interaction.channel.send(
"What was the result of the play?", view=view
)
await view.wait()
if not view.value:
await question.edit(
content="Hmm...you keep thinking on it and get back to me when you're ready.",
view=None,
)
return
elif view.value == "FO":
await question.edit(
content="**The ball dropped foul; batter swings again.**",
view=None,
)
patch_play(this_play.id, defender_id=False, check_pos="false")
await interaction.channel.send(
content=None,
embed=await self.initialize_play_plus_embed(
this_game, full_length=False
),
)
return
else:
await question.delete()
patch_play(this_play.id, error=1)
if error_allowed == "1 base":
num_bases = 1
elif error_allowed == "2 bases":
num_bases = 2
elif error_allowed == "3 bases":
num_bases = 3
advance_runners(this_play.id, num_bases=num_bases, is_error=True)
complete_play(this_play.id, batter_to_base=num_bases)
patch_play(this_play.id, locked=False)
await interaction.channel.send(
content=None,
embed=await self.initialize_play_plus_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
# )
# logger.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),
),
)
def_team_id = (
this_game.away_team_id
if this_play.inning_half == "Bot"
else this_game.home_team_id
)
def_team = await db_get("teams", object_id=def_team_id)
embed = get_team_embed(f'{def_team["sname"]} {position}', def_team)
embed.description = f'{defender["p_name"]}'
embed.set_image(url=defender["image"])
if this_game.is_pd:
embed.set_footer(
text=f"Paper Dynasty 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="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"Paper Dynasty 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))