497 lines
21 KiB
Python
497 lines
21 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, LegalityCheckNotRequired, PlayNotFoundException, log_exception
|
|
from helpers import get_card_embeds, random_insult
|
|
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
|
|
from utilities.embeds import image_embed
|
|
|
|
|
|
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], responders: list[discord.User] = None):
|
|
self.embed = base_embed
|
|
self.session = session
|
|
self.play = this_play
|
|
self.sorted_lineups = sorted_lineups
|
|
self.responders = responders
|
|
super().__init__(options=options)
|
|
|
|
async def callback(self, interaction: discord.Interaction):
|
|
if self.responders is not None and interaction.user not in self.responders:
|
|
await interaction.response.send_message(
|
|
content=random_insult(),
|
|
ephemeral=True,
|
|
delete_after=5
|
|
)
|
|
await interaction.response.defer(thinking=True)
|
|
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,
|
|
responders=self.responders
|
|
)
|
|
new_view = DropdownView(
|
|
dropdown_objects=[player_dropdown],
|
|
timeout=60
|
|
)
|
|
|
|
await interaction.edit_original_response(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] = ..., responders: list[discord.User] = None) -> None:
|
|
logger.info(f'Inside SelectStartingPitcher init function')
|
|
self.game = this_game
|
|
self.team = this_team
|
|
self.session = session
|
|
self.league_name = league_name
|
|
self.responders = responders
|
|
super().__init__(custom_id=custom_id, placeholder=placeholder, options=options)
|
|
|
|
async def callback(self, interaction: discord.Interaction):
|
|
if self.responders is not None and interaction.user not in self.responders:
|
|
await interaction.response.send_message(
|
|
content=random_insult(),
|
|
ephemeral=True,
|
|
delete_after=5
|
|
)
|
|
await interaction.response.defer(thinking=True)
|
|
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')
|
|
|
|
try:
|
|
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
|
|
except LegalityCheckNotRequired:
|
|
pass
|
|
|
|
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()
|
|
|
|
logger.info(f'trying to delete interaction: {interaction}')
|
|
try:
|
|
# await interaction.delete_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'The {self.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):
|
|
def __init__(self, this_game: Game, this_team: Team, batting_order: int, session: Session, custom_id: str = MISSING, placeholder: str | None = None, options: List[SelectOption] = ..., responders: list[discord.User] = None) -> None:
|
|
logger.info(f'Inside SelectReliefPitcher init function')
|
|
self.game = this_game
|
|
self.team = this_team
|
|
self.batting_order = int(batting_order)
|
|
self.session = session
|
|
self.responders = responders
|
|
super().__init__(custom_id=custom_id, placeholder=placeholder, options=options)
|
|
|
|
async def callback(self, interaction: discord.Interaction):
|
|
if self.responders is not None and interaction.user not in self.responders:
|
|
await interaction.response.send_message(
|
|
content=random_insult(),
|
|
ephemeral=True,
|
|
delete_after=5
|
|
)
|
|
await interaction.response.defer(thinking=True)
|
|
logger.info(f'SelectReliefPitcher - selection: {self.values[0]}')
|
|
|
|
this_play = self.game.current_play_or_none(self.session)
|
|
if this_play is None:
|
|
log_exception(PlayNotFoundException, 'Could not find current play to make pitching change. If you are trying to swap SPs, run `/set pitcher` again.')
|
|
|
|
# Get Human RP card
|
|
human_rp_card = await get_card_or_none(self.session, card_id=self.values[0])
|
|
if human_rp_card is None:
|
|
log_exception(CardNotFoundException, f'Card ID {self.values[0]} not found')
|
|
|
|
if human_rp_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_rp_card.player.name} and belongs to {human_rp_card.team.sname}. Will you double check that?'
|
|
)
|
|
return
|
|
|
|
await get_position(self.session, human_rp_card, 'P')
|
|
|
|
logger.info(f'Adding the RP lineup')
|
|
human_rp_lineup = Lineup(
|
|
team_id=self.team.id,
|
|
player_id=human_rp_card.player.id,
|
|
card_id=self.values[0],
|
|
position='P',
|
|
batting_order=self.batting_order,
|
|
is_fatigued=False,
|
|
game=self.game,
|
|
replacing_id=this_play.pitcher.id,
|
|
after_play=max(this_play.play_num - 1, 0)
|
|
)
|
|
self.session.add(human_rp_lineup)
|
|
|
|
logger.info(f'De-activating the old pitcher')
|
|
this_play.pitcher.active = False
|
|
self.session.add(this_play.pitcher)
|
|
|
|
logger.info(f'Checking for batting order != 10 ({self.batting_order})')
|
|
if self.batting_order != 10:
|
|
logger.info(f'Getting the player in the {self.batting_order} spot')
|
|
this_lineup = get_one_lineup(self.session, self.game, self.team, active=True, batting_order=self.batting_order)
|
|
logger.info(f'subbing lineup: {this_lineup.player.name_with_desc}')
|
|
if this_lineup != this_play.pitcher:
|
|
this_lineup.active = False
|
|
self.session.add(this_lineup)
|
|
|
|
logger.info(f'Setting new pitcher on current play')
|
|
this_play.pitcher = human_rp_lineup
|
|
self.session.add(this_play)
|
|
|
|
logger.info(f'Committing changes')
|
|
try:
|
|
self.session.commit()
|
|
except Exception as e:
|
|
log_exception(e, 'Couldn\'t commit database changes')
|
|
|
|
try:
|
|
logger.info(f'Responding to player')
|
|
await interaction.edit_original_response(
|
|
content=f'**{human_rp_card.player.name_with_desc}** has entered for the {human_rp_card.team.lname}!\n\nRun `/substitute` to make any other moves and `/gamestate` if you are ready to continue.',
|
|
view=None
|
|
)
|
|
except Exception as e:
|
|
log_exception(e, 'Couldn\'t clean up after selecting rp')
|
|
|
|
|
|
|
|
class SelectSubPosition(discord.ui.Select):
|
|
def __init__(self, session: Session, this_lineup: Lineup, custom_id = ..., placeholder = None, options: List[SelectOption] = ..., responders: list[discord.User] = None):
|
|
self.session = session
|
|
self.this_lineup = this_lineup
|
|
self.responders = responders
|
|
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):
|
|
if self.responders is not None and interaction.user not in self.responders:
|
|
await interaction.response.send_message(
|
|
content=random_insult(),
|
|
ephemeral=True,
|
|
delete_after=5
|
|
)
|
|
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] = ..., responders: list[discord.User] = None):
|
|
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
|
|
self.responders = responders
|
|
super().__init__(custom_id=custom_id, placeholder=placeholder, min_values=1, max_values=1, options=options)
|
|
|
|
async def callback(self, interaction: discord.Interaction):
|
|
if self.responders is not None and interaction.user not in self.responders:
|
|
await interaction.response.send_message(
|
|
content=random_insult(),
|
|
ephemeral=True,
|
|
delete_after=5
|
|
)
|
|
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', responders=[interaction.user]) 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')
|
|
|
|
if position not in ['DH', 'PR', 'PH']:
|
|
pos_rating = await get_position(self.session, human_batter_card, position)
|
|
|
|
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: Team, responders: list[discord.User] = None):
|
|
logger.info(f'Inside SelectPokemonEvolution init function')
|
|
|
|
self.team = this_team
|
|
self.responders = responders
|
|
super().__init__(placeholder=placeholder, min_values=min_values, max_values=max_values, options=options)
|
|
logger.info(f'init complete')
|
|
|
|
async def callback(self, interaction: discord.Interaction):
|
|
logger.info(f'entering pokemon evolution callback')
|
|
if self.responders is not None and interaction.user not in self.responders:
|
|
await interaction.response.send_message(
|
|
content=random_insult(),
|
|
ephemeral=True,
|
|
delete_after=5
|
|
)
|
|
logger.info(f'deferring interaction')
|
|
await interaction.response.defer()
|
|
|
|
try:
|
|
card_id = self.values[0]
|
|
logger.info(f'evolving card_id: {card_id}')
|
|
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
|
|
)
|
|
logger.info(f'evo mon: {card_id}')
|
|
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']
|
|
logger.info(f'pack_id: {pack_id}')
|
|
logger.info(f'Posting evolved card')
|
|
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)
|
|
|
|
embed = image_embed(
|
|
image_url=evo_mon['image'],
|
|
title=evo_mon['p_name'],
|
|
desc=f'{evo_mon['cardset']['name']} / {evo_mon['mlbclub']}',
|
|
color=evo_mon['rarity']['color'],
|
|
thumbnail_url=evo_mon['headshot'],
|
|
author_name=self.team.lname,
|
|
author_icon=self.team.logo
|
|
)
|
|
|
|
await asyncio.sleep(3)
|
|
await interaction.channel.send(
|
|
content=f'## {this_card["player"]["p_name"].upper()} evolved into {evo_mon["p_name"].upper()}!',
|
|
embeds=[embed]
|
|
)
|
|
except Exception as e:
|
|
logger.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!')
|