CLAUDE: Fix gauntlet game creation and Event 9 issues
Multiple fixes to resolve PlayNotFoundException and lineup initialization errors: 1. gauntlets.py: - Fixed Team object subscriptable errors (use .id instead of ['id']) - Added fallback cardsets (24, 25, 26) for Event 9 RP shortage - Fixed draft_team type handling (can be Team object or dict) 2. cogs/gameplay.py: - Fixed gauntlet game creation flow to read field player lineup from sheets - Catches LineupsMissingException when SP not yet selected - Instructs user to run /gamestate after SP selection 3. utilities/dropdown.py: - Fixed SelectStartingPitcher to create own session instead of using closed session - Store game/team IDs instead of objects to avoid detached session issues - Added exception handling for failed legality check API calls These changes fix the issue where gauntlet games would fail to initialize because the SP lineup entry wasn't being committed to the database. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
07195f9ad3
commit
c043948238
@ -656,12 +656,9 @@ class Gameplay(commands.Cog):
|
|||||||
confirmation_message='Got it!'
|
confirmation_message='Got it!'
|
||||||
)
|
)
|
||||||
|
|
||||||
sp_view = starting_pitcher_dropdown_view(session, this_game, human_team, this_game.league_name, [interaction.user])
|
# Read the 9 field players from sheets (this will fail to initialize play without SP)
|
||||||
await interaction.channel.send(content=f'### {human_team.lname} Starting Pitcher', view=sp_view)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await asyncio.sleep(5)
|
await read_lineup(
|
||||||
this_play = await read_lineup(
|
|
||||||
session,
|
session,
|
||||||
interaction,
|
interaction,
|
||||||
this_game=this_game,
|
this_game=this_game,
|
||||||
@ -671,16 +668,17 @@ class Gameplay(commands.Cog):
|
|||||||
league_name=this_game.game_type
|
league_name=this_game.game_type
|
||||||
)
|
)
|
||||||
except LineupsMissingException as e:
|
except LineupsMissingException as e:
|
||||||
logger.error(f'Attempting to start game, pausing for 5 seconds: {e}')
|
# Expected - can't initialize play without SP yet
|
||||||
await asyncio.sleep(5)
|
logger.info(f'Field player lineup read from sheets, waiting for SP selection: {e}')
|
||||||
|
|
||||||
try:
|
sp_view = starting_pitcher_dropdown_view(session, this_game, human_team, this_game.league_name, [interaction.user])
|
||||||
this_play = this_game.current_play_or_none(session)
|
await interaction.channel.send(content=f'### {human_team.lname} Starting Pitcher', view=sp_view)
|
||||||
await self.post_play(session, interaction, this_play, buffer_message='Game on!')
|
|
||||||
except LineupsMissingException as e:
|
# Don't try to initialize play immediately - wait for user to select SP
|
||||||
await interaction.channel.send(
|
# The play will be initialized when they run /gamestate
|
||||||
content=f'Run `/gamestate` once you have selected a Starting Pitcher to get going!'
|
await interaction.channel.send(
|
||||||
)
|
content=f'Once you\'ve selected your Starting Pitcher, run `/gamestate` to get the game started!'
|
||||||
|
)
|
||||||
|
|
||||||
@group_new_game.command(name='exhibition', description='Start a new custom game against an AI')
|
@group_new_game.command(name='exhibition', description='Start a new custom game against an AI')
|
||||||
@app_commands.choices(
|
@app_commands.choices(
|
||||||
|
|||||||
22
gauntlets.py
22
gauntlets.py
@ -423,9 +423,12 @@ async def run_draft(interaction: discord.Interaction, main_team: Team, this_even
|
|||||||
if this_event['id'] in [1, 2]:
|
if this_event['id'] in [1, 2]:
|
||||||
max_counts['MVP'] = 2
|
max_counts['MVP'] = 2
|
||||||
elif this_event['id'] in [5, 6, 8, 9]:
|
elif this_event['id'] in [5, 6, 8, 9]:
|
||||||
|
# Handle draft_team as either Team object or dict
|
||||||
|
dt_season = draft_team.season if isinstance(draft_team, Team) else draft_team['season']
|
||||||
|
dt_id = draft_team.id if isinstance(draft_team, Team) else draft_team['id']
|
||||||
g_query = await db_get(
|
g_query = await db_get(
|
||||||
'games',
|
'games',
|
||||||
params=[('season', draft_team.season), ('team1_id', draft_team.id), ('gauntlet_id', this_event['id'])]
|
params=[('season', dt_season), ('team1_id', dt_id), ('gauntlet_id', this_event['id'])]
|
||||||
)
|
)
|
||||||
if g_query['count'] > 4:
|
if g_query['count'] > 4:
|
||||||
game_count = g_query['count']
|
game_count = g_query['count']
|
||||||
@ -779,6 +782,14 @@ async def run_draft(interaction: discord.Interaction, main_team: Team, this_even
|
|||||||
slot_params.extend(params)
|
slot_params.extend(params)
|
||||||
p_query = await db_get('players/random', params=slot_params)
|
p_query = await db_get('players/random', params=slot_params)
|
||||||
|
|
||||||
|
# Fallback for Event 9 RP shortage
|
||||||
|
if this_event['id'] == 9 and x == 'RP' and p_query['count'] < 3:
|
||||||
|
logger.warning(f'Low RP count ({p_query["count"]}) in Event 9, expanding cardsets to 24, 25, 26')
|
||||||
|
fallback_params = [p for p in slot_params if p[0] != 'cardset_id']
|
||||||
|
fallback_params.extend([('cardset_id', 24), ('cardset_id', 25), ('cardset_id', 26)])
|
||||||
|
p_query = await db_get('players/random', params=fallback_params)
|
||||||
|
logger.info(f'Fallback query returned {p_query["count"]} RP options')
|
||||||
|
|
||||||
if p_query['count'] > 0:
|
if p_query['count'] > 0:
|
||||||
# test_player_list = ''
|
# test_player_list = ''
|
||||||
# for z in p_query['players']:
|
# for z in p_query['players']:
|
||||||
@ -1720,10 +1731,13 @@ async def run_draft(interaction: discord.Interaction, main_team: Team, this_even
|
|||||||
raise KeyError(f'I gotta be honest - I shit the bed here and wasn\'t able to get you enough players to fill '
|
raise KeyError(f'I gotta be honest - I shit the bed here and wasn\'t able to get you enough players to fill '
|
||||||
f'a team. I have to wipe this team, but please draft again after you tell Cal his bot sucks.')
|
f'a team. I have to wipe this team, but please draft again after you tell Cal his bot sucks.')
|
||||||
|
|
||||||
|
# Handle draft_team as either Team object or dict
|
||||||
|
draft_team_id = draft_team.id if isinstance(draft_team, Team) else draft_team['id']
|
||||||
|
|
||||||
this_pack = await db_post(
|
this_pack = await db_post(
|
||||||
'packs/one',
|
'packs/one',
|
||||||
payload={
|
payload={
|
||||||
'team_id': draft_team.id,
|
'team_id': draft_team_id,
|
||||||
'pack_type_id': 2,
|
'pack_type_id': 2,
|
||||||
'open_time': datetime.datetime.timestamp(datetime.datetime.now()) * 1000
|
'open_time': datetime.datetime.timestamp(datetime.datetime.now()) * 1000
|
||||||
}
|
}
|
||||||
@ -1731,13 +1745,13 @@ async def run_draft(interaction: discord.Interaction, main_team: Team, this_even
|
|||||||
await db_post(
|
await db_post(
|
||||||
'cards',
|
'cards',
|
||||||
payload={'cards': [
|
payload={'cards': [
|
||||||
{'player_id': x['player_id'], 'team_id': draft_team.id, 'pack_id': this_pack['id']} for x in all_players
|
{'player_id': x['player_id'], 'team_id': draft_team_id, 'pack_id': this_pack['id']} for x in all_players
|
||||||
]}
|
]}
|
||||||
)
|
)
|
||||||
await db_post(
|
await db_post(
|
||||||
'gauntletruns',
|
'gauntletruns',
|
||||||
payload={
|
payload={
|
||||||
'team_id': draft_team.id,
|
'team_id': draft_team_id,
|
||||||
'gauntlet_id': this_event['id']
|
'gauntlet_id': this_event['id']
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -110,9 +110,9 @@ class SelectViewDefense(discord.ui.Select):
|
|||||||
class SelectStartingPitcher(discord.ui.Select):
|
class SelectStartingPitcher(discord.ui.Select):
|
||||||
def __init__(self, this_game: Game, this_team: Team, session: Session, league_name: str, custom_id: str = MISSING, placeholder: str | None = None, options: List[SelectOption] = ..., responders: list[discord.User] = None) -> None:
|
def __init__(self, this_game: Game, this_team: Team, session: Session, league_name: str, custom_id: str = MISSING, placeholder: str | None = None, options: List[SelectOption] = ..., responders: list[discord.User] = None) -> None:
|
||||||
logger.info(f'Inside SelectStartingPitcher init function')
|
logger.info(f'Inside SelectStartingPitcher init function')
|
||||||
self.game = this_game
|
# Store IDs instead of objects to avoid session issues
|
||||||
self.team = this_team
|
self.game_id = this_game.id
|
||||||
self.session = session
|
self.team_id = this_team.id
|
||||||
self.league_name = league_name
|
self.league_name = league_name
|
||||||
self.responders = responders
|
self.responders = responders
|
||||||
super().__init__(custom_id=custom_id, placeholder=placeholder, options=options)
|
super().__init__(custom_id=custom_id, placeholder=placeholder, options=options)
|
||||||
@ -127,52 +127,61 @@ class SelectStartingPitcher(discord.ui.Select):
|
|||||||
await interaction.response.defer(thinking=True)
|
await interaction.response.defer(thinking=True)
|
||||||
logger.info(f'SelectStartingPitcher - selection: {self.values[0]}')
|
logger.info(f'SelectStartingPitcher - selection: {self.values[0]}')
|
||||||
|
|
||||||
# Get Human SP card
|
# Create a new session for this callback
|
||||||
human_sp_card = await get_card_or_none(self.session, card_id=self.values[0])
|
from in_game.gameplay_models import engine
|
||||||
if human_sp_card is None:
|
with Session(engine) as session:
|
||||||
log_exception(CardNotFoundException, f'Card ID {self.values[0]} not found')
|
# Get fresh game and team objects
|
||||||
|
this_game = session.get(Game, self.game_id)
|
||||||
|
this_team = session.get(Team, self.team_id)
|
||||||
|
|
||||||
if human_sp_card.team_id != self.team.id:
|
# Get Human SP card
|
||||||
logger.error(f'Card_id {self.values[0]} does not belong to {self.team.abbrev} in Game {self.game.id}')
|
human_sp_card = await get_card_or_none(session, card_id=self.values[0])
|
||||||
await interaction.channel.send(
|
if human_sp_card is None:
|
||||||
f'Uh oh. Card ID {self.values[0]} is {human_sp_card.player.name} and belongs to {human_sp_card.team.sname}. Will you double check that before we get started?'
|
log_exception(CardNotFoundException, f'Card ID {self.values[0]} not found')
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
await get_position(self.session, human_sp_card, 'P')
|
if human_sp_card.team_id != this_team.id:
|
||||||
|
logger.error(f'Card_id {self.values[0]} does not belong to {this_team.abbrev} in Game {this_game.id}')
|
||||||
try:
|
await interaction.channel.send(
|
||||||
legal_data = await legal_check([self.values[0]], difficulty_name=self.league_name)
|
f'Uh oh. Card ID {self.values[0]} is {human_sp_card.player.name} and belongs to {human_sp_card.team.sname}. Will you double check that before we get started?'
|
||||||
if not legal_data['legal']:
|
|
||||||
await interaction.edit_original_response(
|
|
||||||
content=f'It looks like this is a Ranked Legal game and {human_sp_card.player.name_with_desc} is not legal in {self.league_name} games. You can start a new game once you pick a new SP.'
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
except LegalityCheckNotRequired:
|
|
||||||
pass
|
|
||||||
|
|
||||||
human_sp_lineup = Lineup(
|
await get_position(session, human_sp_card, 'P')
|
||||||
team_id=self.team.id,
|
|
||||||
player_id=human_sp_card.player.id,
|
|
||||||
card_id=self.values[0],
|
|
||||||
position='P',
|
|
||||||
batting_order=10,
|
|
||||||
is_fatigued=False,
|
|
||||||
game=self.game
|
|
||||||
)
|
|
||||||
self.session.add(human_sp_lineup)
|
|
||||||
self.session.commit()
|
|
||||||
|
|
||||||
logger.info(f'trying to delete interaction: {interaction}')
|
try:
|
||||||
try:
|
legal_data = await legal_check([self.values[0]], difficulty_name=self.league_name)
|
||||||
# await interaction.delete_original_response()
|
if not legal_data['legal']:
|
||||||
await interaction.edit_original_response(
|
await interaction.edit_original_response(
|
||||||
# content=f'The {self.team.lname} are starting **{human_sp_card.player.name_with_desc}**!\n\nRun `/set lineup` to import your lineup and `/gamestate` if you are ready to play.',
|
content=f'It looks like this is a Ranked Legal game and {human_sp_card.player.name_with_desc} is not legal in {self.league_name} games. You can start a new game once you pick a new SP.'
|
||||||
content=f'The {self.team.lname} are starting **{human_sp_card.player.name_with_desc}**!',
|
)
|
||||||
view=None
|
return
|
||||||
|
except (LegalityCheckNotRequired, Exception) as e:
|
||||||
|
# Skip legality check if not required or if it fails
|
||||||
|
logger.info(f'Skipping legality check: {e}')
|
||||||
|
pass
|
||||||
|
|
||||||
|
human_sp_lineup = Lineup(
|
||||||
|
team_id=this_team.id,
|
||||||
|
player_id=human_sp_card.player.id,
|
||||||
|
card_id=self.values[0],
|
||||||
|
position='P',
|
||||||
|
batting_order=10,
|
||||||
|
is_fatigued=False,
|
||||||
|
game=this_game
|
||||||
)
|
)
|
||||||
except Exception as e:
|
session.add(human_sp_lineup)
|
||||||
log_exception(e, 'Couldn\'t clean up after selecting sp')
|
session.commit()
|
||||||
|
|
||||||
|
logger.info(f'trying to delete interaction: {interaction}')
|
||||||
|
try:
|
||||||
|
# await interaction.delete_original_response()
|
||||||
|
await interaction.edit_original_response(
|
||||||
|
# content=f'The {this_team.lname} are starting **{human_sp_card.player.name_with_desc}**!\n\nRun `/set lineup` to import your lineup and `/gamestate` if you are ready to play.',
|
||||||
|
content=f'The {this_team.lname} are starting **{human_sp_card.player.name_with_desc}**!',
|
||||||
|
view=None
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
log_exception(e, 'Couldn\'t clean up after selecting sp')
|
||||||
|
|
||||||
|
|
||||||
class SelectReliefPitcher(discord.ui.Select):
|
class SelectReliefPitcher(discord.ui.Select):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user