Add unlimited new-game

Add pitcher substitution
Add AI pitcher subs
This commit is contained in:
Cal Corum 2025-02-01 21:32:40 -06:00
parent e850ee5519
commit 7d54d9ea34
3 changed files with 184 additions and 17 deletions

View File

@ -13,7 +13,7 @@ import sqlalchemy
from sqlmodel import func, or_ from sqlmodel import func, or_
from api_calls import db_get from api_calls import db_get
from command_logic.logic_gameplay import bunts, chaos, complete_game, doubles, flyballs, frame_checks, get_full_roster_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, groundballs, hit_by_pitch, homeruns, is_game_over, lineouts, manual_end_game, new_game_checks, new_game_conflicts, popouts, read_lineup, select_ai_reliever, show_defense_cards, singles, starting_pitcher_dropdown_view, steals, strikeouts, sub_batter_dropdown_view, substitute_player, triples, undo_play, update_game_settings, walks, xchecks, activate_last_play from command_logic.logic_gameplay import bunts, chaos, complete_game, doubles, flyballs, frame_checks, get_full_roster_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, groundballs, hit_by_pitch, homeruns, is_game_over, lineouts, manual_end_game, new_game_checks, new_game_conflicts, popouts, read_lineup, relief_pitcher_dropdown_view, select_ai_reliever, show_defense_cards, singles, starting_pitcher_dropdown_view, steals, strikeouts, sub_batter_dropdown_view, substitute_player, triples, undo_play, update_game_settings, walks, xchecks, activate_last_play
from dice import ab_roll from dice import ab_roll
from exceptions import * from exceptions import *
import gauntlets import gauntlets
@ -23,9 +23,9 @@ from helpers import CARDSETS, DEFENSE_LITERAL, PD_PLAYERS_ROLE_NAME, SELECT_CARD
from in_game.ai_manager import get_starting_pitcher, get_starting_lineup from in_game.ai_manager import get_starting_pitcher, get_starting_lineup
from in_game.game_helpers import PUBLIC_FIELDS_CATEGORY_NAME, legal_check from in_game.game_helpers import PUBLIC_FIELDS_CATEGORY_NAME, legal_check
from in_game.gameplay_models import GameCardsetLink, Lineup, Play, Session, engine, player_description, select, Game from in_game.gameplay_models import GameCardsetLink, Lineup, Play, Session, engine, player_description, select, Game
from in_game.gameplay_queries import get_cardset_or_none, get_one_lineup, get_position, get_channel_game_or_none, get_active_games_by_team, get_game_lineups, get_team_or_none from in_game.gameplay_queries import get_cardset_or_none, get_one_lineup, get_plays_by_pitcher, get_position, get_channel_game_or_none, get_active_games_by_team, get_game_lineups, get_team_or_none
from utilities.buttons import Confirm, ScorebugButtons, ask_confirm from utilities.buttons import Confirm, ScorebugButtons, ask_confirm, ask_with_buttons
from utilities.dropdown import DropdownView from utilities.dropdown import DropdownView
@ -434,7 +434,7 @@ class Gameplay(commands.Cog):
channel_id=interaction.channel_id, channel_id=interaction.channel_id,
season=current['season'], season=current['season'],
week=current['week'], week=current['week'],
first_message=None if interaction.message is None else interaction.message.channel.id, first_message=None if interaction.message is None else interaction.message.id,
ai_team='away' if is_home else 'home', ai_team='away' if is_home else 'home',
away_roster_id=69 if is_home else int(roster.value), away_roster_id=69 if is_home else int(roster.value),
home_roster_id=int(roster.value) if is_home else 69, home_roster_id=int(roster.value) if is_home else 69,
@ -552,7 +552,7 @@ class Gameplay(commands.Cog):
channel_id=interaction.channel_id, channel_id=interaction.channel_id,
season=current['season'], season=current['season'],
week=week_num, week=week_num,
first_message=None if interaction.message is None else interaction.message.channel.id, first_message=None if interaction.message is None else interaction.message.id,
ai_team='away' if away_team.is_ai else 'home', ai_team='away' if away_team.is_ai else 'home',
game_type='exhibition' game_type='exhibition'
) )
@ -689,8 +689,106 @@ class Gameplay(commands.Cog):
view=view view=view
) )
# TODO: add new-game unlimited, and ranked # TODO: add new-game unlimited, and ranked
@group_new_game.command(name='unlimited', description='Start a new Unlimited game against another human')
@app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME)
async def new_game_unlimited_command(self, interaction: discord.Interaction, away_team_abbrev: str, home_team_abbrev: str):
await interaction.response.defer()
with Session(engine) as session:
teams = await new_game_checks(session, interaction, away_team_abbrev, home_team_abbrev)
if teams is None:
logger.error(f'Received None from new_game_checks, cancelling new game')
return
away_team = teams['away_team']
home_team = teams['home_team']
if away_team.is_ai or home_team.is_ai:
await interaction.edit_original_response(
content=f'Unlimited games are for two human-run teams. To play against the AI, you can play `mlb-campaign`, `gauntlet`, or `exhibition` game modes.'
)
return
current = await db_get('current')
week_num = current['week']
logger.info(f'gameplay - new_game_unlimited - Season: {current["season"]} / Week: {week_num} / Away Team: {away_team.description} / Home Team: {home_team.description}')
this_game = Game(
away_team_id=away_team.id,
home_team_id=home_team.id,
away_roster_id=None,
home_roster_id=None,
channel_id=interaction.channel_id,
season=current['season'],
week=week_num,
first_message=None if interaction.message is None else interaction.message.id,
game_type='exhibition'
)
await interaction.edit_original_response(
content=f'Let\'s get set up for **{away_team.lname}** @ **{home_team.lname}**!'
)
away_role = await team_role(interaction, away_team)
home_role = await team_role(interaction, home_team)
away_roster_id = await ask_with_buttons(
interaction=interaction,
button_options=[
'Primary', 'Secondary', 'Ranked'
],
question=f'{away_role.mention}\nWhich roster should I pull for you?',
delete_question=False,
confirmation_message=f'Got it! As soon as the {home_team.sname} select their roster, I will pull them both in at once.'
)
home_roster_id = await ask_with_buttons(
interaction=interaction,
button_options=[
'Primary', 'Secondary', 'Ranked'
],
question=f'{home_role.mention}\nWhich roster should I pull for you?',
delete_question=False,
confirmation_message=f'Got it! Off to Sheets I go for the {away_team.abbrev} roster...'
)
if away_roster_id and home_roster_id:
if away_roster_id == 'Primary':
away_roster_id = 1
elif away_roster_id == 'Secondary':
away_roster_id = 2
else:
away_roster_id = 3
if home_roster_id == 'Primary':
home_roster_id = 1
elif home_roster_id == 'Secondary':
home_roster_id = 2
else:
home_roster_id = 3
logger.info(f'Setting roster IDs - away: {away_roster_id} / home: {home_roster_id}')
this_game.away_roster_id = away_roster_id
this_game.home_roster_id = home_roster_id
session.add(this_game)
session.commit()
logger.info(f'Pulling away team\'s roster')
away_roster = await get_full_roster_from_sheets(session, interaction, self.sheets, this_game, away_team, away_roster_id)
# if away_roster:
logger.info(f'Pulling home team\'s roster')
await interaction.channel.send(
content=f'And now for the {home_team.abbrev} sheet...'
)
home_roster = await get_full_roster_from_sheets(session, interaction, self.sheets, this_game, home_team,home_roster_id)
# if home_roster:
await interaction.channel.send(
content=f'{away_role.mention} @ {home_role.mention}\n\nThe game is set, both of you may run `/set <starting-pitcher and lineup>` to start!'
)
@commands.command(name='force-endgame', help='Mod: Force a game to end without stats') @commands.command(name='force-endgame', help='Mod: Force a game to end without stats')
async def force_end_game_command(self, ctx: commands.Context): async def force_end_game_command(self, ctx: commands.Context):
@ -750,8 +848,11 @@ class Gameplay(commands.Cog):
) )
return return
this_team = this_game.away_team if this_game.ai_team == 'home' else this_game.home_team if this_game.away_team.gmid == interaction.user.id:
if interaction.user.id != this_team.gmid: this_team = this_game.away_team
elif this_game.home_team.gmid == interaction.user.id:
this_team = this_game.home_team
else:
logger.info(f'{interaction.user.name} tried to run a command in Game {this_game.id} when they aren\'t a GM in the game.') logger.info(f'{interaction.user.name} tried to run a command in Game {this_game.id} when they aren\'t a GM in the game.')
await interaction.edit_original_response(content='Bruh. Only GMs of the active teams can pull lineups.') await interaction.edit_original_response(content='Bruh. Only GMs of the active teams can pull lineups.')
return return
@ -796,8 +897,11 @@ class Gameplay(commands.Cog):
) )
return return
this_team = this_game.human_team if this_game.away_team.gmid == interaction.user.id:
if interaction.user.id != this_team.gmid: this_team = this_game.away_team
elif this_game.home_team.gmid == interaction.user.id:
this_team = this_game.home_team
else:
logger.info(f'{interaction.user.name} tried to run a command in Game {this_game.id} when they aren\'t a GM in the game.') logger.info(f'{interaction.user.name} tried to run a command in Game {this_game.id} when they aren\'t a GM in the game.')
await interaction.edit_original_response(content='Bruh. Only GMs of the active teams can pull lineups.') await interaction.edit_original_response(content='Bruh. Only GMs of the active teams can pull lineups.')
return return
@ -904,6 +1008,46 @@ class Gameplay(commands.Cog):
bat_view = sub_batter_dropdown_view(session, this_game, owner_team, this_order, [interaction.user]) bat_view = sub_batter_dropdown_view(session, this_game, owner_team, this_order, [interaction.user])
await interaction.edit_original_response(content=f'### {owner_team.lname} Substitution', view=bat_view) await interaction.edit_original_response(content=f'### {owner_team.lname} Substitution', view=bat_view)
@group_substitution.command(name='pitcher', description='Make a pitching substitution')
async def sub_pitcher_command(self, interaction: discord.Interaction, batting_order: Literal['dh-spot', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] = '10'):
with Session(engine) as session:
this_game, owner_team, this_play = await checks_log_interaction(session, interaction, command_name='substitute batter')
if owner_team != this_play.pitcher.team:
logger.warning(f'User {interaction.user.name} ({owner_team.abbrev}) tried to run a sub for the {this_play.pitcher.team.lname}')
await interaction.edit_original_response(
content=f'Please run pitcher subs when your team is on defense. If you are pinch-hitting for a pitcher already in the lineup, use `/substitute batter`'
)
return
if batting_order != '10' and this_play.pitcher.batting_order == 10:
forfeit_dh = await ask_confirm(
interaction,
f'Are you sure you want to forfeit the DH?'
)
if not forfeit_dh:
await interaction.edit_original_response(
content=f'Fine, be that way.'
)
return
if not this_play.is_new_inning:
pitcher_plays = get_plays_by_pitcher(session, this_game, this_play.pitcher)
batters_faced = sum(1 for x in pitcher_plays if x.pa == 1)
if batters_faced < 3:
await interaction.edit_original_response(
content=f'Looks like **{this_play.pitcher.player.name}** has only faced {batters_faced} of the 3-batter minimum.'
)
return
rp_view = relief_pitcher_dropdown_view(session, this_game, this_play.pitcher.team, batting_order, responders=[interaction.user])
rp_message = await interaction.edit_original_response(
content=f'### {this_play.pitcher.team.lname} Relief Pitcher',
view=rp_view
)
group_log = app_commands.Group(name='log', description='Log a play in this channel\'s game') group_log = app_commands.Group(name='log', description='Log a play in this channel\'s game')
@group_log.command(name='flyball', description='Flyballs: a, b, ballpark, bq, c') @group_log.command(name='flyball', description='Flyballs: a, b, ballpark, bq, c')

