paper-dynasty-discord/utilities/dropdown.py
Cal Corum f40347e0bb Fixed chaos button bug
Added batting statline to batter & OT to catchers
2024-12-25 16:10:24 -06:00

343 lines
14 KiB
Python

import asyncio
import datetime
from typing import List
import discord
import logging
from discord import SelectOption
from discord.utils import MISSING
from sqlmodel import Session
from api_calls import db_delete, db_get, db_post
from exceptions import CardNotFoundException, PlayNotFoundException, log_exception
from helpers import get_card_embeds
from in_game.game_helpers import legal_check
from in_game.gameplay_models import Game, Lineup, Play, Team
from in_game.gameplay_queries import get_one_lineup, get_position, get_card_or_none
from utilities.buttons import ask_confirm
logger = logging.getLogger('discord_app')
class DropdownOptions(discord.ui.Select):
def __init__(self, option_list: list, placeholder: str = 'Make your selection', min_values: int = 1, max_values: int = 1, callback=None):
# Set the options that will be presented inside the dropdown
# options = [
# discord.SelectOption(label='Red', description='Your favourite colour is red', emoji='🟥'),
# discord.SelectOption(label='Green', description='Your favourite colour is green', emoji='🟩'),
# discord.SelectOption(label='Blue', description='Your favourite colour is blue', emoji='🟦'),
# ]
# The placeholder is what will be shown when no option is chosen
# The min and max values indicate we can only pick one of the three options
# The options parameter defines the dropdown options. We defined this above
# If a default option is set on any SelectOption, the View will not process if only the default is
# selected by the user
self.custom_callback = callback
super().__init__(
placeholder=placeholder,
min_values=min_values,
max_values=max_values,
options=option_list
)
async def callback(self, interaction: discord.Interaction):
# Use the interaction object to send a response message containing
# the user's favourite colour or choice. The self object refers to the
# Select object, and the values attribute gets a list of the user's
# selected options. We only want the first one.
# await interaction.response.send_message(f'Your favourite colour is {self.values[0]}')
logger.info(f'Dropdown callback: {self.custom_callback}')
await self.custom_callback(interaction, self.values)
class DropdownView(discord.ui.View):
"""
https://discordpy.readthedocs.io/en/latest/interactions/api.html#select
"""
def __init__(self, dropdown_objects: list[discord.ui.Select], timeout: float = 300.0):
super().__init__(timeout=timeout)
# self.add_item(Dropdown())
for x in dropdown_objects:
self.add_item(x)
class SelectViewDefense(discord.ui.Select):
def __init__(self, options: list, this_play: Play, base_embed: discord.Embed, session: Session, sorted_lineups: list[Lineup]):
self.embed = base_embed
self.session = session
self.play = this_play
self.sorted_lineups = sorted_lineups
super().__init__(options=options)
async def callback(self, interaction: discord.Interaction):
logger.info(f'SelectViewDefense - selection: {self.values[0]}')
this_lineup = self.session.get(Lineup, self.values[0])
self.embed.set_image(url=this_lineup.player.image)
select_player_options = [
discord.SelectOption(label=f'{x.position} - {x.player.name}', value=f'{x.id}', default=this_lineup.position == x.position) for x in self.sorted_lineups
]
player_dropdown = SelectViewDefense(
options=select_player_options,
this_play=self.play,
base_embed=self.embed,
session=self.session,
sorted_lineups=self.sorted_lineups
)
new_view = DropdownView(
dropdown_objects=[player_dropdown],
timeout=60
)
await interaction.response.edit_message(content=None, embed=self.embed, view=new_view)
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] = ...) -> None:
logger.info(f'Inside SelectStartingPitcher init function')
self.game = this_game
self.team = this_team
self.session = session
self.league_name = league_name
super().__init__(custom_id=custom_id, placeholder=placeholder, options=options)
async def callback(self, interaction: discord.Interaction):
logger.info(f'SelectStartingPitcher - selection: {self.values[0]}')
# Get Human SP card
human_sp_card = await get_card_or_none(self.session, card_id=self.values[0])
if human_sp_card is None:
log_exception(CardNotFoundException, f'Card ID {self.values[0]} not found')
if human_sp_card.team_id != self.team.id:
logger.error(f'Card_id {self.values[0]} does not belong to {self.team.abbrev} in Game {self.game.id}')
await interaction.channel.send(
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?'
)
return
await get_position(self.session, human_sp_card, 'P')
legal_data = await legal_check([self.values[0]], difficulty_name=self.league_name)
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
human_sp_lineup = Lineup(
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()
await interaction.response.edit_message(
content=f'The {self.team.lname} are starting **{human_sp_card.player.name_with_desc}**!\n\nRun `/read-lineup` when you are ready to begin.',
view=None
)
class SelectSubPosition(discord.ui.Select):
def __init__(self, session: Session, this_lineup: Lineup, custom_id = ..., placeholder = None, options: List[SelectOption] = ...):
self.session = session
self.this_lineup = this_lineup
super().__init__(custom_id=custom_id, placeholder=placeholder, min_values=1, max_values=1, options=options, disabled=False)
async def callback(self, interaction: discord.Interaction):
logger.info(f'Setting sub position to {self.values[0]}')
await interaction.edit_original_response(view=None)
if self.values[0] == 'PH':
await interaction.channel.send(content=f'Their position is set to Pinch Hitter.')
return
else:
await get_position(self.session, self.this_lineup.card_id, position=self.values[0])
self.this_lineup.position = self.values[0]
for option in self.options:
if option.value == self.values[0]:
this_label = option.label
self.this_lineup.position = self.values[0]
self.session.add(self.this_lineup)
self.session.commit()
await interaction.channel.send(content=f'Their position is set to {this_label}.')
class SelectBatterSub(discord.ui.Select):
def __init__(self, this_game: Game, this_team: Team, session: Session, batting_order: int, custom_id: str = MISSING, placeholder: str | None = None, options: List[SelectOption] = ...):
logger.info(f'Inside SelectBatterSub init function')
self.game = this_game
self.team = this_team
self.session = session
# self.league_name = league_name
self.batting_order = batting_order
super().__init__(custom_id=custom_id, placeholder=placeholder, min_values=1, max_values=1, options=options)
async def callback(self, interaction: discord.Interaction):
await interaction.response.defer()
logger.info(f'Setting batter sub to Card ID: {self.values[0]}')
# Get Human batter card
human_batter_card = await get_card_or_none(self.session, card_id=self.values[0])
if human_batter_card is None:
log_exception(CardNotFoundException, f'Card ID {self.values[0]} not found')
if human_batter_card.team_id != self.team.id:
logger.error(f'Card_id {self.values[0]} does not belong to {self.team.abbrev} in Game {self.game.id}')
await interaction.channel.send(
f'Uh oh. Card ID {self.values[0]} is {human_batter_card.player.name} and belongs to {human_batter_card.team.sname}. Will you double check that before we get started?'
)
return
# legal_data = await legal_check([self.values[0]], difficulty_name=self.league_name)
# if not legal_data['legal']:
# await interaction.edit_original_response(
# content=f'It looks like this is a Ranked Legal game and {human_batter_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
this_play = self.game.current_play_or_none(self.session)
if this_play is None:
log_exception(PlayNotFoundException, 'Play not found during substitution')
logger.info(f'this_play: {this_play}')
last_lineup = get_one_lineup(
session=self.session,
this_game=self.game,
this_team=self.team,
active=True,
batting_order=self.batting_order
)
same_position = await ask_confirm(
interaction,
question=f'Will **{human_batter_card.player.name}** replace {last_lineup.player.name} as the {last_lineup.position}?',
label_type='yes'
)
if same_position:
position = last_lineup.position
pos_text = ''
view = None
else:
pos_dict_list = {
'Pinch Hitter': 'PH',
'Catcher': 'C',
'First Base': '1B',
'Second Base': '2B',
'Third Base': '3B',
'Shortstop': 'SS',
'Left Field': 'LF',
'Center Field': 'CF',
'Right Field': 'RF',
'Pinch Runner': 'PR',
'Pitcher': 'P'
}
position = 'PH'
pos_text = 'What position will they play?'
options=[SelectSubPosition(label=f'{x}', value=pos_dict_list[x], default=x=='Pinch Hitter') for x in pos_dict_list]
view = DropdownView(dropdown_objects=options)
last_lineup.active = False
self.session.add(last_lineup)
logger.info(f'Set {last_lineup.card.player.name_with_desc} as inactive')
human_bat_lineup = Lineup(
team=self.team,
player=human_batter_card.player,
card=human_batter_card,
position=position,
batting_order=self.batting_order,
game=self.game,
after_play=max(this_play.play_num - 1, 0),
replacing_id=last_lineup.id
)
logger.info(f'new lineup: {human_bat_lineup}')
self.session.add(human_bat_lineup)
# self.session.commit()
try:
logger.info(f'Inserted {human_bat_lineup.card.player.name_with_desc} in the {self.batting_order} spot')
this_play.batter = human_bat_lineup
this_play.batter_pos = position
except Exception as e:
logger.error(e, exc_info=True, stack_info=True)
self.session.add(this_play)
self.session.commit()
await interaction.edit_original_response(
content=f'{human_batter_card.player.name_with_desc} has entered in the {self.batting_order} spot. {pos_text}',
view=view
)
class SelectPokemonEvolution(discord.ui.Select):
def __init__(self, *, placeholder = 'Evolve the selected Pokemon', min_values = 1, max_values = 1, options = List[SelectOption], this_team):
logging.info(f'Inside SelectPokemonEvolution init function')
self.team = this_team
super().__init__(placeholder=placeholder, min_values=min_values, max_values=max_values, options=options)
async def callback(self, interaction: discord.Interaction):
await interaction.response.defer()
try:
card_id = self.values[0]
this_card = await db_get(
'cards',
object_id=card_id,
none_okay=False
)
evo_mon = await db_get(
'players',
object_id=this_card['player']['fangr_id'],
none_okay=False
)
p_query = await db_post(
'packs/one',
payload={
'team_id': self.team['id'],
'pack_type_id': 4,
'open_time': datetime.datetime.timestamp(datetime.datetime.now()) * 1000}
)
pack_id = p_query['id']
await db_post(
'cards',
payload={'cards': [ {'player_id': evo_mon['player_id'], 'team_id': self.team['id'], 'pack_id': pack_id}]},
timeout=10
)
await interaction.edit_original_response(
content=f'## {this_card["player"]["p_name"].upper()} is evolving!',
embeds=await get_card_embeds(this_card),
view=None
)
await db_delete('cards', object_id=card_id)
await asyncio.sleep(3)
await interaction.channel.send(
content=f'## {this_card["player"]["p_name"].upper()} evolved into {evo_mon["p_name"].upper()}!',
embeds=await get_card_embeds({'team': self.team, 'player': evo_mon})
)
except Exception as e:
logging.error(f'Failed to evolve a pokemon: {e}', exc_info=True, stack_info=True)
await interaction.edit_original_response(content=f'Oh no, the evolution failed! Go ping the shit out of Cal so he can evolve it for you!')