View File

@ -20,7 +20,7 @@ from in_game.gameplay_models import BattingCard, Card, Game, Lineup, PositionRat
from in_game.gameplay_queries import get_active_games_by_team, get_available_batters, get_batter_card, get_batting_statline, get_game_cardset_links, get_or_create_ai_card, get_pitcher_runs_by_innings, get_pitching_statline, get_plays_by_pitcher, get_position, get_available_pitchers, get_card_or_none, get_channel_game_or_none, get_db_ready_decisions, get_db_ready_plays, get_game_lineups, get_last_team_play, get_one_lineup, get_player_id_from_dict, get_player_name_from_dict, get_player_or_none, get_sorted_lineups, get_team_or_none, get_players_last_pa, post_game_rewards from in_game.gameplay_queries import get_active_games_by_team, get_available_batters, get_batter_card, get_batting_statline, get_game_cardset_links, get_or_create_ai_card, get_pitcher_runs_by_innings, get_pitching_statline, get_plays_by_pitcher, get_position, get_available_pitchers, get_card_or_none, get_channel_game_or_none, get_db_ready_decisions, get_db_ready_plays, get_game_lineups, get_last_team_play, get_one_lineup, get_player_id_from_dict, get_player_name_from_dict, get_player_or_none, get_sorted_lineups, get_team_or_none, get_players_last_pa, post_game_rewards
from in_game.managerai_responses import DefenseResponse from in_game.managerai_responses import DefenseResponse
from utilities.buttons import ButtonOptions, Confirm, ask_confirm, ask_with_buttons from utilities.buttons import ButtonOptions, Confirm, ask_confirm, ask_with_buttons
from utilities.dropdown import DropdownView, SelectBatterSub, SelectStartingPitcher, SelectViewDefense from utilities.dropdown import DropdownView, SelectBatterSub, SelectReliefPitcher, SelectStartingPitcher, SelectViewDefense
from utilities.embeds import image_embed from utilities.embeds import image_embed
from utilities.pages import Pagination from utilities.pages import Pagination
@ -336,6 +336,23 @@ def starting_pitcher_dropdown_view(session: Session, this_game: Game, human_team
return DropdownView(dropdown_objects=[sp_selection]) return DropdownView(dropdown_objects=[sp_selection])
def relief_pitcher_dropdown_view(session: Session, this_game: Game, human_team: Team, batting_order: int, responders: list[discord.User] = None):
pitchers = get_available_pitchers(session, this_game, human_team)
logger.info(f'sorted pitchers: {pitchers}')
if len(pitchers) == 0:
log_exception(MissingRosterException, 'No pitchers were found to select RP')
rp_selection = SelectReliefPitcher(
this_game=this_game,
this_team=human_team,
batting_order=batting_order,
session=session,
options=[SelectOption(label=f'{x.player.name_with_desc} (S{x.pitcherscouting.pitchingcard.starter_rating}/R{x.pitcherscouting.pitchingcard.relief_rating})', value=x.id) for x in pitchers],
placeholder='Select your relief pitcher',
responders=responders
)
return DropdownView(dropdown_objects=[rp_selection])
def sub_batter_dropdown_view(session: Session, this_game: Game, human_team: Team, batting_order: int, responders: list[discord.User]): def sub_batter_dropdown_view(session: Session, this_game: Game, human_team: Team, batting_order: int, responders: list[discord.User]):
batters = get_available_batters(session, this_game, human_team) batters = get_available_batters(session, this_game, human_team)
logger.info(f'batters: {batters}') logger.info(f'batters: {batters}')
@ -363,7 +380,7 @@ async def read_lineup(session: Session, interaction: discord.Interaction, this_g
) )
if len(existing_lineups) > 1: if len(existing_lineups) > 1:
await interaction.edit_original_response( await interaction.edit_original_response(
f'It looks like the {lineup_team.sname} already have a lineup. Run `/substitution` to make changes.' f'It looks like the {lineup_team.sname} already have a lineup. Run `/substitute` to make changes.'
) )
return return
@ -2841,9 +2858,10 @@ def undo_play(session: Session, this_play: Play):
new_player_ids = [] new_player_ids = []
new_players = session.exec(select(Lineup).where(Lineup.game == this_game, Lineup.after_play >= after_play_min)).all() new_players = session.exec(select(Lineup).where(Lineup.game == this_game, Lineup.after_play >= after_play_min)).all()
logger.info(f'Subs to roll back: {new_players}') logger.info(f'Subs to roll back: {new_players}')
for lineup in new_players: for x in new_players:
new_players.append(lineup.id) logger.info(f'Marking {x} for deletion')
old_player = session.get(Lineup, lineup.replacing_id) new_player_ids.append(x.id)
old_player = session.get(Lineup, x.replacing_id)
old_player.active = True old_player.active = True
session.add(old_player) session.add(old_player)
@ -3786,13 +3804,16 @@ def substitute_player(session, this_play: Play, old_player: Lineup, new_player:
replacing_id=old_player.id replacing_id=old_player.id
) )
logger.info(f'new_lineup: {new_lineup}') logger.info(f'new_lineup: {new_lineup}')
session.add(new_lineup) session.add(new_lineup)
logger.info(f'De-activating last player') logger.info(f'De-activating last player')
old_player.active = False old_player.active = False
session.add(old_player) session.add(old_player)
logger.info(f'Updating play\'s pitcher')
this_play.pitcher = new_lineup
session.add(this_play)
session.commit() session.commit()
session.refresh(new_lineup) session.refresh(new_lineup)
return new_lineup return new_lineup

View File

@ -325,6 +325,8 @@ INSULTS = [
'Why are you even here? Get lost.', 'Why are you even here? Get lost.',
'Why are you even here? Scram.', 'Why are you even here? Scram.',
'Why are you even here? No one knows who you are.', 'Why are you even here? No one knows who you are.',
'HEY, DON\'T TOUCH ME!',
'Hey, don\'t touch me!'
] ]